blob: 64ab848a052e88ab8957897bec1ca2198602bb69 [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
RoboErika278ea72014-04-24 14:49:01 -070019import android.Manifest;
Jaewan Kim50269362016-12-23 11:22:02 +090020import android.annotation.NonNull;
RoboErik8a2cfc32014-05-16 11:19:38 -070021import android.app.Activity;
RoboErike7880d82014-04-30 12:48:25 -070022import android.app.ActivityManager;
RoboErik9a9d0b52014-05-20 14:53:39 -070023import android.app.KeyguardManager;
RoboErikb214efb2014-07-24 13:20:30 -070024import android.app.PendingIntent;
25import android.app.PendingIntent.CanceledException;
RoboErik9a9d0b52014-05-20 14:53:39 -070026import android.content.ActivityNotFoundException;
RoboErik8a2cfc32014-05-16 11:19:38 -070027import android.content.BroadcastReceiver;
RoboErike7880d82014-04-30 12:48:25 -070028import android.content.ComponentName;
RoboErik6f0e4dd2014-06-17 16:56:27 -070029import android.content.ContentResolver;
RoboErik01fe6612014-02-13 14:19:04 -080030import android.content.Context;
RoboErik8a2cfc32014-05-16 11:19:38 -070031import android.content.Intent;
RoboErika278ea72014-04-24 14:49:01 -070032import android.content.pm.PackageManager;
Jaewan Kima7dce192017-02-16 17:10:54 +090033import android.content.pm.UserInfo;
RoboErik7aef77b2014-08-08 15:56:54 -070034import android.database.ContentObserver;
RoboErik3c45c292014-07-08 16:47:31 -070035import android.media.AudioManager;
John Spurlockeb69e242015-02-17 17:15:04 -050036import android.media.AudioManagerInternal;
RoboErik94c716e2014-09-14 13:54:31 -070037import android.media.AudioSystem;
RoboErikb69ffd42014-05-30 14:57:59 -070038import android.media.IAudioService;
RoboErik19c95182014-06-23 15:38:48 -070039import android.media.IRemoteVolumeController;
RoboErik2e7a9162014-06-04 16:53:45 -070040import android.media.session.IActiveSessionsListener;
Jaewan Kimbd16f452017-02-03 16:21:38 +090041import android.media.session.ICallback;
Jaewan Kim6e2b01c2017-01-19 16:33:14 -080042import android.media.session.IOnMediaKeyListener;
Jaewan Kim50269362016-12-23 11:22:02 +090043import android.media.session.IOnVolumeKeyLongPressListener;
RoboErik07c70772014-03-20 13:33:52 -070044import android.media.session.ISession;
45import android.media.session.ISessionCallback;
46import android.media.session.ISessionManager;
Jeff Browndba34ba2014-06-24 20:46:03 -070047import android.media.session.MediaSession;
Jaewan Kim6e2b01c2017-01-19 16:33:14 -080048import android.media.session.MediaSessionManager;
RoboErik7aef77b2014-08-08 15:56:54 -070049import android.net.Uri;
RoboErik01fe6612014-02-13 14:19:04 -080050import android.os.Binder;
RoboErik8a2cfc32014-05-16 11:19:38 -070051import android.os.Bundle;
RoboErik8ae0f342014-02-24 18:02:08 -080052import android.os.Handler;
RoboErike7880d82014-04-30 12:48:25 -070053import android.os.IBinder;
RoboErik2e7a9162014-06-04 16:53:45 -070054import android.os.Message;
RoboErik8a2cfc32014-05-16 11:19:38 -070055import android.os.PowerManager;
Jaewan Kim8f729082016-06-21 12:36:26 +090056import android.os.Process;
RoboErik01fe6612014-02-13 14:19:04 -080057import android.os.RemoteException;
RoboErik8a2cfc32014-05-16 11:19:38 -070058import android.os.ResultReceiver;
RoboErikb69ffd42014-05-30 14:57:59 -070059import android.os.ServiceManager;
Jaewan Kim92dea332017-02-02 11:52:08 +090060import android.os.SystemProperties;
RoboErike7880d82014-04-30 12:48:25 -070061import android.os.UserHandle;
Jaewan Kim8f729082016-06-21 12:36:26 +090062import android.os.UserManager;
RoboErike7880d82014-04-30 12:48:25 -070063import android.provider.Settings;
RoboErik9a9d0b52014-05-20 14:53:39 -070064import android.speech.RecognizerIntent;
RoboErik01fe6612014-02-13 14:19:04 -080065import android.text.TextUtils;
Jaewan Kim92dea332017-02-02 11:52:08 +090066import android.util.IntArray;
RoboErik01fe6612014-02-13 14:19:04 -080067import android.util.Log;
Jeff Brown38d3feb2015-03-19 18:26:30 -070068import android.util.Slog;
RoboErik4646d282014-05-13 10:13:04 -070069import android.util.SparseArray;
Jaewan Kima7dce192017-02-16 17:10:54 +090070import android.util.SparseIntArray;
RoboErik8a2cfc32014-05-16 11:19:38 -070071import android.view.KeyEvent;
Jaewan Kimd61a87b2017-02-17 23:14:10 +090072import android.view.ViewConfiguration;
RoboErik01fe6612014-02-13 14:19:04 -080073
Jeff Sharkeyfe9a53b2017-03-31 14:08:23 -060074import com.android.internal.util.DumpUtils;
John Spurlockeb69e242015-02-17 17:15:04 -050075import com.android.server.LocalServices;
RoboErik01fe6612014-02-13 14:19:04 -080076import com.android.server.SystemService;
RoboErika278ea72014-04-24 14:49:01 -070077import com.android.server.Watchdog;
78import com.android.server.Watchdog.Monitor;
RoboErik01fe6612014-02-13 14:19:04 -080079
RoboErika278ea72014-04-24 14:49:01 -070080import java.io.FileDescriptor;
81import java.io.PrintWriter;
RoboErik01fe6612014-02-13 14:19:04 -080082import java.util.ArrayList;
Jaewan Kim8f729082016-06-21 12:36:26 +090083import java.util.Arrays;
RoboErike7880d82014-04-30 12:48:25 -070084import java.util.List;
RoboErik01fe6612014-02-13 14:19:04 -080085
86/**
87 * System implementation of MediaSessionManager
88 */
RoboErika278ea72014-04-24 14:49:01 -070089public class MediaSessionService extends SystemService implements Monitor {
RoboErik01fe6612014-02-13 14:19:04 -080090 private static final String TAG = "MediaSessionService";
Jaewan Kim92dea332017-02-02 11:52:08 +090091 static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
Jaewan Kim50269362016-12-23 11:22:02 +090092 // Leave log for key event always.
93 private static final boolean DEBUG_KEY_EVENT = true;
RoboErik01fe6612014-02-13 14:19:04 -080094
RoboErik418c10c2014-05-19 09:25:25 -070095 private static final int WAKELOCK_TIMEOUT = 5000;
Jaewan Kim6e2b01c2017-01-19 16:33:14 -080096 private static final int MEDIA_KEY_LISTENER_TIMEOUT = 1000;
RoboErik418c10c2014-05-19 09:25:25 -070097
RoboErik01fe6612014-02-13 14:19:04 -080098 private final SessionManagerImpl mSessionManagerImpl;
99
Jaewan Kima7dce192017-02-16 17:10:54 +0900100 // Keeps the full user id for each user.
101 private final SparseIntArray mFullUserIds = new SparseIntArray();
102 private final SparseArray<FullUserRecord> mUserRecords = new SparseArray<FullUserRecord>();
RoboErik2e7a9162014-06-04 16:53:45 -0700103 private final ArrayList<SessionsListenerRecord> mSessionsListeners
104 = new ArrayList<SessionsListenerRecord>();
RoboErik01fe6612014-02-13 14:19:04 -0800105 private final Object mLock = new Object();
RoboErik2e7a9162014-06-04 16:53:45 -0700106 private final MessageHandler mHandler = new MessageHandler();
RoboErik8a2cfc32014-05-16 11:19:38 -0700107 private final PowerManager.WakeLock mMediaEventWakeLock;
Jaewan Kimd61a87b2017-02-17 23:14:10 +0900108 private final int mLongPressTimeout;
RoboErik01fe6612014-02-13 14:19:04 -0800109
RoboErik9a9d0b52014-05-20 14:53:39 -0700110 private KeyguardManager mKeyguardManager;
RoboErikb69ffd42014-05-30 14:57:59 -0700111 private IAudioService mAudioService;
John Spurlockeb69e242015-02-17 17:15:04 -0500112 private AudioManagerInternal mAudioManagerInternal;
RoboErik6f0e4dd2014-06-17 16:56:27 -0700113 private ContentResolver mContentResolver;
RoboErik7aef77b2014-08-08 15:56:54 -0700114 private SettingsObserver mSettingsObserver;
RoboErik9a9d0b52014-05-20 14:53:39 -0700115
Jaewan Kima7dce192017-02-16 17:10:54 +0900116 // The FullUserRecord of the current users. (i.e. The foreground user that isn't a profile)
117 // It's always not null after the MediaSessionService is started.
118 private FullUserRecord mCurrentFullUserRecord;
119 private MediaSessionRecord mGlobalPrioritySession;
Jaewan Kim92dea332017-02-02 11:52:08 +0900120 private AudioPlaybackMonitor mAudioPlaybackMonitor;
RoboErike7880d82014-04-30 12:48:25 -0700121
RoboErik19c95182014-06-23 15:38:48 -0700122 // Used to notify system UI when remote volume was changed. TODO find a
123 // better way to handle this.
124 private IRemoteVolumeController mRvc;
125
RoboErik01fe6612014-02-13 14:19:04 -0800126 public MediaSessionService(Context context) {
127 super(context);
128 mSessionManagerImpl = new SessionManagerImpl();
RoboErik8a2cfc32014-05-16 11:19:38 -0700129 PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
130 mMediaEventWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "handleMediaEvent");
Jaewan Kimd61a87b2017-02-17 23:14:10 +0900131 mLongPressTimeout = ViewConfiguration.getLongPressTimeout();
RoboErik01fe6612014-02-13 14:19:04 -0800132 }
133
134 @Override
135 public void onStart() {
136 publishBinderService(Context.MEDIA_SESSION_SERVICE, mSessionManagerImpl);
RoboErika278ea72014-04-24 14:49:01 -0700137 Watchdog.getInstance().addMonitor(this);
RoboErik9a9d0b52014-05-20 14:53:39 -0700138 mKeyguardManager =
139 (KeyguardManager) getContext().getSystemService(Context.KEYGUARD_SERVICE);
RoboErikb69ffd42014-05-30 14:57:59 -0700140 mAudioService = getAudioService();
Jaewan Kim92dea332017-02-02 11:52:08 +0900141 mAudioPlaybackMonitor = new AudioPlaybackMonitor(getContext(), mAudioService,
142 new AudioPlaybackMonitor.OnAudioPlaybackStartedListener() {
143 @Override
144 public void onAudioPlaybackStarted(int uid) {
145 synchronized (mLock) {
146 FullUserRecord user =
147 getFullUserRecordLocked(UserHandle.getUserId(uid));
148 if (user != null) {
149 user.mPriorityStack.updateMediaButtonSessionIfNeeded();
150 }
151 }
152 }
153 });
John Spurlockeb69e242015-02-17 17:15:04 -0500154 mAudioManagerInternal = LocalServices.getService(AudioManagerInternal.class);
RoboErik6f0e4dd2014-06-17 16:56:27 -0700155 mContentResolver = getContext().getContentResolver();
RoboErik7aef77b2014-08-08 15:56:54 -0700156 mSettingsObserver = new SettingsObserver();
157 mSettingsObserver.observe();
RoboErikc8f92d12015-01-05 16:48:07 -0800158
159 updateUser();
RoboErikb69ffd42014-05-30 14:57:59 -0700160 }
161
162 private IAudioService getAudioService() {
163 IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE);
164 return IAudioService.Stub.asInterface(b);
RoboErik07c70772014-03-20 13:33:52 -0700165 }
166
Jaewan Kima7dce192017-02-16 17:10:54 +0900167 private boolean isGlobalPriorityActiveLocked() {
168 return mGlobalPrioritySession != null && mGlobalPrioritySession.isActive();
169 }
170
RoboErika8f95142014-05-05 14:23:49 -0700171 public void updateSession(MediaSessionRecord record) {
RoboErike7880d82014-04-30 12:48:25 -0700172 synchronized (mLock) {
Jaewan Kima7dce192017-02-16 17:10:54 +0900173 FullUserRecord user = getFullUserRecordLocked(record.getUserId());
174 if (user == null || !user.mPriorityStack.contains(record)) {
RoboErik4646d282014-05-13 10:13:04 -0700175 Log.d(TAG, "Unknown session updated. Ignoring.");
176 return;
177 }
Jaewan Kima7dce192017-02-16 17:10:54 +0900178 user.mPriorityStack.onSessionStateChange(record);
179 if ((record.getFlags() & MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY) != 0) {
180 mGlobalPrioritySession = record;
Jaewan Kim92dea332017-02-02 11:52:08 +0900181 user.pushAddressedPlayerChangedLocked();
Jaewan Kima7dce192017-02-16 17:10:54 +0900182 }
Jaewan Kim92dea332017-02-02 11:52:08 +0900183 mHandler.postSessionsChanged(record.getUserId());
RoboErike7880d82014-04-30 12:48:25 -0700184 }
185 }
186
RoboErik9c5b7cb2015-01-15 15:09:09 -0800187 /**
Hyundo Moona055f132017-01-13 15:31:06 +0900188 * Tells the system UI that volume has changed on an active remote session.
RoboErik9c5b7cb2015-01-15 15:09:09 -0800189 */
190 public void notifyRemoteVolumeChanged(int flags, MediaSessionRecord session) {
Hyundo Moona055f132017-01-13 15:31:06 +0900191 if (mRvc == null || !session.isActive()) {
RoboErik9c5b7cb2015-01-15 15:09:09 -0800192 return;
193 }
194 try {
195 mRvc.remoteVolumeChanged(session.getControllerBinder(), flags);
196 } catch (Exception e) {
197 Log.wtf(TAG, "Error sending volume change to system UI.", e);
198 }
199 }
200
Jaewan Kim92dea332017-02-02 11:52:08 +0900201 public void onSessionPlaystateChanged(MediaSessionRecord record, int oldState, int newState) {
RoboErika8f95142014-05-05 14:23:49 -0700202 synchronized (mLock) {
Jaewan Kima7dce192017-02-16 17:10:54 +0900203 FullUserRecord user = getFullUserRecordLocked(record.getUserId());
204 if (user == null || !user.mPriorityStack.contains(record)) {
RoboErik4646d282014-05-13 10:13:04 -0700205 Log.d(TAG, "Unknown session changed playback state. Ignoring.");
206 return;
207 }
Jaewan Kim92dea332017-02-02 11:52:08 +0900208 user.mPriorityStack.onPlaystateChanged(record, oldState, newState);
RoboErika8f95142014-05-05 14:23:49 -0700209 }
210 }
211
RoboErik19c95182014-06-23 15:38:48 -0700212 public void onSessionPlaybackTypeChanged(MediaSessionRecord record) {
213 synchronized (mLock) {
Jaewan Kima7dce192017-02-16 17:10:54 +0900214 FullUserRecord user = getFullUserRecordLocked(record.getUserId());
215 if (user == null || !user.mPriorityStack.contains(record)) {
RoboErik19c95182014-06-23 15:38:48 -0700216 Log.d(TAG, "Unknown session changed playback type. Ignoring.");
217 return;
218 }
219 pushRemoteVolumeUpdateLocked(record.getUserId());
220 }
221 }
222
RoboErika278ea72014-04-24 14:49:01 -0700223 @Override
Jaewan Kim8f729082016-06-21 12:36:26 +0900224 public void onStartUser(int userId) {
225 if (DEBUG) Log.d(TAG, "onStartUser: " + userId);
RoboErik4646d282014-05-13 10:13:04 -0700226 updateUser();
227 }
228
229 @Override
Jaewan Kim8f729082016-06-21 12:36:26 +0900230 public void onSwitchUser(int userId) {
231 if (DEBUG) Log.d(TAG, "onSwitchUser: " + userId);
RoboErik4646d282014-05-13 10:13:04 -0700232 updateUser();
233 }
234
235 @Override
Jaewan Kim8f729082016-06-21 12:36:26 +0900236 public void onStopUser(int userId) {
237 if (DEBUG) Log.d(TAG, "onStopUser: " + userId);
RoboErik4646d282014-05-13 10:13:04 -0700238 synchronized (mLock) {
Jaewan Kima7dce192017-02-16 17:10:54 +0900239 FullUserRecord user = getFullUserRecordLocked(userId);
RoboErik4646d282014-05-13 10:13:04 -0700240 if (user != null) {
Jaewan Kima7dce192017-02-16 17:10:54 +0900241 if (user.mFullUserId == userId) {
242 user.destroySessionsForUserLocked(UserHandle.USER_ALL);
243 mUserRecords.remove(userId);
244 } else {
245 user.destroySessionsForUserLocked(userId);
246 }
RoboErik4646d282014-05-13 10:13:04 -0700247 }
Jaewan Kim8f729082016-06-21 12:36:26 +0900248 updateUser();
RoboErik4646d282014-05-13 10:13:04 -0700249 }
250 }
251
252 @Override
RoboErika278ea72014-04-24 14:49:01 -0700253 public void monitor() {
254 synchronized (mLock) {
255 // Check for deadlock
256 }
257 }
258
RoboErik4646d282014-05-13 10:13:04 -0700259 protected void enforcePhoneStatePermission(int pid, int uid) {
260 if (getContext().checkPermission(android.Manifest.permission.MODIFY_PHONE_STATE, pid, uid)
261 != PackageManager.PERMISSION_GRANTED) {
262 throw new SecurityException("Must hold the MODIFY_PHONE_STATE permission.");
263 }
264 }
265
RoboErik01fe6612014-02-13 14:19:04 -0800266 void sessionDied(MediaSessionRecord session) {
RoboErika278ea72014-04-24 14:49:01 -0700267 synchronized (mLock) {
RoboErik01fe6612014-02-13 14:19:04 -0800268 destroySessionLocked(session);
269 }
270 }
271
272 void destroySession(MediaSessionRecord session) {
RoboErika278ea72014-04-24 14:49:01 -0700273 synchronized (mLock) {
RoboErik01fe6612014-02-13 14:19:04 -0800274 destroySessionLocked(session);
275 }
276 }
277
RoboErik4646d282014-05-13 10:13:04 -0700278 private void updateUser() {
279 synchronized (mLock) {
Jaewan Kim8f729082016-06-21 12:36:26 +0900280 UserManager manager = (UserManager) getContext().getSystemService(Context.USER_SERVICE);
Jaewan Kima7dce192017-02-16 17:10:54 +0900281 mFullUserIds.clear();
282 List<UserInfo> allUsers = manager.getUsers();
283 if (allUsers != null) {
284 for (UserInfo userInfo : allUsers) {
285 if (userInfo.isManagedProfile()) {
286 mFullUserIds.put(userInfo.id, userInfo.profileGroupId);
287 } else {
288 mFullUserIds.put(userInfo.id, userInfo.id);
289 if (mUserRecords.get(userInfo.id) == null) {
290 mUserRecords.put(userInfo.id, new FullUserRecord(userInfo.id));
291 }
292 }
Jaewan Kim8f729082016-06-21 12:36:26 +0900293 }
RoboErik4646d282014-05-13 10:13:04 -0700294 }
Jaewan Kima7dce192017-02-16 17:10:54 +0900295 // Ensure that the current full user exists.
296 int currentFullUserId = ActivityManager.getCurrentUser();
297 mCurrentFullUserRecord = mUserRecords.get(currentFullUserId);
298 if (mCurrentFullUserRecord == null) {
299 Log.w(TAG, "Cannot find FullUserInfo for the current user " + currentFullUserId);
300 mCurrentFullUserRecord = new FullUserRecord(currentFullUserId);
301 mUserRecords.put(currentFullUserId, mCurrentFullUserRecord);
302 }
303 mFullUserIds.put(currentFullUserId, currentFullUserId);
RoboErik4646d282014-05-13 10:13:04 -0700304 }
305 }
306
RoboErik7aef77b2014-08-08 15:56:54 -0700307 private void updateActiveSessionListeners() {
308 synchronized (mLock) {
309 for (int i = mSessionsListeners.size() - 1; i >= 0; i--) {
310 SessionsListenerRecord listener = mSessionsListeners.get(i);
311 try {
312 enforceMediaPermissions(listener.mComponentName, listener.mPid, listener.mUid,
313 listener.mUserId);
314 } catch (SecurityException e) {
315 Log.i(TAG, "ActiveSessionsListener " + listener.mComponentName
316 + " is no longer authorized. Disconnecting.");
317 mSessionsListeners.remove(i);
318 try {
319 listener.mListener
320 .onActiveSessionsChanged(new ArrayList<MediaSession.Token>());
321 } catch (Exception e1) {
322 // ignore
323 }
324 }
325 }
326 }
327 }
328
RoboErik4646d282014-05-13 10:13:04 -0700329 /*
330 * When a session is removed several things need to happen.
331 * 1. We need to remove it from the relevant user.
332 * 2. We need to remove it from the priority stack.
333 * 3. We need to remove it from all sessions.
334 * 4. If this is the system priority session we need to clear it.
335 * 5. We need to unlink to death from the cb binder
336 * 6. We need to tell the session to do any final cleanup (onDestroy)
337 */
RoboErik01fe6612014-02-13 14:19:04 -0800338 private void destroySessionLocked(MediaSessionRecord session) {
Insun Kang30be970a2015-11-26 15:35:44 +0900339 if (DEBUG) {
Jaewan Kim5e1476e2016-07-19 22:25:39 +0900340 Log.d(TAG, "Destroying " + session);
Insun Kang30be970a2015-11-26 15:35:44 +0900341 }
RoboErik4646d282014-05-13 10:13:04 -0700342 int userId = session.getUserId();
Jaewan Kima7dce192017-02-16 17:10:54 +0900343 FullUserRecord user = getFullUserRecordLocked(userId);
RoboErik4646d282014-05-13 10:13:04 -0700344 if (user != null) {
345 user.removeSessionLocked(session);
346 }
Jaewan Kima7dce192017-02-16 17:10:54 +0900347 if (mGlobalPrioritySession == session) {
348 mGlobalPrioritySession = null;
Jaewan Kim92dea332017-02-02 11:52:08 +0900349 if (session.isActive() && user != null) {
350 user.pushAddressedPlayerChangedLocked();
351 }
Jaewan Kima7dce192017-02-16 17:10:54 +0900352 }
RoboErik4646d282014-05-13 10:13:04 -0700353
354 try {
355 session.getCallback().asBinder().unlinkToDeath(session, 0);
356 } catch (Exception e) {
357 // ignore exceptions while destroying a session.
358 }
359 session.onDestroy();
Jaewan Kim92dea332017-02-02 11:52:08 +0900360 mHandler.postSessionsChanged(session.getUserId());
RoboErik01fe6612014-02-13 14:19:04 -0800361 }
362
363 private void enforcePackageName(String packageName, int uid) {
364 if (TextUtils.isEmpty(packageName)) {
365 throw new IllegalArgumentException("packageName may not be empty");
366 }
367 String[] packages = getContext().getPackageManager().getPackagesForUid(uid);
368 final int packageCount = packages.length;
369 for (int i = 0; i < packageCount; i++) {
370 if (packageName.equals(packages[i])) {
371 return;
372 }
373 }
374 throw new IllegalArgumentException("packageName is not owned by the calling process");
375 }
376
RoboErike7880d82014-04-30 12:48:25 -0700377 /**
378 * Checks a caller's authorization to register an IRemoteControlDisplay.
379 * Authorization is granted if one of the following is true:
380 * <ul>
381 * <li>the caller has android.Manifest.permission.MEDIA_CONTENT_CONTROL
382 * permission</li>
RoboErika5b02322014-05-07 17:05:49 -0700383 * <li>the caller's listener is one of the enabled notification listeners
384 * for the caller's user</li>
RoboErike7880d82014-04-30 12:48:25 -0700385 * </ul>
386 */
RoboErika5b02322014-05-07 17:05:49 -0700387 private void enforceMediaPermissions(ComponentName compName, int pid, int uid,
388 int resolvedUserId) {
Julia Reynoldsbb983d202017-01-06 09:54:20 -0500389 if (isCurrentVolumeController(uid, pid)) return;
RoboErike7880d82014-04-30 12:48:25 -0700390 if (getContext()
391 .checkPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL, pid, uid)
392 != PackageManager.PERMISSION_GRANTED
RoboErika5b02322014-05-07 17:05:49 -0700393 && !isEnabledNotificationListener(compName, UserHandle.getUserId(uid),
394 resolvedUserId)) {
RoboErike7880d82014-04-30 12:48:25 -0700395 throw new SecurityException("Missing permission to control media.");
396 }
397 }
398
Julia Reynoldsbb983d202017-01-06 09:54:20 -0500399 private boolean isCurrentVolumeController(int uid, int pid) {
400 return getContext().checkPermission(android.Manifest.permission.STATUS_BAR_SERVICE,
401 pid, uid) == PackageManager.PERMISSION_GRANTED;
John Spurlockbe19ed02015-02-22 10:57:55 -0500402 }
403
404 private void enforceSystemUiPermission(String action, int pid, int uid) {
Julia Reynoldsbb983d202017-01-06 09:54:20 -0500405 if (!isCurrentVolumeController(uid, pid)) {
RoboErik19c95182014-06-23 15:38:48 -0700406 throw new SecurityException("Only system ui may " + action);
407 }
408 }
409
RoboErika5b02322014-05-07 17:05:49 -0700410 /**
411 * This checks if the component is an enabled notification listener for the
412 * specified user. Enabled components may only operate on behalf of the user
413 * they're running as.
414 *
415 * @param compName The component that is enabled.
416 * @param userId The user id of the caller.
417 * @param forUserId The user id they're making the request on behalf of.
418 * @return True if the component is enabled, false otherwise
419 */
420 private boolean isEnabledNotificationListener(ComponentName compName, int userId,
421 int forUserId) {
422 if (userId != forUserId) {
423 // You may not access another user's content as an enabled listener.
424 return false;
425 }
RoboErik51fa6bc2014-06-20 14:59:58 -0700426 if (DEBUG) {
427 Log.d(TAG, "Checking if enabled notification listener " + compName);
428 }
RoboErike7880d82014-04-30 12:48:25 -0700429 if (compName != null) {
RoboErik6f0e4dd2014-06-17 16:56:27 -0700430 final String enabledNotifListeners = Settings.Secure.getStringForUser(mContentResolver,
RoboErike7880d82014-04-30 12:48:25 -0700431 Settings.Secure.ENABLED_NOTIFICATION_LISTENERS,
RoboErika5b02322014-05-07 17:05:49 -0700432 userId);
RoboErike7880d82014-04-30 12:48:25 -0700433 if (enabledNotifListeners != null) {
434 final String[] components = enabledNotifListeners.split(":");
435 for (int i = 0; i < components.length; i++) {
436 final ComponentName component =
437 ComponentName.unflattenFromString(components[i]);
438 if (component != null) {
439 if (compName.equals(component)) {
440 if (DEBUG) {
Jaewan Kim5e1476e2016-07-19 22:25:39 +0900441 Log.d(TAG, "ok to get sessions. " + component +
RoboErike7880d82014-04-30 12:48:25 -0700442 " is authorized notification listener");
443 }
444 return true;
445 }
446 }
447 }
448 }
449 if (DEBUG) {
Jaewan Kim5e1476e2016-07-19 22:25:39 +0900450 Log.d(TAG, "not ok to get sessions. " + compName +
RoboErika5b02322014-05-07 17:05:49 -0700451 " is not in list of ENABLED_NOTIFICATION_LISTENERS for user " + userId);
RoboErike7880d82014-04-30 12:48:25 -0700452 }
453 }
454 return false;
455 }
456
RoboErika5b02322014-05-07 17:05:49 -0700457 private MediaSessionRecord createSessionInternal(int callerPid, int callerUid, int userId,
RoboErik4646d282014-05-13 10:13:04 -0700458 String callerPackageName, ISessionCallback cb, String tag) throws RemoteException {
RoboErik01fe6612014-02-13 14:19:04 -0800459 synchronized (mLock) {
RoboErika5b02322014-05-07 17:05:49 -0700460 return createSessionLocked(callerPid, callerUid, userId, callerPackageName, cb, tag);
RoboErik01fe6612014-02-13 14:19:04 -0800461 }
462 }
463
RoboErik4646d282014-05-13 10:13:04 -0700464 /*
465 * When a session is created the following things need to happen.
RoboErik8a2cfc32014-05-16 11:19:38 -0700466 * 1. Its callback binder needs a link to death
RoboErik4646d282014-05-13 10:13:04 -0700467 * 2. It needs to be added to all sessions.
468 * 3. It needs to be added to the priority stack.
469 * 4. It needs to be added to the relevant user record.
470 */
RoboErika5b02322014-05-07 17:05:49 -0700471 private MediaSessionRecord createSessionLocked(int callerPid, int callerUid, int userId,
472 String callerPackageName, ISessionCallback cb, String tag) {
Jaewan Kima7dce192017-02-16 17:10:54 +0900473 FullUserRecord user = getFullUserRecordLocked(userId);
Dongwon Kang8cf39c52016-07-29 13:20:39 -0700474 if (user == null) {
475 Log.wtf(TAG, "Request from invalid user: " + userId);
476 throw new RuntimeException("Session request from invalid user.");
477 }
478
RoboErika5b02322014-05-07 17:05:49 -0700479 final MediaSessionRecord session = new MediaSessionRecord(callerPid, callerUid, userId,
Jaewan Kim92dea332017-02-02 11:52:08 +0900480 callerPackageName, cb, tag, this, mHandler.getLooper());
RoboErik01fe6612014-02-13 14:19:04 -0800481 try {
482 cb.asBinder().linkToDeath(session, 0);
483 } catch (RemoteException e) {
484 throw new RuntimeException("Media Session owner died prematurely.", e);
485 }
RoboErik4646d282014-05-13 10:13:04 -0700486
RoboErik4646d282014-05-13 10:13:04 -0700487 user.addSessionLocked(session);
Jaewan Kim92dea332017-02-02 11:52:08 +0900488 mHandler.postSessionsChanged(userId);
RoboErik2e7a9162014-06-04 16:53:45 -0700489
RoboErik01fe6612014-02-13 14:19:04 -0800490 if (DEBUG) {
Jaewan Kim5e1476e2016-07-19 22:25:39 +0900491 Log.d(TAG, "Created session for " + callerPackageName + " with tag " + tag);
RoboErik01fe6612014-02-13 14:19:04 -0800492 }
493 return session;
494 }
495
RoboErik2e7a9162014-06-04 16:53:45 -0700496 private int findIndexOfSessionsListenerLocked(IActiveSessionsListener listener) {
497 for (int i = mSessionsListeners.size() - 1; i >= 0; i--) {
RoboErika08adb242014-11-21 18:28:18 -0800498 if (mSessionsListeners.get(i).mListener.asBinder() == listener.asBinder()) {
RoboErik2e7a9162014-06-04 16:53:45 -0700499 return i;
500 }
501 }
502 return -1;
503 }
504
RoboErik2e7a9162014-06-04 16:53:45 -0700505 private void pushSessionsChanged(int userId) {
506 synchronized (mLock) {
Jaewan Kima7dce192017-02-16 17:10:54 +0900507 FullUserRecord user = getFullUserRecordLocked(userId);
508 if (user == null) {
509 Log.w(TAG, "pushSessionsChanged failed. No user with id=" + userId);
510 return;
511 }
512 List<MediaSessionRecord> records = user.mPriorityStack.getActiveSessions(userId);
RoboErik2e7a9162014-06-04 16:53:45 -0700513 int size = records.size();
Jeff Browndba34ba2014-06-24 20:46:03 -0700514 ArrayList<MediaSession.Token> tokens = new ArrayList<MediaSession.Token>();
RoboErik2e7a9162014-06-04 16:53:45 -0700515 for (int i = 0; i < size; i++) {
Jeff Browndba34ba2014-06-24 20:46:03 -0700516 tokens.add(new MediaSession.Token(records.get(i).getControllerBinder()));
RoboErik2e7a9162014-06-04 16:53:45 -0700517 }
RoboErik19c95182014-06-23 15:38:48 -0700518 pushRemoteVolumeUpdateLocked(userId);
RoboErik2e7a9162014-06-04 16:53:45 -0700519 for (int i = mSessionsListeners.size() - 1; i >= 0; i--) {
520 SessionsListenerRecord record = mSessionsListeners.get(i);
521 if (record.mUserId == UserHandle.USER_ALL || record.mUserId == userId) {
522 try {
523 record.mListener.onActiveSessionsChanged(tokens);
524 } catch (RemoteException e) {
525 Log.w(TAG, "Dead ActiveSessionsListener in pushSessionsChanged, removing",
526 e);
527 mSessionsListeners.remove(i);
528 }
529 }
530 }
531 }
532 }
533
RoboErik19c95182014-06-23 15:38:48 -0700534 private void pushRemoteVolumeUpdateLocked(int userId) {
535 if (mRvc != null) {
536 try {
Jaewan Kima7dce192017-02-16 17:10:54 +0900537 FullUserRecord user = getFullUserRecordLocked(userId);
538 if (user == null) {
539 Log.w(TAG, "pushRemoteVolumeUpdateLocked failed. No user with id=" + userId);
540 return;
541 }
542 MediaSessionRecord record = user.mPriorityStack.getDefaultRemoteSession(userId);
RoboErik19c95182014-06-23 15:38:48 -0700543 mRvc.updateRemoteController(record == null ? null : record.getControllerBinder());
544 } catch (RemoteException e) {
545 Log.wtf(TAG, "Error sending default remote volume to sys ui.", e);
546 }
547 }
548 }
549
Jaewan Kim92dea332017-02-02 11:52:08 +0900550 /**
551 * Called when the media button receiver for the {@param record} is changed.
552 *
553 * @param record the media session whose media button receiver is updated.
554 */
555 public void onMediaButtonReceiverChanged(MediaSessionRecord record) {
556 synchronized (mLock) {
557 FullUserRecord user = getFullUserRecordLocked(record.getUserId());
558 MediaSessionRecord mediaButtonSession =
559 user.mPriorityStack.getMediaButtonSession();
560 if (record == mediaButtonSession) {
561 user.rememberMediaButtonReceiverLocked(mediaButtonSession);
562 }
563 }
564 }
565
Jaewan Kim50269362016-12-23 11:22:02 +0900566 private String getCallingPackageName(int uid) {
567 String[] packages = getContext().getPackageManager().getPackagesForUid(uid);
568 if (packages != null && packages.length > 0) {
569 return packages[0];
570 }
571 return "";
572 }
573
Jaewan Kimd61a87b2017-02-17 23:14:10 +0900574 private void dispatchVolumeKeyLongPressLocked(KeyEvent keyEvent) {
Jaewan Kimd61a87b2017-02-17 23:14:10 +0900575 try {
Jaewan Kima7dce192017-02-16 17:10:54 +0900576 mCurrentFullUserRecord.mOnVolumeKeyLongPressListener.onVolumeKeyLongPress(keyEvent);
Jaewan Kimd61a87b2017-02-17 23:14:10 +0900577 } catch (RemoteException e) {
578 Log.w(TAG, "Failed to send " + keyEvent + " to volume key long-press listener");
579 }
580 }
581
Jaewan Kima7dce192017-02-16 17:10:54 +0900582 private FullUserRecord getFullUserRecordLocked(int userId) {
583 int fullUserId = mFullUserIds.get(userId, -1);
584 if (fullUserId < 0) {
585 return null;
586 }
587 return mUserRecords.get(fullUserId);
588 }
589
RoboErik4646d282014-05-13 10:13:04 -0700590 /**
Jaewan Kima7dce192017-02-16 17:10:54 +0900591 * Information about a full user and its corresponding managed profiles.
592 *
593 * <p>Since the full user runs together with its managed profiles, a user wouldn't differentiate
594 * them when he/she presses a media/volume button. So keeping media sessions for them in one
595 * place makes more sense and increases the readability.</p>
596 * <p>The contents of this object is guarded by {@link #mLock}.
RoboErik4646d282014-05-13 10:13:04 -0700597 */
Jaewan Kim92dea332017-02-02 11:52:08 +0900598 final class FullUserRecord implements MediaSessionStack.OnMediaButtonSessionChangedListener {
Jaewan Kima7dce192017-02-16 17:10:54 +0900599 private static final String COMPONENT_NAME_USER_ID_DELIM = ",";
600 private final int mFullUserId;
Jaewan Kim92dea332017-02-02 11:52:08 +0900601 private final MediaSessionStack mPriorityStack;
RoboErikb214efb2014-07-24 13:20:30 -0700602 private PendingIntent mLastMediaButtonReceiver;
RoboErikc8f92d12015-01-05 16:48:07 -0800603 private ComponentName mRestoredMediaButtonReceiver;
Jaewan Kima7dce192017-02-16 17:10:54 +0900604 private int mRestoredMediaButtonReceiverUserId;
RoboErik4646d282014-05-13 10:13:04 -0700605
Jaewan Kim50269362016-12-23 11:22:02 +0900606 private IOnVolumeKeyLongPressListener mOnVolumeKeyLongPressListener;
607 private int mOnVolumeKeyLongPressListenerUid;
608 private KeyEvent mInitialDownVolumeKeyEvent;
609 private int mInitialDownVolumeStream;
610 private boolean mInitialDownMusicOnly;
611
Jaewan Kim6e2b01c2017-01-19 16:33:14 -0800612 private IOnMediaKeyListener mOnMediaKeyListener;
613 private int mOnMediaKeyListenerUid;
Jaewan Kima7dce192017-02-16 17:10:54 +0900614 private ICallback mCallback;
Jaewan Kim6e2b01c2017-01-19 16:33:14 -0800615
Jaewan Kima7dce192017-02-16 17:10:54 +0900616 public FullUserRecord(int fullUserId) {
617 mFullUserId = fullUserId;
Jaewan Kim92dea332017-02-02 11:52:08 +0900618 mPriorityStack = new MediaSessionStack(mAudioPlaybackMonitor, this);
Jaewan Kima7dce192017-02-16 17:10:54 +0900619 // Restore the remembered media button receiver before the boot.
620 String mediaButtonReceiver = Settings.Secure.getStringForUser(mContentResolver,
621 Settings.System.MEDIA_BUTTON_RECEIVER, mFullUserId);
622 if (mediaButtonReceiver == null) {
623 return;
624 }
625 String[] tokens = mediaButtonReceiver.split(COMPONENT_NAME_USER_ID_DELIM);
626 if (tokens == null || tokens.length != 2) {
627 return;
628 }
629 mRestoredMediaButtonReceiver = ComponentName.unflattenFromString(tokens[0]);
630 mRestoredMediaButtonReceiverUserId = Integer.parseInt(tokens[1]);
RoboErik4646d282014-05-13 10:13:04 -0700631 }
632
Jaewan Kima7dce192017-02-16 17:10:54 +0900633 public void destroySessionsForUserLocked(int userId) {
Jaewan Kim92dea332017-02-02 11:52:08 +0900634 List<MediaSessionRecord> sessions = mPriorityStack.getPriorityList(false, userId);
Jaewan Kima7dce192017-02-16 17:10:54 +0900635 for (MediaSessionRecord session : sessions) {
RoboErik4646d282014-05-13 10:13:04 -0700636 MediaSessionService.this.destroySessionLocked(session);
RoboErik4646d282014-05-13 10:13:04 -0700637 }
638 }
639
RoboErik4646d282014-05-13 10:13:04 -0700640 public void addSessionLocked(MediaSessionRecord session) {
Jaewan Kim92dea332017-02-02 11:52:08 +0900641 mPriorityStack.addSession(session);
RoboErik4646d282014-05-13 10:13:04 -0700642 }
643
644 public void removeSessionLocked(MediaSessionRecord session) {
Jaewan Kima7dce192017-02-16 17:10:54 +0900645 mPriorityStack.removeSession(session);
RoboErik4646d282014-05-13 10:13:04 -0700646 }
647
648 public void dumpLocked(PrintWriter pw, String prefix) {
Jaewan Kima7dce192017-02-16 17:10:54 +0900649 pw.print(prefix + "Record for full_user=" + mFullUserId);
650 // Dump managed profile user ids associated with this user.
651 int size = mFullUserIds.size();
652 for (int i = 0; i < size; i++) {
653 if (mFullUserIds.keyAt(i) != mFullUserIds.valueAt(i)
654 && mFullUserIds.valueAt(i) == mFullUserId) {
655 pw.print(", profile_user=" + mFullUserIds.keyAt(i));
656 }
657 }
658 pw.println();
RoboErik4646d282014-05-13 10:13:04 -0700659 String indent = prefix + " ";
Jaewan Kima7dce192017-02-16 17:10:54 +0900660 pw.println(indent + "Volume key long-press listener: " + mOnVolumeKeyLongPressListener);
661 pw.println(indent + "Volume key long-press listener package: " +
Jaewan Kim50269362016-12-23 11:22:02 +0900662 getCallingPackageName(mOnVolumeKeyLongPressListenerUid));
Jaewan Kim6e2b01c2017-01-19 16:33:14 -0800663 pw.println(indent + "Media key listener: " + mOnMediaKeyListener);
664 pw.println(indent + "Media key listener package: " +
665 getCallingPackageName(mOnMediaKeyListenerUid));
Jaewan Kima7dce192017-02-16 17:10:54 +0900666 pw.println(indent + "Callback: " + mCallback);
667 pw.println(indent + "Last MediaButtonReceiver: " + mLastMediaButtonReceiver);
668 pw.println(indent + "Restored MediaButtonReceiver: " + mRestoredMediaButtonReceiver);
669 mPriorityStack.dump(pw, indent);
670 }
671
Jaewan Kim92dea332017-02-02 11:52:08 +0900672 @Override
673 public void onMediaButtonSessionChanged(MediaSessionRecord oldMediaButtonSession,
674 MediaSessionRecord newMediaButtonSession) {
675 if (DEBUG_KEY_EVENT) {
676 Log.d(TAG, "Media button session will be changed to " + newMediaButtonSession);
677 }
678 synchronized (mLock) {
679 if (oldMediaButtonSession != null) {
680 mHandler.postSessionsChanged(oldMediaButtonSession.getUserId());
681 }
682 if (newMediaButtonSession != null) {
683 rememberMediaButtonReceiverLocked(newMediaButtonSession);
684 mHandler.postSessionsChanged(newMediaButtonSession.getUserId());
685 }
686 pushAddressedPlayerChangedLocked();
687 }
688 }
689
690 // Remember media button receiver and keep it in the persistent storage.
691 public void rememberMediaButtonReceiverLocked(MediaSessionRecord record) {
Jaewan Kima7dce192017-02-16 17:10:54 +0900692 PendingIntent receiver = record.getMediaButtonReceiver();
Jaewan Kima7dce192017-02-16 17:10:54 +0900693 mLastMediaButtonReceiver = receiver;
Jaewan Kim92dea332017-02-02 11:52:08 +0900694 mRestoredMediaButtonReceiver = null;
695 String componentName = "";
696 if (receiver != null) {
697 ComponentName component = receiver.getIntent().getComponent();
698 if (component != null
699 && record.getPackageName().equals(component.getPackageName())) {
700 componentName = component.flattenToString();
701 }
RoboErik4646d282014-05-13 10:13:04 -0700702 }
Jaewan Kim92dea332017-02-02 11:52:08 +0900703 Settings.Secure.putStringForUser(mContentResolver,
704 Settings.System.MEDIA_BUTTON_RECEIVER,
705 componentName + COMPONENT_NAME_USER_ID_DELIM + record.getUserId(),
706 mFullUserId);
RoboErik4646d282014-05-13 10:13:04 -0700707 }
RoboErikc8f92d12015-01-05 16:48:07 -0800708
Jaewan Kima7dce192017-02-16 17:10:54 +0900709 private void pushAddressedPlayerChangedLocked() {
710 if (mCallback == null) {
711 return;
RoboErikc8f92d12015-01-05 16:48:07 -0800712 }
Jaewan Kima7dce192017-02-16 17:10:54 +0900713 try {
714 MediaSessionRecord mediaButtonSession = getMediaButtonSessionLocked();
715 if (mediaButtonSession != null) {
716 mCallback.onAddressedPlayerChangedToMediaSession(
717 new MediaSession.Token(mediaButtonSession.getControllerBinder()));
718 } else if (mCurrentFullUserRecord.mLastMediaButtonReceiver != null) {
719 mCallback.onAddressedPlayerChangedToMediaButtonReceiver(
720 mCurrentFullUserRecord.mLastMediaButtonReceiver
721 .getIntent().getComponent());
722 } else if (mCurrentFullUserRecord.mRestoredMediaButtonReceiver != null) {
723 mCallback.onAddressedPlayerChangedToMediaButtonReceiver(
724 mCurrentFullUserRecord.mRestoredMediaButtonReceiver);
725 }
726 } catch (RemoteException e) {
727 Log.w(TAG, "Failed to pushAddressedPlayerChangedLocked", e);
728 }
729 }
730
731 private MediaSessionRecord getMediaButtonSessionLocked() {
Jaewan Kim92dea332017-02-02 11:52:08 +0900732 return isGlobalPriorityActiveLocked()
733 ? mGlobalPrioritySession : mPriorityStack.getMediaButtonSession();
RoboErikc8f92d12015-01-05 16:48:07 -0800734 }
RoboErik4646d282014-05-13 10:13:04 -0700735 }
736
RoboErik2e7a9162014-06-04 16:53:45 -0700737 final class SessionsListenerRecord implements IBinder.DeathRecipient {
738 private final IActiveSessionsListener mListener;
RoboErik7aef77b2014-08-08 15:56:54 -0700739 private final ComponentName mComponentName;
RoboErik2e7a9162014-06-04 16:53:45 -0700740 private final int mUserId;
RoboErik7aef77b2014-08-08 15:56:54 -0700741 private final int mPid;
742 private final int mUid;
RoboErik2e7a9162014-06-04 16:53:45 -0700743
RoboErik7aef77b2014-08-08 15:56:54 -0700744 public SessionsListenerRecord(IActiveSessionsListener listener,
745 ComponentName componentName,
746 int userId, int pid, int uid) {
RoboErik2e7a9162014-06-04 16:53:45 -0700747 mListener = listener;
RoboErik7aef77b2014-08-08 15:56:54 -0700748 mComponentName = componentName;
RoboErik2e7a9162014-06-04 16:53:45 -0700749 mUserId = userId;
RoboErik7aef77b2014-08-08 15:56:54 -0700750 mPid = pid;
751 mUid = uid;
RoboErik2e7a9162014-06-04 16:53:45 -0700752 }
753
754 @Override
755 public void binderDied() {
756 synchronized (mLock) {
757 mSessionsListeners.remove(this);
758 }
759 }
760 }
761
RoboErik7aef77b2014-08-08 15:56:54 -0700762 final class SettingsObserver extends ContentObserver {
763 private final Uri mSecureSettingsUri = Settings.Secure.getUriFor(
764 Settings.Secure.ENABLED_NOTIFICATION_LISTENERS);
765
766 private SettingsObserver() {
767 super(null);
768 }
769
770 private void observe() {
771 mContentResolver.registerContentObserver(mSecureSettingsUri,
772 false, this, UserHandle.USER_ALL);
773 }
774
775 @Override
776 public void onChange(boolean selfChange, Uri uri) {
777 updateActiveSessionListeners();
778 }
779 }
780
RoboErik07c70772014-03-20 13:33:52 -0700781 class SessionManagerImpl extends ISessionManager.Stub {
RoboErik8a2cfc32014-05-16 11:19:38 -0700782 private static final String EXTRA_WAKELOCK_ACQUIRED =
783 "android.media.AudioService.WAKELOCK_ACQUIRED";
784 private static final int WAKELOCK_RELEASE_ON_FINISHED = 1980; // magic number
785
RoboErik9a9d0b52014-05-20 14:53:39 -0700786 private boolean mVoiceButtonDown = false;
787 private boolean mVoiceButtonHandled = false;
788
RoboErik07c70772014-03-20 13:33:52 -0700789 @Override
RoboErika5b02322014-05-07 17:05:49 -0700790 public ISession createSession(String packageName, ISessionCallback cb, String tag,
791 int userId) throws RemoteException {
RoboErik01fe6612014-02-13 14:19:04 -0800792 final int pid = Binder.getCallingPid();
793 final int uid = Binder.getCallingUid();
794 final long token = Binder.clearCallingIdentity();
795 try {
796 enforcePackageName(packageName, uid);
RoboErika5b02322014-05-07 17:05:49 -0700797 int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId,
798 false /* allowAll */, true /* requireFull */, "createSession", packageName);
RoboErik01fe6612014-02-13 14:19:04 -0800799 if (cb == null) {
800 throw new IllegalArgumentException("Controller callback cannot be null");
801 }
RoboErika5b02322014-05-07 17:05:49 -0700802 return createSessionInternal(pid, uid, resolvedUserId, packageName, cb, tag)
803 .getSessionBinder();
RoboErike7880d82014-04-30 12:48:25 -0700804 } finally {
805 Binder.restoreCallingIdentity(token);
806 }
807 }
808
809 @Override
RoboErika5b02322014-05-07 17:05:49 -0700810 public List<IBinder> getSessions(ComponentName componentName, int userId) {
RoboErike7880d82014-04-30 12:48:25 -0700811 final int pid = Binder.getCallingPid();
812 final int uid = Binder.getCallingUid();
813 final long token = Binder.clearCallingIdentity();
814
815 try {
RoboErik2e7a9162014-06-04 16:53:45 -0700816 int resolvedUserId = verifySessionsRequest(componentName, userId, pid, uid);
RoboErike7880d82014-04-30 12:48:25 -0700817 ArrayList<IBinder> binders = new ArrayList<IBinder>();
818 synchronized (mLock) {
Jaewan Kima7dce192017-02-16 17:10:54 +0900819 if (resolvedUserId == UserHandle.USER_ALL) {
820 int size = mUserRecords.size();
821 for (int i = 0; i < size; i++) {
822 List<MediaSessionRecord> records =
823 mUserRecords.valueAt(i).mPriorityStack.getActiveSessions(
824 resolvedUserId);
825 for (MediaSessionRecord record : records) {
826 binders.add(record.getControllerBinder().asBinder());
827 }
828 }
829 } else {
830 FullUserRecord user = getFullUserRecordLocked(resolvedUserId);
831 if (user == null) {
832 Log.w(TAG, "getSessions failed. Unknown user " + userId);
833 return binders;
834 }
835 List<MediaSessionRecord> records = user.mPriorityStack
836 .getActiveSessions(resolvedUserId);
837 for (MediaSessionRecord record : records) {
838 binders.add(record.getControllerBinder().asBinder());
839 }
RoboErike7880d82014-04-30 12:48:25 -0700840 }
841 }
842 return binders;
RoboErik01fe6612014-02-13 14:19:04 -0800843 } finally {
844 Binder.restoreCallingIdentity(token);
845 }
846 }
RoboErika278ea72014-04-24 14:49:01 -0700847
RoboErik2e7a9162014-06-04 16:53:45 -0700848 @Override
849 public void addSessionsListener(IActiveSessionsListener listener,
850 ComponentName componentName, int userId) throws RemoteException {
851 final int pid = Binder.getCallingPid();
852 final int uid = Binder.getCallingUid();
853 final long token = Binder.clearCallingIdentity();
854
855 try {
856 int resolvedUserId = verifySessionsRequest(componentName, userId, pid, uid);
857 synchronized (mLock) {
858 int index = findIndexOfSessionsListenerLocked(listener);
859 if (index != -1) {
860 Log.w(TAG, "ActiveSessionsListener is already added, ignoring");
861 return;
862 }
863 SessionsListenerRecord record = new SessionsListenerRecord(listener,
RoboErik7aef77b2014-08-08 15:56:54 -0700864 componentName, resolvedUserId, pid, uid);
RoboErik2e7a9162014-06-04 16:53:45 -0700865 try {
866 listener.asBinder().linkToDeath(record, 0);
867 } catch (RemoteException e) {
868 Log.e(TAG, "ActiveSessionsListener is dead, ignoring it", e);
869 return;
870 }
871 mSessionsListeners.add(record);
872 }
873 } finally {
874 Binder.restoreCallingIdentity(token);
875 }
876 }
877
878 @Override
879 public void removeSessionsListener(IActiveSessionsListener listener)
880 throws RemoteException {
881 synchronized (mLock) {
882 int index = findIndexOfSessionsListenerLocked(listener);
883 if (index != -1) {
884 SessionsListenerRecord record = mSessionsListeners.remove(index);
885 try {
886 record.mListener.asBinder().unlinkToDeath(record, 0);
887 } catch (Exception e) {
888 // ignore exceptions, the record is being removed
889 }
890 }
891 }
892 }
893
RoboErik8a2cfc32014-05-16 11:19:38 -0700894 /**
895 * Handles the dispatching of the media button events to one of the
896 * registered listeners, or if there was none, broadcast an
897 * ACTION_MEDIA_BUTTON intent to the rest of the system.
898 *
899 * @param keyEvent a non-null KeyEvent whose key code is one of the
900 * supported media buttons
901 * @param needWakeLock true if a PARTIAL_WAKE_LOCK needs to be held
902 * while this key event is dispatched.
903 */
904 @Override
905 public void dispatchMediaKeyEvent(KeyEvent keyEvent, boolean needWakeLock) {
906 if (keyEvent == null || !KeyEvent.isMediaKey(keyEvent.getKeyCode())) {
907 Log.w(TAG, "Attempted to dispatch null or non-media key event.");
908 return;
909 }
Jeff Brown38d3feb2015-03-19 18:26:30 -0700910
RoboErik8a2cfc32014-05-16 11:19:38 -0700911 final int pid = Binder.getCallingPid();
912 final int uid = Binder.getCallingUid();
913 final long token = Binder.clearCallingIdentity();
RoboErik8a2cfc32014-05-16 11:19:38 -0700914 try {
Jeff Brown221a8272015-03-23 13:53:09 -0700915 if (DEBUG) {
916 Log.d(TAG, "dispatchMediaKeyEvent, pid=" + pid + ", uid=" + uid + ", event="
917 + keyEvent);
918 }
Jeff Brown38d3feb2015-03-19 18:26:30 -0700919 if (!isUserSetupComplete()) {
920 // Global media key handling can have the side-effect of starting new
921 // activities which is undesirable while setup is in progress.
922 Slog.i(TAG, "Not dispatching media key event because user "
923 + "setup is in progress.");
924 return;
925 }
926
RoboErik8a2cfc32014-05-16 11:19:38 -0700927 synchronized (mLock) {
Jaewan Kima7dce192017-02-16 17:10:54 +0900928 boolean isGlobalPriorityActive = isGlobalPriorityActiveLocked();
Jaewan Kim51255012017-02-24 16:19:14 +0900929 if (isGlobalPriorityActive && uid != Process.SYSTEM_UID) {
930 // Prevent dispatching key event through reflection while the global
931 // priority session is active.
932 Slog.i(TAG, "Only the system can dispatch media key event "
933 + "to the global priority session.");
934 return;
935 }
Jaewan Kim98003d32017-02-24 18:33:04 +0900936 if (!isGlobalPriorityActive) {
Jaewan Kima7dce192017-02-16 17:10:54 +0900937 if (mCurrentFullUserRecord.mOnMediaKeyListener != null) {
Jaewan Kim98003d32017-02-24 18:33:04 +0900938 if (DEBUG_KEY_EVENT) {
Jaewan Kima7dce192017-02-16 17:10:54 +0900939 Log.d(TAG, "Send " + keyEvent + " to the media key listener");
Jaewan Kim98003d32017-02-24 18:33:04 +0900940 }
941 try {
Jaewan Kima7dce192017-02-16 17:10:54 +0900942 mCurrentFullUserRecord.mOnMediaKeyListener.onMediaKey(keyEvent,
Jaewan Kim98003d32017-02-24 18:33:04 +0900943 new MediaKeyListenerResultReceiver(keyEvent, needWakeLock));
944 return;
945 } catch (RemoteException e) {
Jaewan Kima7dce192017-02-16 17:10:54 +0900946 Log.w(TAG, "Failed to send " + keyEvent
947 + " to the media key listener");
Jaewan Kim98003d32017-02-24 18:33:04 +0900948 }
949 }
950 }
Jaewan Kim51255012017-02-24 16:19:14 +0900951 if (!isGlobalPriorityActive && isVoiceKey(keyEvent.getKeyCode())) {
Jaewan Kim6e2b01c2017-01-19 16:33:14 -0800952 handleVoiceKeyEventLocked(keyEvent, needWakeLock);
RoboErik8a2cfc32014-05-16 11:19:38 -0700953 } else {
Jaewan Kim98003d32017-02-24 18:33:04 +0900954 dispatchMediaKeyEventLocked(keyEvent, needWakeLock);
RoboErik8a2cfc32014-05-16 11:19:38 -0700955 }
956 }
957 } finally {
958 Binder.restoreCallingIdentity(token);
959 }
960 }
961
RoboErika278ea72014-04-24 14:49:01 -0700962 @Override
Jaewan Kimbd16f452017-02-03 16:21:38 +0900963 public void setCallback(ICallback callback) {
964 final int pid = Binder.getCallingPid();
965 final int uid = Binder.getCallingUid();
966 final long token = Binder.clearCallingIdentity();
967 try {
968 if (uid != Process.BLUETOOTH_UID) {
969 throw new SecurityException("Only Bluetooth service processes can set"
970 + " Callback");
971 }
972 synchronized (mLock) {
Jaewan Kima7dce192017-02-16 17:10:54 +0900973 int userId = UserHandle.getUserId(uid);
974 FullUserRecord user = getFullUserRecordLocked(userId);
975 if (user == null || user.mFullUserId != userId) {
976 Log.w(TAG, "Only the full user can set the callback"
977 + ", userId=" + userId);
978 return;
979 }
980 user.mCallback = callback;
981 Log.d(TAG, "The callback " + user.mCallback
Jaewan Kimbd16f452017-02-03 16:21:38 +0900982 + " is set by " + getCallingPackageName(uid));
Jaewan Kima7dce192017-02-16 17:10:54 +0900983 if (user.mCallback == null) {
Jaewan Kimbd16f452017-02-03 16:21:38 +0900984 return;
985 }
986 try {
Jaewan Kima7dce192017-02-16 17:10:54 +0900987 user.mCallback.asBinder().linkToDeath(
Jaewan Kimbd16f452017-02-03 16:21:38 +0900988 new IBinder.DeathRecipient() {
989 @Override
990 public void binderDied() {
991 synchronized (mLock) {
Jaewan Kima7dce192017-02-16 17:10:54 +0900992 user.mCallback = null;
Jaewan Kimbd16f452017-02-03 16:21:38 +0900993 }
994 }
995 }, 0);
Jaewan Kima7dce192017-02-16 17:10:54 +0900996 user.pushAddressedPlayerChangedLocked();
Jaewan Kimbd16f452017-02-03 16:21:38 +0900997 } catch (RemoteException e) {
998 Log.w(TAG, "Failed to set callback", e);
Jaewan Kima7dce192017-02-16 17:10:54 +0900999 user.mCallback = null;
Jaewan Kimbd16f452017-02-03 16:21:38 +09001000 }
1001 }
1002 } finally {
1003 Binder.restoreCallingIdentity(token);
1004 }
1005 }
1006
1007 @Override
Jaewan Kim50269362016-12-23 11:22:02 +09001008 public void setOnVolumeKeyLongPressListener(IOnVolumeKeyLongPressListener listener) {
1009 final int pid = Binder.getCallingPid();
1010 final int uid = Binder.getCallingUid();
1011 final long token = Binder.clearCallingIdentity();
1012 try {
1013 // Enforce SET_VOLUME_KEY_LONG_PRESS_LISTENER permission.
1014 if (getContext().checkPermission(
1015 android.Manifest.permission.SET_VOLUME_KEY_LONG_PRESS_LISTENER, pid, uid)
1016 != PackageManager.PERMISSION_GRANTED) {
1017 throw new SecurityException("Must hold the SET_VOLUME_KEY_LONG_PRESS_LISTENER" +
1018 " permission.");
1019 }
1020
1021 synchronized (mLock) {
Jaewan Kima7dce192017-02-16 17:10:54 +09001022 int userId = UserHandle.getUserId(uid);
1023 FullUserRecord user = getFullUserRecordLocked(userId);
1024 if (user == null || user.mFullUserId != userId) {
1025 Log.w(TAG, "Only the full user can set the volume key long-press listener"
1026 + ", userId=" + userId);
1027 return;
1028 }
Jaewan Kim50269362016-12-23 11:22:02 +09001029 if (user.mOnVolumeKeyLongPressListener != null &&
1030 user.mOnVolumeKeyLongPressListenerUid != uid) {
Jaewan Kima7dce192017-02-16 17:10:54 +09001031 Log.w(TAG, "The volume key long-press listener cannot be reset"
1032 + " by another app , mOnVolumeKeyLongPressListener="
1033 + user.mOnVolumeKeyLongPressListenerUid
1034 + ", uid=" + uid);
Jaewan Kim50269362016-12-23 11:22:02 +09001035 return;
1036 }
1037
1038 user.mOnVolumeKeyLongPressListener = listener;
1039 user.mOnVolumeKeyLongPressListenerUid = uid;
1040
Jaewan Kima7dce192017-02-16 17:10:54 +09001041 Log.d(TAG, "The volume key long-press listener "
Jaewan Kim50269362016-12-23 11:22:02 +09001042 + listener + " is set by " + getCallingPackageName(uid));
1043
1044 if (user.mOnVolumeKeyLongPressListener != null) {
1045 try {
1046 user.mOnVolumeKeyLongPressListener.asBinder().linkToDeath(
1047 new IBinder.DeathRecipient() {
1048 @Override
1049 public void binderDied() {
1050 synchronized (mLock) {
1051 user.mOnVolumeKeyLongPressListener = null;
1052 }
1053 }
1054 }, 0);
1055 } catch (RemoteException e) {
1056 Log.w(TAG, "Failed to set death recipient "
1057 + user.mOnVolumeKeyLongPressListener);
1058 user.mOnVolumeKeyLongPressListener = null;
1059 }
1060 }
1061 }
1062 } finally {
1063 Binder.restoreCallingIdentity(token);
1064 }
1065 }
1066
Jaewan Kim6e2b01c2017-01-19 16:33:14 -08001067 @Override
1068 public void setOnMediaKeyListener(IOnMediaKeyListener listener) {
1069 final int pid = Binder.getCallingPid();
1070 final int uid = Binder.getCallingUid();
1071 final long token = Binder.clearCallingIdentity();
1072 try {
1073 // Enforce SET_MEDIA_KEY_LISTENER permission.
1074 if (getContext().checkPermission(
1075 android.Manifest.permission.SET_MEDIA_KEY_LISTENER, pid, uid)
1076 != PackageManager.PERMISSION_GRANTED) {
1077 throw new SecurityException("Must hold the SET_MEDIA_KEY_LISTENER" +
1078 " permission.");
1079 }
1080
1081 synchronized (mLock) {
1082 int userId = UserHandle.getUserId(uid);
Jaewan Kima7dce192017-02-16 17:10:54 +09001083 FullUserRecord user = getFullUserRecordLocked(userId);
1084 if (user == null || user.mFullUserId != userId) {
1085 Log.w(TAG, "Only the full user can set the media key listener"
1086 + ", userId=" + userId);
1087 return;
1088 }
Jaewan Kim6e2b01c2017-01-19 16:33:14 -08001089 if (user.mOnMediaKeyListener != null && user.mOnMediaKeyListenerUid != uid) {
Jaewan Kima7dce192017-02-16 17:10:54 +09001090 Log.w(TAG, "The media key listener cannot be reset by another app. "
1091 + ", mOnMediaKeyListenerUid=" + user.mOnMediaKeyListenerUid
1092 + ", uid=" + uid);
Jaewan Kim6e2b01c2017-01-19 16:33:14 -08001093 return;
1094 }
1095
1096 user.mOnMediaKeyListener = listener;
1097 user.mOnMediaKeyListenerUid = uid;
1098
Jaewan Kima7dce192017-02-16 17:10:54 +09001099 Log.d(TAG, "The media key listener " + user.mOnMediaKeyListener
Jaewan Kim6e2b01c2017-01-19 16:33:14 -08001100 + " is set by " + getCallingPackageName(uid));
1101
1102 if (user.mOnMediaKeyListener != null) {
1103 try {
1104 user.mOnMediaKeyListener.asBinder().linkToDeath(
1105 new IBinder.DeathRecipient() {
1106 @Override
1107 public void binderDied() {
1108 synchronized (mLock) {
1109 user.mOnMediaKeyListener = null;
1110 }
1111 }
1112 }, 0);
1113 } catch (RemoteException e) {
1114 Log.w(TAG, "Failed to set death recipient " + user.mOnMediaKeyListener);
1115 user.mOnMediaKeyListener = null;
1116 }
1117 }
1118 }
1119 } finally {
1120 Binder.restoreCallingIdentity(token);
1121 }
1122 }
1123
Jaewan Kim50269362016-12-23 11:22:02 +09001124 /**
1125 * Handles the dispatching of the volume button events to one of the
1126 * registered listeners. If there's a volume key long-press listener and
1127 * there's no active global priority session, long-pressess will be sent to the
1128 * long-press listener instead of adjusting volume.
1129 *
1130 * @param keyEvent a non-null KeyEvent whose key code is one of the
1131 * {@link KeyEvent#KEYCODE_VOLUME_UP},
1132 * {@link KeyEvent#KEYCODE_VOLUME_DOWN},
1133 * or {@link KeyEvent#KEYCODE_VOLUME_MUTE}.
1134 * @param stream stream type to adjust volume.
1135 * @param musicOnly true if both UI nor haptic feedback aren't needed when adjust volume.
1136 */
1137 @Override
1138 public void dispatchVolumeKeyEvent(KeyEvent keyEvent, int stream, boolean musicOnly) {
1139 if (keyEvent == null ||
1140 (keyEvent.getKeyCode() != KeyEvent.KEYCODE_VOLUME_UP
1141 && keyEvent.getKeyCode() != KeyEvent.KEYCODE_VOLUME_DOWN
1142 && keyEvent.getKeyCode() != KeyEvent.KEYCODE_VOLUME_MUTE)) {
1143 Log.w(TAG, "Attempted to dispatch null or non-volume key event.");
1144 return;
1145 }
1146
1147 final int pid = Binder.getCallingPid();
1148 final int uid = Binder.getCallingUid();
1149 final long token = Binder.clearCallingIdentity();
1150
Jaewan Kimb2781e72017-03-02 09:57:09 +09001151 if (DEBUG_KEY_EVENT) {
Jaewan Kim50269362016-12-23 11:22:02 +09001152 Log.d(TAG, "dispatchVolumeKeyEvent, pid=" + pid + ", uid=" + uid + ", event="
1153 + keyEvent);
1154 }
1155
1156 try {
1157 synchronized (mLock) {
Jaewan Kima7dce192017-02-16 17:10:54 +09001158 if (isGlobalPriorityActiveLocked()
1159 || mCurrentFullUserRecord.mOnVolumeKeyLongPressListener == null) {
Jaewan Kim50269362016-12-23 11:22:02 +09001160 dispatchVolumeKeyEventLocked(keyEvent, stream, musicOnly);
1161 } else {
1162 // TODO: Consider the case when both volume up and down keys are pressed
1163 // at the same time.
1164 if (keyEvent.getAction() == KeyEvent.ACTION_DOWN) {
1165 if (keyEvent.getRepeatCount() == 0) {
Jaewan Kimd61a87b2017-02-17 23:14:10 +09001166 // Keeps the copy of the KeyEvent because it can be reused.
Jaewan Kima7dce192017-02-16 17:10:54 +09001167 mCurrentFullUserRecord.mInitialDownVolumeKeyEvent =
1168 KeyEvent.obtain(keyEvent);
1169 mCurrentFullUserRecord.mInitialDownVolumeStream = stream;
1170 mCurrentFullUserRecord.mInitialDownMusicOnly = musicOnly;
Jaewan Kimd61a87b2017-02-17 23:14:10 +09001171 mHandler.sendMessageDelayed(
1172 mHandler.obtainMessage(
Jaewan Kima7dce192017-02-16 17:10:54 +09001173 MessageHandler.MSG_VOLUME_INITIAL_DOWN,
1174 mCurrentFullUserRecord.mFullUserId, 0),
Jaewan Kimd61a87b2017-02-17 23:14:10 +09001175 mLongPressTimeout);
Jaewan Kim50269362016-12-23 11:22:02 +09001176 }
1177 if (keyEvent.getRepeatCount() > 0 || keyEvent.isLongPress()) {
Jaewan Kimd61a87b2017-02-17 23:14:10 +09001178 mHandler.removeMessages(MessageHandler.MSG_VOLUME_INITIAL_DOWN);
Jaewan Kima7dce192017-02-16 17:10:54 +09001179 if (mCurrentFullUserRecord.mInitialDownVolumeKeyEvent != null) {
Jaewan Kim50269362016-12-23 11:22:02 +09001180 dispatchVolumeKeyLongPressLocked(
Jaewan Kima7dce192017-02-16 17:10:54 +09001181 mCurrentFullUserRecord.mInitialDownVolumeKeyEvent);
Jaewan Kim50269362016-12-23 11:22:02 +09001182 // Mark that the key is already handled.
Jaewan Kima7dce192017-02-16 17:10:54 +09001183 mCurrentFullUserRecord.mInitialDownVolumeKeyEvent = null;
Jaewan Kim50269362016-12-23 11:22:02 +09001184 }
1185 dispatchVolumeKeyLongPressLocked(keyEvent);
1186 }
1187 } else { // if up
Jaewan Kimd61a87b2017-02-17 23:14:10 +09001188 mHandler.removeMessages(MessageHandler.MSG_VOLUME_INITIAL_DOWN);
Jaewan Kima7dce192017-02-16 17:10:54 +09001189 if (mCurrentFullUserRecord.mInitialDownVolumeKeyEvent != null
1190 && mCurrentFullUserRecord.mInitialDownVolumeKeyEvent
1191 .getDownTime() == keyEvent.getDownTime()) {
Jaewan Kim50269362016-12-23 11:22:02 +09001192 // Short-press. Should change volume.
1193 dispatchVolumeKeyEventLocked(
Jaewan Kima7dce192017-02-16 17:10:54 +09001194 mCurrentFullUserRecord.mInitialDownVolumeKeyEvent,
1195 mCurrentFullUserRecord.mInitialDownVolumeStream,
1196 mCurrentFullUserRecord.mInitialDownMusicOnly);
Jaewan Kim50269362016-12-23 11:22:02 +09001197 dispatchVolumeKeyEventLocked(keyEvent, stream, musicOnly);
1198 } else {
1199 dispatchVolumeKeyLongPressLocked(keyEvent);
1200 }
1201 }
1202 }
1203 }
1204 } finally {
1205 Binder.restoreCallingIdentity(token);
1206 }
1207 }
1208
Jaewan Kim50269362016-12-23 11:22:02 +09001209 private void dispatchVolumeKeyEventLocked(
1210 KeyEvent keyEvent, int stream, boolean musicOnly) {
1211 boolean down = keyEvent.getAction() == KeyEvent.ACTION_DOWN;
1212 boolean up = keyEvent.getAction() == KeyEvent.ACTION_UP;
1213 int direction = 0;
1214 boolean isMute = false;
1215 switch (keyEvent.getKeyCode()) {
1216 case KeyEvent.KEYCODE_VOLUME_UP:
1217 direction = AudioManager.ADJUST_RAISE;
1218 break;
1219 case KeyEvent.KEYCODE_VOLUME_DOWN:
1220 direction = AudioManager.ADJUST_LOWER;
1221 break;
1222 case KeyEvent.KEYCODE_VOLUME_MUTE:
1223 isMute = true;
1224 break;
1225 }
1226 if (down || up) {
1227 int flags = AudioManager.FLAG_FROM_KEY;
1228 if (musicOnly) {
1229 // This flag is used when the screen is off to only affect active media.
1230 flags |= AudioManager.FLAG_ACTIVE_MEDIA_ONLY;
1231 } else {
1232 // These flags are consistent with the home screen
1233 if (up) {
1234 flags |= AudioManager.FLAG_PLAY_SOUND | AudioManager.FLAG_VIBRATE;
1235 } else {
1236 flags |= AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_VIBRATE;
1237 }
1238 }
1239 if (direction != 0) {
1240 // If this is action up we want to send a beep for non-music events
1241 if (up) {
1242 direction = 0;
1243 }
1244 dispatchAdjustVolumeLocked(stream, direction, flags);
1245 } else if (isMute) {
1246 if (down && keyEvent.getRepeatCount() == 0) {
1247 dispatchAdjustVolumeLocked(stream, AudioManager.ADJUST_TOGGLE_MUTE, flags);
1248 }
1249 }
1250 }
1251 }
1252
1253 @Override
RoboErik7c82ced2014-12-04 17:39:08 -08001254 public void dispatchAdjustVolume(int suggestedStream, int delta, int flags) {
RoboErikb69ffd42014-05-30 14:57:59 -07001255 final long token = Binder.clearCallingIdentity();
1256 try {
1257 synchronized (mLock) {
Jaewan Kim50269362016-12-23 11:22:02 +09001258 dispatchAdjustVolumeLocked(suggestedStream, delta, flags);
RoboErikb69ffd42014-05-30 14:57:59 -07001259 }
1260 } finally {
1261 Binder.restoreCallingIdentity(token);
1262 }
1263 }
1264
1265 @Override
RoboErik19c95182014-06-23 15:38:48 -07001266 public void setRemoteVolumeController(IRemoteVolumeController rvc) {
1267 final int pid = Binder.getCallingPid();
1268 final int uid = Binder.getCallingUid();
1269 final long token = Binder.clearCallingIdentity();
1270 try {
John Spurlockeb69e242015-02-17 17:15:04 -05001271 enforceSystemUiPermission("listen for volume changes", pid, uid);
RoboErik19c95182014-06-23 15:38:48 -07001272 mRvc = rvc;
1273 } finally {
1274 Binder.restoreCallingIdentity(token);
1275 }
1276 }
1277
1278 @Override
RoboErikde9ba392014-09-26 12:51:01 -07001279 public boolean isGlobalPriorityActive() {
Jaewan Kim51255012017-02-24 16:19:14 +09001280 synchronized (mLock) {
Jaewan Kima7dce192017-02-16 17:10:54 +09001281 return isGlobalPriorityActiveLocked();
Jaewan Kim51255012017-02-24 16:19:14 +09001282 }
RoboErikde9ba392014-09-26 12:51:01 -07001283 }
1284
1285 @Override
RoboErika278ea72014-04-24 14:49:01 -07001286 public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
Jeff Sharkeyfe9a53b2017-03-31 14:08:23 -06001287 if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) return;
RoboErika278ea72014-04-24 14:49:01 -07001288
1289 pw.println("MEDIA SESSION SERVICE (dumpsys media_session)");
1290 pw.println();
1291
1292 synchronized (mLock) {
RoboErika08adb242014-11-21 18:28:18 -08001293 pw.println(mSessionsListeners.size() + " sessions listeners.");
Jaewan Kima7dce192017-02-16 17:10:54 +09001294 pw.println("Global priority session is " + mGlobalPrioritySession);
RoboErik4646d282014-05-13 10:13:04 -07001295 pw.println("User Records:");
Jaewan Kime0ca3f32017-02-16 15:52:39 +09001296 int count = mUserRecords.size();
RoboErika278ea72014-04-24 14:49:01 -07001297 for (int i = 0; i < count; i++) {
Jaewan Kima7dce192017-02-16 17:10:54 +09001298 mUserRecords.valueAt(i).dumpLocked(pw, "");
RoboErika278ea72014-04-24 14:49:01 -07001299 }
Jaewan Kim92dea332017-02-02 11:52:08 +09001300 mAudioPlaybackMonitor.dump(pw, "");
RoboErika278ea72014-04-24 14:49:01 -07001301 }
1302 }
RoboErik8a2cfc32014-05-16 11:19:38 -07001303
RoboErik2e7a9162014-06-04 16:53:45 -07001304 private int verifySessionsRequest(ComponentName componentName, int userId, final int pid,
1305 final int uid) {
1306 String packageName = null;
1307 if (componentName != null) {
1308 // If they gave us a component name verify they own the
1309 // package
1310 packageName = componentName.getPackageName();
1311 enforcePackageName(packageName, uid);
1312 }
1313 // Check that they can make calls on behalf of the user and
1314 // get the final user id
1315 int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId,
1316 true /* allowAll */, true /* requireFull */, "getSessions", packageName);
1317 // Check if they have the permissions or their component is
1318 // enabled for the user they're calling from.
1319 enforceMediaPermissions(componentName, pid, uid, resolvedUserId);
1320 return resolvedUserId;
1321 }
1322
Jaewan Kim50269362016-12-23 11:22:02 +09001323 private void dispatchAdjustVolumeLocked(int suggestedStream, int direction, int flags) {
Jaewan Kima7dce192017-02-16 17:10:54 +09001324 MediaSessionRecord session = isGlobalPriorityActiveLocked() ? mGlobalPrioritySession
1325 : mCurrentFullUserRecord.mPriorityStack.getDefaultVolumeSession();
Jaewan Kim50269362016-12-23 11:22:02 +09001326
RoboErik9c785402014-11-11 16:52:26 -08001327 boolean preferSuggestedStream = false;
1328 if (isValidLocalStreamType(suggestedStream)
1329 && AudioSystem.isStreamActive(suggestedStream, 0)) {
1330 preferSuggestedStream = true;
1331 }
Jaewan Kimb2781e72017-03-02 09:57:09 +09001332 if (DEBUG_KEY_EVENT) {
Jaewan Kim5e1476e2016-07-19 22:25:39 +09001333 Log.d(TAG, "Adjusting " + session + " by " + direction + ". flags="
1334 + flags + ", suggestedStream=" + suggestedStream
1335 + ", preferSuggestedStream=" + preferSuggestedStream);
1336 }
RoboErik9c785402014-11-11 16:52:26 -08001337 if (session == null || preferSuggestedStream) {
RoboErik94c716e2014-09-14 13:54:31 -07001338 if ((flags & AudioManager.FLAG_ACTIVE_MEDIA_ONLY) != 0
1339 && !AudioSystem.isStreamActive(AudioManager.STREAM_MUSIC, 0)) {
RoboErik3c45c292014-07-08 16:47:31 -07001340 if (DEBUG) {
1341 Log.d(TAG, "No active session to adjust, skipping media only volume event");
RoboErik3c45c292014-07-08 16:47:31 -07001342 }
RoboErikb7c014c2014-07-22 15:58:22 -07001343 return;
RoboErik3c45c292014-07-08 16:47:31 -07001344 }
Shibin George19e84042016-06-14 20:42:13 +05301345
1346 // Execute mAudioService.adjustSuggestedStreamVolume() on
1347 // handler thread of MediaSessionService.
1348 // This will release the MediaSessionService.mLock sooner and avoid
1349 // a potential deadlock between MediaSessionService.mLock and
1350 // ActivityManagerService lock.
1351 mHandler.post(new Runnable() {
1352 @Override
1353 public void run() {
1354 try {
1355 String packageName = getContext().getOpPackageName();
1356 mAudioService.adjustSuggestedStreamVolume(direction, suggestedStream,
1357 flags, packageName, TAG);
1358 } catch (RemoteException e) {
1359 Log.e(TAG, "Error adjusting default volume.", e);
1360 }
1361 }
1362 });
RoboErikb69ffd42014-05-30 14:57:59 -07001363 } else {
RoboErik0dac35a2014-08-12 15:48:49 -07001364 session.adjustVolume(direction, flags, getContext().getPackageName(),
Jaewan Kim8f729082016-06-21 12:36:26 +09001365 Process.SYSTEM_UID, true);
RoboErikb69ffd42014-05-30 14:57:59 -07001366 }
1367 }
1368
Jaewan Kim6e2b01c2017-01-19 16:33:14 -08001369 private void handleVoiceKeyEventLocked(KeyEvent keyEvent, boolean needWakeLock) {
RoboErik9a9d0b52014-05-20 14:53:39 -07001370 int action = keyEvent.getAction();
1371 boolean isLongPress = (keyEvent.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0;
1372 if (action == KeyEvent.ACTION_DOWN) {
1373 if (keyEvent.getRepeatCount() == 0) {
1374 mVoiceButtonDown = true;
1375 mVoiceButtonHandled = false;
1376 } else if (mVoiceButtonDown && !mVoiceButtonHandled && isLongPress) {
1377 mVoiceButtonHandled = true;
1378 startVoiceInput(needWakeLock);
1379 }
1380 } else if (action == KeyEvent.ACTION_UP) {
1381 if (mVoiceButtonDown) {
1382 mVoiceButtonDown = false;
1383 if (!mVoiceButtonHandled && !keyEvent.isCanceled()) {
1384 // Resend the down then send this event through
1385 KeyEvent downEvent = KeyEvent.changeAction(keyEvent, KeyEvent.ACTION_DOWN);
Jaewan Kim98003d32017-02-24 18:33:04 +09001386 dispatchMediaKeyEventLocked(downEvent, needWakeLock);
1387 dispatchMediaKeyEventLocked(keyEvent, needWakeLock);
RoboErik9a9d0b52014-05-20 14:53:39 -07001388 }
1389 }
1390 }
1391 }
1392
Jaewan Kim98003d32017-02-24 18:33:04 +09001393 private void dispatchMediaKeyEventLocked(KeyEvent keyEvent, boolean needWakeLock) {
Jaewan Kima7dce192017-02-16 17:10:54 +09001394 MediaSessionRecord session = mCurrentFullUserRecord.getMediaButtonSessionLocked();
RoboErik9a9d0b52014-05-20 14:53:39 -07001395 if (session != null) {
Jaewan Kim50269362016-12-23 11:22:02 +09001396 if (DEBUG_KEY_EVENT) {
Jaewan Kim5e1476e2016-07-19 22:25:39 +09001397 Log.d(TAG, "Sending " + keyEvent + " to " + session);
RoboErik9a9d0b52014-05-20 14:53:39 -07001398 }
1399 if (needWakeLock) {
1400 mKeyEventReceiver.aquireWakeLockLocked();
1401 }
Jaewan Kim50269362016-12-23 11:22:02 +09001402 // 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 -07001403 session.sendMediaButton(keyEvent,
1404 needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1,
Jaewan Kim8f729082016-06-21 12:36:26 +09001405 mKeyEventReceiver, Process.SYSTEM_UID,
Donghyun Cho1ea56832016-02-23 16:30:07 +09001406 getContext().getPackageName());
Jaewan Kima7dce192017-02-16 17:10:54 +09001407 if (mCurrentFullUserRecord.mCallback != null) {
Jaewan Kimbd16f452017-02-03 16:21:38 +09001408 try {
Jaewan Kima7dce192017-02-16 17:10:54 +09001409 mCurrentFullUserRecord.mCallback.onMediaKeyEventDispatchedToMediaSession(
1410 keyEvent,
Jaewan Kimbd16f452017-02-03 16:21:38 +09001411 new MediaSession.Token(session.getControllerBinder()));
1412 } catch (RemoteException e) {
1413 Log.w(TAG, "Failed to send callback", e);
1414 }
1415 }
Jaewan Kima7dce192017-02-16 17:10:54 +09001416 } else if (mCurrentFullUserRecord.mLastMediaButtonReceiver != null
1417 || mCurrentFullUserRecord.mRestoredMediaButtonReceiver != null) {
1418 if (needWakeLock) {
1419 mKeyEventReceiver.aquireWakeLockLocked();
1420 }
1421 Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
1422 mediaButtonIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
1423 mediaButtonIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
1424 try {
1425 if (mCurrentFullUserRecord.mLastMediaButtonReceiver != null) {
1426 PendingIntent receiver = mCurrentFullUserRecord.mLastMediaButtonReceiver;
1427 if (DEBUG_KEY_EVENT) {
1428 Log.d(TAG, "Sending " + keyEvent
Jaewan Kim92dea332017-02-02 11:52:08 +09001429 + " to the last known PendingIntent " + receiver);
Jaewan Kima7dce192017-02-16 17:10:54 +09001430 }
1431 receiver.send(getContext(),
1432 needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1,
1433 mediaButtonIntent, mKeyEventReceiver, mHandler);
1434 if (mCurrentFullUserRecord.mCallback != null) {
1435 ComponentName componentName = mCurrentFullUserRecord
1436 .mLastMediaButtonReceiver.getIntent().getComponent();
1437 if (componentName != null) {
1438 mCurrentFullUserRecord.mCallback
1439 .onMediaKeyEventDispatchedToMediaButtonReceiver(
1440 keyEvent, componentName);
Jaewan Kimbd16f452017-02-03 16:21:38 +09001441 }
RoboErikc8f92d12015-01-05 16:48:07 -08001442 }
Jaewan Kima7dce192017-02-16 17:10:54 +09001443 } else {
1444 ComponentName receiver =
1445 mCurrentFullUserRecord.mRestoredMediaButtonReceiver;
1446 if (DEBUG_KEY_EVENT) {
1447 Log.d(TAG, "Sending " + keyEvent + " to the restored intent "
1448 + receiver);
1449 }
1450 mediaButtonIntent.setComponent(receiver);
1451 getContext().sendBroadcastAsUser(mediaButtonIntent,
Jaewan Kim92dea332017-02-02 11:52:08 +09001452 UserHandle.of(mCurrentFullUserRecord
1453 .mRestoredMediaButtonReceiverUserId));
Jaewan Kima7dce192017-02-16 17:10:54 +09001454 if (mCurrentFullUserRecord.mCallback != null) {
1455 mCurrentFullUserRecord.mCallback
1456 .onMediaKeyEventDispatchedToMediaButtonReceiver(
1457 keyEvent, receiver);
1458 }
RoboErikb214efb2014-07-24 13:20:30 -07001459 }
Jaewan Kima7dce192017-02-16 17:10:54 +09001460 } catch (CanceledException e) {
1461 Log.i(TAG, "Error sending key event to media button receiver "
1462 + mCurrentFullUserRecord.mLastMediaButtonReceiver, e);
1463 } catch (RemoteException e) {
1464 Log.w(TAG, "Failed to send callback", e);
RoboErik9a9d0b52014-05-20 14:53:39 -07001465 }
RoboErik9a9d0b52014-05-20 14:53:39 -07001466 }
1467 }
1468
1469 private void startVoiceInput(boolean needWakeLock) {
1470 Intent voiceIntent = null;
1471 // select which type of search to launch:
1472 // - screen on and device unlocked: action is ACTION_WEB_SEARCH
1473 // - device locked or screen off: action is
1474 // ACTION_VOICE_SEARCH_HANDS_FREE
1475 // with EXTRA_SECURE set to true if the device is securely locked
1476 PowerManager pm = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE);
1477 boolean isLocked = mKeyguardManager != null && mKeyguardManager.isKeyguardLocked();
1478 if (!isLocked && pm.isScreenOn()) {
1479 voiceIntent = new Intent(android.speech.RecognizerIntent.ACTION_WEB_SEARCH);
1480 Log.i(TAG, "voice-based interactions: about to use ACTION_WEB_SEARCH");
1481 } else {
1482 voiceIntent = new Intent(RecognizerIntent.ACTION_VOICE_SEARCH_HANDS_FREE);
1483 voiceIntent.putExtra(RecognizerIntent.EXTRA_SECURE,
1484 isLocked && mKeyguardManager.isKeyguardSecure());
1485 Log.i(TAG, "voice-based interactions: about to use ACTION_VOICE_SEARCH_HANDS_FREE");
1486 }
1487 // start the search activity
1488 if (needWakeLock) {
1489 mMediaEventWakeLock.acquire();
1490 }
1491 try {
1492 if (voiceIntent != null) {
1493 voiceIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
1494 | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
Jaewan Kim8f729082016-06-21 12:36:26 +09001495 if (DEBUG) Log.d(TAG, "voiceIntent: " + voiceIntent);
RoboErik9a9d0b52014-05-20 14:53:39 -07001496 getContext().startActivityAsUser(voiceIntent, UserHandle.CURRENT);
1497 }
1498 } catch (ActivityNotFoundException e) {
1499 Log.w(TAG, "No activity for search: " + e);
1500 } finally {
1501 if (needWakeLock) {
1502 mMediaEventWakeLock.release();
1503 }
1504 }
1505 }
1506
1507 private boolean isVoiceKey(int keyCode) {
1508 return keyCode == KeyEvent.KEYCODE_HEADSETHOOK;
1509 }
1510
Jeff Brown38d3feb2015-03-19 18:26:30 -07001511 private boolean isUserSetupComplete() {
1512 return Settings.Secure.getIntForUser(getContext().getContentResolver(),
1513 Settings.Secure.USER_SETUP_COMPLETE, 0, UserHandle.USER_CURRENT) != 0;
1514 }
1515
RoboErik9c785402014-11-11 16:52:26 -08001516 // we only handle public stream types, which are 0-5
1517 private boolean isValidLocalStreamType(int streamType) {
1518 return streamType >= AudioManager.STREAM_VOICE_CALL
1519 && streamType <= AudioManager.STREAM_NOTIFICATION;
1520 }
1521
Jaewan Kim6e2b01c2017-01-19 16:33:14 -08001522 private class MediaKeyListenerResultReceiver extends ResultReceiver implements Runnable {
1523 private KeyEvent mKeyEvent;
1524 private boolean mNeedWakeLock;
1525 private boolean mHandled;
1526
1527 private MediaKeyListenerResultReceiver(KeyEvent keyEvent, boolean needWakeLock) {
1528 super(mHandler);
1529 mHandler.postDelayed(this, MEDIA_KEY_LISTENER_TIMEOUT);
1530 mKeyEvent = keyEvent;
1531 mNeedWakeLock = needWakeLock;
1532 }
1533
1534 @Override
1535 public void run() {
1536 Log.d(TAG, "The media key listener is timed-out for " + mKeyEvent);
1537 dispatchMediaKeyEvent();
1538 }
1539
1540 @Override
1541 protected void onReceiveResult(int resultCode, Bundle resultData) {
1542 if (resultCode == MediaSessionManager.RESULT_MEDIA_KEY_HANDLED) {
1543 mHandled = true;
1544 mHandler.removeCallbacks(this);
1545 return;
1546 }
1547 dispatchMediaKeyEvent();
1548 }
1549
1550 private void dispatchMediaKeyEvent() {
1551 if (mHandled) {
1552 return;
1553 }
1554 mHandled = true;
1555 mHandler.removeCallbacks(this);
1556 synchronized (mLock) {
Jaewan Kima7dce192017-02-16 17:10:54 +09001557 if (!isGlobalPriorityActiveLocked()
Jaewan Kim98003d32017-02-24 18:33:04 +09001558 && isVoiceKey(mKeyEvent.getKeyCode())) {
1559 handleVoiceKeyEventLocked(mKeyEvent, mNeedWakeLock);
1560 } else {
1561 dispatchMediaKeyEventLocked(mKeyEvent, mNeedWakeLock);
1562 }
Jaewan Kim6e2b01c2017-01-19 16:33:14 -08001563 }
1564 }
1565 }
1566
RoboErik418c10c2014-05-19 09:25:25 -07001567 private KeyEventWakeLockReceiver mKeyEventReceiver = new KeyEventWakeLockReceiver(mHandler);
1568
RoboErikb214efb2014-07-24 13:20:30 -07001569 class KeyEventWakeLockReceiver extends ResultReceiver implements Runnable,
1570 PendingIntent.OnFinished {
RoboErik418c10c2014-05-19 09:25:25 -07001571 private final Handler mHandler;
1572 private int mRefCount = 0;
1573 private int mLastTimeoutId = 0;
1574
1575 public KeyEventWakeLockReceiver(Handler handler) {
1576 super(handler);
1577 mHandler = handler;
1578 }
1579
1580 public void onTimeout() {
1581 synchronized (mLock) {
1582 if (mRefCount == 0) {
1583 // We've already released it, so just return
1584 return;
1585 }
1586 mLastTimeoutId++;
1587 mRefCount = 0;
1588 releaseWakeLockLocked();
1589 }
1590 }
1591
1592 public void aquireWakeLockLocked() {
1593 if (mRefCount == 0) {
1594 mMediaEventWakeLock.acquire();
1595 }
1596 mRefCount++;
1597 mHandler.removeCallbacks(this);
1598 mHandler.postDelayed(this, WAKELOCK_TIMEOUT);
1599
1600 }
1601
1602 @Override
1603 public void run() {
1604 onTimeout();
1605 }
1606
RoboErik8a2cfc32014-05-16 11:19:38 -07001607 @Override
1608 protected void onReceiveResult(int resultCode, Bundle resultData) {
RoboErik418c10c2014-05-19 09:25:25 -07001609 if (resultCode < mLastTimeoutId) {
1610 // Ignore results from calls that were before the last
1611 // timeout, just in case.
1612 return;
1613 } else {
1614 synchronized (mLock) {
1615 if (mRefCount > 0) {
1616 mRefCount--;
1617 if (mRefCount == 0) {
1618 releaseWakeLockLocked();
1619 }
1620 }
RoboErik8a2cfc32014-05-16 11:19:38 -07001621 }
1622 }
1623 }
RoboErik418c10c2014-05-19 09:25:25 -07001624
1625 private void releaseWakeLockLocked() {
1626 mMediaEventWakeLock.release();
1627 mHandler.removeCallbacks(this);
1628 }
RoboErikb214efb2014-07-24 13:20:30 -07001629
1630 @Override
1631 public void onSendFinished(PendingIntent pendingIntent, Intent intent, int resultCode,
1632 String resultData, Bundle resultExtras) {
1633 onReceiveResult(resultCode, null);
1634 }
RoboErik8a2cfc32014-05-16 11:19:38 -07001635 };
1636
1637 BroadcastReceiver mKeyEventDone = new BroadcastReceiver() {
1638 @Override
1639 public void onReceive(Context context, Intent intent) {
1640 if (intent == null) {
1641 return;
1642 }
1643 Bundle extras = intent.getExtras();
1644 if (extras == null) {
1645 return;
1646 }
1647 synchronized (mLock) {
1648 if (extras.containsKey(EXTRA_WAKELOCK_ACQUIRED)
1649 && mMediaEventWakeLock.isHeld()) {
1650 mMediaEventWakeLock.release();
1651 }
1652 }
1653 }
1654 };
RoboErik01fe6612014-02-13 14:19:04 -08001655 }
1656
RoboErik2e7a9162014-06-04 16:53:45 -07001657 final class MessageHandler extends Handler {
1658 private static final int MSG_SESSIONS_CHANGED = 1;
Jaewan Kimd61a87b2017-02-17 23:14:10 +09001659 private static final int MSG_VOLUME_INITIAL_DOWN = 2;
Jaewan Kim92dea332017-02-02 11:52:08 +09001660 private final SparseArray<Integer> mIntegerCache = new SparseArray<>();
RoboErik2e7a9162014-06-04 16:53:45 -07001661
1662 @Override
1663 public void handleMessage(Message msg) {
1664 switch (msg.what) {
1665 case MSG_SESSIONS_CHANGED:
Jaewan Kim92dea332017-02-02 11:52:08 +09001666 pushSessionsChanged((int) msg.obj);
RoboErik2e7a9162014-06-04 16:53:45 -07001667 break;
Jaewan Kimd61a87b2017-02-17 23:14:10 +09001668 case MSG_VOLUME_INITIAL_DOWN:
1669 synchronized (mLock) {
Jaewan Kima7dce192017-02-16 17:10:54 +09001670 FullUserRecord user = mUserRecords.get((int) msg.arg1);
Jaewan Kimd61a87b2017-02-17 23:14:10 +09001671 if (user != null && user.mInitialDownVolumeKeyEvent != null) {
1672 dispatchVolumeKeyLongPressLocked(user.mInitialDownVolumeKeyEvent);
1673 // Mark that the key is already handled.
1674 user.mInitialDownVolumeKeyEvent = null;
1675 }
1676 }
1677 break;
RoboErik2e7a9162014-06-04 16:53:45 -07001678 }
1679 }
1680
Jaewan Kim92dea332017-02-02 11:52:08 +09001681 public void postSessionsChanged(int userId) {
1682 // Use object instead of the arguments when posting message to remove pending requests.
1683 Integer userIdInteger = mIntegerCache.get(userId);
1684 if (userIdInteger == null) {
1685 userIdInteger = Integer.valueOf(userId);
1686 mIntegerCache.put(userId, userIdInteger);
1687 }
1688 removeMessages(MSG_SESSIONS_CHANGED, userIdInteger);
1689 obtainMessage(MSG_SESSIONS_CHANGED, userIdInteger).sendToTarget();
RoboErik2e7a9162014-06-04 16:53:45 -07001690 }
1691 }
RoboErik01fe6612014-02-13 14:19:04 -08001692}