blob: 67920edcf56a8852258d4600d6079847833fbeb2 [file] [log] [blame]
RoboErik01fe6612014-02-13 14:19:04 -08001/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.server.media;
18
RoboErike7880d82014-04-30 12:48:25 -070019import android.app.ActivityManager;
Julia Reynoldsb852e562017-06-06 16:14:18 -040020import android.app.INotificationManager;
RoboErik9a9d0b52014-05-20 14:53:39 -070021import android.app.KeyguardManager;
RoboErikb214efb2014-07-24 13:20:30 -070022import android.app.PendingIntent;
23import android.app.PendingIntent.CanceledException;
RoboErik9a9d0b52014-05-20 14:53:39 -070024import android.content.ActivityNotFoundException;
RoboErik8a2cfc32014-05-16 11:19:38 -070025import android.content.BroadcastReceiver;
RoboErike7880d82014-04-30 12:48:25 -070026import android.content.ComponentName;
RoboErik6f0e4dd2014-06-17 16:56:27 -070027import android.content.ContentResolver;
RoboErik01fe6612014-02-13 14:19:04 -080028import android.content.Context;
RoboErik8a2cfc32014-05-16 11:19:38 -070029import android.content.Intent;
RoboErika278ea72014-04-24 14:49:01 -070030import android.content.pm.PackageManager;
Jaewan Kima7dce192017-02-16 17:10:54 +090031import android.content.pm.UserInfo;
RoboErik7aef77b2014-08-08 15:56:54 -070032import android.database.ContentObserver;
RoboErik3c45c292014-07-08 16:47:31 -070033import android.media.AudioManager;
John Spurlockeb69e242015-02-17 17:15:04 -050034import android.media.AudioManagerInternal;
RoboErik94c716e2014-09-14 13:54:31 -070035import android.media.AudioSystem;
RoboErikb69ffd42014-05-30 14:57:59 -070036import android.media.IAudioService;
RoboErik19c95182014-06-23 15:38:48 -070037import android.media.IRemoteVolumeController;
RoboErik2e7a9162014-06-04 16:53:45 -070038import android.media.session.IActiveSessionsListener;
Jaewan Kimbd16f452017-02-03 16:21:38 +090039import android.media.session.ICallback;
Jaewan Kim6e2b01c2017-01-19 16:33:14 -080040import android.media.session.IOnMediaKeyListener;
Jaewan Kim50269362016-12-23 11:22:02 +090041import android.media.session.IOnVolumeKeyLongPressListener;
RoboErik07c70772014-03-20 13:33:52 -070042import android.media.session.ISession;
43import android.media.session.ISessionCallback;
44import android.media.session.ISessionManager;
Jeff Browndba34ba2014-06-24 20:46:03 -070045import android.media.session.MediaSession;
Jaewan Kim6e2b01c2017-01-19 16:33:14 -080046import android.media.session.MediaSessionManager;
RoboErik7aef77b2014-08-08 15:56:54 -070047import android.net.Uri;
RoboErik01fe6612014-02-13 14:19:04 -080048import android.os.Binder;
RoboErik8a2cfc32014-05-16 11:19:38 -070049import android.os.Bundle;
RoboErik8ae0f342014-02-24 18:02:08 -080050import android.os.Handler;
RoboErike7880d82014-04-30 12:48:25 -070051import android.os.IBinder;
RoboErik2e7a9162014-06-04 16:53:45 -070052import android.os.Message;
RoboErik8a2cfc32014-05-16 11:19:38 -070053import android.os.PowerManager;
Jaewan Kim8f729082016-06-21 12:36:26 +090054import android.os.Process;
RoboErik01fe6612014-02-13 14:19:04 -080055import android.os.RemoteException;
RoboErik8a2cfc32014-05-16 11:19:38 -070056import android.os.ResultReceiver;
RoboErikb69ffd42014-05-30 14:57:59 -070057import android.os.ServiceManager;
RoboErike7880d82014-04-30 12:48:25 -070058import android.os.UserHandle;
Jaewan Kim8f729082016-06-21 12:36:26 +090059import android.os.UserManager;
RoboErike7880d82014-04-30 12:48:25 -070060import android.provider.Settings;
RoboErik9a9d0b52014-05-20 14:53:39 -070061import android.speech.RecognizerIntent;
RoboErik01fe6612014-02-13 14:19:04 -080062import android.text.TextUtils;
63import android.util.Log;
Jeff Brown38d3feb2015-03-19 18:26:30 -070064import android.util.Slog;
RoboErik4646d282014-05-13 10:13:04 -070065import android.util.SparseArray;
Jaewan Kima7dce192017-02-16 17:10:54 +090066import android.util.SparseIntArray;
RoboErik8a2cfc32014-05-16 11:19:38 -070067import android.view.KeyEvent;
Jaewan Kimd61a87b2017-02-17 23:14:10 +090068import android.view.ViewConfiguration;
RoboErik01fe6612014-02-13 14:19:04 -080069
Jeff Sharkeyfe9a53b2017-03-31 14:08:23 -060070import com.android.internal.util.DumpUtils;
John Spurlockeb69e242015-02-17 17:15:04 -050071import com.android.server.LocalServices;
RoboErik01fe6612014-02-13 14:19:04 -080072import com.android.server.SystemService;
RoboErika278ea72014-04-24 14:49:01 -070073import com.android.server.Watchdog;
74import com.android.server.Watchdog.Monitor;
RoboErik01fe6612014-02-13 14:19:04 -080075
RoboErika278ea72014-04-24 14:49:01 -070076import java.io.FileDescriptor;
77import java.io.PrintWriter;
RoboErik01fe6612014-02-13 14:19:04 -080078import java.util.ArrayList;
RoboErike7880d82014-04-30 12:48:25 -070079import java.util.List;
RoboErik01fe6612014-02-13 14:19:04 -080080
81/**
82 * System implementation of MediaSessionManager
83 */
RoboErika278ea72014-04-24 14:49:01 -070084public class MediaSessionService extends SystemService implements Monitor {
RoboErik01fe6612014-02-13 14:19:04 -080085 private static final String TAG = "MediaSessionService";
Jaewan Kim92dea332017-02-02 11:52:08 +090086 static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
Jaewan Kim50269362016-12-23 11:22:02 +090087 // Leave log for key event always.
88 private static final boolean DEBUG_KEY_EVENT = true;
RoboErik01fe6612014-02-13 14:19:04 -080089
RoboErik418c10c2014-05-19 09:25:25 -070090 private static final int WAKELOCK_TIMEOUT = 5000;
Jaewan Kim6e2b01c2017-01-19 16:33:14 -080091 private static final int MEDIA_KEY_LISTENER_TIMEOUT = 1000;
RoboErik418c10c2014-05-19 09:25:25 -070092
RoboErik01fe6612014-02-13 14:19:04 -080093 private final SessionManagerImpl mSessionManagerImpl;
94
Jaewan Kima7dce192017-02-16 17:10:54 +090095 // Keeps the full user id for each user.
96 private final SparseIntArray mFullUserIds = new SparseIntArray();
97 private final SparseArray<FullUserRecord> mUserRecords = new SparseArray<FullUserRecord>();
RoboErik2e7a9162014-06-04 16:53:45 -070098 private final ArrayList<SessionsListenerRecord> mSessionsListeners
99 = new ArrayList<SessionsListenerRecord>();
RoboErik01fe6612014-02-13 14:19:04 -0800100 private final Object mLock = new Object();
RoboErik2e7a9162014-06-04 16:53:45 -0700101 private final MessageHandler mHandler = new MessageHandler();
RoboErik8a2cfc32014-05-16 11:19:38 -0700102 private final PowerManager.WakeLock mMediaEventWakeLock;
Jaewan Kimd61a87b2017-02-17 23:14:10 +0900103 private final int mLongPressTimeout;
RoboErik01fe6612014-02-13 14:19:04 -0800104
RoboErik9a9d0b52014-05-20 14:53:39 -0700105 private KeyguardManager mKeyguardManager;
RoboErikb69ffd42014-05-30 14:57:59 -0700106 private IAudioService mAudioService;
John Spurlockeb69e242015-02-17 17:15:04 -0500107 private AudioManagerInternal mAudioManagerInternal;
RoboErik6f0e4dd2014-06-17 16:56:27 -0700108 private ContentResolver mContentResolver;
RoboErik7aef77b2014-08-08 15:56:54 -0700109 private SettingsObserver mSettingsObserver;
Julia Reynoldsb852e562017-06-06 16:14:18 -0400110 private INotificationManager mNotificationManager;
RoboErik9a9d0b52014-05-20 14:53:39 -0700111
Jaewan Kima7dce192017-02-16 17:10:54 +0900112 // The FullUserRecord of the current users. (i.e. The foreground user that isn't a profile)
113 // It's always not null after the MediaSessionService is started.
114 private FullUserRecord mCurrentFullUserRecord;
115 private MediaSessionRecord mGlobalPrioritySession;
Jaewan Kim92dea332017-02-02 11:52:08 +0900116 private AudioPlaybackMonitor mAudioPlaybackMonitor;
RoboErike7880d82014-04-30 12:48:25 -0700117
RoboErik19c95182014-06-23 15:38:48 -0700118 // Used to notify system UI when remote volume was changed. TODO find a
119 // better way to handle this.
120 private IRemoteVolumeController mRvc;
121
RoboErik01fe6612014-02-13 14:19:04 -0800122 public MediaSessionService(Context context) {
123 super(context);
124 mSessionManagerImpl = new SessionManagerImpl();
RoboErik8a2cfc32014-05-16 11:19:38 -0700125 PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
126 mMediaEventWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "handleMediaEvent");
Jaewan Kimd61a87b2017-02-17 23:14:10 +0900127 mLongPressTimeout = ViewConfiguration.getLongPressTimeout();
Julia Reynoldsb852e562017-06-06 16:14:18 -0400128 mNotificationManager = INotificationManager.Stub.asInterface(
129 ServiceManager.getService(Context.NOTIFICATION_SERVICE));
RoboErik01fe6612014-02-13 14:19:04 -0800130 }
131
132 @Override
133 public void onStart() {
134 publishBinderService(Context.MEDIA_SESSION_SERVICE, mSessionManagerImpl);
RoboErika278ea72014-04-24 14:49:01 -0700135 Watchdog.getInstance().addMonitor(this);
RoboErik9a9d0b52014-05-20 14:53:39 -0700136 mKeyguardManager =
137 (KeyguardManager) getContext().getSystemService(Context.KEYGUARD_SERVICE);
RoboErikb69ffd42014-05-30 14:57:59 -0700138 mAudioService = getAudioService();
Jaewan Kim92dea332017-02-02 11:52:08 +0900139 mAudioPlaybackMonitor = new AudioPlaybackMonitor(getContext(), mAudioService,
140 new AudioPlaybackMonitor.OnAudioPlaybackStartedListener() {
141 @Override
142 public void onAudioPlaybackStarted(int uid) {
143 synchronized (mLock) {
144 FullUserRecord user =
145 getFullUserRecordLocked(UserHandle.getUserId(uid));
146 if (user != null) {
147 user.mPriorityStack.updateMediaButtonSessionIfNeeded();
148 }
149 }
150 }
151 });
John Spurlockeb69e242015-02-17 17:15:04 -0500152 mAudioManagerInternal = LocalServices.getService(AudioManagerInternal.class);
RoboErik6f0e4dd2014-06-17 16:56:27 -0700153 mContentResolver = getContext().getContentResolver();
RoboErik7aef77b2014-08-08 15:56:54 -0700154 mSettingsObserver = new SettingsObserver();
155 mSettingsObserver.observe();
RoboErikc8f92d12015-01-05 16:48:07 -0800156
157 updateUser();
RoboErikb69ffd42014-05-30 14:57:59 -0700158 }
159
160 private IAudioService getAudioService() {
161 IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE);
162 return IAudioService.Stub.asInterface(b);
RoboErik07c70772014-03-20 13:33:52 -0700163 }
164
Jaewan Kima7dce192017-02-16 17:10:54 +0900165 private boolean isGlobalPriorityActiveLocked() {
166 return mGlobalPrioritySession != null && mGlobalPrioritySession.isActive();
167 }
168
RoboErika8f95142014-05-05 14:23:49 -0700169 public void updateSession(MediaSessionRecord record) {
RoboErike7880d82014-04-30 12:48:25 -0700170 synchronized (mLock) {
Jaewan Kima7dce192017-02-16 17:10:54 +0900171 FullUserRecord user = getFullUserRecordLocked(record.getUserId());
Jaewan Kim101b4d52017-05-18 13:23:11 +0900172 if (user == null) {
173 Log.w(TAG, "Unknown session updated. Ignoring.");
RoboErik4646d282014-05-13 10:13:04 -0700174 return;
175 }
Jaewan Kima7dce192017-02-16 17:10:54 +0900176 if ((record.getFlags() & MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY) != 0) {
Jaewan Kim101b4d52017-05-18 13:23:11 +0900177 if (mGlobalPrioritySession != record) {
178 Log.d(TAG, "Global priority session is changed from " + mGlobalPrioritySession
179 + " to " + record);
180 mGlobalPrioritySession = record;
181 if (user != null && user.mPriorityStack.contains(record)) {
182 // Handle the global priority session separately.
183 // Otherwise, it will be the media button session even after it becomes
184 // inactive because it has been the lastly played media app.
185 user.mPriorityStack.removeSession(record);
186 }
187 }
188 if (DEBUG_KEY_EVENT) {
189 Log.d(TAG, "Global priority session is updated, active=" + record.isActive());
190 }
Jaewan Kim92dea332017-02-02 11:52:08 +0900191 user.pushAddressedPlayerChangedLocked();
Jaewan Kim101b4d52017-05-18 13:23:11 +0900192 } else {
193 if (!user.mPriorityStack.contains(record)) {
194 Log.w(TAG, "Unknown session updated. Ignoring.");
195 return;
196 }
197 user.mPriorityStack.onSessionStateChange(record);
Jaewan Kima7dce192017-02-16 17:10:54 +0900198 }
Jaewan Kim92dea332017-02-02 11:52:08 +0900199 mHandler.postSessionsChanged(record.getUserId());
RoboErike7880d82014-04-30 12:48:25 -0700200 }
201 }
202
Jaewan Kim101b4d52017-05-18 13:23:11 +0900203 private List<MediaSessionRecord> getActiveSessionsLocked(int userId) {
204 List<MediaSessionRecord> records;
205 if (userId == UserHandle.USER_ALL) {
206 records = new ArrayList<>();
207 int size = mUserRecords.size();
208 for (int i = 0; i < size; i++) {
209 records.addAll(mUserRecords.valueAt(i).mPriorityStack.getActiveSessions(userId));
210 }
211 } else {
212 FullUserRecord user = getFullUserRecordLocked(userId);
213 if (user == null) {
214 Log.w(TAG, "getSessions failed. Unknown user " + userId);
215 return new ArrayList<>();
216 }
217 records = user.mPriorityStack.getActiveSessions(userId);
218 }
219
220 // Return global priority session at the first whenever it's asked.
221 if (isGlobalPriorityActiveLocked()
222 && (userId == UserHandle.USER_ALL
223 || userId == mGlobalPrioritySession.getUserId())) {
224 records.add(0, mGlobalPrioritySession);
225 }
226 return records;
227 }
228
RoboErik9c5b7cb2015-01-15 15:09:09 -0800229 /**
Hyundo Moona055f132017-01-13 15:31:06 +0900230 * Tells the system UI that volume has changed on an active remote session.
RoboErik9c5b7cb2015-01-15 15:09:09 -0800231 */
232 public void notifyRemoteVolumeChanged(int flags, MediaSessionRecord session) {
Hyundo Moona055f132017-01-13 15:31:06 +0900233 if (mRvc == null || !session.isActive()) {
RoboErik9c5b7cb2015-01-15 15:09:09 -0800234 return;
235 }
236 try {
237 mRvc.remoteVolumeChanged(session.getControllerBinder(), flags);
238 } catch (Exception e) {
239 Log.wtf(TAG, "Error sending volume change to system UI.", e);
240 }
241 }
242
Jaewan Kim92dea332017-02-02 11:52:08 +0900243 public void onSessionPlaystateChanged(MediaSessionRecord record, int oldState, int newState) {
RoboErika8f95142014-05-05 14:23:49 -0700244 synchronized (mLock) {
Jaewan Kima7dce192017-02-16 17:10:54 +0900245 FullUserRecord user = getFullUserRecordLocked(record.getUserId());
246 if (user == null || !user.mPriorityStack.contains(record)) {
RoboErik4646d282014-05-13 10:13:04 -0700247 Log.d(TAG, "Unknown session changed playback state. Ignoring.");
248 return;
249 }
Jaewan Kim92dea332017-02-02 11:52:08 +0900250 user.mPriorityStack.onPlaystateChanged(record, oldState, newState);
RoboErika8f95142014-05-05 14:23:49 -0700251 }
252 }
253
RoboErik19c95182014-06-23 15:38:48 -0700254 public void onSessionPlaybackTypeChanged(MediaSessionRecord record) {
255 synchronized (mLock) {
Jaewan Kima7dce192017-02-16 17:10:54 +0900256 FullUserRecord user = getFullUserRecordLocked(record.getUserId());
257 if (user == null || !user.mPriorityStack.contains(record)) {
RoboErik19c95182014-06-23 15:38:48 -0700258 Log.d(TAG, "Unknown session changed playback type. Ignoring.");
259 return;
260 }
261 pushRemoteVolumeUpdateLocked(record.getUserId());
262 }
263 }
264
RoboErika278ea72014-04-24 14:49:01 -0700265 @Override
Jaewan Kim8f729082016-06-21 12:36:26 +0900266 public void onStartUser(int userId) {
267 if (DEBUG) Log.d(TAG, "onStartUser: " + userId);
RoboErik4646d282014-05-13 10:13:04 -0700268 updateUser();
269 }
270
271 @Override
Jaewan Kim8f729082016-06-21 12:36:26 +0900272 public void onSwitchUser(int userId) {
273 if (DEBUG) Log.d(TAG, "onSwitchUser: " + userId);
RoboErik4646d282014-05-13 10:13:04 -0700274 updateUser();
275 }
276
277 @Override
Jaewan Kim8f729082016-06-21 12:36:26 +0900278 public void onStopUser(int userId) {
279 if (DEBUG) Log.d(TAG, "onStopUser: " + userId);
RoboErik4646d282014-05-13 10:13:04 -0700280 synchronized (mLock) {
Jaewan Kima7dce192017-02-16 17:10:54 +0900281 FullUserRecord user = getFullUserRecordLocked(userId);
RoboErik4646d282014-05-13 10:13:04 -0700282 if (user != null) {
Jaewan Kima7dce192017-02-16 17:10:54 +0900283 if (user.mFullUserId == userId) {
284 user.destroySessionsForUserLocked(UserHandle.USER_ALL);
285 mUserRecords.remove(userId);
286 } else {
287 user.destroySessionsForUserLocked(userId);
288 }
RoboErik4646d282014-05-13 10:13:04 -0700289 }
Jaewan Kim8f729082016-06-21 12:36:26 +0900290 updateUser();
RoboErik4646d282014-05-13 10:13:04 -0700291 }
292 }
293
294 @Override
RoboErika278ea72014-04-24 14:49:01 -0700295 public void monitor() {
296 synchronized (mLock) {
297 // Check for deadlock
298 }
299 }
300
RoboErik4646d282014-05-13 10:13:04 -0700301 protected void enforcePhoneStatePermission(int pid, int uid) {
302 if (getContext().checkPermission(android.Manifest.permission.MODIFY_PHONE_STATE, pid, uid)
303 != PackageManager.PERMISSION_GRANTED) {
304 throw new SecurityException("Must hold the MODIFY_PHONE_STATE permission.");
305 }
306 }
307
RoboErik01fe6612014-02-13 14:19:04 -0800308 void sessionDied(MediaSessionRecord session) {
RoboErika278ea72014-04-24 14:49:01 -0700309 synchronized (mLock) {
RoboErik01fe6612014-02-13 14:19:04 -0800310 destroySessionLocked(session);
311 }
312 }
313
314 void destroySession(MediaSessionRecord session) {
RoboErika278ea72014-04-24 14:49:01 -0700315 synchronized (mLock) {
RoboErik01fe6612014-02-13 14:19:04 -0800316 destroySessionLocked(session);
317 }
318 }
319
RoboErik4646d282014-05-13 10:13:04 -0700320 private void updateUser() {
321 synchronized (mLock) {
Jaewan Kim8f729082016-06-21 12:36:26 +0900322 UserManager manager = (UserManager) getContext().getSystemService(Context.USER_SERVICE);
Jaewan Kima7dce192017-02-16 17:10:54 +0900323 mFullUserIds.clear();
324 List<UserInfo> allUsers = manager.getUsers();
325 if (allUsers != null) {
326 for (UserInfo userInfo : allUsers) {
327 if (userInfo.isManagedProfile()) {
328 mFullUserIds.put(userInfo.id, userInfo.profileGroupId);
329 } else {
330 mFullUserIds.put(userInfo.id, userInfo.id);
331 if (mUserRecords.get(userInfo.id) == null) {
332 mUserRecords.put(userInfo.id, new FullUserRecord(userInfo.id));
333 }
334 }
Jaewan Kim8f729082016-06-21 12:36:26 +0900335 }
RoboErik4646d282014-05-13 10:13:04 -0700336 }
Jaewan Kima7dce192017-02-16 17:10:54 +0900337 // Ensure that the current full user exists.
338 int currentFullUserId = ActivityManager.getCurrentUser();
339 mCurrentFullUserRecord = mUserRecords.get(currentFullUserId);
340 if (mCurrentFullUserRecord == null) {
341 Log.w(TAG, "Cannot find FullUserInfo for the current user " + currentFullUserId);
342 mCurrentFullUserRecord = new FullUserRecord(currentFullUserId);
343 mUserRecords.put(currentFullUserId, mCurrentFullUserRecord);
344 }
345 mFullUserIds.put(currentFullUserId, currentFullUserId);
RoboErik4646d282014-05-13 10:13:04 -0700346 }
347 }
348
RoboErik7aef77b2014-08-08 15:56:54 -0700349 private void updateActiveSessionListeners() {
350 synchronized (mLock) {
351 for (int i = mSessionsListeners.size() - 1; i >= 0; i--) {
352 SessionsListenerRecord listener = mSessionsListeners.get(i);
353 try {
354 enforceMediaPermissions(listener.mComponentName, listener.mPid, listener.mUid,
355 listener.mUserId);
356 } catch (SecurityException e) {
357 Log.i(TAG, "ActiveSessionsListener " + listener.mComponentName
358 + " is no longer authorized. Disconnecting.");
359 mSessionsListeners.remove(i);
360 try {
361 listener.mListener
362 .onActiveSessionsChanged(new ArrayList<MediaSession.Token>());
363 } catch (Exception e1) {
364 // ignore
365 }
366 }
367 }
368 }
369 }
370
RoboErik4646d282014-05-13 10:13:04 -0700371 /*
372 * When a session is removed several things need to happen.
373 * 1. We need to remove it from the relevant user.
374 * 2. We need to remove it from the priority stack.
375 * 3. We need to remove it from all sessions.
376 * 4. If this is the system priority session we need to clear it.
377 * 5. We need to unlink to death from the cb binder
378 * 6. We need to tell the session to do any final cleanup (onDestroy)
379 */
RoboErik01fe6612014-02-13 14:19:04 -0800380 private void destroySessionLocked(MediaSessionRecord session) {
Insun Kang30be970a2015-11-26 15:35:44 +0900381 if (DEBUG) {
Jaewan Kim5e1476e2016-07-19 22:25:39 +0900382 Log.d(TAG, "Destroying " + session);
Insun Kang30be970a2015-11-26 15:35:44 +0900383 }
Jaewan Kim101b4d52017-05-18 13:23:11 +0900384 FullUserRecord user = getFullUserRecordLocked(session.getUserId());
Jaewan Kima7dce192017-02-16 17:10:54 +0900385 if (mGlobalPrioritySession == session) {
386 mGlobalPrioritySession = null;
Jaewan Kim92dea332017-02-02 11:52:08 +0900387 if (session.isActive() && user != null) {
388 user.pushAddressedPlayerChangedLocked();
389 }
Jaewan Kim101b4d52017-05-18 13:23:11 +0900390 } else {
391 if (user != null) {
392 user.mPriorityStack.removeSession(session);
393 }
Jaewan Kima7dce192017-02-16 17:10:54 +0900394 }
RoboErik4646d282014-05-13 10:13:04 -0700395
396 try {
397 session.getCallback().asBinder().unlinkToDeath(session, 0);
398 } catch (Exception e) {
399 // ignore exceptions while destroying a session.
400 }
401 session.onDestroy();
Jaewan Kim92dea332017-02-02 11:52:08 +0900402 mHandler.postSessionsChanged(session.getUserId());
RoboErik01fe6612014-02-13 14:19:04 -0800403 }
404
405 private void enforcePackageName(String packageName, int uid) {
406 if (TextUtils.isEmpty(packageName)) {
407 throw new IllegalArgumentException("packageName may not be empty");
408 }
409 String[] packages = getContext().getPackageManager().getPackagesForUid(uid);
410 final int packageCount = packages.length;
411 for (int i = 0; i < packageCount; i++) {
412 if (packageName.equals(packages[i])) {
413 return;
414 }
415 }
416 throw new IllegalArgumentException("packageName is not owned by the calling process");
417 }
418
RoboErike7880d82014-04-30 12:48:25 -0700419 /**
420 * Checks a caller's authorization to register an IRemoteControlDisplay.
421 * Authorization is granted if one of the following is true:
422 * <ul>
423 * <li>the caller has android.Manifest.permission.MEDIA_CONTENT_CONTROL
424 * permission</li>
RoboErika5b02322014-05-07 17:05:49 -0700425 * <li>the caller's listener is one of the enabled notification listeners
426 * for the caller's user</li>
RoboErike7880d82014-04-30 12:48:25 -0700427 * </ul>
428 */
RoboErika5b02322014-05-07 17:05:49 -0700429 private void enforceMediaPermissions(ComponentName compName, int pid, int uid,
430 int resolvedUserId) {
Julia Reynoldsbb983d202017-01-06 09:54:20 -0500431 if (isCurrentVolumeController(uid, pid)) return;
RoboErike7880d82014-04-30 12:48:25 -0700432 if (getContext()
433 .checkPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL, pid, uid)
434 != PackageManager.PERMISSION_GRANTED
RoboErika5b02322014-05-07 17:05:49 -0700435 && !isEnabledNotificationListener(compName, UserHandle.getUserId(uid),
436 resolvedUserId)) {
RoboErike7880d82014-04-30 12:48:25 -0700437 throw new SecurityException("Missing permission to control media.");
438 }
439 }
440
Julia Reynoldsbb983d202017-01-06 09:54:20 -0500441 private boolean isCurrentVolumeController(int uid, int pid) {
442 return getContext().checkPermission(android.Manifest.permission.STATUS_BAR_SERVICE,
443 pid, uid) == PackageManager.PERMISSION_GRANTED;
John Spurlockbe19ed02015-02-22 10:57:55 -0500444 }
445
446 private void enforceSystemUiPermission(String action, int pid, int uid) {
Julia Reynoldsbb983d202017-01-06 09:54:20 -0500447 if (!isCurrentVolumeController(uid, pid)) {
RoboErik19c95182014-06-23 15:38:48 -0700448 throw new SecurityException("Only system ui may " + action);
449 }
450 }
451
RoboErika5b02322014-05-07 17:05:49 -0700452 /**
453 * This checks if the component is an enabled notification listener for the
454 * specified user. Enabled components may only operate on behalf of the user
455 * they're running as.
456 *
457 * @param compName The component that is enabled.
458 * @param userId The user id of the caller.
459 * @param forUserId The user id they're making the request on behalf of.
460 * @return True if the component is enabled, false otherwise
461 */
462 private boolean isEnabledNotificationListener(ComponentName compName, int userId,
463 int forUserId) {
464 if (userId != forUserId) {
465 // You may not access another user's content as an enabled listener.
466 return false;
467 }
RoboErik51fa6bc2014-06-20 14:59:58 -0700468 if (DEBUG) {
469 Log.d(TAG, "Checking if enabled notification listener " + compName);
470 }
RoboErike7880d82014-04-30 12:48:25 -0700471 if (compName != null) {
Julia Reynoldsb852e562017-06-06 16:14:18 -0400472 try {
473 return mNotificationManager.isNotificationListenerAccessGrantedForUser(
474 compName, userId);
475 } catch(RemoteException e) {
476 Log.w(TAG, "Dead NotificationManager in isEnabledNotificationListener", e);
RoboErike7880d82014-04-30 12:48:25 -0700477 }
478 }
479 return false;
480 }
481
RoboErika5b02322014-05-07 17:05:49 -0700482 private MediaSessionRecord createSessionInternal(int callerPid, int callerUid, int userId,
RoboErik4646d282014-05-13 10:13:04 -0700483 String callerPackageName, ISessionCallback cb, String tag) throws RemoteException {
RoboErik01fe6612014-02-13 14:19:04 -0800484 synchronized (mLock) {
RoboErika5b02322014-05-07 17:05:49 -0700485 return createSessionLocked(callerPid, callerUid, userId, callerPackageName, cb, tag);
RoboErik01fe6612014-02-13 14:19:04 -0800486 }
487 }
488
RoboErik4646d282014-05-13 10:13:04 -0700489 /*
490 * When a session is created the following things need to happen.
RoboErik8a2cfc32014-05-16 11:19:38 -0700491 * 1. Its callback binder needs a link to death
RoboErik4646d282014-05-13 10:13:04 -0700492 * 2. It needs to be added to all sessions.
493 * 3. It needs to be added to the priority stack.
494 * 4. It needs to be added to the relevant user record.
495 */
RoboErika5b02322014-05-07 17:05:49 -0700496 private MediaSessionRecord createSessionLocked(int callerPid, int callerUid, int userId,
497 String callerPackageName, ISessionCallback cb, String tag) {
Jaewan Kima7dce192017-02-16 17:10:54 +0900498 FullUserRecord user = getFullUserRecordLocked(userId);
Dongwon Kang8cf39c52016-07-29 13:20:39 -0700499 if (user == null) {
500 Log.wtf(TAG, "Request from invalid user: " + userId);
501 throw new RuntimeException("Session request from invalid user.");
502 }
503
RoboErika5b02322014-05-07 17:05:49 -0700504 final MediaSessionRecord session = new MediaSessionRecord(callerPid, callerUid, userId,
Jaewan Kim92dea332017-02-02 11:52:08 +0900505 callerPackageName, cb, tag, this, mHandler.getLooper());
RoboErik01fe6612014-02-13 14:19:04 -0800506 try {
507 cb.asBinder().linkToDeath(session, 0);
508 } catch (RemoteException e) {
509 throw new RuntimeException("Media Session owner died prematurely.", e);
510 }
RoboErik4646d282014-05-13 10:13:04 -0700511
Jaewan Kim101b4d52017-05-18 13:23:11 +0900512 user.mPriorityStack.addSession(session);
Jaewan Kim92dea332017-02-02 11:52:08 +0900513 mHandler.postSessionsChanged(userId);
RoboErik2e7a9162014-06-04 16:53:45 -0700514
RoboErik01fe6612014-02-13 14:19:04 -0800515 if (DEBUG) {
Jaewan Kim5e1476e2016-07-19 22:25:39 +0900516 Log.d(TAG, "Created session for " + callerPackageName + " with tag " + tag);
RoboErik01fe6612014-02-13 14:19:04 -0800517 }
518 return session;
519 }
520
RoboErik2e7a9162014-06-04 16:53:45 -0700521 private int findIndexOfSessionsListenerLocked(IActiveSessionsListener listener) {
522 for (int i = mSessionsListeners.size() - 1; i >= 0; i--) {
RoboErika08adb242014-11-21 18:28:18 -0800523 if (mSessionsListeners.get(i).mListener.asBinder() == listener.asBinder()) {
RoboErik2e7a9162014-06-04 16:53:45 -0700524 return i;
525 }
526 }
527 return -1;
528 }
529
RoboErik2e7a9162014-06-04 16:53:45 -0700530 private void pushSessionsChanged(int userId) {
531 synchronized (mLock) {
Jaewan Kima7dce192017-02-16 17:10:54 +0900532 FullUserRecord user = getFullUserRecordLocked(userId);
533 if (user == null) {
534 Log.w(TAG, "pushSessionsChanged failed. No user with id=" + userId);
535 return;
536 }
Jaewan Kim101b4d52017-05-18 13:23:11 +0900537 List<MediaSessionRecord> records = getActiveSessionsLocked(userId);
RoboErik2e7a9162014-06-04 16:53:45 -0700538 int size = records.size();
Jeff Browndba34ba2014-06-24 20:46:03 -0700539 ArrayList<MediaSession.Token> tokens = new ArrayList<MediaSession.Token>();
RoboErik2e7a9162014-06-04 16:53:45 -0700540 for (int i = 0; i < size; i++) {
Jeff Browndba34ba2014-06-24 20:46:03 -0700541 tokens.add(new MediaSession.Token(records.get(i).getControllerBinder()));
RoboErik2e7a9162014-06-04 16:53:45 -0700542 }
RoboErik19c95182014-06-23 15:38:48 -0700543 pushRemoteVolumeUpdateLocked(userId);
RoboErik2e7a9162014-06-04 16:53:45 -0700544 for (int i = mSessionsListeners.size() - 1; i >= 0; i--) {
545 SessionsListenerRecord record = mSessionsListeners.get(i);
546 if (record.mUserId == UserHandle.USER_ALL || record.mUserId == userId) {
547 try {
548 record.mListener.onActiveSessionsChanged(tokens);
549 } catch (RemoteException e) {
550 Log.w(TAG, "Dead ActiveSessionsListener in pushSessionsChanged, removing",
551 e);
552 mSessionsListeners.remove(i);
553 }
554 }
555 }
556 }
557 }
558
RoboErik19c95182014-06-23 15:38:48 -0700559 private void pushRemoteVolumeUpdateLocked(int userId) {
560 if (mRvc != null) {
561 try {
Jaewan Kima7dce192017-02-16 17:10:54 +0900562 FullUserRecord user = getFullUserRecordLocked(userId);
563 if (user == null) {
564 Log.w(TAG, "pushRemoteVolumeUpdateLocked failed. No user with id=" + userId);
565 return;
566 }
567 MediaSessionRecord record = user.mPriorityStack.getDefaultRemoteSession(userId);
RoboErik19c95182014-06-23 15:38:48 -0700568 mRvc.updateRemoteController(record == null ? null : record.getControllerBinder());
569 } catch (RemoteException e) {
570 Log.wtf(TAG, "Error sending default remote volume to sys ui.", e);
571 }
572 }
573 }
574
Jaewan Kim92dea332017-02-02 11:52:08 +0900575 /**
576 * Called when the media button receiver for the {@param record} is changed.
577 *
578 * @param record the media session whose media button receiver is updated.
579 */
580 public void onMediaButtonReceiverChanged(MediaSessionRecord record) {
581 synchronized (mLock) {
582 FullUserRecord user = getFullUserRecordLocked(record.getUserId());
583 MediaSessionRecord mediaButtonSession =
584 user.mPriorityStack.getMediaButtonSession();
585 if (record == mediaButtonSession) {
586 user.rememberMediaButtonReceiverLocked(mediaButtonSession);
587 }
588 }
589 }
590
Jaewan Kim50269362016-12-23 11:22:02 +0900591 private String getCallingPackageName(int uid) {
592 String[] packages = getContext().getPackageManager().getPackagesForUid(uid);
593 if (packages != null && packages.length > 0) {
594 return packages[0];
595 }
596 return "";
597 }
598
Jaewan Kimd61a87b2017-02-17 23:14:10 +0900599 private void dispatchVolumeKeyLongPressLocked(KeyEvent keyEvent) {
Jaewan Kimd61a87b2017-02-17 23:14:10 +0900600 try {
Jaewan Kima7dce192017-02-16 17:10:54 +0900601 mCurrentFullUserRecord.mOnVolumeKeyLongPressListener.onVolumeKeyLongPress(keyEvent);
Jaewan Kimd61a87b2017-02-17 23:14:10 +0900602 } catch (RemoteException e) {
603 Log.w(TAG, "Failed to send " + keyEvent + " to volume key long-press listener");
604 }
605 }
606
Jaewan Kima7dce192017-02-16 17:10:54 +0900607 private FullUserRecord getFullUserRecordLocked(int userId) {
608 int fullUserId = mFullUserIds.get(userId, -1);
609 if (fullUserId < 0) {
610 return null;
611 }
612 return mUserRecords.get(fullUserId);
613 }
614
RoboErik4646d282014-05-13 10:13:04 -0700615 /**
Jaewan Kima7dce192017-02-16 17:10:54 +0900616 * Information about a full user and its corresponding managed profiles.
617 *
618 * <p>Since the full user runs together with its managed profiles, a user wouldn't differentiate
619 * them when he/she presses a media/volume button. So keeping media sessions for them in one
620 * place makes more sense and increases the readability.</p>
621 * <p>The contents of this object is guarded by {@link #mLock}.
RoboErik4646d282014-05-13 10:13:04 -0700622 */
Jaewan Kim92dea332017-02-02 11:52:08 +0900623 final class FullUserRecord implements MediaSessionStack.OnMediaButtonSessionChangedListener {
Jaewan Kima7dce192017-02-16 17:10:54 +0900624 private static final String COMPONENT_NAME_USER_ID_DELIM = ",";
625 private final int mFullUserId;
Jaewan Kim92dea332017-02-02 11:52:08 +0900626 private final MediaSessionStack mPriorityStack;
RoboErikb214efb2014-07-24 13:20:30 -0700627 private PendingIntent mLastMediaButtonReceiver;
RoboErikc8f92d12015-01-05 16:48:07 -0800628 private ComponentName mRestoredMediaButtonReceiver;
Jaewan Kima7dce192017-02-16 17:10:54 +0900629 private int mRestoredMediaButtonReceiverUserId;
RoboErik4646d282014-05-13 10:13:04 -0700630
Jaewan Kim50269362016-12-23 11:22:02 +0900631 private IOnVolumeKeyLongPressListener mOnVolumeKeyLongPressListener;
632 private int mOnVolumeKeyLongPressListenerUid;
633 private KeyEvent mInitialDownVolumeKeyEvent;
634 private int mInitialDownVolumeStream;
635 private boolean mInitialDownMusicOnly;
636
Jaewan Kim6e2b01c2017-01-19 16:33:14 -0800637 private IOnMediaKeyListener mOnMediaKeyListener;
638 private int mOnMediaKeyListenerUid;
Jaewan Kima7dce192017-02-16 17:10:54 +0900639 private ICallback mCallback;
Jaewan Kim6e2b01c2017-01-19 16:33:14 -0800640
Jaewan Kima7dce192017-02-16 17:10:54 +0900641 public FullUserRecord(int fullUserId) {
642 mFullUserId = fullUserId;
Jaewan Kim92dea332017-02-02 11:52:08 +0900643 mPriorityStack = new MediaSessionStack(mAudioPlaybackMonitor, this);
Jaewan Kima7dce192017-02-16 17:10:54 +0900644 // Restore the remembered media button receiver before the boot.
645 String mediaButtonReceiver = Settings.Secure.getStringForUser(mContentResolver,
646 Settings.System.MEDIA_BUTTON_RECEIVER, mFullUserId);
647 if (mediaButtonReceiver == null) {
648 return;
649 }
650 String[] tokens = mediaButtonReceiver.split(COMPONENT_NAME_USER_ID_DELIM);
651 if (tokens == null || tokens.length != 2) {
652 return;
653 }
654 mRestoredMediaButtonReceiver = ComponentName.unflattenFromString(tokens[0]);
655 mRestoredMediaButtonReceiverUserId = Integer.parseInt(tokens[1]);
RoboErik4646d282014-05-13 10:13:04 -0700656 }
657
Jaewan Kima7dce192017-02-16 17:10:54 +0900658 public void destroySessionsForUserLocked(int userId) {
Jaewan Kim92dea332017-02-02 11:52:08 +0900659 List<MediaSessionRecord> sessions = mPriorityStack.getPriorityList(false, userId);
Jaewan Kima7dce192017-02-16 17:10:54 +0900660 for (MediaSessionRecord session : sessions) {
RoboErik4646d282014-05-13 10:13:04 -0700661 MediaSessionService.this.destroySessionLocked(session);
RoboErik4646d282014-05-13 10:13:04 -0700662 }
663 }
664
RoboErik4646d282014-05-13 10:13:04 -0700665 public void dumpLocked(PrintWriter pw, String prefix) {
Jaewan Kima7dce192017-02-16 17:10:54 +0900666 pw.print(prefix + "Record for full_user=" + mFullUserId);
667 // Dump managed profile user ids associated with this user.
668 int size = mFullUserIds.size();
669 for (int i = 0; i < size; i++) {
670 if (mFullUserIds.keyAt(i) != mFullUserIds.valueAt(i)
671 && mFullUserIds.valueAt(i) == mFullUserId) {
672 pw.print(", profile_user=" + mFullUserIds.keyAt(i));
673 }
674 }
675 pw.println();
RoboErik4646d282014-05-13 10:13:04 -0700676 String indent = prefix + " ";
Jaewan Kima7dce192017-02-16 17:10:54 +0900677 pw.println(indent + "Volume key long-press listener: " + mOnVolumeKeyLongPressListener);
678 pw.println(indent + "Volume key long-press listener package: " +
Jaewan Kim50269362016-12-23 11:22:02 +0900679 getCallingPackageName(mOnVolumeKeyLongPressListenerUid));
Jaewan Kim6e2b01c2017-01-19 16:33:14 -0800680 pw.println(indent + "Media key listener: " + mOnMediaKeyListener);
681 pw.println(indent + "Media key listener package: " +
682 getCallingPackageName(mOnMediaKeyListenerUid));
Jaewan Kima7dce192017-02-16 17:10:54 +0900683 pw.println(indent + "Callback: " + mCallback);
684 pw.println(indent + "Last MediaButtonReceiver: " + mLastMediaButtonReceiver);
685 pw.println(indent + "Restored MediaButtonReceiver: " + mRestoredMediaButtonReceiver);
686 mPriorityStack.dump(pw, indent);
687 }
688
Jaewan Kim92dea332017-02-02 11:52:08 +0900689 @Override
690 public void onMediaButtonSessionChanged(MediaSessionRecord oldMediaButtonSession,
691 MediaSessionRecord newMediaButtonSession) {
692 if (DEBUG_KEY_EVENT) {
Jaewan Kim98e4aaf2017-05-12 17:06:47 +0900693 Log.d(TAG, "Media button session is changed to " + newMediaButtonSession);
Jaewan Kim92dea332017-02-02 11:52:08 +0900694 }
695 synchronized (mLock) {
696 if (oldMediaButtonSession != null) {
697 mHandler.postSessionsChanged(oldMediaButtonSession.getUserId());
698 }
699 if (newMediaButtonSession != null) {
700 rememberMediaButtonReceiverLocked(newMediaButtonSession);
701 mHandler.postSessionsChanged(newMediaButtonSession.getUserId());
702 }
703 pushAddressedPlayerChangedLocked();
704 }
705 }
706
707 // Remember media button receiver and keep it in the persistent storage.
708 public void rememberMediaButtonReceiverLocked(MediaSessionRecord record) {
Jaewan Kima7dce192017-02-16 17:10:54 +0900709 PendingIntent receiver = record.getMediaButtonReceiver();
Jaewan Kima7dce192017-02-16 17:10:54 +0900710 mLastMediaButtonReceiver = receiver;
Jaewan Kim92dea332017-02-02 11:52:08 +0900711 mRestoredMediaButtonReceiver = null;
712 String componentName = "";
713 if (receiver != null) {
714 ComponentName component = receiver.getIntent().getComponent();
715 if (component != null
716 && record.getPackageName().equals(component.getPackageName())) {
717 componentName = component.flattenToString();
718 }
RoboErik4646d282014-05-13 10:13:04 -0700719 }
Jaewan Kim92dea332017-02-02 11:52:08 +0900720 Settings.Secure.putStringForUser(mContentResolver,
721 Settings.System.MEDIA_BUTTON_RECEIVER,
722 componentName + COMPONENT_NAME_USER_ID_DELIM + record.getUserId(),
723 mFullUserId);
RoboErik4646d282014-05-13 10:13:04 -0700724 }
RoboErikc8f92d12015-01-05 16:48:07 -0800725
Jaewan Kima7dce192017-02-16 17:10:54 +0900726 private void pushAddressedPlayerChangedLocked() {
727 if (mCallback == null) {
728 return;
RoboErikc8f92d12015-01-05 16:48:07 -0800729 }
Jaewan Kima7dce192017-02-16 17:10:54 +0900730 try {
731 MediaSessionRecord mediaButtonSession = getMediaButtonSessionLocked();
732 if (mediaButtonSession != null) {
733 mCallback.onAddressedPlayerChangedToMediaSession(
734 new MediaSession.Token(mediaButtonSession.getControllerBinder()));
735 } else if (mCurrentFullUserRecord.mLastMediaButtonReceiver != null) {
736 mCallback.onAddressedPlayerChangedToMediaButtonReceiver(
737 mCurrentFullUserRecord.mLastMediaButtonReceiver
738 .getIntent().getComponent());
739 } else if (mCurrentFullUserRecord.mRestoredMediaButtonReceiver != null) {
740 mCallback.onAddressedPlayerChangedToMediaButtonReceiver(
741 mCurrentFullUserRecord.mRestoredMediaButtonReceiver);
742 }
743 } catch (RemoteException e) {
744 Log.w(TAG, "Failed to pushAddressedPlayerChangedLocked", e);
745 }
746 }
747
748 private MediaSessionRecord getMediaButtonSessionLocked() {
Jaewan Kim92dea332017-02-02 11:52:08 +0900749 return isGlobalPriorityActiveLocked()
750 ? mGlobalPrioritySession : mPriorityStack.getMediaButtonSession();
RoboErikc8f92d12015-01-05 16:48:07 -0800751 }
RoboErik4646d282014-05-13 10:13:04 -0700752 }
753
RoboErik2e7a9162014-06-04 16:53:45 -0700754 final class SessionsListenerRecord implements IBinder.DeathRecipient {
755 private final IActiveSessionsListener mListener;
RoboErik7aef77b2014-08-08 15:56:54 -0700756 private final ComponentName mComponentName;
RoboErik2e7a9162014-06-04 16:53:45 -0700757 private final int mUserId;
RoboErik7aef77b2014-08-08 15:56:54 -0700758 private final int mPid;
759 private final int mUid;
RoboErik2e7a9162014-06-04 16:53:45 -0700760
RoboErik7aef77b2014-08-08 15:56:54 -0700761 public SessionsListenerRecord(IActiveSessionsListener listener,
762 ComponentName componentName,
763 int userId, int pid, int uid) {
RoboErik2e7a9162014-06-04 16:53:45 -0700764 mListener = listener;
RoboErik7aef77b2014-08-08 15:56:54 -0700765 mComponentName = componentName;
RoboErik2e7a9162014-06-04 16:53:45 -0700766 mUserId = userId;
RoboErik7aef77b2014-08-08 15:56:54 -0700767 mPid = pid;
768 mUid = uid;
RoboErik2e7a9162014-06-04 16:53:45 -0700769 }
770
771 @Override
772 public void binderDied() {
773 synchronized (mLock) {
774 mSessionsListeners.remove(this);
775 }
776 }
777 }
778
RoboErik7aef77b2014-08-08 15:56:54 -0700779 final class SettingsObserver extends ContentObserver {
780 private final Uri mSecureSettingsUri = Settings.Secure.getUriFor(
781 Settings.Secure.ENABLED_NOTIFICATION_LISTENERS);
782
783 private SettingsObserver() {
784 super(null);
785 }
786
787 private void observe() {
788 mContentResolver.registerContentObserver(mSecureSettingsUri,
789 false, this, UserHandle.USER_ALL);
790 }
791
792 @Override
793 public void onChange(boolean selfChange, Uri uri) {
794 updateActiveSessionListeners();
795 }
796 }
797
RoboErik07c70772014-03-20 13:33:52 -0700798 class SessionManagerImpl extends ISessionManager.Stub {
RoboErik8a2cfc32014-05-16 11:19:38 -0700799 private static final String EXTRA_WAKELOCK_ACQUIRED =
800 "android.media.AudioService.WAKELOCK_ACQUIRED";
801 private static final int WAKELOCK_RELEASE_ON_FINISHED = 1980; // magic number
802
RoboErik9a9d0b52014-05-20 14:53:39 -0700803 private boolean mVoiceButtonDown = false;
804 private boolean mVoiceButtonHandled = false;
805
RoboErik07c70772014-03-20 13:33:52 -0700806 @Override
RoboErika5b02322014-05-07 17:05:49 -0700807 public ISession createSession(String packageName, ISessionCallback cb, String tag,
808 int userId) throws RemoteException {
RoboErik01fe6612014-02-13 14:19:04 -0800809 final int pid = Binder.getCallingPid();
810 final int uid = Binder.getCallingUid();
811 final long token = Binder.clearCallingIdentity();
812 try {
813 enforcePackageName(packageName, uid);
RoboErika5b02322014-05-07 17:05:49 -0700814 int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId,
815 false /* allowAll */, true /* requireFull */, "createSession", packageName);
RoboErik01fe6612014-02-13 14:19:04 -0800816 if (cb == null) {
817 throw new IllegalArgumentException("Controller callback cannot be null");
818 }
RoboErika5b02322014-05-07 17:05:49 -0700819 return createSessionInternal(pid, uid, resolvedUserId, packageName, cb, tag)
820 .getSessionBinder();
RoboErike7880d82014-04-30 12:48:25 -0700821 } finally {
822 Binder.restoreCallingIdentity(token);
823 }
824 }
825
826 @Override
RoboErika5b02322014-05-07 17:05:49 -0700827 public List<IBinder> getSessions(ComponentName componentName, int userId) {
RoboErike7880d82014-04-30 12:48:25 -0700828 final int pid = Binder.getCallingPid();
829 final int uid = Binder.getCallingUid();
830 final long token = Binder.clearCallingIdentity();
831
832 try {
RoboErik2e7a9162014-06-04 16:53:45 -0700833 int resolvedUserId = verifySessionsRequest(componentName, userId, pid, uid);
RoboErike7880d82014-04-30 12:48:25 -0700834 ArrayList<IBinder> binders = new ArrayList<IBinder>();
835 synchronized (mLock) {
Jaewan Kim101b4d52017-05-18 13:23:11 +0900836 List<MediaSessionRecord> records = getActiveSessionsLocked(resolvedUserId);
837 for (MediaSessionRecord record : records) {
838 binders.add(record.getControllerBinder().asBinder());
RoboErike7880d82014-04-30 12:48:25 -0700839 }
840 }
841 return binders;
RoboErik01fe6612014-02-13 14:19:04 -0800842 } finally {
843 Binder.restoreCallingIdentity(token);
844 }
845 }
RoboErika278ea72014-04-24 14:49:01 -0700846
RoboErik2e7a9162014-06-04 16:53:45 -0700847 @Override
848 public void addSessionsListener(IActiveSessionsListener listener,
849 ComponentName componentName, int userId) throws RemoteException {
850 final int pid = Binder.getCallingPid();
851 final int uid = Binder.getCallingUid();
852 final long token = Binder.clearCallingIdentity();
853
854 try {
855 int resolvedUserId = verifySessionsRequest(componentName, userId, pid, uid);
856 synchronized (mLock) {
857 int index = findIndexOfSessionsListenerLocked(listener);
858 if (index != -1) {
859 Log.w(TAG, "ActiveSessionsListener is already added, ignoring");
860 return;
861 }
862 SessionsListenerRecord record = new SessionsListenerRecord(listener,
RoboErik7aef77b2014-08-08 15:56:54 -0700863 componentName, resolvedUserId, pid, uid);
RoboErik2e7a9162014-06-04 16:53:45 -0700864 try {
865 listener.asBinder().linkToDeath(record, 0);
866 } catch (RemoteException e) {
867 Log.e(TAG, "ActiveSessionsListener is dead, ignoring it", e);
868 return;
869 }
870 mSessionsListeners.add(record);
871 }
872 } finally {
873 Binder.restoreCallingIdentity(token);
874 }
875 }
876
877 @Override
878 public void removeSessionsListener(IActiveSessionsListener listener)
879 throws RemoteException {
880 synchronized (mLock) {
881 int index = findIndexOfSessionsListenerLocked(listener);
882 if (index != -1) {
883 SessionsListenerRecord record = mSessionsListeners.remove(index);
884 try {
885 record.mListener.asBinder().unlinkToDeath(record, 0);
886 } catch (Exception e) {
887 // ignore exceptions, the record is being removed
888 }
889 }
890 }
891 }
892
RoboErik8a2cfc32014-05-16 11:19:38 -0700893 /**
894 * Handles the dispatching of the media button events to one of the
895 * registered listeners, or if there was none, broadcast an
896 * ACTION_MEDIA_BUTTON intent to the rest of the system.
897 *
898 * @param keyEvent a non-null KeyEvent whose key code is one of the
899 * supported media buttons
900 * @param needWakeLock true if a PARTIAL_WAKE_LOCK needs to be held
901 * while this key event is dispatched.
902 */
903 @Override
904 public void dispatchMediaKeyEvent(KeyEvent keyEvent, boolean needWakeLock) {
905 if (keyEvent == null || !KeyEvent.isMediaKey(keyEvent.getKeyCode())) {
906 Log.w(TAG, "Attempted to dispatch null or non-media key event.");
907 return;
908 }
Jeff Brown38d3feb2015-03-19 18:26:30 -0700909
RoboErik8a2cfc32014-05-16 11:19:38 -0700910 final int pid = Binder.getCallingPid();
911 final int uid = Binder.getCallingUid();
912 final long token = Binder.clearCallingIdentity();
RoboErik8a2cfc32014-05-16 11:19:38 -0700913 try {
Jeff Brown221a8272015-03-23 13:53:09 -0700914 if (DEBUG) {
915 Log.d(TAG, "dispatchMediaKeyEvent, pid=" + pid + ", uid=" + uid + ", event="
916 + keyEvent);
917 }
Jeff Brown38d3feb2015-03-19 18:26:30 -0700918 if (!isUserSetupComplete()) {
919 // Global media key handling can have the side-effect of starting new
920 // activities which is undesirable while setup is in progress.
921 Slog.i(TAG, "Not dispatching media key event because user "
922 + "setup is in progress.");
923 return;
924 }
925
RoboErik8a2cfc32014-05-16 11:19:38 -0700926 synchronized (mLock) {
Jaewan Kima7dce192017-02-16 17:10:54 +0900927 boolean isGlobalPriorityActive = isGlobalPriorityActiveLocked();
Jaewan Kim51255012017-02-24 16:19:14 +0900928 if (isGlobalPriorityActive && uid != Process.SYSTEM_UID) {
929 // Prevent dispatching key event through reflection while the global
930 // priority session is active.
931 Slog.i(TAG, "Only the system can dispatch media key event "
932 + "to the global priority session.");
933 return;
934 }
Jaewan Kim98003d32017-02-24 18:33:04 +0900935 if (!isGlobalPriorityActive) {
Jaewan Kima7dce192017-02-16 17:10:54 +0900936 if (mCurrentFullUserRecord.mOnMediaKeyListener != null) {
Jaewan Kim98003d32017-02-24 18:33:04 +0900937 if (DEBUG_KEY_EVENT) {
Jaewan Kima7dce192017-02-16 17:10:54 +0900938 Log.d(TAG, "Send " + keyEvent + " to the media key listener");
Jaewan Kim98003d32017-02-24 18:33:04 +0900939 }
940 try {
Jaewan Kima7dce192017-02-16 17:10:54 +0900941 mCurrentFullUserRecord.mOnMediaKeyListener.onMediaKey(keyEvent,
Jaewan Kim98003d32017-02-24 18:33:04 +0900942 new MediaKeyListenerResultReceiver(keyEvent, needWakeLock));
943 return;
944 } catch (RemoteException e) {
Jaewan Kima7dce192017-02-16 17:10:54 +0900945 Log.w(TAG, "Failed to send " + keyEvent
946 + " to the media key listener");
Jaewan Kim98003d32017-02-24 18:33:04 +0900947 }
948 }
949 }
Jaewan Kim51255012017-02-24 16:19:14 +0900950 if (!isGlobalPriorityActive && isVoiceKey(keyEvent.getKeyCode())) {
Jaewan Kim6e2b01c2017-01-19 16:33:14 -0800951 handleVoiceKeyEventLocked(keyEvent, needWakeLock);
RoboErik8a2cfc32014-05-16 11:19:38 -0700952 } else {
Jaewan Kim98003d32017-02-24 18:33:04 +0900953 dispatchMediaKeyEventLocked(keyEvent, needWakeLock);
RoboErik8a2cfc32014-05-16 11:19:38 -0700954 }
955 }
956 } finally {
957 Binder.restoreCallingIdentity(token);
958 }
959 }
960
RoboErika278ea72014-04-24 14:49:01 -0700961 @Override
Jaewan Kimbd16f452017-02-03 16:21:38 +0900962 public void setCallback(ICallback callback) {
963 final int pid = Binder.getCallingPid();
964 final int uid = Binder.getCallingUid();
965 final long token = Binder.clearCallingIdentity();
966 try {
Amith Yamasanie259ad22017-04-24 11:30:19 -0700967 if (!UserHandle.isSameApp(uid, Process.BLUETOOTH_UID)) {
Jaewan Kimbd16f452017-02-03 16:21:38 +0900968 throw new SecurityException("Only Bluetooth service processes can set"
969 + " Callback");
970 }
971 synchronized (mLock) {
Jaewan Kima7dce192017-02-16 17:10:54 +0900972 int userId = UserHandle.getUserId(uid);
973 FullUserRecord user = getFullUserRecordLocked(userId);
974 if (user == null || user.mFullUserId != userId) {
975 Log.w(TAG, "Only the full user can set the callback"
976 + ", userId=" + userId);
977 return;
978 }
979 user.mCallback = callback;
980 Log.d(TAG, "The callback " + user.mCallback
Jaewan Kimbd16f452017-02-03 16:21:38 +0900981 + " is set by " + getCallingPackageName(uid));
Jaewan Kima7dce192017-02-16 17:10:54 +0900982 if (user.mCallback == null) {
Jaewan Kimbd16f452017-02-03 16:21:38 +0900983 return;
984 }
985 try {
Jaewan Kima7dce192017-02-16 17:10:54 +0900986 user.mCallback.asBinder().linkToDeath(
Jaewan Kimbd16f452017-02-03 16:21:38 +0900987 new IBinder.DeathRecipient() {
988 @Override
989 public void binderDied() {
990 synchronized (mLock) {
Jaewan Kima7dce192017-02-16 17:10:54 +0900991 user.mCallback = null;
Jaewan Kimbd16f452017-02-03 16:21:38 +0900992 }
993 }
994 }, 0);
Jaewan Kima7dce192017-02-16 17:10:54 +0900995 user.pushAddressedPlayerChangedLocked();
Jaewan Kimbd16f452017-02-03 16:21:38 +0900996 } catch (RemoteException e) {
997 Log.w(TAG, "Failed to set callback", e);
Jaewan Kima7dce192017-02-16 17:10:54 +0900998 user.mCallback = null;
Jaewan Kimbd16f452017-02-03 16:21:38 +0900999 }
1000 }
1001 } finally {
1002 Binder.restoreCallingIdentity(token);
1003 }
1004 }
1005
1006 @Override
Jaewan Kim50269362016-12-23 11:22:02 +09001007 public void setOnVolumeKeyLongPressListener(IOnVolumeKeyLongPressListener listener) {
1008 final int pid = Binder.getCallingPid();
1009 final int uid = Binder.getCallingUid();
1010 final long token = Binder.clearCallingIdentity();
1011 try {
1012 // Enforce SET_VOLUME_KEY_LONG_PRESS_LISTENER permission.
1013 if (getContext().checkPermission(
1014 android.Manifest.permission.SET_VOLUME_KEY_LONG_PRESS_LISTENER, pid, uid)
1015 != PackageManager.PERMISSION_GRANTED) {
1016 throw new SecurityException("Must hold the SET_VOLUME_KEY_LONG_PRESS_LISTENER" +
1017 " permission.");
1018 }
1019
1020 synchronized (mLock) {
Jaewan Kima7dce192017-02-16 17:10:54 +09001021 int userId = UserHandle.getUserId(uid);
1022 FullUserRecord user = getFullUserRecordLocked(userId);
1023 if (user == null || user.mFullUserId != userId) {
1024 Log.w(TAG, "Only the full user can set the volume key long-press listener"
1025 + ", userId=" + userId);
1026 return;
1027 }
Jaewan Kim50269362016-12-23 11:22:02 +09001028 if (user.mOnVolumeKeyLongPressListener != null &&
1029 user.mOnVolumeKeyLongPressListenerUid != uid) {
Jaewan Kima7dce192017-02-16 17:10:54 +09001030 Log.w(TAG, "The volume key long-press listener cannot be reset"
1031 + " by another app , mOnVolumeKeyLongPressListener="
1032 + user.mOnVolumeKeyLongPressListenerUid
1033 + ", uid=" + uid);
Jaewan Kim50269362016-12-23 11:22:02 +09001034 return;
1035 }
1036
1037 user.mOnVolumeKeyLongPressListener = listener;
1038 user.mOnVolumeKeyLongPressListenerUid = uid;
1039
Jaewan Kima7dce192017-02-16 17:10:54 +09001040 Log.d(TAG, "The volume key long-press listener "
Jaewan Kim50269362016-12-23 11:22:02 +09001041 + listener + " is set by " + getCallingPackageName(uid));
1042
1043 if (user.mOnVolumeKeyLongPressListener != null) {
1044 try {
1045 user.mOnVolumeKeyLongPressListener.asBinder().linkToDeath(
1046 new IBinder.DeathRecipient() {
1047 @Override
1048 public void binderDied() {
1049 synchronized (mLock) {
1050 user.mOnVolumeKeyLongPressListener = null;
1051 }
1052 }
1053 }, 0);
1054 } catch (RemoteException e) {
1055 Log.w(TAG, "Failed to set death recipient "
1056 + user.mOnVolumeKeyLongPressListener);
1057 user.mOnVolumeKeyLongPressListener = null;
1058 }
1059 }
1060 }
1061 } finally {
1062 Binder.restoreCallingIdentity(token);
1063 }
1064 }
1065
Jaewan Kim6e2b01c2017-01-19 16:33:14 -08001066 @Override
1067 public void setOnMediaKeyListener(IOnMediaKeyListener listener) {
1068 final int pid = Binder.getCallingPid();
1069 final int uid = Binder.getCallingUid();
1070 final long token = Binder.clearCallingIdentity();
1071 try {
1072 // Enforce SET_MEDIA_KEY_LISTENER permission.
1073 if (getContext().checkPermission(
1074 android.Manifest.permission.SET_MEDIA_KEY_LISTENER, pid, uid)
1075 != PackageManager.PERMISSION_GRANTED) {
1076 throw new SecurityException("Must hold the SET_MEDIA_KEY_LISTENER" +
1077 " permission.");
1078 }
1079
1080 synchronized (mLock) {
1081 int userId = UserHandle.getUserId(uid);
Jaewan Kima7dce192017-02-16 17:10:54 +09001082 FullUserRecord user = getFullUserRecordLocked(userId);
1083 if (user == null || user.mFullUserId != userId) {
1084 Log.w(TAG, "Only the full user can set the media key listener"
1085 + ", userId=" + userId);
1086 return;
1087 }
Jaewan Kim6e2b01c2017-01-19 16:33:14 -08001088 if (user.mOnMediaKeyListener != null && user.mOnMediaKeyListenerUid != uid) {
Jaewan Kima7dce192017-02-16 17:10:54 +09001089 Log.w(TAG, "The media key listener cannot be reset by another app. "
1090 + ", mOnMediaKeyListenerUid=" + user.mOnMediaKeyListenerUid
1091 + ", uid=" + uid);
Jaewan Kim6e2b01c2017-01-19 16:33:14 -08001092 return;
1093 }
1094
1095 user.mOnMediaKeyListener = listener;
1096 user.mOnMediaKeyListenerUid = uid;
1097
Jaewan Kima7dce192017-02-16 17:10:54 +09001098 Log.d(TAG, "The media key listener " + user.mOnMediaKeyListener
Jaewan Kim6e2b01c2017-01-19 16:33:14 -08001099 + " is set by " + getCallingPackageName(uid));
1100
1101 if (user.mOnMediaKeyListener != null) {
1102 try {
1103 user.mOnMediaKeyListener.asBinder().linkToDeath(
1104 new IBinder.DeathRecipient() {
1105 @Override
1106 public void binderDied() {
1107 synchronized (mLock) {
1108 user.mOnMediaKeyListener = null;
1109 }
1110 }
1111 }, 0);
1112 } catch (RemoteException e) {
1113 Log.w(TAG, "Failed to set death recipient " + user.mOnMediaKeyListener);
1114 user.mOnMediaKeyListener = null;
1115 }
1116 }
1117 }
1118 } finally {
1119 Binder.restoreCallingIdentity(token);
1120 }
1121 }
1122
Jaewan Kim50269362016-12-23 11:22:02 +09001123 /**
1124 * Handles the dispatching of the volume button events to one of the
1125 * registered listeners. If there's a volume key long-press listener and
1126 * there's no active global priority session, long-pressess will be sent to the
1127 * long-press listener instead of adjusting volume.
1128 *
1129 * @param keyEvent a non-null KeyEvent whose key code is one of the
1130 * {@link KeyEvent#KEYCODE_VOLUME_UP},
1131 * {@link KeyEvent#KEYCODE_VOLUME_DOWN},
1132 * or {@link KeyEvent#KEYCODE_VOLUME_MUTE}.
1133 * @param stream stream type to adjust volume.
1134 * @param musicOnly true if both UI nor haptic feedback aren't needed when adjust volume.
1135 */
1136 @Override
1137 public void dispatchVolumeKeyEvent(KeyEvent keyEvent, int stream, boolean musicOnly) {
1138 if (keyEvent == null ||
1139 (keyEvent.getKeyCode() != KeyEvent.KEYCODE_VOLUME_UP
1140 && keyEvent.getKeyCode() != KeyEvent.KEYCODE_VOLUME_DOWN
1141 && keyEvent.getKeyCode() != KeyEvent.KEYCODE_VOLUME_MUTE)) {
1142 Log.w(TAG, "Attempted to dispatch null or non-volume key event.");
1143 return;
1144 }
1145
1146 final int pid = Binder.getCallingPid();
1147 final int uid = Binder.getCallingUid();
1148 final long token = Binder.clearCallingIdentity();
1149
Jaewan Kimb2781e72017-03-02 09:57:09 +09001150 if (DEBUG_KEY_EVENT) {
Jaewan Kim50269362016-12-23 11:22:02 +09001151 Log.d(TAG, "dispatchVolumeKeyEvent, pid=" + pid + ", uid=" + uid + ", event="
1152 + keyEvent);
1153 }
1154
1155 try {
1156 synchronized (mLock) {
Jaewan Kima7dce192017-02-16 17:10:54 +09001157 if (isGlobalPriorityActiveLocked()
1158 || mCurrentFullUserRecord.mOnVolumeKeyLongPressListener == null) {
Jaewan Kim50269362016-12-23 11:22:02 +09001159 dispatchVolumeKeyEventLocked(keyEvent, stream, musicOnly);
1160 } else {
1161 // TODO: Consider the case when both volume up and down keys are pressed
1162 // at the same time.
1163 if (keyEvent.getAction() == KeyEvent.ACTION_DOWN) {
1164 if (keyEvent.getRepeatCount() == 0) {
Jaewan Kimd61a87b2017-02-17 23:14:10 +09001165 // Keeps the copy of the KeyEvent because it can be reused.
Jaewan Kima7dce192017-02-16 17:10:54 +09001166 mCurrentFullUserRecord.mInitialDownVolumeKeyEvent =
1167 KeyEvent.obtain(keyEvent);
1168 mCurrentFullUserRecord.mInitialDownVolumeStream = stream;
1169 mCurrentFullUserRecord.mInitialDownMusicOnly = musicOnly;
Jaewan Kimd61a87b2017-02-17 23:14:10 +09001170 mHandler.sendMessageDelayed(
1171 mHandler.obtainMessage(
Jaewan Kima7dce192017-02-16 17:10:54 +09001172 MessageHandler.MSG_VOLUME_INITIAL_DOWN,
1173 mCurrentFullUserRecord.mFullUserId, 0),
Jaewan Kimd61a87b2017-02-17 23:14:10 +09001174 mLongPressTimeout);
Jaewan Kim50269362016-12-23 11:22:02 +09001175 }
1176 if (keyEvent.getRepeatCount() > 0 || keyEvent.isLongPress()) {
Jaewan Kimd61a87b2017-02-17 23:14:10 +09001177 mHandler.removeMessages(MessageHandler.MSG_VOLUME_INITIAL_DOWN);
Jaewan Kima7dce192017-02-16 17:10:54 +09001178 if (mCurrentFullUserRecord.mInitialDownVolumeKeyEvent != null) {
Jaewan Kim50269362016-12-23 11:22:02 +09001179 dispatchVolumeKeyLongPressLocked(
Jaewan Kima7dce192017-02-16 17:10:54 +09001180 mCurrentFullUserRecord.mInitialDownVolumeKeyEvent);
Jaewan Kim50269362016-12-23 11:22:02 +09001181 // Mark that the key is already handled.
Jaewan Kima7dce192017-02-16 17:10:54 +09001182 mCurrentFullUserRecord.mInitialDownVolumeKeyEvent = null;
Jaewan Kim50269362016-12-23 11:22:02 +09001183 }
1184 dispatchVolumeKeyLongPressLocked(keyEvent);
1185 }
1186 } else { // if up
Jaewan Kimd61a87b2017-02-17 23:14:10 +09001187 mHandler.removeMessages(MessageHandler.MSG_VOLUME_INITIAL_DOWN);
Jaewan Kima7dce192017-02-16 17:10:54 +09001188 if (mCurrentFullUserRecord.mInitialDownVolumeKeyEvent != null
1189 && mCurrentFullUserRecord.mInitialDownVolumeKeyEvent
1190 .getDownTime() == keyEvent.getDownTime()) {
Jaewan Kim50269362016-12-23 11:22:02 +09001191 // Short-press. Should change volume.
1192 dispatchVolumeKeyEventLocked(
Jaewan Kima7dce192017-02-16 17:10:54 +09001193 mCurrentFullUserRecord.mInitialDownVolumeKeyEvent,
1194 mCurrentFullUserRecord.mInitialDownVolumeStream,
1195 mCurrentFullUserRecord.mInitialDownMusicOnly);
Jaewan Kim50269362016-12-23 11:22:02 +09001196 dispatchVolumeKeyEventLocked(keyEvent, stream, musicOnly);
1197 } else {
1198 dispatchVolumeKeyLongPressLocked(keyEvent);
1199 }
1200 }
1201 }
1202 }
1203 } finally {
1204 Binder.restoreCallingIdentity(token);
1205 }
1206 }
1207
Jaewan Kim50269362016-12-23 11:22:02 +09001208 private void dispatchVolumeKeyEventLocked(
1209 KeyEvent keyEvent, int stream, boolean musicOnly) {
1210 boolean down = keyEvent.getAction() == KeyEvent.ACTION_DOWN;
1211 boolean up = keyEvent.getAction() == KeyEvent.ACTION_UP;
1212 int direction = 0;
1213 boolean isMute = false;
1214 switch (keyEvent.getKeyCode()) {
1215 case KeyEvent.KEYCODE_VOLUME_UP:
1216 direction = AudioManager.ADJUST_RAISE;
1217 break;
1218 case KeyEvent.KEYCODE_VOLUME_DOWN:
1219 direction = AudioManager.ADJUST_LOWER;
1220 break;
1221 case KeyEvent.KEYCODE_VOLUME_MUTE:
1222 isMute = true;
1223 break;
1224 }
1225 if (down || up) {
1226 int flags = AudioManager.FLAG_FROM_KEY;
1227 if (musicOnly) {
1228 // This flag is used when the screen is off to only affect active media.
1229 flags |= AudioManager.FLAG_ACTIVE_MEDIA_ONLY;
1230 } else {
1231 // These flags are consistent with the home screen
1232 if (up) {
1233 flags |= AudioManager.FLAG_PLAY_SOUND | AudioManager.FLAG_VIBRATE;
1234 } else {
1235 flags |= AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_VIBRATE;
1236 }
1237 }
1238 if (direction != 0) {
1239 // If this is action up we want to send a beep for non-music events
1240 if (up) {
1241 direction = 0;
1242 }
1243 dispatchAdjustVolumeLocked(stream, direction, flags);
1244 } else if (isMute) {
1245 if (down && keyEvent.getRepeatCount() == 0) {
1246 dispatchAdjustVolumeLocked(stream, AudioManager.ADJUST_TOGGLE_MUTE, flags);
1247 }
1248 }
1249 }
1250 }
1251
1252 @Override
RoboErik7c82ced2014-12-04 17:39:08 -08001253 public void dispatchAdjustVolume(int suggestedStream, int delta, int flags) {
RoboErikb69ffd42014-05-30 14:57:59 -07001254 final long token = Binder.clearCallingIdentity();
1255 try {
1256 synchronized (mLock) {
Jaewan Kim50269362016-12-23 11:22:02 +09001257 dispatchAdjustVolumeLocked(suggestedStream, delta, flags);
RoboErikb69ffd42014-05-30 14:57:59 -07001258 }
1259 } finally {
1260 Binder.restoreCallingIdentity(token);
1261 }
1262 }
1263
1264 @Override
RoboErik19c95182014-06-23 15:38:48 -07001265 public void setRemoteVolumeController(IRemoteVolumeController rvc) {
1266 final int pid = Binder.getCallingPid();
1267 final int uid = Binder.getCallingUid();
1268 final long token = Binder.clearCallingIdentity();
1269 try {
John Spurlockeb69e242015-02-17 17:15:04 -05001270 enforceSystemUiPermission("listen for volume changes", pid, uid);
RoboErik19c95182014-06-23 15:38:48 -07001271 mRvc = rvc;
1272 } finally {
1273 Binder.restoreCallingIdentity(token);
1274 }
1275 }
1276
1277 @Override
RoboErikde9ba392014-09-26 12:51:01 -07001278 public boolean isGlobalPriorityActive() {
Jaewan Kim51255012017-02-24 16:19:14 +09001279 synchronized (mLock) {
Jaewan Kima7dce192017-02-16 17:10:54 +09001280 return isGlobalPriorityActiveLocked();
Jaewan Kim51255012017-02-24 16:19:14 +09001281 }
RoboErikde9ba392014-09-26 12:51:01 -07001282 }
1283
1284 @Override
RoboErika278ea72014-04-24 14:49:01 -07001285 public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
Jeff Sharkeyfe9a53b2017-03-31 14:08:23 -06001286 if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) return;
RoboErika278ea72014-04-24 14:49:01 -07001287
1288 pw.println("MEDIA SESSION SERVICE (dumpsys media_session)");
1289 pw.println();
1290
1291 synchronized (mLock) {
RoboErika08adb242014-11-21 18:28:18 -08001292 pw.println(mSessionsListeners.size() + " sessions listeners.");
Jaewan Kima7dce192017-02-16 17:10:54 +09001293 pw.println("Global priority session is " + mGlobalPrioritySession);
Jaewan Kim101b4d52017-05-18 13:23:11 +09001294 if (mGlobalPrioritySession != null) {
1295 mGlobalPrioritySession.dump(pw, " ");
1296 }
RoboErik4646d282014-05-13 10:13:04 -07001297 pw.println("User Records:");
Jaewan Kime0ca3f32017-02-16 15:52:39 +09001298 int count = mUserRecords.size();
RoboErika278ea72014-04-24 14:49:01 -07001299 for (int i = 0; i < count; i++) {
Jaewan Kima7dce192017-02-16 17:10:54 +09001300 mUserRecords.valueAt(i).dumpLocked(pw, "");
RoboErika278ea72014-04-24 14:49:01 -07001301 }
Jaewan Kim92dea332017-02-02 11:52:08 +09001302 mAudioPlaybackMonitor.dump(pw, "");
RoboErika278ea72014-04-24 14:49:01 -07001303 }
1304 }
RoboErik8a2cfc32014-05-16 11:19:38 -07001305
RoboErik2e7a9162014-06-04 16:53:45 -07001306 private int verifySessionsRequest(ComponentName componentName, int userId, final int pid,
1307 final int uid) {
1308 String packageName = null;
1309 if (componentName != null) {
1310 // If they gave us a component name verify they own the
1311 // package
1312 packageName = componentName.getPackageName();
1313 enforcePackageName(packageName, uid);
1314 }
1315 // Check that they can make calls on behalf of the user and
1316 // get the final user id
1317 int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId,
1318 true /* allowAll */, true /* requireFull */, "getSessions", packageName);
1319 // Check if they have the permissions or their component is
1320 // enabled for the user they're calling from.
1321 enforceMediaPermissions(componentName, pid, uid, resolvedUserId);
1322 return resolvedUserId;
1323 }
1324
Jaewan Kim50269362016-12-23 11:22:02 +09001325 private void dispatchAdjustVolumeLocked(int suggestedStream, int direction, int flags) {
Jaewan Kima7dce192017-02-16 17:10:54 +09001326 MediaSessionRecord session = isGlobalPriorityActiveLocked() ? mGlobalPrioritySession
1327 : mCurrentFullUserRecord.mPriorityStack.getDefaultVolumeSession();
Jaewan Kim50269362016-12-23 11:22:02 +09001328
RoboErik9c785402014-11-11 16:52:26 -08001329 boolean preferSuggestedStream = false;
1330 if (isValidLocalStreamType(suggestedStream)
1331 && AudioSystem.isStreamActive(suggestedStream, 0)) {
1332 preferSuggestedStream = true;
1333 }
Jaewan Kimb2781e72017-03-02 09:57:09 +09001334 if (DEBUG_KEY_EVENT) {
Jaewan Kim5e1476e2016-07-19 22:25:39 +09001335 Log.d(TAG, "Adjusting " + session + " by " + direction + ". flags="
1336 + flags + ", suggestedStream=" + suggestedStream
1337 + ", preferSuggestedStream=" + preferSuggestedStream);
1338 }
RoboErik9c785402014-11-11 16:52:26 -08001339 if (session == null || preferSuggestedStream) {
RoboErik94c716e2014-09-14 13:54:31 -07001340 if ((flags & AudioManager.FLAG_ACTIVE_MEDIA_ONLY) != 0
1341 && !AudioSystem.isStreamActive(AudioManager.STREAM_MUSIC, 0)) {
RoboErik3c45c292014-07-08 16:47:31 -07001342 if (DEBUG) {
1343 Log.d(TAG, "No active session to adjust, skipping media only volume event");
RoboErik3c45c292014-07-08 16:47:31 -07001344 }
RoboErikb7c014c2014-07-22 15:58:22 -07001345 return;
RoboErik3c45c292014-07-08 16:47:31 -07001346 }
Shibin George19e84042016-06-14 20:42:13 +05301347
1348 // Execute mAudioService.adjustSuggestedStreamVolume() on
1349 // handler thread of MediaSessionService.
1350 // This will release the MediaSessionService.mLock sooner and avoid
1351 // a potential deadlock between MediaSessionService.mLock and
1352 // ActivityManagerService lock.
1353 mHandler.post(new Runnable() {
1354 @Override
1355 public void run() {
1356 try {
1357 String packageName = getContext().getOpPackageName();
1358 mAudioService.adjustSuggestedStreamVolume(direction, suggestedStream,
1359 flags, packageName, TAG);
1360 } catch (RemoteException e) {
1361 Log.e(TAG, "Error adjusting default volume.", e);
1362 }
1363 }
1364 });
RoboErikb69ffd42014-05-30 14:57:59 -07001365 } else {
RoboErik0dac35a2014-08-12 15:48:49 -07001366 session.adjustVolume(direction, flags, getContext().getPackageName(),
Jaewan Kim8f729082016-06-21 12:36:26 +09001367 Process.SYSTEM_UID, true);
RoboErikb69ffd42014-05-30 14:57:59 -07001368 }
1369 }
1370
Jaewan Kim6e2b01c2017-01-19 16:33:14 -08001371 private void handleVoiceKeyEventLocked(KeyEvent keyEvent, boolean needWakeLock) {
RoboErik9a9d0b52014-05-20 14:53:39 -07001372 int action = keyEvent.getAction();
1373 boolean isLongPress = (keyEvent.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0;
1374 if (action == KeyEvent.ACTION_DOWN) {
1375 if (keyEvent.getRepeatCount() == 0) {
1376 mVoiceButtonDown = true;
1377 mVoiceButtonHandled = false;
1378 } else if (mVoiceButtonDown && !mVoiceButtonHandled && isLongPress) {
1379 mVoiceButtonHandled = true;
1380 startVoiceInput(needWakeLock);
1381 }
1382 } else if (action == KeyEvent.ACTION_UP) {
1383 if (mVoiceButtonDown) {
1384 mVoiceButtonDown = false;
1385 if (!mVoiceButtonHandled && !keyEvent.isCanceled()) {
1386 // Resend the down then send this event through
1387 KeyEvent downEvent = KeyEvent.changeAction(keyEvent, KeyEvent.ACTION_DOWN);
Jaewan Kim98003d32017-02-24 18:33:04 +09001388 dispatchMediaKeyEventLocked(downEvent, needWakeLock);
1389 dispatchMediaKeyEventLocked(keyEvent, needWakeLock);
RoboErik9a9d0b52014-05-20 14:53:39 -07001390 }
1391 }
1392 }
1393 }
1394
Jaewan Kim98003d32017-02-24 18:33:04 +09001395 private void dispatchMediaKeyEventLocked(KeyEvent keyEvent, boolean needWakeLock) {
Jaewan Kima7dce192017-02-16 17:10:54 +09001396 MediaSessionRecord session = mCurrentFullUserRecord.getMediaButtonSessionLocked();
RoboErik9a9d0b52014-05-20 14:53:39 -07001397 if (session != null) {
Jaewan Kim50269362016-12-23 11:22:02 +09001398 if (DEBUG_KEY_EVENT) {
Jaewan Kim5e1476e2016-07-19 22:25:39 +09001399 Log.d(TAG, "Sending " + keyEvent + " to " + session);
RoboErik9a9d0b52014-05-20 14:53:39 -07001400 }
1401 if (needWakeLock) {
1402 mKeyEventReceiver.aquireWakeLockLocked();
1403 }
Jaewan Kim50269362016-12-23 11:22:02 +09001404 // If we don't need a wakelock use -1 as the id so we won't release it later.
RoboErik9a9d0b52014-05-20 14:53:39 -07001405 session.sendMediaButton(keyEvent,
1406 needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1,
Jaewan Kim8f729082016-06-21 12:36:26 +09001407 mKeyEventReceiver, Process.SYSTEM_UID,
Donghyun Cho1ea56832016-02-23 16:30:07 +09001408 getContext().getPackageName());
Jaewan Kima7dce192017-02-16 17:10:54 +09001409 if (mCurrentFullUserRecord.mCallback != null) {
Jaewan Kimbd16f452017-02-03 16:21:38 +09001410 try {
Jaewan Kima7dce192017-02-16 17:10:54 +09001411 mCurrentFullUserRecord.mCallback.onMediaKeyEventDispatchedToMediaSession(
1412 keyEvent,
Jaewan Kimbd16f452017-02-03 16:21:38 +09001413 new MediaSession.Token(session.getControllerBinder()));
1414 } catch (RemoteException e) {
1415 Log.w(TAG, "Failed to send callback", e);
1416 }
1417 }
Jaewan Kima7dce192017-02-16 17:10:54 +09001418 } else if (mCurrentFullUserRecord.mLastMediaButtonReceiver != null
1419 || mCurrentFullUserRecord.mRestoredMediaButtonReceiver != null) {
1420 if (needWakeLock) {
1421 mKeyEventReceiver.aquireWakeLockLocked();
1422 }
1423 Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
1424 mediaButtonIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
1425 mediaButtonIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
1426 try {
1427 if (mCurrentFullUserRecord.mLastMediaButtonReceiver != null) {
1428 PendingIntent receiver = mCurrentFullUserRecord.mLastMediaButtonReceiver;
1429 if (DEBUG_KEY_EVENT) {
1430 Log.d(TAG, "Sending " + keyEvent
Jaewan Kim92dea332017-02-02 11:52:08 +09001431 + " to the last known PendingIntent " + receiver);
Jaewan Kima7dce192017-02-16 17:10:54 +09001432 }
1433 receiver.send(getContext(),
1434 needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1,
1435 mediaButtonIntent, mKeyEventReceiver, mHandler);
1436 if (mCurrentFullUserRecord.mCallback != null) {
1437 ComponentName componentName = mCurrentFullUserRecord
1438 .mLastMediaButtonReceiver.getIntent().getComponent();
1439 if (componentName != null) {
1440 mCurrentFullUserRecord.mCallback
1441 .onMediaKeyEventDispatchedToMediaButtonReceiver(
1442 keyEvent, componentName);
Jaewan Kimbd16f452017-02-03 16:21:38 +09001443 }
RoboErikc8f92d12015-01-05 16:48:07 -08001444 }
Jaewan Kima7dce192017-02-16 17:10:54 +09001445 } else {
1446 ComponentName receiver =
1447 mCurrentFullUserRecord.mRestoredMediaButtonReceiver;
1448 if (DEBUG_KEY_EVENT) {
1449 Log.d(TAG, "Sending " + keyEvent + " to the restored intent "
1450 + receiver);
1451 }
1452 mediaButtonIntent.setComponent(receiver);
1453 getContext().sendBroadcastAsUser(mediaButtonIntent,
Jaewan Kim92dea332017-02-02 11:52:08 +09001454 UserHandle.of(mCurrentFullUserRecord
1455 .mRestoredMediaButtonReceiverUserId));
Jaewan Kima7dce192017-02-16 17:10:54 +09001456 if (mCurrentFullUserRecord.mCallback != null) {
1457 mCurrentFullUserRecord.mCallback
1458 .onMediaKeyEventDispatchedToMediaButtonReceiver(
1459 keyEvent, receiver);
1460 }
RoboErikb214efb2014-07-24 13:20:30 -07001461 }
Jaewan Kima7dce192017-02-16 17:10:54 +09001462 } catch (CanceledException e) {
1463 Log.i(TAG, "Error sending key event to media button receiver "
1464 + mCurrentFullUserRecord.mLastMediaButtonReceiver, e);
1465 } catch (RemoteException e) {
1466 Log.w(TAG, "Failed to send callback", e);
RoboErik9a9d0b52014-05-20 14:53:39 -07001467 }
RoboErik9a9d0b52014-05-20 14:53:39 -07001468 }
1469 }
1470
1471 private void startVoiceInput(boolean needWakeLock) {
1472 Intent voiceIntent = null;
1473 // select which type of search to launch:
1474 // - screen on and device unlocked: action is ACTION_WEB_SEARCH
1475 // - device locked or screen off: action is
1476 // ACTION_VOICE_SEARCH_HANDS_FREE
1477 // with EXTRA_SECURE set to true if the device is securely locked
1478 PowerManager pm = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE);
1479 boolean isLocked = mKeyguardManager != null && mKeyguardManager.isKeyguardLocked();
1480 if (!isLocked && pm.isScreenOn()) {
1481 voiceIntent = new Intent(android.speech.RecognizerIntent.ACTION_WEB_SEARCH);
1482 Log.i(TAG, "voice-based interactions: about to use ACTION_WEB_SEARCH");
1483 } else {
1484 voiceIntent = new Intent(RecognizerIntent.ACTION_VOICE_SEARCH_HANDS_FREE);
1485 voiceIntent.putExtra(RecognizerIntent.EXTRA_SECURE,
1486 isLocked && mKeyguardManager.isKeyguardSecure());
1487 Log.i(TAG, "voice-based interactions: about to use ACTION_VOICE_SEARCH_HANDS_FREE");
1488 }
1489 // start the search activity
1490 if (needWakeLock) {
1491 mMediaEventWakeLock.acquire();
1492 }
1493 try {
1494 if (voiceIntent != null) {
1495 voiceIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
1496 | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
Jaewan Kim8f729082016-06-21 12:36:26 +09001497 if (DEBUG) Log.d(TAG, "voiceIntent: " + voiceIntent);
RoboErik9a9d0b52014-05-20 14:53:39 -07001498 getContext().startActivityAsUser(voiceIntent, UserHandle.CURRENT);
1499 }
1500 } catch (ActivityNotFoundException e) {
1501 Log.w(TAG, "No activity for search: " + e);
1502 } finally {
1503 if (needWakeLock) {
1504 mMediaEventWakeLock.release();
1505 }
1506 }
1507 }
1508
1509 private boolean isVoiceKey(int keyCode) {
Jaewan Kimba18d8e2017-05-12 17:37:57 +09001510 return keyCode == KeyEvent.KEYCODE_HEADSETHOOK
1511 || keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE;
RoboErik9a9d0b52014-05-20 14:53:39 -07001512 }
1513
Jeff Brown38d3feb2015-03-19 18:26:30 -07001514 private boolean isUserSetupComplete() {
1515 return Settings.Secure.getIntForUser(getContext().getContentResolver(),
1516 Settings.Secure.USER_SETUP_COMPLETE, 0, UserHandle.USER_CURRENT) != 0;
1517 }
1518
RoboErik9c785402014-11-11 16:52:26 -08001519 // we only handle public stream types, which are 0-5
1520 private boolean isValidLocalStreamType(int streamType) {
1521 return streamType >= AudioManager.STREAM_VOICE_CALL
1522 && streamType <= AudioManager.STREAM_NOTIFICATION;
1523 }
1524
Jaewan Kim6e2b01c2017-01-19 16:33:14 -08001525 private class MediaKeyListenerResultReceiver extends ResultReceiver implements Runnable {
1526 private KeyEvent mKeyEvent;
1527 private boolean mNeedWakeLock;
1528 private boolean mHandled;
1529
1530 private MediaKeyListenerResultReceiver(KeyEvent keyEvent, boolean needWakeLock) {
1531 super(mHandler);
1532 mHandler.postDelayed(this, MEDIA_KEY_LISTENER_TIMEOUT);
1533 mKeyEvent = keyEvent;
1534 mNeedWakeLock = needWakeLock;
1535 }
1536
1537 @Override
1538 public void run() {
1539 Log.d(TAG, "The media key listener is timed-out for " + mKeyEvent);
1540 dispatchMediaKeyEvent();
1541 }
1542
1543 @Override
1544 protected void onReceiveResult(int resultCode, Bundle resultData) {
1545 if (resultCode == MediaSessionManager.RESULT_MEDIA_KEY_HANDLED) {
1546 mHandled = true;
1547 mHandler.removeCallbacks(this);
1548 return;
1549 }
1550 dispatchMediaKeyEvent();
1551 }
1552
1553 private void dispatchMediaKeyEvent() {
1554 if (mHandled) {
1555 return;
1556 }
1557 mHandled = true;
1558 mHandler.removeCallbacks(this);
1559 synchronized (mLock) {
Jaewan Kima7dce192017-02-16 17:10:54 +09001560 if (!isGlobalPriorityActiveLocked()
Jaewan Kim98003d32017-02-24 18:33:04 +09001561 && isVoiceKey(mKeyEvent.getKeyCode())) {
1562 handleVoiceKeyEventLocked(mKeyEvent, mNeedWakeLock);
1563 } else {
1564 dispatchMediaKeyEventLocked(mKeyEvent, mNeedWakeLock);
1565 }
Jaewan Kim6e2b01c2017-01-19 16:33:14 -08001566 }
1567 }
1568 }
1569
RoboErik418c10c2014-05-19 09:25:25 -07001570 private KeyEventWakeLockReceiver mKeyEventReceiver = new KeyEventWakeLockReceiver(mHandler);
1571
RoboErikb214efb2014-07-24 13:20:30 -07001572 class KeyEventWakeLockReceiver extends ResultReceiver implements Runnable,
1573 PendingIntent.OnFinished {
RoboErik418c10c2014-05-19 09:25:25 -07001574 private final Handler mHandler;
1575 private int mRefCount = 0;
1576 private int mLastTimeoutId = 0;
1577
1578 public KeyEventWakeLockReceiver(Handler handler) {
1579 super(handler);
1580 mHandler = handler;
1581 }
1582
1583 public void onTimeout() {
1584 synchronized (mLock) {
1585 if (mRefCount == 0) {
1586 // We've already released it, so just return
1587 return;
1588 }
1589 mLastTimeoutId++;
1590 mRefCount = 0;
1591 releaseWakeLockLocked();
1592 }
1593 }
1594
1595 public void aquireWakeLockLocked() {
1596 if (mRefCount == 0) {
1597 mMediaEventWakeLock.acquire();
1598 }
1599 mRefCount++;
1600 mHandler.removeCallbacks(this);
1601 mHandler.postDelayed(this, WAKELOCK_TIMEOUT);
1602
1603 }
1604
1605 @Override
1606 public void run() {
1607 onTimeout();
1608 }
1609
RoboErik8a2cfc32014-05-16 11:19:38 -07001610 @Override
1611 protected void onReceiveResult(int resultCode, Bundle resultData) {
RoboErik418c10c2014-05-19 09:25:25 -07001612 if (resultCode < mLastTimeoutId) {
1613 // Ignore results from calls that were before the last
1614 // timeout, just in case.
1615 return;
1616 } else {
1617 synchronized (mLock) {
1618 if (mRefCount > 0) {
1619 mRefCount--;
1620 if (mRefCount == 0) {
1621 releaseWakeLockLocked();
1622 }
1623 }
RoboErik8a2cfc32014-05-16 11:19:38 -07001624 }
1625 }
1626 }
RoboErik418c10c2014-05-19 09:25:25 -07001627
1628 private void releaseWakeLockLocked() {
1629 mMediaEventWakeLock.release();
1630 mHandler.removeCallbacks(this);
1631 }
RoboErikb214efb2014-07-24 13:20:30 -07001632
1633 @Override
1634 public void onSendFinished(PendingIntent pendingIntent, Intent intent, int resultCode,
1635 String resultData, Bundle resultExtras) {
1636 onReceiveResult(resultCode, null);
1637 }
RoboErik8a2cfc32014-05-16 11:19:38 -07001638 };
1639
1640 BroadcastReceiver mKeyEventDone = new BroadcastReceiver() {
1641 @Override
1642 public void onReceive(Context context, Intent intent) {
1643 if (intent == null) {
1644 return;
1645 }
1646 Bundle extras = intent.getExtras();
1647 if (extras == null) {
1648 return;
1649 }
1650 synchronized (mLock) {
1651 if (extras.containsKey(EXTRA_WAKELOCK_ACQUIRED)
1652 && mMediaEventWakeLock.isHeld()) {
1653 mMediaEventWakeLock.release();
1654 }
1655 }
1656 }
1657 };
RoboErik01fe6612014-02-13 14:19:04 -08001658 }
1659
RoboErik2e7a9162014-06-04 16:53:45 -07001660 final class MessageHandler extends Handler {
1661 private static final int MSG_SESSIONS_CHANGED = 1;
Jaewan Kimd61a87b2017-02-17 23:14:10 +09001662 private static final int MSG_VOLUME_INITIAL_DOWN = 2;
Jaewan Kim92dea332017-02-02 11:52:08 +09001663 private final SparseArray<Integer> mIntegerCache = new SparseArray<>();
RoboErik2e7a9162014-06-04 16:53:45 -07001664
1665 @Override
1666 public void handleMessage(Message msg) {
1667 switch (msg.what) {
1668 case MSG_SESSIONS_CHANGED:
Jaewan Kim92dea332017-02-02 11:52:08 +09001669 pushSessionsChanged((int) msg.obj);
RoboErik2e7a9162014-06-04 16:53:45 -07001670 break;
Jaewan Kimd61a87b2017-02-17 23:14:10 +09001671 case MSG_VOLUME_INITIAL_DOWN:
1672 synchronized (mLock) {
Jaewan Kima7dce192017-02-16 17:10:54 +09001673 FullUserRecord user = mUserRecords.get((int) msg.arg1);
Jaewan Kimd61a87b2017-02-17 23:14:10 +09001674 if (user != null && user.mInitialDownVolumeKeyEvent != null) {
1675 dispatchVolumeKeyLongPressLocked(user.mInitialDownVolumeKeyEvent);
1676 // Mark that the key is already handled.
1677 user.mInitialDownVolumeKeyEvent = null;
1678 }
1679 }
1680 break;
RoboErik2e7a9162014-06-04 16:53:45 -07001681 }
1682 }
1683
Jaewan Kim92dea332017-02-02 11:52:08 +09001684 public void postSessionsChanged(int userId) {
1685 // Use object instead of the arguments when posting message to remove pending requests.
1686 Integer userIdInteger = mIntegerCache.get(userId);
1687 if (userIdInteger == null) {
1688 userIdInteger = Integer.valueOf(userId);
1689 mIntegerCache.put(userId, userIdInteger);
1690 }
1691 removeMessages(MSG_SESSIONS_CHANGED, userIdInteger);
1692 obtainMessage(MSG_SESSIONS_CHANGED, userIdInteger).sendToTarget();
RoboErik2e7a9162014-06-04 16:53:45 -07001693 }
1694 }
RoboErik01fe6612014-02-13 14:19:04 -08001695}