blob: 8c2ef20827ed3289764552626951cc016209b02a [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
Jaewan Kimf7a77062018-01-27 01:34:24 +090019import static android.media.SessionToken2.TYPE_SESSION;
20
RoboErike7880d82014-04-30 12:48:25 -070021import android.app.ActivityManager;
Jaewan Kimfed49502018-03-05 17:51:12 +090022import android.app.AppGlobals;
Julia Reynoldsb852e562017-06-06 16:14:18 -040023import android.app.INotificationManager;
RoboErik9a9d0b52014-05-20 14:53:39 -070024import android.app.KeyguardManager;
RoboErikb214efb2014-07-24 13:20:30 -070025import android.app.PendingIntent;
26import android.app.PendingIntent.CanceledException;
RoboErik9a9d0b52014-05-20 14:53:39 -070027import android.content.ActivityNotFoundException;
RoboErik8a2cfc32014-05-16 11:19:38 -070028import android.content.BroadcastReceiver;
RoboErike7880d82014-04-30 12:48:25 -070029import android.content.ComponentName;
RoboErik6f0e4dd2014-06-17 16:56:27 -070030import android.content.ContentResolver;
RoboErik01fe6612014-02-13 14:19:04 -080031import android.content.Context;
RoboErik8a2cfc32014-05-16 11:19:38 -070032import android.content.Intent;
Jaewan Kim66d451b2018-02-12 21:23:06 +090033import android.content.IntentFilter;
Jaewan Kimfed49502018-03-05 17:51:12 +090034import android.content.pm.IPackageManager;
RoboErika278ea72014-04-24 14:49:01 -070035import android.content.pm.PackageManager;
Jaewan Kimf7a77062018-01-27 01:34:24 +090036import android.content.pm.PackageManager.NameNotFoundException;
Jaewan Kimceb6b6e2018-01-21 20:56:10 +090037import android.content.pm.ResolveInfo;
38import android.content.pm.ServiceInfo;
Jaewan Kima7dce192017-02-16 17:10:54 +090039import android.content.pm.UserInfo;
RoboErik7aef77b2014-08-08 15:56:54 -070040import android.database.ContentObserver;
RoboErik3c45c292014-07-08 16:47:31 -070041import android.media.AudioManager;
Sungsoo Lim875e6972017-11-03 02:22:35 +000042import android.media.AudioPlaybackConfiguration;
RoboErik94c716e2014-09-14 13:54:31 -070043import android.media.AudioSystem;
RoboErikb69ffd42014-05-30 14:57:59 -070044import android.media.IAudioService;
RoboErik19c95182014-06-23 15:38:48 -070045import android.media.IRemoteVolumeController;
Jaewan Kim379e30d2018-01-29 11:57:04 +090046import android.media.ISessionTokensListener;
Sungsoo Lim117c7f72018-02-13 16:02:24 +090047import android.media.MediaController2;
Jaewan Kimbcecf312018-01-23 19:30:42 +090048import android.media.MediaLibraryService2;
Jaewan Kimceb6b6e2018-01-21 20:56:10 +090049import android.media.MediaSessionService2;
Jaewan Kim04de5de2018-01-25 02:24:03 +090050import android.media.SessionToken2;
RoboErik2e7a9162014-06-04 16:53:45 -070051import android.media.session.IActiveSessionsListener;
Jaewan Kimbd16f452017-02-03 16:21:38 +090052import android.media.session.ICallback;
Jaewan Kim6e2b01c2017-01-19 16:33:14 -080053import android.media.session.IOnMediaKeyListener;
Jaewan Kim50269362016-12-23 11:22:02 +090054import android.media.session.IOnVolumeKeyLongPressListener;
RoboErik07c70772014-03-20 13:33:52 -070055import android.media.session.ISession;
56import android.media.session.ISessionCallback;
57import android.media.session.ISessionManager;
Jeff Browndba34ba2014-06-24 20:46:03 -070058import android.media.session.MediaSession;
Jaewan Kim6e2b01c2017-01-19 16:33:14 -080059import android.media.session.MediaSessionManager;
RoboErik7aef77b2014-08-08 15:56:54 -070060import android.net.Uri;
RoboErik01fe6612014-02-13 14:19:04 -080061import android.os.Binder;
RoboErik8a2cfc32014-05-16 11:19:38 -070062import android.os.Bundle;
RoboErik8ae0f342014-02-24 18:02:08 -080063import android.os.Handler;
RoboErike7880d82014-04-30 12:48:25 -070064import android.os.IBinder;
RoboErik2e7a9162014-06-04 16:53:45 -070065import android.os.Message;
RoboErik8a2cfc32014-05-16 11:19:38 -070066import android.os.PowerManager;
Jaewan Kim8f729082016-06-21 12:36:26 +090067import android.os.Process;
RoboErik01fe6612014-02-13 14:19:04 -080068import android.os.RemoteException;
RoboErik8a2cfc32014-05-16 11:19:38 -070069import android.os.ResultReceiver;
RoboErikb69ffd42014-05-30 14:57:59 -070070import android.os.ServiceManager;
RoboErike7880d82014-04-30 12:48:25 -070071import android.os.UserHandle;
Jaewan Kim8f729082016-06-21 12:36:26 +090072import android.os.UserManager;
RoboErike7880d82014-04-30 12:48:25 -070073import android.provider.Settings;
RoboErik9a9d0b52014-05-20 14:53:39 -070074import android.speech.RecognizerIntent;
RoboErik01fe6612014-02-13 14:19:04 -080075import android.text.TextUtils;
Sungsoo Lim117c7f72018-02-13 16:02:24 +090076import android.util.ArrayMap;
RoboErik01fe6612014-02-13 14:19:04 -080077import android.util.Log;
Jeff Brown38d3feb2015-03-19 18:26:30 -070078import android.util.Slog;
RoboErik4646d282014-05-13 10:13:04 -070079import android.util.SparseArray;
Jaewan Kima7dce192017-02-16 17:10:54 +090080import android.util.SparseIntArray;
RoboErik8a2cfc32014-05-16 11:19:38 -070081import android.view.KeyEvent;
Jaewan Kimd61a87b2017-02-17 23:14:10 +090082import android.view.ViewConfiguration;
RoboErik01fe6612014-02-13 14:19:04 -080083
Jaewan Kim66d451b2018-02-12 21:23:06 +090084import com.android.internal.os.BackgroundThread;
Jeff Sharkeyfe9a53b2017-03-31 14:08:23 -060085import com.android.internal.util.DumpUtils;
RoboErik01fe6612014-02-13 14:19:04 -080086import com.android.server.SystemService;
RoboErika278ea72014-04-24 14:49:01 -070087import com.android.server.Watchdog;
88import com.android.server.Watchdog.Monitor;
RoboErik01fe6612014-02-13 14:19:04 -080089
RoboErika278ea72014-04-24 14:49:01 -070090import java.io.FileDescriptor;
91import java.io.PrintWriter;
RoboErik01fe6612014-02-13 14:19:04 -080092import java.util.ArrayList;
Sungsoo Lim117c7f72018-02-13 16:02:24 +090093import java.util.HashSet;
RoboErike7880d82014-04-30 12:48:25 -070094import java.util.List;
Sungsoo Lim117c7f72018-02-13 16:02:24 +090095import java.util.Map;
96import java.util.Set;
Sungsoo Lim375efa12018-03-02 10:17:08 +090097import java.util.NoSuchElementException;
RoboErik01fe6612014-02-13 14:19:04 -080098
99/**
100 * System implementation of MediaSessionManager
101 */
RoboErika278ea72014-04-24 14:49:01 -0700102public class MediaSessionService extends SystemService implements Monitor {
RoboErik01fe6612014-02-13 14:19:04 -0800103 private static final String TAG = "MediaSessionService";
Jaewan Kim92dea332017-02-02 11:52:08 +0900104 static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
Jaewan Kim50269362016-12-23 11:22:02 +0900105 // Leave log for key event always.
106 private static final boolean DEBUG_KEY_EVENT = true;
RoboErik01fe6612014-02-13 14:19:04 -0800107
RoboErik418c10c2014-05-19 09:25:25 -0700108 private static final int WAKELOCK_TIMEOUT = 5000;
Jaewan Kim6e2b01c2017-01-19 16:33:14 -0800109 private static final int MEDIA_KEY_LISTENER_TIMEOUT = 1000;
RoboErik418c10c2014-05-19 09:25:25 -0700110
RoboErik01fe6612014-02-13 14:19:04 -0800111 private final SessionManagerImpl mSessionManagerImpl;
112
Jaewan Kima7dce192017-02-16 17:10:54 +0900113 // Keeps the full user id for each user.
114 private final SparseIntArray mFullUserIds = new SparseIntArray();
115 private final SparseArray<FullUserRecord> mUserRecords = new SparseArray<FullUserRecord>();
RoboErik2e7a9162014-06-04 16:53:45 -0700116 private final ArrayList<SessionsListenerRecord> mSessionsListeners
117 = new ArrayList<SessionsListenerRecord>();
RoboErik01fe6612014-02-13 14:19:04 -0800118 private final Object mLock = new Object();
RoboErik2e7a9162014-06-04 16:53:45 -0700119 private final MessageHandler mHandler = new MessageHandler();
RoboErik8a2cfc32014-05-16 11:19:38 -0700120 private final PowerManager.WakeLock mMediaEventWakeLock;
Jaewan Kimd61a87b2017-02-17 23:14:10 +0900121 private final int mLongPressTimeout;
Jaewan Kimfed49502018-03-05 17:51:12 +0900122 private final INotificationManager mNotificationManager;
123 private final IPackageManager mPackageManager;
RoboErik01fe6612014-02-13 14:19:04 -0800124
RoboErik9a9d0b52014-05-20 14:53:39 -0700125 private KeyguardManager mKeyguardManager;
RoboErikb69ffd42014-05-30 14:57:59 -0700126 private IAudioService mAudioService;
RoboErik6f0e4dd2014-06-17 16:56:27 -0700127 private ContentResolver mContentResolver;
RoboErik7aef77b2014-08-08 15:56:54 -0700128 private SettingsObserver mSettingsObserver;
Jaewan Kimfdb612e2017-07-01 09:23:41 +0900129 private boolean mHasFeatureLeanback;
RoboErik9a9d0b52014-05-20 14:53:39 -0700130
Jaewan Kima7dce192017-02-16 17:10:54 +0900131 // The FullUserRecord of the current users. (i.e. The foreground user that isn't a profile)
132 // It's always not null after the MediaSessionService is started.
133 private FullUserRecord mCurrentFullUserRecord;
134 private MediaSessionRecord mGlobalPrioritySession;
Sungsoo Lim875e6972017-11-03 02:22:35 +0000135 private AudioPlayerStateMonitor mAudioPlayerStateMonitor;
RoboErike7880d82014-04-30 12:48:25 -0700136
RoboErik19c95182014-06-23 15:38:48 -0700137 // Used to notify system UI when remote volume was changed. TODO find a
138 // better way to handle this.
139 private IRemoteVolumeController mRvc;
140
Jaewan Kimceb6b6e2018-01-21 20:56:10 +0900141 // MediaSession2 support
Jaewan Kim005e2bb2018-03-02 15:55:12 +0900142 // TODO(jaewan): Support multi-user and managed profile. (b/73597722)
143 // TODO(jaewan): Make it priority list for handling volume/media key. (b/73760382)
Sungsoo Lim117c7f72018-02-13 16:02:24 +0900144 private final Map<SessionToken2, MediaController2> mSessionRecords = new ArrayMap<>();
Jaewan Kimceb6b6e2018-01-21 20:56:10 +0900145
Sungsoo Lim375efa12018-03-02 10:17:08 +0900146 private final List<SessionTokensListenerRecord> mSessionTokensListeners = new ArrayList<>();
147
RoboErik01fe6612014-02-13 14:19:04 -0800148 public MediaSessionService(Context context) {
149 super(context);
150 mSessionManagerImpl = new SessionManagerImpl();
RoboErik8a2cfc32014-05-16 11:19:38 -0700151 PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
152 mMediaEventWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "handleMediaEvent");
Jaewan Kimd61a87b2017-02-17 23:14:10 +0900153 mLongPressTimeout = ViewConfiguration.getLongPressTimeout();
Julia Reynoldsb852e562017-06-06 16:14:18 -0400154 mNotificationManager = INotificationManager.Stub.asInterface(
155 ServiceManager.getService(Context.NOTIFICATION_SERVICE));
Jaewan Kimfed49502018-03-05 17:51:12 +0900156 mPackageManager = AppGlobals.getPackageManager();
RoboErik01fe6612014-02-13 14:19:04 -0800157 }
158
159 @Override
160 public void onStart() {
161 publishBinderService(Context.MEDIA_SESSION_SERVICE, mSessionManagerImpl);
RoboErika278ea72014-04-24 14:49:01 -0700162 Watchdog.getInstance().addMonitor(this);
RoboErik9a9d0b52014-05-20 14:53:39 -0700163 mKeyguardManager =
164 (KeyguardManager) getContext().getSystemService(Context.KEYGUARD_SERVICE);
RoboErikb69ffd42014-05-30 14:57:59 -0700165 mAudioService = getAudioService();
Sungsoo Lim875e6972017-11-03 02:22:35 +0000166 mAudioPlayerStateMonitor = AudioPlayerStateMonitor.getInstance();
167 mAudioPlayerStateMonitor.registerListener(
Sungsoo Lim2afdbc42017-11-01 13:45:59 +0900168 (config, isRemoved) -> {
169 if (isRemoved || !config.isActive() || config.getPlayerType()
170 == AudioPlaybackConfiguration.PLAYER_TYPE_JAM_SOUNDPOOL) {
171 return;
Jaewan Kim92dea332017-02-02 11:52:08 +0900172 }
Sungsoo Lim2afdbc42017-11-01 13:45:59 +0900173 synchronized (mLock) {
174 FullUserRecord user = getFullUserRecordLocked(
175 UserHandle.getUserId(config.getClientUid()));
176 if (user != null) {
177 user.mPriorityStack.updateMediaButtonSessionIfNeeded();
178 }
179 }
180 }, null /* handler */);
Sungsoo Lim875e6972017-11-03 02:22:35 +0000181 mAudioPlayerStateMonitor.registerSelfIntoAudioServiceIfNeeded(mAudioService);
RoboErik6f0e4dd2014-06-17 16:56:27 -0700182 mContentResolver = getContext().getContentResolver();
RoboErik7aef77b2014-08-08 15:56:54 -0700183 mSettingsObserver = new SettingsObserver();
184 mSettingsObserver.observe();
Jaewan Kimfdb612e2017-07-01 09:23:41 +0900185 mHasFeatureLeanback = getContext().getPackageManager().hasSystemFeature(
186 PackageManager.FEATURE_LEANBACK);
RoboErikc8f92d12015-01-05 16:48:07 -0800187
188 updateUser();
Jaewan Kimceb6b6e2018-01-21 20:56:10 +0900189
Jaewan Kim66d451b2018-02-12 21:23:06 +0900190 registerPackageBroadcastReceivers();
Jaewan Kim005e2bb2018-03-02 15:55:12 +0900191 // TODO(jaewan): Query per users (b/73597722)
Jaewan Kimceb6b6e2018-01-21 20:56:10 +0900192 buildMediaSessionService2List();
RoboErikb69ffd42014-05-30 14:57:59 -0700193 }
194
195 private IAudioService getAudioService() {
196 IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE);
197 return IAudioService.Stub.asInterface(b);
RoboErik07c70772014-03-20 13:33:52 -0700198 }
199
Jaewan Kima7dce192017-02-16 17:10:54 +0900200 private boolean isGlobalPriorityActiveLocked() {
201 return mGlobalPrioritySession != null && mGlobalPrioritySession.isActive();
202 }
203
RoboErika8f95142014-05-05 14:23:49 -0700204 public void updateSession(MediaSessionRecord record) {
RoboErike7880d82014-04-30 12:48:25 -0700205 synchronized (mLock) {
Jaewan Kima7dce192017-02-16 17:10:54 +0900206 FullUserRecord user = getFullUserRecordLocked(record.getUserId());
Jaewan Kim101b4d52017-05-18 13:23:11 +0900207 if (user == null) {
208 Log.w(TAG, "Unknown session updated. Ignoring.");
RoboErik4646d282014-05-13 10:13:04 -0700209 return;
210 }
Jaewan Kima7dce192017-02-16 17:10:54 +0900211 if ((record.getFlags() & MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY) != 0) {
Jaewan Kim101b4d52017-05-18 13:23:11 +0900212 if (DEBUG_KEY_EVENT) {
213 Log.d(TAG, "Global priority session is updated, active=" + record.isActive());
214 }
Jaewan Kim92dea332017-02-02 11:52:08 +0900215 user.pushAddressedPlayerChangedLocked();
Jaewan Kim101b4d52017-05-18 13:23:11 +0900216 } else {
217 if (!user.mPriorityStack.contains(record)) {
218 Log.w(TAG, "Unknown session updated. Ignoring.");
219 return;
220 }
221 user.mPriorityStack.onSessionStateChange(record);
Jaewan Kima7dce192017-02-16 17:10:54 +0900222 }
Jaewan Kim92dea332017-02-02 11:52:08 +0900223 mHandler.postSessionsChanged(record.getUserId());
RoboErike7880d82014-04-30 12:48:25 -0700224 }
225 }
226
Jaewan Kimfa85b602017-10-10 16:49:58 +0900227 public void setGlobalPrioritySession(MediaSessionRecord record) {
228 synchronized (mLock) {
229 FullUserRecord user = getFullUserRecordLocked(record.getUserId());
230 if (mGlobalPrioritySession != record) {
231 Log.d(TAG, "Global priority session is changed from " + mGlobalPrioritySession
232 + " to " + record);
233 mGlobalPrioritySession = record;
234 if (user != null && user.mPriorityStack.contains(record)) {
235 // Handle the global priority session separately.
236 // Otherwise, it can be the media button session regardless of the active state
237 // because it or other system components might have been the lastly played media
238 // app.
239 user.mPriorityStack.removeSession(record);
240 }
241 }
242 }
243 }
244
Jaewan Kim101b4d52017-05-18 13:23:11 +0900245 private List<MediaSessionRecord> getActiveSessionsLocked(int userId) {
Jaewan Kimda74a152017-10-03 23:58:11 +0900246 List<MediaSessionRecord> records = new ArrayList<>();
Jaewan Kim101b4d52017-05-18 13:23:11 +0900247 if (userId == UserHandle.USER_ALL) {
Jaewan Kim101b4d52017-05-18 13:23:11 +0900248 int size = mUserRecords.size();
249 for (int i = 0; i < size; i++) {
250 records.addAll(mUserRecords.valueAt(i).mPriorityStack.getActiveSessions(userId));
251 }
252 } else {
253 FullUserRecord user = getFullUserRecordLocked(userId);
254 if (user == null) {
255 Log.w(TAG, "getSessions failed. Unknown user " + userId);
Jaewan Kimda74a152017-10-03 23:58:11 +0900256 return records;
Jaewan Kim101b4d52017-05-18 13:23:11 +0900257 }
Jaewan Kimda74a152017-10-03 23:58:11 +0900258 records.addAll(user.mPriorityStack.getActiveSessions(userId));
Jaewan Kim101b4d52017-05-18 13:23:11 +0900259 }
260
261 // Return global priority session at the first whenever it's asked.
262 if (isGlobalPriorityActiveLocked()
263 && (userId == UserHandle.USER_ALL
264 || userId == mGlobalPrioritySession.getUserId())) {
265 records.add(0, mGlobalPrioritySession);
266 }
267 return records;
268 }
269
RoboErik9c5b7cb2015-01-15 15:09:09 -0800270 /**
Hyundo Moona055f132017-01-13 15:31:06 +0900271 * Tells the system UI that volume has changed on an active remote session.
RoboErik9c5b7cb2015-01-15 15:09:09 -0800272 */
273 public void notifyRemoteVolumeChanged(int flags, MediaSessionRecord session) {
Hyundo Moona055f132017-01-13 15:31:06 +0900274 if (mRvc == null || !session.isActive()) {
RoboErik9c5b7cb2015-01-15 15:09:09 -0800275 return;
276 }
277 try {
278 mRvc.remoteVolumeChanged(session.getControllerBinder(), flags);
279 } catch (Exception e) {
280 Log.wtf(TAG, "Error sending volume change to system UI.", e);
281 }
282 }
283
Jaewan Kim92dea332017-02-02 11:52:08 +0900284 public void onSessionPlaystateChanged(MediaSessionRecord record, int oldState, int newState) {
RoboErika8f95142014-05-05 14:23:49 -0700285 synchronized (mLock) {
Jaewan Kima7dce192017-02-16 17:10:54 +0900286 FullUserRecord user = getFullUserRecordLocked(record.getUserId());
287 if (user == null || !user.mPriorityStack.contains(record)) {
RoboErik4646d282014-05-13 10:13:04 -0700288 Log.d(TAG, "Unknown session changed playback state. Ignoring.");
289 return;
290 }
Jaewan Kim92dea332017-02-02 11:52:08 +0900291 user.mPriorityStack.onPlaystateChanged(record, oldState, newState);
RoboErika8f95142014-05-05 14:23:49 -0700292 }
293 }
294
RoboErik19c95182014-06-23 15:38:48 -0700295 public void onSessionPlaybackTypeChanged(MediaSessionRecord record) {
296 synchronized (mLock) {
Jaewan Kima7dce192017-02-16 17:10:54 +0900297 FullUserRecord user = getFullUserRecordLocked(record.getUserId());
298 if (user == null || !user.mPriorityStack.contains(record)) {
RoboErik19c95182014-06-23 15:38:48 -0700299 Log.d(TAG, "Unknown session changed playback type. Ignoring.");
300 return;
301 }
302 pushRemoteVolumeUpdateLocked(record.getUserId());
303 }
304 }
305
RoboErika278ea72014-04-24 14:49:01 -0700306 @Override
Jaewan Kim8f729082016-06-21 12:36:26 +0900307 public void onStartUser(int userId) {
308 if (DEBUG) Log.d(TAG, "onStartUser: " + userId);
RoboErik4646d282014-05-13 10:13:04 -0700309 updateUser();
310 }
311
312 @Override
Jaewan Kim8f729082016-06-21 12:36:26 +0900313 public void onSwitchUser(int userId) {
314 if (DEBUG) Log.d(TAG, "onSwitchUser: " + userId);
RoboErik4646d282014-05-13 10:13:04 -0700315 updateUser();
316 }
317
318 @Override
Jaewan Kim8f729082016-06-21 12:36:26 +0900319 public void onStopUser(int userId) {
320 if (DEBUG) Log.d(TAG, "onStopUser: " + userId);
RoboErik4646d282014-05-13 10:13:04 -0700321 synchronized (mLock) {
Jaewan Kima7dce192017-02-16 17:10:54 +0900322 FullUserRecord user = getFullUserRecordLocked(userId);
RoboErik4646d282014-05-13 10:13:04 -0700323 if (user != null) {
Jaewan Kima7dce192017-02-16 17:10:54 +0900324 if (user.mFullUserId == userId) {
325 user.destroySessionsForUserLocked(UserHandle.USER_ALL);
326 mUserRecords.remove(userId);
327 } else {
328 user.destroySessionsForUserLocked(userId);
329 }
RoboErik4646d282014-05-13 10:13:04 -0700330 }
Jaewan Kim8f729082016-06-21 12:36:26 +0900331 updateUser();
RoboErik4646d282014-05-13 10:13:04 -0700332 }
333 }
334
335 @Override
RoboErika278ea72014-04-24 14:49:01 -0700336 public void monitor() {
337 synchronized (mLock) {
338 // Check for deadlock
339 }
340 }
341
RoboErik4646d282014-05-13 10:13:04 -0700342 protected void enforcePhoneStatePermission(int pid, int uid) {
343 if (getContext().checkPermission(android.Manifest.permission.MODIFY_PHONE_STATE, pid, uid)
344 != PackageManager.PERMISSION_GRANTED) {
345 throw new SecurityException("Must hold the MODIFY_PHONE_STATE permission.");
346 }
347 }
348
RoboErik01fe6612014-02-13 14:19:04 -0800349 void sessionDied(MediaSessionRecord session) {
RoboErika278ea72014-04-24 14:49:01 -0700350 synchronized (mLock) {
RoboErik01fe6612014-02-13 14:19:04 -0800351 destroySessionLocked(session);
352 }
353 }
354
355 void destroySession(MediaSessionRecord session) {
RoboErika278ea72014-04-24 14:49:01 -0700356 synchronized (mLock) {
RoboErik01fe6612014-02-13 14:19:04 -0800357 destroySessionLocked(session);
358 }
359 }
360
RoboErik4646d282014-05-13 10:13:04 -0700361 private void updateUser() {
362 synchronized (mLock) {
Jaewan Kim8f729082016-06-21 12:36:26 +0900363 UserManager manager = (UserManager) getContext().getSystemService(Context.USER_SERVICE);
Jaewan Kima7dce192017-02-16 17:10:54 +0900364 mFullUserIds.clear();
365 List<UserInfo> allUsers = manager.getUsers();
366 if (allUsers != null) {
367 for (UserInfo userInfo : allUsers) {
368 if (userInfo.isManagedProfile()) {
369 mFullUserIds.put(userInfo.id, userInfo.profileGroupId);
370 } else {
371 mFullUserIds.put(userInfo.id, userInfo.id);
372 if (mUserRecords.get(userInfo.id) == null) {
373 mUserRecords.put(userInfo.id, new FullUserRecord(userInfo.id));
374 }
375 }
Jaewan Kim8f729082016-06-21 12:36:26 +0900376 }
RoboErik4646d282014-05-13 10:13:04 -0700377 }
Jaewan Kima7dce192017-02-16 17:10:54 +0900378 // Ensure that the current full user exists.
379 int currentFullUserId = ActivityManager.getCurrentUser();
380 mCurrentFullUserRecord = mUserRecords.get(currentFullUserId);
381 if (mCurrentFullUserRecord == null) {
382 Log.w(TAG, "Cannot find FullUserInfo for the current user " + currentFullUserId);
383 mCurrentFullUserRecord = new FullUserRecord(currentFullUserId);
384 mUserRecords.put(currentFullUserId, mCurrentFullUserRecord);
385 }
386 mFullUserIds.put(currentFullUserId, currentFullUserId);
RoboErik4646d282014-05-13 10:13:04 -0700387 }
388 }
389
RoboErik7aef77b2014-08-08 15:56:54 -0700390 private void updateActiveSessionListeners() {
391 synchronized (mLock) {
392 for (int i = mSessionsListeners.size() - 1; i >= 0; i--) {
393 SessionsListenerRecord listener = mSessionsListeners.get(i);
394 try {
395 enforceMediaPermissions(listener.mComponentName, listener.mPid, listener.mUid,
396 listener.mUserId);
397 } catch (SecurityException e) {
398 Log.i(TAG, "ActiveSessionsListener " + listener.mComponentName
399 + " is no longer authorized. Disconnecting.");
400 mSessionsListeners.remove(i);
401 try {
402 listener.mListener
403 .onActiveSessionsChanged(new ArrayList<MediaSession.Token>());
404 } catch (Exception e1) {
405 // ignore
406 }
407 }
408 }
409 }
410 }
411
RoboErik4646d282014-05-13 10:13:04 -0700412 /*
413 * When a session is removed several things need to happen.
414 * 1. We need to remove it from the relevant user.
415 * 2. We need to remove it from the priority stack.
416 * 3. We need to remove it from all sessions.
417 * 4. If this is the system priority session we need to clear it.
418 * 5. We need to unlink to death from the cb binder
419 * 6. We need to tell the session to do any final cleanup (onDestroy)
420 */
RoboErik01fe6612014-02-13 14:19:04 -0800421 private void destroySessionLocked(MediaSessionRecord session) {
Insun Kang30be970a2015-11-26 15:35:44 +0900422 if (DEBUG) {
Jaewan Kim5e1476e2016-07-19 22:25:39 +0900423 Log.d(TAG, "Destroying " + session);
Insun Kang30be970a2015-11-26 15:35:44 +0900424 }
Jaewan Kim101b4d52017-05-18 13:23:11 +0900425 FullUserRecord user = getFullUserRecordLocked(session.getUserId());
Jaewan Kima7dce192017-02-16 17:10:54 +0900426 if (mGlobalPrioritySession == session) {
427 mGlobalPrioritySession = null;
Jaewan Kim92dea332017-02-02 11:52:08 +0900428 if (session.isActive() && user != null) {
429 user.pushAddressedPlayerChangedLocked();
430 }
Jaewan Kim101b4d52017-05-18 13:23:11 +0900431 } else {
432 if (user != null) {
433 user.mPriorityStack.removeSession(session);
434 }
Jaewan Kima7dce192017-02-16 17:10:54 +0900435 }
RoboErik4646d282014-05-13 10:13:04 -0700436
437 try {
438 session.getCallback().asBinder().unlinkToDeath(session, 0);
439 } catch (Exception e) {
440 // ignore exceptions while destroying a session.
441 }
442 session.onDestroy();
Jaewan Kim92dea332017-02-02 11:52:08 +0900443 mHandler.postSessionsChanged(session.getUserId());
RoboErik01fe6612014-02-13 14:19:04 -0800444 }
445
Jaewan Kim66d451b2018-02-12 21:23:06 +0900446 private void registerPackageBroadcastReceivers() {
447 // TODO(jaewan): Only consider changed packages when building session service list
448 // when we make this multi-user aware. At that time,
449 // use PackageMonitor.getChangingUserId() to know which user has changed.
Jaewan Kim005e2bb2018-03-02 15:55:12 +0900450 // (b/73597722)
Jaewan Kim66d451b2018-02-12 21:23:06 +0900451 IntentFilter filter = new IntentFilter();
452 filter.addDataScheme("package");
453 filter.addAction(Intent.ACTION_PACKAGE_ADDED);
454 filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
455 filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
456 filter.addAction(Intent.ACTION_PACKAGES_SUSPENDED);
457 filter.addAction(Intent.ACTION_PACKAGES_UNSUSPENDED);
458 filter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
459 filter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
460 filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
461
462 getContext().registerReceiverAsUser(new BroadcastReceiver() {
463 @Override
464 public void onReceive(Context context, Intent intent) {
465 final int changeUserId = intent.getIntExtra(
466 Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
467 if (changeUserId == UserHandle.USER_NULL) {
468 Log.w(TAG, "Intent broadcast does not contain user handle: "+ intent);
469 return;
470 }
471 // Check if the package is replacing (i.e. reinstalling)
472 final boolean isReplacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
Jaewan Kim005e2bb2018-03-02 15:55:12 +0900473 // TODO(jaewan): Add multi-user support with this. (b/73597722)
Jaewan Kim66d451b2018-02-12 21:23:06 +0900474 // final int uid = intent.getIntExtra(Intent.EXTRA_UID, 0);
475
476 if (DEBUG) {
477 Log.d(TAG, "Received change in packages, intent=" + intent);
478 }
479 switch (intent.getAction()) {
480 case Intent.ACTION_PACKAGE_ADDED:
481 case Intent.ACTION_PACKAGE_REMOVED:
482 case Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE:
483 case Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE:
484 if (isReplacing) {
485 // Ignore if the package(s) are replacing. In that case, followings will
486 // happen in order.
487 // 1. ACTION_PACKAGE_REMOVED with isReplacing=true
488 // 2. ACTION_PACKAGE_ADDED with isReplacing=true
489 // 3. ACTION_PACKAGE_REPLACED
490 // (Note that ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE and
491 // ACTION_EXTERNAL_APPLICATIONS_AVAILABLE will be also called with
492 // isReplacing=true for both ASEC hosted packages and packages in
493 // external storage)
494 // Since we only want to update session service list once, ignore
495 // actions above when replacing.
496 // Replacing will be handled only once with the ACTION_PACKAGE_REPLACED.
497 break;
498 }
499 // pass-through
500 case Intent.ACTION_PACKAGE_CHANGED:
501 case Intent.ACTION_PACKAGES_SUSPENDED:
502 case Intent.ACTION_PACKAGES_UNSUSPENDED:
503 case Intent.ACTION_PACKAGE_REPLACED:
504 buildMediaSessionService2List();
505 }
506 }
507 }, UserHandle.ALL, filter, null, BackgroundThread.getHandler());
508 }
509
Jaewan Kimceb6b6e2018-01-21 20:56:10 +0900510 private void buildMediaSessionService2List() {
511 if (DEBUG) {
512 Log.d(TAG, "buildMediaSessionService2List");
513 }
Jaewan Kim005e2bb2018-03-02 15:55:12 +0900514 // TODO(jaewan): Also query for managed profile users. (b/73597722)
Jaewan Kimf7a77062018-01-27 01:34:24 +0900515 PackageManager manager = getContext().getPackageManager();
Jaewan Kimbcecf312018-01-23 19:30:42 +0900516 List<ResolveInfo> services = new ArrayList<>();
517 // If multiple actions are declared for a service, browser gets higher priority.
Jaewan Kimf7a77062018-01-27 01:34:24 +0900518 List<ResolveInfo> libraryServices = manager.queryIntentServices(
Jaewan Kimbcecf312018-01-23 19:30:42 +0900519 new Intent(MediaLibraryService2.SERVICE_INTERFACE), PackageManager.GET_META_DATA);
520 if (libraryServices != null) {
521 services.addAll(libraryServices);
522 }
Jaewan Kimf7a77062018-01-27 01:34:24 +0900523 List<ResolveInfo> sessionServices = manager.queryIntentServices(
Jaewan Kimbcecf312018-01-23 19:30:42 +0900524 new Intent(MediaSessionService2.SERVICE_INTERFACE), PackageManager.GET_META_DATA);
525 if (sessionServices != null) {
526 services.addAll(sessionServices);
527 }
Jaewan Kimceb6b6e2018-01-21 20:56:10 +0900528 synchronized (mLock) {
Jaewan Kim66d451b2018-02-12 21:23:06 +0900529 // List to keep the session services that need be removed because they don't exist
530 // in the 'services' above.
Sungsoo Lim375efa12018-03-02 10:17:08 +0900531 boolean notifySessionTokensUpdated = false;
Sungsoo Limc0d54a22018-02-27 09:05:21 +0900532 Set<SessionToken2> sessionTokensToRemove = new HashSet<>();
533 for (SessionToken2 token : mSessionRecords.keySet()) {
534 if (token.getType() != TYPE_SESSION) {
535 sessionTokensToRemove.add(token);
Jaewan Kim66d451b2018-02-12 21:23:06 +0900536 }
Jaewan Kimceb6b6e2018-01-21 20:56:10 +0900537 }
Sungsoo Lim117c7f72018-02-13 16:02:24 +0900538
Jaewan Kimceb6b6e2018-01-21 20:56:10 +0900539 for (int i = 0; i < services.size(); i++) {
540 if (services.get(i) == null || services.get(i).serviceInfo == null) {
541 continue;
542 }
543 ServiceInfo serviceInfo = services.get(i).serviceInfo;
Jaewan Kimf7a77062018-01-27 01:34:24 +0900544 int uid;
545 try {
Jaewan Kim005e2bb2018-03-02 15:55:12 +0900546 // TODO(jaewan): Do this per user. (b/73597722)
Jaewan Kimf7a77062018-01-27 01:34:24 +0900547 uid = manager.getPackageUid(serviceInfo.packageName,
548 PackageManager.GET_META_DATA);
549 } catch (NameNotFoundException e) {
550 continue;
551 }
Jaewan Kim66d451b2018-02-12 21:23:06 +0900552 SessionToken2 token;
Jaewan Kim44fec2d2018-01-29 21:49:41 +0900553 try {
Jaewan Kim66d451b2018-02-12 21:23:06 +0900554 token = new SessionToken2(getContext(),
Jaewan Kim44fec2d2018-01-29 21:49:41 +0900555 serviceInfo.packageName, serviceInfo.name, uid);
Jaewan Kim66d451b2018-02-12 21:23:06 +0900556 } catch (IllegalArgumentException e) {
557 Log.w(TAG, "Invalid session service", e);
558 continue;
559 }
Sungsoo Lim117c7f72018-02-13 16:02:24 +0900560 // If the token already exists, keep it in the mSessions by removing from
561 // sessionTokensToRemove.
562 if (!sessionTokensToRemove.remove(token)) {
Jaewan Kim66d451b2018-02-12 21:23:06 +0900563 // New session service is found.
Sungsoo Lim375efa12018-03-02 10:17:08 +0900564 notifySessionTokensUpdated |= addSessionRecordLocked(token);
Jaewan Kimceb6b6e2018-01-21 20:56:10 +0900565 }
566 }
Sungsoo Lim117c7f72018-02-13 16:02:24 +0900567 for (SessionToken2 token : sessionTokensToRemove) {
568 mSessionRecords.remove(token);
Sungsoo Lim375efa12018-03-02 10:17:08 +0900569 notifySessionTokensUpdated |= removeSessionRecordLocked(token);
570 }
571
572 if (notifySessionTokensUpdated) {
573 // TODO(jaewan): Pass proper user id to postSessionTokensUpdated(...)
574 postSessionTokensUpdated(UserHandle.USER_ALL);
Jaewan Kim66d451b2018-02-12 21:23:06 +0900575 }
Jaewan Kimceb6b6e2018-01-21 20:56:10 +0900576 }
577 if (DEBUG) {
Sungsoo Lim117c7f72018-02-13 16:02:24 +0900578 Log.d(TAG, "Found " + mSessionRecords.size() + " session services");
579 for (SessionToken2 token : mSessionRecords.keySet()) {
580 Log.d(TAG, " " + token);
Jaewan Kimceb6b6e2018-01-21 20:56:10 +0900581 }
582 }
583 }
584
RoboErik01fe6612014-02-13 14:19:04 -0800585 private void enforcePackageName(String packageName, int uid) {
586 if (TextUtils.isEmpty(packageName)) {
587 throw new IllegalArgumentException("packageName may not be empty");
588 }
589 String[] packages = getContext().getPackageManager().getPackagesForUid(uid);
590 final int packageCount = packages.length;
591 for (int i = 0; i < packageCount; i++) {
592 if (packageName.equals(packages[i])) {
593 return;
594 }
595 }
596 throw new IllegalArgumentException("packageName is not owned by the calling process");
597 }
598
RoboErike7880d82014-04-30 12:48:25 -0700599 /**
600 * Checks a caller's authorization to register an IRemoteControlDisplay.
601 * Authorization is granted if one of the following is true:
602 * <ul>
603 * <li>the caller has android.Manifest.permission.MEDIA_CONTENT_CONTROL
604 * permission</li>
RoboErika5b02322014-05-07 17:05:49 -0700605 * <li>the caller's listener is one of the enabled notification listeners
606 * for the caller's user</li>
RoboErike7880d82014-04-30 12:48:25 -0700607 * </ul>
608 */
RoboErika5b02322014-05-07 17:05:49 -0700609 private void enforceMediaPermissions(ComponentName compName, int pid, int uid,
610 int resolvedUserId) {
Hyundo Moonf84c1c02018-03-19 20:33:27 +0900611 if (isCurrentVolumeController(pid, uid)) return;
RoboErike7880d82014-04-30 12:48:25 -0700612 if (getContext()
613 .checkPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL, pid, uid)
614 != PackageManager.PERMISSION_GRANTED
RoboErika5b02322014-05-07 17:05:49 -0700615 && !isEnabledNotificationListener(compName, UserHandle.getUserId(uid),
616 resolvedUserId)) {
RoboErike7880d82014-04-30 12:48:25 -0700617 throw new SecurityException("Missing permission to control media.");
618 }
619 }
620
Hyundo Moonf84c1c02018-03-19 20:33:27 +0900621 private boolean isCurrentVolumeController(int pid, int uid) {
Julia Reynoldsbb983d202017-01-06 09:54:20 -0500622 return getContext().checkPermission(android.Manifest.permission.STATUS_BAR_SERVICE,
623 pid, uid) == PackageManager.PERMISSION_GRANTED;
John Spurlockbe19ed02015-02-22 10:57:55 -0500624 }
625
626 private void enforceSystemUiPermission(String action, int pid, int uid) {
Hyundo Moonf84c1c02018-03-19 20:33:27 +0900627 if (!isCurrentVolumeController(pid, uid)) {
RoboErik19c95182014-06-23 15:38:48 -0700628 throw new SecurityException("Only system ui may " + action);
629 }
630 }
631
RoboErika5b02322014-05-07 17:05:49 -0700632 /**
633 * This checks if the component is an enabled notification listener for the
634 * specified user. Enabled components may only operate on behalf of the user
635 * they're running as.
636 *
637 * @param compName The component that is enabled.
638 * @param userId The user id of the caller.
639 * @param forUserId The user id they're making the request on behalf of.
640 * @return True if the component is enabled, false otherwise
641 */
642 private boolean isEnabledNotificationListener(ComponentName compName, int userId,
643 int forUserId) {
644 if (userId != forUserId) {
645 // You may not access another user's content as an enabled listener.
646 return false;
647 }
RoboErik51fa6bc2014-06-20 14:59:58 -0700648 if (DEBUG) {
649 Log.d(TAG, "Checking if enabled notification listener " + compName);
650 }
RoboErike7880d82014-04-30 12:48:25 -0700651 if (compName != null) {
Julia Reynoldsb852e562017-06-06 16:14:18 -0400652 try {
653 return mNotificationManager.isNotificationListenerAccessGrantedForUser(
654 compName, userId);
655 } catch(RemoteException e) {
656 Log.w(TAG, "Dead NotificationManager in isEnabledNotificationListener", e);
RoboErike7880d82014-04-30 12:48:25 -0700657 }
658 }
659 return false;
660 }
661
RoboErika5b02322014-05-07 17:05:49 -0700662 private MediaSessionRecord createSessionInternal(int callerPid, int callerUid, int userId,
RoboErik4646d282014-05-13 10:13:04 -0700663 String callerPackageName, ISessionCallback cb, String tag) throws RemoteException {
RoboErik01fe6612014-02-13 14:19:04 -0800664 synchronized (mLock) {
RoboErika5b02322014-05-07 17:05:49 -0700665 return createSessionLocked(callerPid, callerUid, userId, callerPackageName, cb, tag);
RoboErik01fe6612014-02-13 14:19:04 -0800666 }
667 }
668
RoboErik4646d282014-05-13 10:13:04 -0700669 /*
670 * When a session is created the following things need to happen.
RoboErik8a2cfc32014-05-16 11:19:38 -0700671 * 1. Its callback binder needs a link to death
RoboErik4646d282014-05-13 10:13:04 -0700672 * 2. It needs to be added to all sessions.
673 * 3. It needs to be added to the priority stack.
674 * 4. It needs to be added to the relevant user record.
675 */
RoboErika5b02322014-05-07 17:05:49 -0700676 private MediaSessionRecord createSessionLocked(int callerPid, int callerUid, int userId,
677 String callerPackageName, ISessionCallback cb, String tag) {
Jaewan Kima7dce192017-02-16 17:10:54 +0900678 FullUserRecord user = getFullUserRecordLocked(userId);
Dongwon Kang8cf39c52016-07-29 13:20:39 -0700679 if (user == null) {
680 Log.wtf(TAG, "Request from invalid user: " + userId);
681 throw new RuntimeException("Session request from invalid user.");
682 }
683
RoboErika5b02322014-05-07 17:05:49 -0700684 final MediaSessionRecord session = new MediaSessionRecord(callerPid, callerUid, userId,
Jaewan Kim92dea332017-02-02 11:52:08 +0900685 callerPackageName, cb, tag, this, mHandler.getLooper());
RoboErik01fe6612014-02-13 14:19:04 -0800686 try {
687 cb.asBinder().linkToDeath(session, 0);
688 } catch (RemoteException e) {
689 throw new RuntimeException("Media Session owner died prematurely.", e);
690 }
RoboErik4646d282014-05-13 10:13:04 -0700691
Jaewan Kim101b4d52017-05-18 13:23:11 +0900692 user.mPriorityStack.addSession(session);
Jaewan Kim92dea332017-02-02 11:52:08 +0900693 mHandler.postSessionsChanged(userId);
RoboErik2e7a9162014-06-04 16:53:45 -0700694
RoboErik01fe6612014-02-13 14:19:04 -0800695 if (DEBUG) {
Jaewan Kim5e1476e2016-07-19 22:25:39 +0900696 Log.d(TAG, "Created session for " + callerPackageName + " with tag " + tag);
RoboErik01fe6612014-02-13 14:19:04 -0800697 }
698 return session;
699 }
700
RoboErik2e7a9162014-06-04 16:53:45 -0700701 private int findIndexOfSessionsListenerLocked(IActiveSessionsListener listener) {
702 for (int i = mSessionsListeners.size() - 1; i >= 0; i--) {
RoboErika08adb242014-11-21 18:28:18 -0800703 if (mSessionsListeners.get(i).mListener.asBinder() == listener.asBinder()) {
RoboErik2e7a9162014-06-04 16:53:45 -0700704 return i;
705 }
706 }
707 return -1;
708 }
709
RoboErik2e7a9162014-06-04 16:53:45 -0700710 private void pushSessionsChanged(int userId) {
711 synchronized (mLock) {
Jaewan Kima7dce192017-02-16 17:10:54 +0900712 FullUserRecord user = getFullUserRecordLocked(userId);
713 if (user == null) {
714 Log.w(TAG, "pushSessionsChanged failed. No user with id=" + userId);
715 return;
716 }
Jaewan Kim101b4d52017-05-18 13:23:11 +0900717 List<MediaSessionRecord> records = getActiveSessionsLocked(userId);
RoboErik2e7a9162014-06-04 16:53:45 -0700718 int size = records.size();
Jeff Browndba34ba2014-06-24 20:46:03 -0700719 ArrayList<MediaSession.Token> tokens = new ArrayList<MediaSession.Token>();
RoboErik2e7a9162014-06-04 16:53:45 -0700720 for (int i = 0; i < size; i++) {
Jeff Browndba34ba2014-06-24 20:46:03 -0700721 tokens.add(new MediaSession.Token(records.get(i).getControllerBinder()));
RoboErik2e7a9162014-06-04 16:53:45 -0700722 }
RoboErik19c95182014-06-23 15:38:48 -0700723 pushRemoteVolumeUpdateLocked(userId);
RoboErik2e7a9162014-06-04 16:53:45 -0700724 for (int i = mSessionsListeners.size() - 1; i >= 0; i--) {
725 SessionsListenerRecord record = mSessionsListeners.get(i);
726 if (record.mUserId == UserHandle.USER_ALL || record.mUserId == userId) {
727 try {
728 record.mListener.onActiveSessionsChanged(tokens);
729 } catch (RemoteException e) {
730 Log.w(TAG, "Dead ActiveSessionsListener in pushSessionsChanged, removing",
731 e);
732 mSessionsListeners.remove(i);
733 }
734 }
735 }
736 }
737 }
738
RoboErik19c95182014-06-23 15:38:48 -0700739 private void pushRemoteVolumeUpdateLocked(int userId) {
740 if (mRvc != null) {
741 try {
Jaewan Kima7dce192017-02-16 17:10:54 +0900742 FullUserRecord user = getFullUserRecordLocked(userId);
743 if (user == null) {
744 Log.w(TAG, "pushRemoteVolumeUpdateLocked failed. No user with id=" + userId);
745 return;
746 }
747 MediaSessionRecord record = user.mPriorityStack.getDefaultRemoteSession(userId);
RoboErik19c95182014-06-23 15:38:48 -0700748 mRvc.updateRemoteController(record == null ? null : record.getControllerBinder());
749 } catch (RemoteException e) {
750 Log.wtf(TAG, "Error sending default remote volume to sys ui.", e);
751 }
752 }
753 }
754
Jaewan Kim92dea332017-02-02 11:52:08 +0900755 /**
756 * Called when the media button receiver for the {@param record} is changed.
757 *
758 * @param record the media session whose media button receiver is updated.
759 */
760 public void onMediaButtonReceiverChanged(MediaSessionRecord record) {
761 synchronized (mLock) {
762 FullUserRecord user = getFullUserRecordLocked(record.getUserId());
763 MediaSessionRecord mediaButtonSession =
764 user.mPriorityStack.getMediaButtonSession();
765 if (record == mediaButtonSession) {
766 user.rememberMediaButtonReceiverLocked(mediaButtonSession);
767 }
768 }
769 }
770
Jaewan Kim50269362016-12-23 11:22:02 +0900771 private String getCallingPackageName(int uid) {
772 String[] packages = getContext().getPackageManager().getPackagesForUid(uid);
773 if (packages != null && packages.length > 0) {
774 return packages[0];
775 }
776 return "";
777 }
778
Jaewan Kimd61a87b2017-02-17 23:14:10 +0900779 private void dispatchVolumeKeyLongPressLocked(KeyEvent keyEvent) {
Jaewan Kimd61a87b2017-02-17 23:14:10 +0900780 try {
Jaewan Kima7dce192017-02-16 17:10:54 +0900781 mCurrentFullUserRecord.mOnVolumeKeyLongPressListener.onVolumeKeyLongPress(keyEvent);
Jaewan Kimd61a87b2017-02-17 23:14:10 +0900782 } catch (RemoteException e) {
783 Log.w(TAG, "Failed to send " + keyEvent + " to volume key long-press listener");
784 }
785 }
786
Jaewan Kima7dce192017-02-16 17:10:54 +0900787 private FullUserRecord getFullUserRecordLocked(int userId) {
788 int fullUserId = mFullUserIds.get(userId, -1);
789 if (fullUserId < 0) {
790 return null;
791 }
792 return mUserRecords.get(fullUserId);
793 }
794
Sungsoo Lim117c7f72018-02-13 16:02:24 +0900795 void destroySession2Internal(SessionToken2 token) {
796 synchronized (mLock) {
Sungsoo Lim375efa12018-03-02 10:17:08 +0900797 boolean notifySessionTokensUpdated = false;
Sungsoo Lim117c7f72018-02-13 16:02:24 +0900798 if (token.getType() == SessionToken2.TYPE_SESSION) {
Sungsoo Lim375efa12018-03-02 10:17:08 +0900799 notifySessionTokensUpdated |= removeSessionRecordLocked(token);
Sungsoo Lim117c7f72018-02-13 16:02:24 +0900800 } else {
Sungsoo Lim375efa12018-03-02 10:17:08 +0900801 notifySessionTokensUpdated |= addSessionRecordLocked(token);
802 }
803 if (notifySessionTokensUpdated) {
804 postSessionTokensUpdated(UserHandle.getUserId(token.getUid()));
Sungsoo Lim117c7f72018-02-13 16:02:24 +0900805 }
806 }
807 }
808
RoboErik4646d282014-05-13 10:13:04 -0700809 /**
Jaewan Kima7dce192017-02-16 17:10:54 +0900810 * Information about a full user and its corresponding managed profiles.
811 *
812 * <p>Since the full user runs together with its managed profiles, a user wouldn't differentiate
813 * them when he/she presses a media/volume button. So keeping media sessions for them in one
814 * place makes more sense and increases the readability.</p>
815 * <p>The contents of this object is guarded by {@link #mLock}.
RoboErik4646d282014-05-13 10:13:04 -0700816 */
Jaewan Kim92dea332017-02-02 11:52:08 +0900817 final class FullUserRecord implements MediaSessionStack.OnMediaButtonSessionChangedListener {
Jaewan Kima7dce192017-02-16 17:10:54 +0900818 private static final String COMPONENT_NAME_USER_ID_DELIM = ",";
819 private final int mFullUserId;
Jaewan Kim92dea332017-02-02 11:52:08 +0900820 private final MediaSessionStack mPriorityStack;
RoboErikb214efb2014-07-24 13:20:30 -0700821 private PendingIntent mLastMediaButtonReceiver;
RoboErikc8f92d12015-01-05 16:48:07 -0800822 private ComponentName mRestoredMediaButtonReceiver;
Jaewan Kima7dce192017-02-16 17:10:54 +0900823 private int mRestoredMediaButtonReceiverUserId;
RoboErik4646d282014-05-13 10:13:04 -0700824
Jaewan Kim50269362016-12-23 11:22:02 +0900825 private IOnVolumeKeyLongPressListener mOnVolumeKeyLongPressListener;
826 private int mOnVolumeKeyLongPressListenerUid;
827 private KeyEvent mInitialDownVolumeKeyEvent;
828 private int mInitialDownVolumeStream;
829 private boolean mInitialDownMusicOnly;
830
Jaewan Kim6e2b01c2017-01-19 16:33:14 -0800831 private IOnMediaKeyListener mOnMediaKeyListener;
832 private int mOnMediaKeyListenerUid;
Jaewan Kima7dce192017-02-16 17:10:54 +0900833 private ICallback mCallback;
Jaewan Kim6e2b01c2017-01-19 16:33:14 -0800834
Jaewan Kima7dce192017-02-16 17:10:54 +0900835 public FullUserRecord(int fullUserId) {
836 mFullUserId = fullUserId;
Sungsoo Lim875e6972017-11-03 02:22:35 +0000837 mPriorityStack = new MediaSessionStack(mAudioPlayerStateMonitor, this);
Jaewan Kima7dce192017-02-16 17:10:54 +0900838 // Restore the remembered media button receiver before the boot.
839 String mediaButtonReceiver = Settings.Secure.getStringForUser(mContentResolver,
840 Settings.System.MEDIA_BUTTON_RECEIVER, mFullUserId);
841 if (mediaButtonReceiver == null) {
842 return;
843 }
844 String[] tokens = mediaButtonReceiver.split(COMPONENT_NAME_USER_ID_DELIM);
845 if (tokens == null || tokens.length != 2) {
846 return;
847 }
848 mRestoredMediaButtonReceiver = ComponentName.unflattenFromString(tokens[0]);
849 mRestoredMediaButtonReceiverUserId = Integer.parseInt(tokens[1]);
RoboErik4646d282014-05-13 10:13:04 -0700850 }
851
Jaewan Kima7dce192017-02-16 17:10:54 +0900852 public void destroySessionsForUserLocked(int userId) {
Jaewan Kim92dea332017-02-02 11:52:08 +0900853 List<MediaSessionRecord> sessions = mPriorityStack.getPriorityList(false, userId);
Jaewan Kima7dce192017-02-16 17:10:54 +0900854 for (MediaSessionRecord session : sessions) {
RoboErik4646d282014-05-13 10:13:04 -0700855 MediaSessionService.this.destroySessionLocked(session);
RoboErik4646d282014-05-13 10:13:04 -0700856 }
857 }
858
RoboErik4646d282014-05-13 10:13:04 -0700859 public void dumpLocked(PrintWriter pw, String prefix) {
Jaewan Kima7dce192017-02-16 17:10:54 +0900860 pw.print(prefix + "Record for full_user=" + mFullUserId);
861 // Dump managed profile user ids associated with this user.
862 int size = mFullUserIds.size();
863 for (int i = 0; i < size; i++) {
864 if (mFullUserIds.keyAt(i) != mFullUserIds.valueAt(i)
865 && mFullUserIds.valueAt(i) == mFullUserId) {
866 pw.print(", profile_user=" + mFullUserIds.keyAt(i));
867 }
868 }
869 pw.println();
RoboErik4646d282014-05-13 10:13:04 -0700870 String indent = prefix + " ";
Jaewan Kima7dce192017-02-16 17:10:54 +0900871 pw.println(indent + "Volume key long-press listener: " + mOnVolumeKeyLongPressListener);
872 pw.println(indent + "Volume key long-press listener package: " +
Jaewan Kim50269362016-12-23 11:22:02 +0900873 getCallingPackageName(mOnVolumeKeyLongPressListenerUid));
Jaewan Kim6e2b01c2017-01-19 16:33:14 -0800874 pw.println(indent + "Media key listener: " + mOnMediaKeyListener);
875 pw.println(indent + "Media key listener package: " +
876 getCallingPackageName(mOnMediaKeyListenerUid));
Jaewan Kima7dce192017-02-16 17:10:54 +0900877 pw.println(indent + "Callback: " + mCallback);
878 pw.println(indent + "Last MediaButtonReceiver: " + mLastMediaButtonReceiver);
879 pw.println(indent + "Restored MediaButtonReceiver: " + mRestoredMediaButtonReceiver);
880 mPriorityStack.dump(pw, indent);
881 }
882
Jaewan Kim92dea332017-02-02 11:52:08 +0900883 @Override
884 public void onMediaButtonSessionChanged(MediaSessionRecord oldMediaButtonSession,
885 MediaSessionRecord newMediaButtonSession) {
886 if (DEBUG_KEY_EVENT) {
Jaewan Kim98e4aaf2017-05-12 17:06:47 +0900887 Log.d(TAG, "Media button session is changed to " + newMediaButtonSession);
Jaewan Kim92dea332017-02-02 11:52:08 +0900888 }
889 synchronized (mLock) {
890 if (oldMediaButtonSession != null) {
891 mHandler.postSessionsChanged(oldMediaButtonSession.getUserId());
892 }
893 if (newMediaButtonSession != null) {
894 rememberMediaButtonReceiverLocked(newMediaButtonSession);
895 mHandler.postSessionsChanged(newMediaButtonSession.getUserId());
896 }
897 pushAddressedPlayerChangedLocked();
898 }
899 }
900
901 // Remember media button receiver and keep it in the persistent storage.
902 public void rememberMediaButtonReceiverLocked(MediaSessionRecord record) {
Jaewan Kima7dce192017-02-16 17:10:54 +0900903 PendingIntent receiver = record.getMediaButtonReceiver();
Jaewan Kima7dce192017-02-16 17:10:54 +0900904 mLastMediaButtonReceiver = receiver;
Jaewan Kim92dea332017-02-02 11:52:08 +0900905 mRestoredMediaButtonReceiver = null;
906 String componentName = "";
907 if (receiver != null) {
908 ComponentName component = receiver.getIntent().getComponent();
909 if (component != null
910 && record.getPackageName().equals(component.getPackageName())) {
911 componentName = component.flattenToString();
912 }
RoboErik4646d282014-05-13 10:13:04 -0700913 }
Jaewan Kim92dea332017-02-02 11:52:08 +0900914 Settings.Secure.putStringForUser(mContentResolver,
915 Settings.System.MEDIA_BUTTON_RECEIVER,
916 componentName + COMPONENT_NAME_USER_ID_DELIM + record.getUserId(),
917 mFullUserId);
RoboErik4646d282014-05-13 10:13:04 -0700918 }
RoboErikc8f92d12015-01-05 16:48:07 -0800919
Jaewan Kima7dce192017-02-16 17:10:54 +0900920 private void pushAddressedPlayerChangedLocked() {
921 if (mCallback == null) {
922 return;
RoboErikc8f92d12015-01-05 16:48:07 -0800923 }
Jaewan Kima7dce192017-02-16 17:10:54 +0900924 try {
925 MediaSessionRecord mediaButtonSession = getMediaButtonSessionLocked();
926 if (mediaButtonSession != null) {
927 mCallback.onAddressedPlayerChangedToMediaSession(
928 new MediaSession.Token(mediaButtonSession.getControllerBinder()));
929 } else if (mCurrentFullUserRecord.mLastMediaButtonReceiver != null) {
930 mCallback.onAddressedPlayerChangedToMediaButtonReceiver(
931 mCurrentFullUserRecord.mLastMediaButtonReceiver
932 .getIntent().getComponent());
933 } else if (mCurrentFullUserRecord.mRestoredMediaButtonReceiver != null) {
934 mCallback.onAddressedPlayerChangedToMediaButtonReceiver(
935 mCurrentFullUserRecord.mRestoredMediaButtonReceiver);
936 }
937 } catch (RemoteException e) {
938 Log.w(TAG, "Failed to pushAddressedPlayerChangedLocked", e);
939 }
940 }
941
942 private MediaSessionRecord getMediaButtonSessionLocked() {
Jaewan Kim92dea332017-02-02 11:52:08 +0900943 return isGlobalPriorityActiveLocked()
944 ? mGlobalPrioritySession : mPriorityStack.getMediaButtonSession();
RoboErikc8f92d12015-01-05 16:48:07 -0800945 }
RoboErik4646d282014-05-13 10:13:04 -0700946 }
947
RoboErik2e7a9162014-06-04 16:53:45 -0700948 final class SessionsListenerRecord implements IBinder.DeathRecipient {
949 private final IActiveSessionsListener mListener;
RoboErik7aef77b2014-08-08 15:56:54 -0700950 private final ComponentName mComponentName;
RoboErik2e7a9162014-06-04 16:53:45 -0700951 private final int mUserId;
RoboErik7aef77b2014-08-08 15:56:54 -0700952 private final int mPid;
953 private final int mUid;
RoboErik2e7a9162014-06-04 16:53:45 -0700954
RoboErik7aef77b2014-08-08 15:56:54 -0700955 public SessionsListenerRecord(IActiveSessionsListener listener,
956 ComponentName componentName,
957 int userId, int pid, int uid) {
RoboErik2e7a9162014-06-04 16:53:45 -0700958 mListener = listener;
RoboErik7aef77b2014-08-08 15:56:54 -0700959 mComponentName = componentName;
RoboErik2e7a9162014-06-04 16:53:45 -0700960 mUserId = userId;
RoboErik7aef77b2014-08-08 15:56:54 -0700961 mPid = pid;
962 mUid = uid;
RoboErik2e7a9162014-06-04 16:53:45 -0700963 }
964
965 @Override
966 public void binderDied() {
967 synchronized (mLock) {
968 mSessionsListeners.remove(this);
969 }
970 }
971 }
972
RoboErik7aef77b2014-08-08 15:56:54 -0700973 final class SettingsObserver extends ContentObserver {
974 private final Uri mSecureSettingsUri = Settings.Secure.getUriFor(
975 Settings.Secure.ENABLED_NOTIFICATION_LISTENERS);
976
977 private SettingsObserver() {
978 super(null);
979 }
980
981 private void observe() {
982 mContentResolver.registerContentObserver(mSecureSettingsUri,
983 false, this, UserHandle.USER_ALL);
984 }
985
986 @Override
987 public void onChange(boolean selfChange, Uri uri) {
988 updateActiveSessionListeners();
989 }
990 }
991
RoboErik07c70772014-03-20 13:33:52 -0700992 class SessionManagerImpl extends ISessionManager.Stub {
RoboErik8a2cfc32014-05-16 11:19:38 -0700993 private static final String EXTRA_WAKELOCK_ACQUIRED =
994 "android.media.AudioService.WAKELOCK_ACQUIRED";
995 private static final int WAKELOCK_RELEASE_ON_FINISHED = 1980; // magic number
996
RoboErik9a9d0b52014-05-20 14:53:39 -0700997 private boolean mVoiceButtonDown = false;
998 private boolean mVoiceButtonHandled = false;
999
RoboErik07c70772014-03-20 13:33:52 -07001000 @Override
RoboErika5b02322014-05-07 17:05:49 -07001001 public ISession createSession(String packageName, ISessionCallback cb, String tag,
1002 int userId) throws RemoteException {
RoboErik01fe6612014-02-13 14:19:04 -08001003 final int pid = Binder.getCallingPid();
1004 final int uid = Binder.getCallingUid();
1005 final long token = Binder.clearCallingIdentity();
1006 try {
1007 enforcePackageName(packageName, uid);
RoboErika5b02322014-05-07 17:05:49 -07001008 int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId,
1009 false /* allowAll */, true /* requireFull */, "createSession", packageName);
RoboErik01fe6612014-02-13 14:19:04 -08001010 if (cb == null) {
1011 throw new IllegalArgumentException("Controller callback cannot be null");
1012 }
RoboErika5b02322014-05-07 17:05:49 -07001013 return createSessionInternal(pid, uid, resolvedUserId, packageName, cb, tag)
1014 .getSessionBinder();
RoboErike7880d82014-04-30 12:48:25 -07001015 } finally {
1016 Binder.restoreCallingIdentity(token);
1017 }
1018 }
1019
1020 @Override
RoboErika5b02322014-05-07 17:05:49 -07001021 public List<IBinder> getSessions(ComponentName componentName, int userId) {
RoboErike7880d82014-04-30 12:48:25 -07001022 final int pid = Binder.getCallingPid();
1023 final int uid = Binder.getCallingUid();
1024 final long token = Binder.clearCallingIdentity();
1025
1026 try {
RoboErik2e7a9162014-06-04 16:53:45 -07001027 int resolvedUserId = verifySessionsRequest(componentName, userId, pid, uid);
RoboErike7880d82014-04-30 12:48:25 -07001028 ArrayList<IBinder> binders = new ArrayList<IBinder>();
1029 synchronized (mLock) {
Jaewan Kim101b4d52017-05-18 13:23:11 +09001030 List<MediaSessionRecord> records = getActiveSessionsLocked(resolvedUserId);
1031 for (MediaSessionRecord record : records) {
1032 binders.add(record.getControllerBinder().asBinder());
RoboErike7880d82014-04-30 12:48:25 -07001033 }
1034 }
1035 return binders;
RoboErik01fe6612014-02-13 14:19:04 -08001036 } finally {
1037 Binder.restoreCallingIdentity(token);
1038 }
1039 }
RoboErika278ea72014-04-24 14:49:01 -07001040
RoboErik2e7a9162014-06-04 16:53:45 -07001041 @Override
1042 public void addSessionsListener(IActiveSessionsListener listener,
1043 ComponentName componentName, int userId) throws RemoteException {
1044 final int pid = Binder.getCallingPid();
1045 final int uid = Binder.getCallingUid();
1046 final long token = Binder.clearCallingIdentity();
1047
1048 try {
1049 int resolvedUserId = verifySessionsRequest(componentName, userId, pid, uid);
1050 synchronized (mLock) {
1051 int index = findIndexOfSessionsListenerLocked(listener);
1052 if (index != -1) {
1053 Log.w(TAG, "ActiveSessionsListener is already added, ignoring");
1054 return;
1055 }
1056 SessionsListenerRecord record = new SessionsListenerRecord(listener,
RoboErik7aef77b2014-08-08 15:56:54 -07001057 componentName, resolvedUserId, pid, uid);
RoboErik2e7a9162014-06-04 16:53:45 -07001058 try {
1059 listener.asBinder().linkToDeath(record, 0);
1060 } catch (RemoteException e) {
1061 Log.e(TAG, "ActiveSessionsListener is dead, ignoring it", e);
1062 return;
1063 }
1064 mSessionsListeners.add(record);
1065 }
1066 } finally {
1067 Binder.restoreCallingIdentity(token);
1068 }
1069 }
1070
1071 @Override
1072 public void removeSessionsListener(IActiveSessionsListener listener)
1073 throws RemoteException {
1074 synchronized (mLock) {
1075 int index = findIndexOfSessionsListenerLocked(listener);
1076 if (index != -1) {
1077 SessionsListenerRecord record = mSessionsListeners.remove(index);
1078 try {
1079 record.mListener.asBinder().unlinkToDeath(record, 0);
1080 } catch (Exception e) {
1081 // ignore exceptions, the record is being removed
1082 }
1083 }
1084 }
1085 }
1086
RoboErik8a2cfc32014-05-16 11:19:38 -07001087 /**
1088 * Handles the dispatching of the media button events to one of the
1089 * registered listeners, or if there was none, broadcast an
1090 * ACTION_MEDIA_BUTTON intent to the rest of the system.
1091 *
1092 * @param keyEvent a non-null KeyEvent whose key code is one of the
1093 * supported media buttons
1094 * @param needWakeLock true if a PARTIAL_WAKE_LOCK needs to be held
1095 * while this key event is dispatched.
1096 */
1097 @Override
1098 public void dispatchMediaKeyEvent(KeyEvent keyEvent, boolean needWakeLock) {
1099 if (keyEvent == null || !KeyEvent.isMediaKey(keyEvent.getKeyCode())) {
1100 Log.w(TAG, "Attempted to dispatch null or non-media key event.");
1101 return;
1102 }
Jeff Brown38d3feb2015-03-19 18:26:30 -07001103
RoboErik8a2cfc32014-05-16 11:19:38 -07001104 final int pid = Binder.getCallingPid();
1105 final int uid = Binder.getCallingUid();
1106 final long token = Binder.clearCallingIdentity();
RoboErik8a2cfc32014-05-16 11:19:38 -07001107 try {
Jeff Brown221a8272015-03-23 13:53:09 -07001108 if (DEBUG) {
1109 Log.d(TAG, "dispatchMediaKeyEvent, pid=" + pid + ", uid=" + uid + ", event="
1110 + keyEvent);
1111 }
Jeff Brown38d3feb2015-03-19 18:26:30 -07001112 if (!isUserSetupComplete()) {
1113 // Global media key handling can have the side-effect of starting new
1114 // activities which is undesirable while setup is in progress.
1115 Slog.i(TAG, "Not dispatching media key event because user "
1116 + "setup is in progress.");
1117 return;
1118 }
1119
RoboErik8a2cfc32014-05-16 11:19:38 -07001120 synchronized (mLock) {
Jaewan Kima7dce192017-02-16 17:10:54 +09001121 boolean isGlobalPriorityActive = isGlobalPriorityActiveLocked();
Jaewan Kim51255012017-02-24 16:19:14 +09001122 if (isGlobalPriorityActive && uid != Process.SYSTEM_UID) {
1123 // Prevent dispatching key event through reflection while the global
1124 // priority session is active.
1125 Slog.i(TAG, "Only the system can dispatch media key event "
1126 + "to the global priority session.");
1127 return;
1128 }
Jaewan Kim98003d32017-02-24 18:33:04 +09001129 if (!isGlobalPriorityActive) {
Jaewan Kima7dce192017-02-16 17:10:54 +09001130 if (mCurrentFullUserRecord.mOnMediaKeyListener != null) {
Jaewan Kim98003d32017-02-24 18:33:04 +09001131 if (DEBUG_KEY_EVENT) {
Jaewan Kima7dce192017-02-16 17:10:54 +09001132 Log.d(TAG, "Send " + keyEvent + " to the media key listener");
Jaewan Kim98003d32017-02-24 18:33:04 +09001133 }
1134 try {
Jaewan Kima7dce192017-02-16 17:10:54 +09001135 mCurrentFullUserRecord.mOnMediaKeyListener.onMediaKey(keyEvent,
Jaewan Kim98003d32017-02-24 18:33:04 +09001136 new MediaKeyListenerResultReceiver(keyEvent, needWakeLock));
1137 return;
1138 } catch (RemoteException e) {
Jaewan Kima7dce192017-02-16 17:10:54 +09001139 Log.w(TAG, "Failed to send " + keyEvent
1140 + " to the media key listener");
Jaewan Kim98003d32017-02-24 18:33:04 +09001141 }
1142 }
1143 }
Jaewan Kim51255012017-02-24 16:19:14 +09001144 if (!isGlobalPriorityActive && isVoiceKey(keyEvent.getKeyCode())) {
Jaewan Kim6e2b01c2017-01-19 16:33:14 -08001145 handleVoiceKeyEventLocked(keyEvent, needWakeLock);
RoboErik8a2cfc32014-05-16 11:19:38 -07001146 } else {
Jaewan Kim98003d32017-02-24 18:33:04 +09001147 dispatchMediaKeyEventLocked(keyEvent, needWakeLock);
RoboErik8a2cfc32014-05-16 11:19:38 -07001148 }
1149 }
1150 } finally {
1151 Binder.restoreCallingIdentity(token);
1152 }
1153 }
1154
RoboErika278ea72014-04-24 14:49:01 -07001155 @Override
Jaewan Kimbd16f452017-02-03 16:21:38 +09001156 public void setCallback(ICallback callback) {
1157 final int pid = Binder.getCallingPid();
1158 final int uid = Binder.getCallingUid();
1159 final long token = Binder.clearCallingIdentity();
1160 try {
Amith Yamasanie259ad22017-04-24 11:30:19 -07001161 if (!UserHandle.isSameApp(uid, Process.BLUETOOTH_UID)) {
Jaewan Kimbd16f452017-02-03 16:21:38 +09001162 throw new SecurityException("Only Bluetooth service processes can set"
1163 + " Callback");
1164 }
1165 synchronized (mLock) {
Jaewan Kima7dce192017-02-16 17:10:54 +09001166 int userId = UserHandle.getUserId(uid);
1167 FullUserRecord user = getFullUserRecordLocked(userId);
1168 if (user == null || user.mFullUserId != userId) {
1169 Log.w(TAG, "Only the full user can set the callback"
1170 + ", userId=" + userId);
1171 return;
1172 }
1173 user.mCallback = callback;
1174 Log.d(TAG, "The callback " + user.mCallback
Jaewan Kimbd16f452017-02-03 16:21:38 +09001175 + " is set by " + getCallingPackageName(uid));
Jaewan Kima7dce192017-02-16 17:10:54 +09001176 if (user.mCallback == null) {
Jaewan Kimbd16f452017-02-03 16:21:38 +09001177 return;
1178 }
1179 try {
Jaewan Kima7dce192017-02-16 17:10:54 +09001180 user.mCallback.asBinder().linkToDeath(
Jaewan Kimbd16f452017-02-03 16:21:38 +09001181 new IBinder.DeathRecipient() {
1182 @Override
1183 public void binderDied() {
1184 synchronized (mLock) {
Jaewan Kima7dce192017-02-16 17:10:54 +09001185 user.mCallback = null;
Jaewan Kimbd16f452017-02-03 16:21:38 +09001186 }
1187 }
1188 }, 0);
Jaewan Kima7dce192017-02-16 17:10:54 +09001189 user.pushAddressedPlayerChangedLocked();
Jaewan Kimbd16f452017-02-03 16:21:38 +09001190 } catch (RemoteException e) {
1191 Log.w(TAG, "Failed to set callback", e);
Jaewan Kima7dce192017-02-16 17:10:54 +09001192 user.mCallback = null;
Jaewan Kimbd16f452017-02-03 16:21:38 +09001193 }
1194 }
1195 } finally {
1196 Binder.restoreCallingIdentity(token);
1197 }
1198 }
1199
1200 @Override
Jaewan Kim50269362016-12-23 11:22:02 +09001201 public void setOnVolumeKeyLongPressListener(IOnVolumeKeyLongPressListener listener) {
1202 final int pid = Binder.getCallingPid();
1203 final int uid = Binder.getCallingUid();
1204 final long token = Binder.clearCallingIdentity();
1205 try {
1206 // Enforce SET_VOLUME_KEY_LONG_PRESS_LISTENER permission.
1207 if (getContext().checkPermission(
1208 android.Manifest.permission.SET_VOLUME_KEY_LONG_PRESS_LISTENER, pid, uid)
1209 != PackageManager.PERMISSION_GRANTED) {
1210 throw new SecurityException("Must hold the SET_VOLUME_KEY_LONG_PRESS_LISTENER" +
1211 " permission.");
1212 }
1213
1214 synchronized (mLock) {
Jaewan Kima7dce192017-02-16 17:10:54 +09001215 int userId = UserHandle.getUserId(uid);
1216 FullUserRecord user = getFullUserRecordLocked(userId);
1217 if (user == null || user.mFullUserId != userId) {
1218 Log.w(TAG, "Only the full user can set the volume key long-press listener"
1219 + ", userId=" + userId);
1220 return;
1221 }
Jaewan Kim50269362016-12-23 11:22:02 +09001222 if (user.mOnVolumeKeyLongPressListener != null &&
1223 user.mOnVolumeKeyLongPressListenerUid != uid) {
Jaewan Kima7dce192017-02-16 17:10:54 +09001224 Log.w(TAG, "The volume key long-press listener cannot be reset"
1225 + " by another app , mOnVolumeKeyLongPressListener="
1226 + user.mOnVolumeKeyLongPressListenerUid
1227 + ", uid=" + uid);
Jaewan Kim50269362016-12-23 11:22:02 +09001228 return;
1229 }
1230
1231 user.mOnVolumeKeyLongPressListener = listener;
1232 user.mOnVolumeKeyLongPressListenerUid = uid;
1233
Jaewan Kima7dce192017-02-16 17:10:54 +09001234 Log.d(TAG, "The volume key long-press listener "
Jaewan Kim50269362016-12-23 11:22:02 +09001235 + listener + " is set by " + getCallingPackageName(uid));
1236
1237 if (user.mOnVolumeKeyLongPressListener != null) {
1238 try {
1239 user.mOnVolumeKeyLongPressListener.asBinder().linkToDeath(
1240 new IBinder.DeathRecipient() {
1241 @Override
1242 public void binderDied() {
1243 synchronized (mLock) {
1244 user.mOnVolumeKeyLongPressListener = null;
1245 }
1246 }
1247 }, 0);
1248 } catch (RemoteException e) {
1249 Log.w(TAG, "Failed to set death recipient "
1250 + user.mOnVolumeKeyLongPressListener);
1251 user.mOnVolumeKeyLongPressListener = null;
1252 }
1253 }
1254 }
1255 } finally {
1256 Binder.restoreCallingIdentity(token);
1257 }
1258 }
1259
Jaewan Kim6e2b01c2017-01-19 16:33:14 -08001260 @Override
1261 public void setOnMediaKeyListener(IOnMediaKeyListener listener) {
1262 final int pid = Binder.getCallingPid();
1263 final int uid = Binder.getCallingUid();
1264 final long token = Binder.clearCallingIdentity();
1265 try {
1266 // Enforce SET_MEDIA_KEY_LISTENER permission.
1267 if (getContext().checkPermission(
1268 android.Manifest.permission.SET_MEDIA_KEY_LISTENER, pid, uid)
1269 != PackageManager.PERMISSION_GRANTED) {
1270 throw new SecurityException("Must hold the SET_MEDIA_KEY_LISTENER" +
1271 " permission.");
1272 }
1273
1274 synchronized (mLock) {
1275 int userId = UserHandle.getUserId(uid);
Jaewan Kima7dce192017-02-16 17:10:54 +09001276 FullUserRecord user = getFullUserRecordLocked(userId);
1277 if (user == null || user.mFullUserId != userId) {
1278 Log.w(TAG, "Only the full user can set the media key listener"
1279 + ", userId=" + userId);
1280 return;
1281 }
Jaewan Kim6e2b01c2017-01-19 16:33:14 -08001282 if (user.mOnMediaKeyListener != null && user.mOnMediaKeyListenerUid != uid) {
Jaewan Kima7dce192017-02-16 17:10:54 +09001283 Log.w(TAG, "The media key listener cannot be reset by another app. "
1284 + ", mOnMediaKeyListenerUid=" + user.mOnMediaKeyListenerUid
1285 + ", uid=" + uid);
Jaewan Kim6e2b01c2017-01-19 16:33:14 -08001286 return;
1287 }
1288
1289 user.mOnMediaKeyListener = listener;
1290 user.mOnMediaKeyListenerUid = uid;
1291
Jaewan Kima7dce192017-02-16 17:10:54 +09001292 Log.d(TAG, "The media key listener " + user.mOnMediaKeyListener
Jaewan Kim6e2b01c2017-01-19 16:33:14 -08001293 + " is set by " + getCallingPackageName(uid));
1294
1295 if (user.mOnMediaKeyListener != null) {
1296 try {
1297 user.mOnMediaKeyListener.asBinder().linkToDeath(
1298 new IBinder.DeathRecipient() {
1299 @Override
1300 public void binderDied() {
1301 synchronized (mLock) {
1302 user.mOnMediaKeyListener = null;
1303 }
1304 }
1305 }, 0);
1306 } catch (RemoteException e) {
1307 Log.w(TAG, "Failed to set death recipient " + user.mOnMediaKeyListener);
1308 user.mOnMediaKeyListener = null;
1309 }
1310 }
1311 }
1312 } finally {
1313 Binder.restoreCallingIdentity(token);
1314 }
1315 }
1316
Jaewan Kim50269362016-12-23 11:22:02 +09001317 /**
1318 * Handles the dispatching of the volume button events to one of the
1319 * registered listeners. If there's a volume key long-press listener and
1320 * there's no active global priority session, long-pressess will be sent to the
1321 * long-press listener instead of adjusting volume.
1322 *
1323 * @param keyEvent a non-null KeyEvent whose key code is one of the
1324 * {@link KeyEvent#KEYCODE_VOLUME_UP},
1325 * {@link KeyEvent#KEYCODE_VOLUME_DOWN},
1326 * or {@link KeyEvent#KEYCODE_VOLUME_MUTE}.
1327 * @param stream stream type to adjust volume.
1328 * @param musicOnly true if both UI nor haptic feedback aren't needed when adjust volume.
1329 */
1330 @Override
1331 public void dispatchVolumeKeyEvent(KeyEvent keyEvent, int stream, boolean musicOnly) {
1332 if (keyEvent == null ||
1333 (keyEvent.getKeyCode() != KeyEvent.KEYCODE_VOLUME_UP
1334 && keyEvent.getKeyCode() != KeyEvent.KEYCODE_VOLUME_DOWN
1335 && keyEvent.getKeyCode() != KeyEvent.KEYCODE_VOLUME_MUTE)) {
1336 Log.w(TAG, "Attempted to dispatch null or non-volume key event.");
1337 return;
1338 }
1339
1340 final int pid = Binder.getCallingPid();
1341 final int uid = Binder.getCallingUid();
1342 final long token = Binder.clearCallingIdentity();
1343
Jaewan Kimb2781e72017-03-02 09:57:09 +09001344 if (DEBUG_KEY_EVENT) {
Jaewan Kim50269362016-12-23 11:22:02 +09001345 Log.d(TAG, "dispatchVolumeKeyEvent, pid=" + pid + ", uid=" + uid + ", event="
1346 + keyEvent);
1347 }
1348
1349 try {
1350 synchronized (mLock) {
Jaewan Kima7dce192017-02-16 17:10:54 +09001351 if (isGlobalPriorityActiveLocked()
1352 || mCurrentFullUserRecord.mOnVolumeKeyLongPressListener == null) {
Jaewan Kim50269362016-12-23 11:22:02 +09001353 dispatchVolumeKeyEventLocked(keyEvent, stream, musicOnly);
1354 } else {
1355 // TODO: Consider the case when both volume up and down keys are pressed
1356 // at the same time.
1357 if (keyEvent.getAction() == KeyEvent.ACTION_DOWN) {
1358 if (keyEvent.getRepeatCount() == 0) {
Jaewan Kimd61a87b2017-02-17 23:14:10 +09001359 // Keeps the copy of the KeyEvent because it can be reused.
Jaewan Kima7dce192017-02-16 17:10:54 +09001360 mCurrentFullUserRecord.mInitialDownVolumeKeyEvent =
1361 KeyEvent.obtain(keyEvent);
1362 mCurrentFullUserRecord.mInitialDownVolumeStream = stream;
1363 mCurrentFullUserRecord.mInitialDownMusicOnly = musicOnly;
Jaewan Kimd61a87b2017-02-17 23:14:10 +09001364 mHandler.sendMessageDelayed(
1365 mHandler.obtainMessage(
Jaewan Kima7dce192017-02-16 17:10:54 +09001366 MessageHandler.MSG_VOLUME_INITIAL_DOWN,
1367 mCurrentFullUserRecord.mFullUserId, 0),
Jaewan Kimd61a87b2017-02-17 23:14:10 +09001368 mLongPressTimeout);
Jaewan Kim50269362016-12-23 11:22:02 +09001369 }
1370 if (keyEvent.getRepeatCount() > 0 || keyEvent.isLongPress()) {
Jaewan Kimd61a87b2017-02-17 23:14:10 +09001371 mHandler.removeMessages(MessageHandler.MSG_VOLUME_INITIAL_DOWN);
Jaewan Kima7dce192017-02-16 17:10:54 +09001372 if (mCurrentFullUserRecord.mInitialDownVolumeKeyEvent != null) {
Jaewan Kim50269362016-12-23 11:22:02 +09001373 dispatchVolumeKeyLongPressLocked(
Jaewan Kima7dce192017-02-16 17:10:54 +09001374 mCurrentFullUserRecord.mInitialDownVolumeKeyEvent);
Jaewan Kim50269362016-12-23 11:22:02 +09001375 // Mark that the key is already handled.
Jaewan Kima7dce192017-02-16 17:10:54 +09001376 mCurrentFullUserRecord.mInitialDownVolumeKeyEvent = null;
Jaewan Kim50269362016-12-23 11:22:02 +09001377 }
1378 dispatchVolumeKeyLongPressLocked(keyEvent);
1379 }
1380 } else { // if up
Jaewan Kimd61a87b2017-02-17 23:14:10 +09001381 mHandler.removeMessages(MessageHandler.MSG_VOLUME_INITIAL_DOWN);
Jaewan Kima7dce192017-02-16 17:10:54 +09001382 if (mCurrentFullUserRecord.mInitialDownVolumeKeyEvent != null
1383 && mCurrentFullUserRecord.mInitialDownVolumeKeyEvent
1384 .getDownTime() == keyEvent.getDownTime()) {
Jaewan Kim50269362016-12-23 11:22:02 +09001385 // Short-press. Should change volume.
1386 dispatchVolumeKeyEventLocked(
Jaewan Kima7dce192017-02-16 17:10:54 +09001387 mCurrentFullUserRecord.mInitialDownVolumeKeyEvent,
1388 mCurrentFullUserRecord.mInitialDownVolumeStream,
1389 mCurrentFullUserRecord.mInitialDownMusicOnly);
Jaewan Kim50269362016-12-23 11:22:02 +09001390 dispatchVolumeKeyEventLocked(keyEvent, stream, musicOnly);
1391 } else {
1392 dispatchVolumeKeyLongPressLocked(keyEvent);
1393 }
1394 }
1395 }
1396 }
1397 } finally {
1398 Binder.restoreCallingIdentity(token);
1399 }
1400 }
1401
Jaewan Kim50269362016-12-23 11:22:02 +09001402 private void dispatchVolumeKeyEventLocked(
1403 KeyEvent keyEvent, int stream, boolean musicOnly) {
1404 boolean down = keyEvent.getAction() == KeyEvent.ACTION_DOWN;
1405 boolean up = keyEvent.getAction() == KeyEvent.ACTION_UP;
1406 int direction = 0;
1407 boolean isMute = false;
1408 switch (keyEvent.getKeyCode()) {
1409 case KeyEvent.KEYCODE_VOLUME_UP:
1410 direction = AudioManager.ADJUST_RAISE;
1411 break;
1412 case KeyEvent.KEYCODE_VOLUME_DOWN:
1413 direction = AudioManager.ADJUST_LOWER;
1414 break;
1415 case KeyEvent.KEYCODE_VOLUME_MUTE:
1416 isMute = true;
1417 break;
1418 }
1419 if (down || up) {
1420 int flags = AudioManager.FLAG_FROM_KEY;
1421 if (musicOnly) {
1422 // This flag is used when the screen is off to only affect active media.
1423 flags |= AudioManager.FLAG_ACTIVE_MEDIA_ONLY;
1424 } else {
1425 // These flags are consistent with the home screen
1426 if (up) {
1427 flags |= AudioManager.FLAG_PLAY_SOUND | AudioManager.FLAG_VIBRATE;
1428 } else {
1429 flags |= AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_VIBRATE;
1430 }
1431 }
1432 if (direction != 0) {
1433 // If this is action up we want to send a beep for non-music events
1434 if (up) {
1435 direction = 0;
1436 }
1437 dispatchAdjustVolumeLocked(stream, direction, flags);
1438 } else if (isMute) {
1439 if (down && keyEvent.getRepeatCount() == 0) {
1440 dispatchAdjustVolumeLocked(stream, AudioManager.ADJUST_TOGGLE_MUTE, flags);
1441 }
1442 }
1443 }
1444 }
1445
1446 @Override
RoboErik7c82ced2014-12-04 17:39:08 -08001447 public void dispatchAdjustVolume(int suggestedStream, int delta, int flags) {
RoboErikb69ffd42014-05-30 14:57:59 -07001448 final long token = Binder.clearCallingIdentity();
1449 try {
1450 synchronized (mLock) {
Jaewan Kim50269362016-12-23 11:22:02 +09001451 dispatchAdjustVolumeLocked(suggestedStream, delta, flags);
RoboErikb69ffd42014-05-30 14:57:59 -07001452 }
1453 } finally {
1454 Binder.restoreCallingIdentity(token);
1455 }
1456 }
1457
1458 @Override
RoboErik19c95182014-06-23 15:38:48 -07001459 public void setRemoteVolumeController(IRemoteVolumeController rvc) {
1460 final int pid = Binder.getCallingPid();
1461 final int uid = Binder.getCallingUid();
1462 final long token = Binder.clearCallingIdentity();
1463 try {
John Spurlockeb69e242015-02-17 17:15:04 -05001464 enforceSystemUiPermission("listen for volume changes", pid, uid);
RoboErik19c95182014-06-23 15:38:48 -07001465 mRvc = rvc;
1466 } finally {
1467 Binder.restoreCallingIdentity(token);
1468 }
1469 }
1470
1471 @Override
RoboErikde9ba392014-09-26 12:51:01 -07001472 public boolean isGlobalPriorityActive() {
Jaewan Kim51255012017-02-24 16:19:14 +09001473 synchronized (mLock) {
Jaewan Kima7dce192017-02-16 17:10:54 +09001474 return isGlobalPriorityActiveLocked();
Jaewan Kim51255012017-02-24 16:19:14 +09001475 }
RoboErikde9ba392014-09-26 12:51:01 -07001476 }
1477
1478 @Override
RoboErika278ea72014-04-24 14:49:01 -07001479 public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
Jeff Sharkeyfe9a53b2017-03-31 14:08:23 -06001480 if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) return;
RoboErika278ea72014-04-24 14:49:01 -07001481
1482 pw.println("MEDIA SESSION SERVICE (dumpsys media_session)");
1483 pw.println();
1484
1485 synchronized (mLock) {
RoboErika08adb242014-11-21 18:28:18 -08001486 pw.println(mSessionsListeners.size() + " sessions listeners.");
Jaewan Kima7dce192017-02-16 17:10:54 +09001487 pw.println("Global priority session is " + mGlobalPrioritySession);
Jaewan Kim101b4d52017-05-18 13:23:11 +09001488 if (mGlobalPrioritySession != null) {
1489 mGlobalPrioritySession.dump(pw, " ");
1490 }
RoboErik4646d282014-05-13 10:13:04 -07001491 pw.println("User Records:");
Jaewan Kime0ca3f32017-02-16 15:52:39 +09001492 int count = mUserRecords.size();
RoboErika278ea72014-04-24 14:49:01 -07001493 for (int i = 0; i < count; i++) {
Jaewan Kima7dce192017-02-16 17:10:54 +09001494 mUserRecords.valueAt(i).dumpLocked(pw, "");
RoboErika278ea72014-04-24 14:49:01 -07001495 }
Sungsoo Lim875e6972017-11-03 02:22:35 +00001496 mAudioPlayerStateMonitor.dump(getContext(), pw, "");
RoboErika278ea72014-04-24 14:49:01 -07001497 }
1498 }
RoboErik8a2cfc32014-05-16 11:19:38 -07001499
Jaewan Kim7e9e4d92018-02-13 22:20:41 +09001500 /**
Jaewan Kimfed49502018-03-05 17:51:12 +09001501 * Returns if the controller's package is trusted (i.e. has either MEDIA_CONTENT_CONTROL
1502 * permission or an enabled notification listener)
1503 *
Hyundo Moonf84c1c02018-03-19 20:33:27 +09001504 * @param controllerPackageName package name of the controller app
1505 * @param controllerPid pid of the controller app
1506 * @param controllerUid uid of the controller app
Jaewan Kimfed49502018-03-05 17:51:12 +09001507 */
1508 @Override
Hyundo Moonf84c1c02018-03-19 20:33:27 +09001509 public boolean isTrusted(String controllerPackageName, int controllerPid, int controllerUid)
1510 throws RemoteException {
1511 final int uid = Binder.getCallingUid();
Jaewan Kimfed49502018-03-05 17:51:12 +09001512 final long token = Binder.clearCallingIdentity();
1513 try {
Hyundo Moonf84c1c02018-03-19 20:33:27 +09001514 return hasMediaControlPermission(UserHandle.getUserId(uid), controllerPackageName,
1515 controllerPid, controllerUid);
Jaewan Kimfed49502018-03-05 17:51:12 +09001516 } finally {
1517 Binder.restoreCallingIdentity(token);
1518 }
Jaewan Kimfed49502018-03-05 17:51:12 +09001519 }
1520
1521 /**
Jaewan Kim7e9e4d92018-02-13 22:20:41 +09001522 * Called when a {@link android.media.MediaSession2} instance is created.
1523 * <p>
1524 * This does two things.
1525 * 1. Keep the newly created session in the service
1526 * 2. Do sanity check to ensure unique id per package, and return result
1527 *
1528 * @param sessionToken SessionToken2 object in bundled form
1529 * @return {@code true} if the session's id isn't used by the package now. {@code false}
1530 * otherwise.
1531 */
Jaewan Kimceb6b6e2018-01-21 20:56:10 +09001532 @Override
Sungsoo Lim117c7f72018-02-13 16:02:24 +09001533 public boolean createSession2(Bundle sessionToken) {
Jaewan Kimf7a77062018-01-27 01:34:24 +09001534 final int uid = Binder.getCallingUid();
Christofer Ã…kersten6823d812018-03-22 23:20:39 +09001535 final SessionToken2 token = SessionToken2.fromBundle(sessionToken);
Jaewan Kimf7a77062018-01-27 01:34:24 +09001536 if (token == null || token.getUid() != uid) {
1537 Log.w(TAG, "onSessionCreated failed, expected caller uid=" + token.getUid()
1538 + " but from uid=" + uid);
1539 }
1540 if (DEBUG) {
Sungsoo Lim117c7f72018-02-13 16:02:24 +09001541 Log.d(TAG, "createSession2: " + token);
Jaewan Kimf7a77062018-01-27 01:34:24 +09001542 }
Jaewan Kimceb6b6e2018-01-21 20:56:10 +09001543 synchronized (mLock) {
Sungsoo Lim117c7f72018-02-13 16:02:24 +09001544 MediaController2 controller = mSessionRecords.get(token);
1545 if (controller != null && controller.isConnected()) {
1546 return false;
Jaewan Kimceb6b6e2018-01-21 20:56:10 +09001547 }
Sungsoo Lim117c7f72018-02-13 16:02:24 +09001548 Context context = getContext();
Sungsoo Lim375efa12018-03-02 10:17:08 +09001549 controller = new MediaController2(context, token, context.getMainExecutor(),
1550 new ControllerCallback(token));
1551 if (addSessionRecordLocked(token, controller)) {
1552 postSessionTokensUpdated(UserHandle.getUserId(token.getUid()));
1553 }
Sungsoo Lim117c7f72018-02-13 16:02:24 +09001554 return true;
Jaewan Kimceb6b6e2018-01-21 20:56:10 +09001555 }
Jaewan Kimf7a77062018-01-27 01:34:24 +09001556 }
1557
Jaewan Kim7e9e4d92018-02-13 22:20:41 +09001558 /**
1559 * Called when a {@link android.media.MediaSession2} instance is closed. (i.e. destroyed)
1560 * <p>
1561 * Ideally service should know that a session is destroyed through the
1562 * {@link android.media.MediaController2.ControllerCallback#onDisconnected()}, which is
1563 * asynchronous call. However, we also need synchronous way together to address timing
1564 * issue. If the package recreates the session almost immediately, which happens commonly
1565 * for tests, service will reject the creation through {@link #onSessionCreated(Bundle)}
1566 * if the service hasn't notified previous destroy yet. This synchronous API will address
1567 * the issue.
1568 *
1569 * @param sessionToken SessionToken2 object in bundled form
1570 */
Jaewan Kimf7a77062018-01-27 01:34:24 +09001571 @Override
Sungsoo Lim117c7f72018-02-13 16:02:24 +09001572 public void destroySession2(Bundle sessionToken) {
Jaewan Kimf7a77062018-01-27 01:34:24 +09001573 final int uid = Binder.getCallingUid();
Christofer Ã…kersten6823d812018-03-22 23:20:39 +09001574 final SessionToken2 token = SessionToken2.fromBundle(sessionToken);
Jaewan Kimf7a77062018-01-27 01:34:24 +09001575 if (token == null || token.getUid() != uid) {
1576 Log.w(TAG, "onSessionDestroyed failed, expected caller uid=" + token.getUid()
1577 + " but from uid=" + uid);
1578 }
1579 if (DEBUG) {
Sungsoo Lim117c7f72018-02-13 16:02:24 +09001580 Log.d(TAG, "destroySession2 " + token);
Jaewan Kimf7a77062018-01-27 01:34:24 +09001581 }
Sungsoo Lim117c7f72018-02-13 16:02:24 +09001582 destroySession2Internal(token);
Jaewan Kimceb6b6e2018-01-21 20:56:10 +09001583 }
1584
Hyundo Moonf84c1c02018-03-19 20:33:27 +09001585 // TODO(jaewan): Make this API take userId as an argument (b/73597722)
Jaewan Kimceb6b6e2018-01-21 20:56:10 +09001586 @Override
1587 public List<Bundle> getSessionTokens(boolean activeSessionOnly,
Hyundo Moonf84c1c02018-03-19 20:33:27 +09001588 boolean sessionServiceOnly, String packageName) throws RemoteException {
1589 final int pid = Binder.getCallingPid();
1590 final int uid = Binder.getCallingUid();
1591 final long token = Binder.clearCallingIdentity();
1592
Jaewan Kimceb6b6e2018-01-21 20:56:10 +09001593 List<Bundle> tokens = new ArrayList<>();
Hyundo Moonf84c1c02018-03-19 20:33:27 +09001594 try {
1595 verifySessionsRequest2(UserHandle.getUserId(uid), packageName, pid, uid);
1596 synchronized (mLock) {
1597 for (Map.Entry<SessionToken2, MediaController2> record
1598 : mSessionRecords.entrySet()) {
1599 boolean isSessionService = (record.getKey().getType() != TYPE_SESSION);
1600 boolean isActive = record.getValue() != null;
1601 if ((activeSessionOnly && !isActive)
1602 || (sessionServiceOnly && !isSessionService)) {
1603 continue;
1604 }
1605 tokens.add(record.getKey().toBundle());
Jaewan Kimceb6b6e2018-01-21 20:56:10 +09001606 }
1607 }
Hyundo Moonf84c1c02018-03-19 20:33:27 +09001608 } finally {
1609 Binder.restoreCallingIdentity(token);
Jaewan Kimceb6b6e2018-01-21 20:56:10 +09001610 }
1611 return tokens;
1612 }
1613
Jaewan Kim379e30d2018-01-29 11:57:04 +09001614 @Override
1615 public void addSessionTokensListener(ISessionTokensListener listener, int userId,
Hyundo Moonf84c1c02018-03-19 20:33:27 +09001616 String packageName) throws RemoteException {
1617 final int pid = Binder.getCallingPid();
1618 final int uid = Binder.getCallingUid();
1619 final long token = Binder.clearCallingIdentity();
1620 try {
1621 int resolvedUserId = verifySessionsRequest2(userId, packageName, pid, uid);
1622 synchronized (mLock) {
1623 final SessionTokensListenerRecord record =
1624 new SessionTokensListenerRecord(listener, resolvedUserId);
1625 try {
1626 listener.asBinder().linkToDeath(record, 0);
1627 } catch (RemoteException e) {
1628 }
1629 mSessionTokensListeners.add(record);
Sungsoo Lim375efa12018-03-02 10:17:08 +09001630 }
Hyundo Moonf84c1c02018-03-19 20:33:27 +09001631 } finally {
1632 Binder.restoreCallingIdentity(token);
Sungsoo Lim375efa12018-03-02 10:17:08 +09001633 }
Jaewan Kim379e30d2018-01-29 11:57:04 +09001634 }
1635
Hyundo Moonf84c1c02018-03-19 20:33:27 +09001636 // TODO(jaewan): Make this API take userId as an argument (b/73597722)
Jaewan Kim379e30d2018-01-29 11:57:04 +09001637 @Override
Hyundo Moonf84c1c02018-03-19 20:33:27 +09001638 public void removeSessionTokensListener(ISessionTokensListener listener,
1639 String packageName) throws RemoteException {
1640 final int pid = Binder.getCallingPid();
1641 final int uid = Binder.getCallingUid();
1642 final long token = Binder.clearCallingIdentity();
1643 try {
1644 verifySessionsRequest2(UserHandle.getUserId(uid), packageName, pid, uid);
1645 synchronized (mLock) {
1646 IBinder listenerBinder = listener.asBinder();
1647 for (SessionTokensListenerRecord record : mSessionTokensListeners) {
1648 if (listenerBinder.equals(record.mListener.asBinder())) {
1649 try {
1650 listenerBinder.unlinkToDeath(record, 0);
1651 } catch (NoSuchElementException e) {
1652 }
1653 mSessionTokensListeners.remove(record);
1654 break;
Sungsoo Lim375efa12018-03-02 10:17:08 +09001655 }
Sungsoo Lim375efa12018-03-02 10:17:08 +09001656 }
1657 }
Hyundo Moonf84c1c02018-03-19 20:33:27 +09001658 } finally {
1659 Binder.restoreCallingIdentity(token);
Sungsoo Lim375efa12018-03-02 10:17:08 +09001660 }
Jaewan Kim379e30d2018-01-29 11:57:04 +09001661 }
1662
Hyundo Moonf84c1c02018-03-19 20:33:27 +09001663 // For MediaSession
RoboErik2e7a9162014-06-04 16:53:45 -07001664 private int verifySessionsRequest(ComponentName componentName, int userId, final int pid,
1665 final int uid) {
1666 String packageName = null;
1667 if (componentName != null) {
1668 // If they gave us a component name verify they own the
1669 // package
1670 packageName = componentName.getPackageName();
1671 enforcePackageName(packageName, uid);
1672 }
1673 // Check that they can make calls on behalf of the user and
1674 // get the final user id
1675 int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId,
1676 true /* allowAll */, true /* requireFull */, "getSessions", packageName);
1677 // Check if they have the permissions or their component is
1678 // enabled for the user they're calling from.
1679 enforceMediaPermissions(componentName, pid, uid, resolvedUserId);
1680 return resolvedUserId;
1681 }
1682
Hyundo Moonf84c1c02018-03-19 20:33:27 +09001683 // For MediaSession2
1684 private int verifySessionsRequest2(int targetUserId, String callerPackageName,
1685 int callerPid, int callerUid) throws RemoteException {
1686 // Check that they can make calls on behalf of the user and get the final user id.
1687 int resolvedUserId = ActivityManager.handleIncomingUser(callerPid, callerUid,
1688 targetUserId, true /* allowAll */, true /* requireFull */, "getSessionTokens",
1689 callerPackageName);
1690 // Check if they have the permissions or their component is
1691 // enabled for the user they're calling from.
1692 if (!hasMediaControlPermission(
1693 resolvedUserId, callerPackageName, callerPid, callerUid)) {
1694 throw new SecurityException("Missing permission to control media.");
1695 }
1696 return resolvedUserId;
1697 }
1698
1699 // For MediaSession2
1700 private boolean hasMediaControlPermission(int resolvedUserId, String packageName,
1701 int pid, int uid) throws RemoteException {
1702 // Allow API calls from the System UI
1703 if (isCurrentVolumeController(pid, uid)) {
1704 return true;
1705 }
1706
1707 // Check if it's system server or has MEDIA_CONTENT_CONTROL.
1708 // Note that system server doesn't have MEDIA_CONTENT_CONTROL, so we need extra
1709 // check here.
1710 if (uid == Process.SYSTEM_UID || getContext().checkPermission(
1711 android.Manifest.permission.MEDIA_CONTENT_CONTROL, pid, uid)
1712 == PackageManager.PERMISSION_GRANTED) {
1713 return true;
1714 } else if (DEBUG) {
1715 Log.d(TAG, packageName + " (uid=" + uid + ") hasn't granted MEDIA_CONTENT_CONTROL");
1716 }
1717
1718 // You may not access another user's content as an enabled listener.
1719 final int userId = UserHandle.getUserId(uid);
1720 if (resolvedUserId != userId) {
1721 return false;
1722 }
1723
1724 // TODO(jaewan): (Post-P) Propose NotificationManager#hasEnabledNotificationListener(
1725 // String pkgName) to notification team for optimization
1726 final List<ComponentName> enabledNotificationListeners =
1727 mNotificationManager.getEnabledNotificationListeners(userId);
1728 if (enabledNotificationListeners != null) {
1729 for (int i = 0; i < enabledNotificationListeners.size(); i++) {
1730 if (TextUtils.equals(packageName,
1731 enabledNotificationListeners.get(i).getPackageName())) {
1732 return true;
1733 }
1734 }
1735 }
1736 if (DEBUG) {
1737 Log.d(TAG, packageName + " (uid=" + uid + ") doesn't have an enabled "
1738 + "notification listener");
1739 }
1740 return false;
1741 }
1742
Jaewan Kim50269362016-12-23 11:22:02 +09001743 private void dispatchAdjustVolumeLocked(int suggestedStream, int direction, int flags) {
Jaewan Kima7dce192017-02-16 17:10:54 +09001744 MediaSessionRecord session = isGlobalPriorityActiveLocked() ? mGlobalPrioritySession
1745 : mCurrentFullUserRecord.mPriorityStack.getDefaultVolumeSession();
Jaewan Kim50269362016-12-23 11:22:02 +09001746
RoboErik9c785402014-11-11 16:52:26 -08001747 boolean preferSuggestedStream = false;
1748 if (isValidLocalStreamType(suggestedStream)
1749 && AudioSystem.isStreamActive(suggestedStream, 0)) {
1750 preferSuggestedStream = true;
1751 }
Jaewan Kimb2781e72017-03-02 09:57:09 +09001752 if (DEBUG_KEY_EVENT) {
Jaewan Kim5e1476e2016-07-19 22:25:39 +09001753 Log.d(TAG, "Adjusting " + session + " by " + direction + ". flags="
1754 + flags + ", suggestedStream=" + suggestedStream
1755 + ", preferSuggestedStream=" + preferSuggestedStream);
1756 }
RoboErik9c785402014-11-11 16:52:26 -08001757 if (session == null || preferSuggestedStream) {
RoboErik94c716e2014-09-14 13:54:31 -07001758 if ((flags & AudioManager.FLAG_ACTIVE_MEDIA_ONLY) != 0
1759 && !AudioSystem.isStreamActive(AudioManager.STREAM_MUSIC, 0)) {
RoboErik3c45c292014-07-08 16:47:31 -07001760 if (DEBUG) {
1761 Log.d(TAG, "No active session to adjust, skipping media only volume event");
RoboErik3c45c292014-07-08 16:47:31 -07001762 }
RoboErikb7c014c2014-07-22 15:58:22 -07001763 return;
RoboErik3c45c292014-07-08 16:47:31 -07001764 }
Shibin George19e84042016-06-14 20:42:13 +05301765
1766 // Execute mAudioService.adjustSuggestedStreamVolume() on
1767 // handler thread of MediaSessionService.
1768 // This will release the MediaSessionService.mLock sooner and avoid
1769 // a potential deadlock between MediaSessionService.mLock and
1770 // ActivityManagerService lock.
1771 mHandler.post(new Runnable() {
1772 @Override
1773 public void run() {
1774 try {
1775 String packageName = getContext().getOpPackageName();
1776 mAudioService.adjustSuggestedStreamVolume(direction, suggestedStream,
1777 flags, packageName, TAG);
1778 } catch (RemoteException e) {
1779 Log.e(TAG, "Error adjusting default volume.", e);
Hyundo Moon739d6c22017-09-18 17:01:48 +09001780 } catch (IllegalArgumentException e) {
1781 Log.e(TAG, "Cannot adjust volume: direction=" + direction
1782 + ", suggestedStream=" + suggestedStream + ", flags=" + flags,
1783 e);
Shibin George19e84042016-06-14 20:42:13 +05301784 }
1785 }
1786 });
RoboErikb69ffd42014-05-30 14:57:59 -07001787 } else {
Hyundo Moonb1e344e2018-03-22 17:22:14 +09001788 session.adjustVolume(getContext().getPackageName(), Process.myPid(),
1789 Process.SYSTEM_UID, direction, flags, true);
RoboErikb69ffd42014-05-30 14:57:59 -07001790 }
1791 }
1792
Jaewan Kim6e2b01c2017-01-19 16:33:14 -08001793 private void handleVoiceKeyEventLocked(KeyEvent keyEvent, boolean needWakeLock) {
RoboErik9a9d0b52014-05-20 14:53:39 -07001794 int action = keyEvent.getAction();
1795 boolean isLongPress = (keyEvent.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0;
1796 if (action == KeyEvent.ACTION_DOWN) {
1797 if (keyEvent.getRepeatCount() == 0) {
1798 mVoiceButtonDown = true;
1799 mVoiceButtonHandled = false;
1800 } else if (mVoiceButtonDown && !mVoiceButtonHandled && isLongPress) {
1801 mVoiceButtonHandled = true;
1802 startVoiceInput(needWakeLock);
1803 }
1804 } else if (action == KeyEvent.ACTION_UP) {
1805 if (mVoiceButtonDown) {
1806 mVoiceButtonDown = false;
1807 if (!mVoiceButtonHandled && !keyEvent.isCanceled()) {
1808 // Resend the down then send this event through
1809 KeyEvent downEvent = KeyEvent.changeAction(keyEvent, KeyEvent.ACTION_DOWN);
Jaewan Kim98003d32017-02-24 18:33:04 +09001810 dispatchMediaKeyEventLocked(downEvent, needWakeLock);
1811 dispatchMediaKeyEventLocked(keyEvent, needWakeLock);
RoboErik9a9d0b52014-05-20 14:53:39 -07001812 }
1813 }
1814 }
1815 }
1816
Jaewan Kim98003d32017-02-24 18:33:04 +09001817 private void dispatchMediaKeyEventLocked(KeyEvent keyEvent, boolean needWakeLock) {
Jaewan Kima7dce192017-02-16 17:10:54 +09001818 MediaSessionRecord session = mCurrentFullUserRecord.getMediaButtonSessionLocked();
RoboErik9a9d0b52014-05-20 14:53:39 -07001819 if (session != null) {
Jaewan Kim50269362016-12-23 11:22:02 +09001820 if (DEBUG_KEY_EVENT) {
Jaewan Kim5e1476e2016-07-19 22:25:39 +09001821 Log.d(TAG, "Sending " + keyEvent + " to " + session);
RoboErik9a9d0b52014-05-20 14:53:39 -07001822 }
1823 if (needWakeLock) {
1824 mKeyEventReceiver.aquireWakeLockLocked();
1825 }
Jaewan Kim50269362016-12-23 11:22:02 +09001826 // If we don't need a wakelock use -1 as the id so we won't release it later.
Hyundo Moonb1e344e2018-03-22 17:22:14 +09001827 session.sendMediaButton(getContext().getPackageName(),
1828 Process.myPid(),
1829 Process.SYSTEM_UID,
1830 keyEvent,
RoboErik9a9d0b52014-05-20 14:53:39 -07001831 needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1,
Hyundo Moonb1e344e2018-03-22 17:22:14 +09001832 mKeyEventReceiver);
Jaewan Kima7dce192017-02-16 17:10:54 +09001833 if (mCurrentFullUserRecord.mCallback != null) {
Jaewan Kimbd16f452017-02-03 16:21:38 +09001834 try {
Jaewan Kima7dce192017-02-16 17:10:54 +09001835 mCurrentFullUserRecord.mCallback.onMediaKeyEventDispatchedToMediaSession(
1836 keyEvent,
Jaewan Kimbd16f452017-02-03 16:21:38 +09001837 new MediaSession.Token(session.getControllerBinder()));
1838 } catch (RemoteException e) {
1839 Log.w(TAG, "Failed to send callback", e);
1840 }
1841 }
Jaewan Kima7dce192017-02-16 17:10:54 +09001842 } else if (mCurrentFullUserRecord.mLastMediaButtonReceiver != null
1843 || mCurrentFullUserRecord.mRestoredMediaButtonReceiver != null) {
1844 if (needWakeLock) {
1845 mKeyEventReceiver.aquireWakeLockLocked();
1846 }
1847 Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
1848 mediaButtonIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
1849 mediaButtonIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
1850 try {
1851 if (mCurrentFullUserRecord.mLastMediaButtonReceiver != null) {
1852 PendingIntent receiver = mCurrentFullUserRecord.mLastMediaButtonReceiver;
1853 if (DEBUG_KEY_EVENT) {
1854 Log.d(TAG, "Sending " + keyEvent
Jaewan Kim92dea332017-02-02 11:52:08 +09001855 + " to the last known PendingIntent " + receiver);
Jaewan Kima7dce192017-02-16 17:10:54 +09001856 }
1857 receiver.send(getContext(),
1858 needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1,
1859 mediaButtonIntent, mKeyEventReceiver, mHandler);
1860 if (mCurrentFullUserRecord.mCallback != null) {
1861 ComponentName componentName = mCurrentFullUserRecord
1862 .mLastMediaButtonReceiver.getIntent().getComponent();
1863 if (componentName != null) {
1864 mCurrentFullUserRecord.mCallback
1865 .onMediaKeyEventDispatchedToMediaButtonReceiver(
1866 keyEvent, componentName);
Jaewan Kimbd16f452017-02-03 16:21:38 +09001867 }
RoboErikc8f92d12015-01-05 16:48:07 -08001868 }
Jaewan Kima7dce192017-02-16 17:10:54 +09001869 } else {
1870 ComponentName receiver =
1871 mCurrentFullUserRecord.mRestoredMediaButtonReceiver;
1872 if (DEBUG_KEY_EVENT) {
1873 Log.d(TAG, "Sending " + keyEvent + " to the restored intent "
1874 + receiver);
1875 }
1876 mediaButtonIntent.setComponent(receiver);
1877 getContext().sendBroadcastAsUser(mediaButtonIntent,
Jaewan Kim92dea332017-02-02 11:52:08 +09001878 UserHandle.of(mCurrentFullUserRecord
1879 .mRestoredMediaButtonReceiverUserId));
Jaewan Kima7dce192017-02-16 17:10:54 +09001880 if (mCurrentFullUserRecord.mCallback != null) {
1881 mCurrentFullUserRecord.mCallback
1882 .onMediaKeyEventDispatchedToMediaButtonReceiver(
1883 keyEvent, receiver);
1884 }
RoboErikb214efb2014-07-24 13:20:30 -07001885 }
Jaewan Kima7dce192017-02-16 17:10:54 +09001886 } catch (CanceledException e) {
1887 Log.i(TAG, "Error sending key event to media button receiver "
1888 + mCurrentFullUserRecord.mLastMediaButtonReceiver, e);
1889 } catch (RemoteException e) {
1890 Log.w(TAG, "Failed to send callback", e);
RoboErik9a9d0b52014-05-20 14:53:39 -07001891 }
RoboErik9a9d0b52014-05-20 14:53:39 -07001892 }
1893 }
1894
1895 private void startVoiceInput(boolean needWakeLock) {
1896 Intent voiceIntent = null;
1897 // select which type of search to launch:
1898 // - screen on and device unlocked: action is ACTION_WEB_SEARCH
1899 // - device locked or screen off: action is
1900 // ACTION_VOICE_SEARCH_HANDS_FREE
1901 // with EXTRA_SECURE set to true if the device is securely locked
1902 PowerManager pm = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE);
1903 boolean isLocked = mKeyguardManager != null && mKeyguardManager.isKeyguardLocked();
1904 if (!isLocked && pm.isScreenOn()) {
1905 voiceIntent = new Intent(android.speech.RecognizerIntent.ACTION_WEB_SEARCH);
1906 Log.i(TAG, "voice-based interactions: about to use ACTION_WEB_SEARCH");
1907 } else {
1908 voiceIntent = new Intent(RecognizerIntent.ACTION_VOICE_SEARCH_HANDS_FREE);
1909 voiceIntent.putExtra(RecognizerIntent.EXTRA_SECURE,
1910 isLocked && mKeyguardManager.isKeyguardSecure());
1911 Log.i(TAG, "voice-based interactions: about to use ACTION_VOICE_SEARCH_HANDS_FREE");
1912 }
1913 // start the search activity
1914 if (needWakeLock) {
1915 mMediaEventWakeLock.acquire();
1916 }
1917 try {
1918 if (voiceIntent != null) {
1919 voiceIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
1920 | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
Jaewan Kim8f729082016-06-21 12:36:26 +09001921 if (DEBUG) Log.d(TAG, "voiceIntent: " + voiceIntent);
RoboErik9a9d0b52014-05-20 14:53:39 -07001922 getContext().startActivityAsUser(voiceIntent, UserHandle.CURRENT);
1923 }
1924 } catch (ActivityNotFoundException e) {
1925 Log.w(TAG, "No activity for search: " + e);
1926 } finally {
1927 if (needWakeLock) {
1928 mMediaEventWakeLock.release();
1929 }
1930 }
1931 }
1932
1933 private boolean isVoiceKey(int keyCode) {
Jaewan Kimba18d8e2017-05-12 17:37:57 +09001934 return keyCode == KeyEvent.KEYCODE_HEADSETHOOK
Jaewan Kimfdb612e2017-07-01 09:23:41 +09001935 || (!mHasFeatureLeanback && keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
RoboErik9a9d0b52014-05-20 14:53:39 -07001936 }
1937
Jeff Brown38d3feb2015-03-19 18:26:30 -07001938 private boolean isUserSetupComplete() {
1939 return Settings.Secure.getIntForUser(getContext().getContentResolver(),
1940 Settings.Secure.USER_SETUP_COMPLETE, 0, UserHandle.USER_CURRENT) != 0;
1941 }
1942
RoboErik9c785402014-11-11 16:52:26 -08001943 // we only handle public stream types, which are 0-5
1944 private boolean isValidLocalStreamType(int streamType) {
1945 return streamType >= AudioManager.STREAM_VOICE_CALL
1946 && streamType <= AudioManager.STREAM_NOTIFICATION;
1947 }
1948
Jaewan Kim6e2b01c2017-01-19 16:33:14 -08001949 private class MediaKeyListenerResultReceiver extends ResultReceiver implements Runnable {
1950 private KeyEvent mKeyEvent;
1951 private boolean mNeedWakeLock;
1952 private boolean mHandled;
1953
1954 private MediaKeyListenerResultReceiver(KeyEvent keyEvent, boolean needWakeLock) {
1955 super(mHandler);
1956 mHandler.postDelayed(this, MEDIA_KEY_LISTENER_TIMEOUT);
1957 mKeyEvent = keyEvent;
1958 mNeedWakeLock = needWakeLock;
1959 }
1960
1961 @Override
1962 public void run() {
1963 Log.d(TAG, "The media key listener is timed-out for " + mKeyEvent);
1964 dispatchMediaKeyEvent();
1965 }
1966
1967 @Override
1968 protected void onReceiveResult(int resultCode, Bundle resultData) {
1969 if (resultCode == MediaSessionManager.RESULT_MEDIA_KEY_HANDLED) {
1970 mHandled = true;
1971 mHandler.removeCallbacks(this);
1972 return;
1973 }
1974 dispatchMediaKeyEvent();
1975 }
1976
1977 private void dispatchMediaKeyEvent() {
1978 if (mHandled) {
1979 return;
1980 }
1981 mHandled = true;
1982 mHandler.removeCallbacks(this);
1983 synchronized (mLock) {
Jaewan Kima7dce192017-02-16 17:10:54 +09001984 if (!isGlobalPriorityActiveLocked()
Jaewan Kim98003d32017-02-24 18:33:04 +09001985 && isVoiceKey(mKeyEvent.getKeyCode())) {
1986 handleVoiceKeyEventLocked(mKeyEvent, mNeedWakeLock);
1987 } else {
1988 dispatchMediaKeyEventLocked(mKeyEvent, mNeedWakeLock);
1989 }
Jaewan Kim6e2b01c2017-01-19 16:33:14 -08001990 }
1991 }
1992 }
1993
RoboErik418c10c2014-05-19 09:25:25 -07001994 private KeyEventWakeLockReceiver mKeyEventReceiver = new KeyEventWakeLockReceiver(mHandler);
1995
RoboErikb214efb2014-07-24 13:20:30 -07001996 class KeyEventWakeLockReceiver extends ResultReceiver implements Runnable,
1997 PendingIntent.OnFinished {
RoboErik418c10c2014-05-19 09:25:25 -07001998 private final Handler mHandler;
1999 private int mRefCount = 0;
2000 private int mLastTimeoutId = 0;
2001
2002 public KeyEventWakeLockReceiver(Handler handler) {
2003 super(handler);
2004 mHandler = handler;
2005 }
2006
2007 public void onTimeout() {
2008 synchronized (mLock) {
2009 if (mRefCount == 0) {
2010 // We've already released it, so just return
2011 return;
2012 }
2013 mLastTimeoutId++;
2014 mRefCount = 0;
2015 releaseWakeLockLocked();
2016 }
2017 }
2018
2019 public void aquireWakeLockLocked() {
2020 if (mRefCount == 0) {
2021 mMediaEventWakeLock.acquire();
2022 }
2023 mRefCount++;
2024 mHandler.removeCallbacks(this);
2025 mHandler.postDelayed(this, WAKELOCK_TIMEOUT);
2026
2027 }
2028
2029 @Override
2030 public void run() {
2031 onTimeout();
2032 }
2033
RoboErik8a2cfc32014-05-16 11:19:38 -07002034 @Override
2035 protected void onReceiveResult(int resultCode, Bundle resultData) {
RoboErik418c10c2014-05-19 09:25:25 -07002036 if (resultCode < mLastTimeoutId) {
2037 // Ignore results from calls that were before the last
2038 // timeout, just in case.
2039 return;
2040 } else {
2041 synchronized (mLock) {
2042 if (mRefCount > 0) {
2043 mRefCount--;
2044 if (mRefCount == 0) {
2045 releaseWakeLockLocked();
2046 }
2047 }
RoboErik8a2cfc32014-05-16 11:19:38 -07002048 }
2049 }
2050 }
RoboErik418c10c2014-05-19 09:25:25 -07002051
2052 private void releaseWakeLockLocked() {
2053 mMediaEventWakeLock.release();
2054 mHandler.removeCallbacks(this);
2055 }
RoboErikb214efb2014-07-24 13:20:30 -07002056
2057 @Override
2058 public void onSendFinished(PendingIntent pendingIntent, Intent intent, int resultCode,
2059 String resultData, Bundle resultExtras) {
2060 onReceiveResult(resultCode, null);
2061 }
RoboErik8a2cfc32014-05-16 11:19:38 -07002062 };
2063
2064 BroadcastReceiver mKeyEventDone = new BroadcastReceiver() {
2065 @Override
2066 public void onReceive(Context context, Intent intent) {
2067 if (intent == null) {
2068 return;
2069 }
2070 Bundle extras = intent.getExtras();
2071 if (extras == null) {
2072 return;
2073 }
2074 synchronized (mLock) {
2075 if (extras.containsKey(EXTRA_WAKELOCK_ACQUIRED)
2076 && mMediaEventWakeLock.isHeld()) {
2077 mMediaEventWakeLock.release();
2078 }
2079 }
2080 }
2081 };
RoboErik01fe6612014-02-13 14:19:04 -08002082 }
2083
RoboErik2e7a9162014-06-04 16:53:45 -07002084 final class MessageHandler extends Handler {
2085 private static final int MSG_SESSIONS_CHANGED = 1;
Jaewan Kimd61a87b2017-02-17 23:14:10 +09002086 private static final int MSG_VOLUME_INITIAL_DOWN = 2;
Sungsoo Lim375efa12018-03-02 10:17:08 +09002087 private static final int MSG_SESSIONS_TOKENS_CHANGED = 3;
Jaewan Kim92dea332017-02-02 11:52:08 +09002088 private final SparseArray<Integer> mIntegerCache = new SparseArray<>();
RoboErik2e7a9162014-06-04 16:53:45 -07002089
2090 @Override
2091 public void handleMessage(Message msg) {
2092 switch (msg.what) {
2093 case MSG_SESSIONS_CHANGED:
Jaewan Kim92dea332017-02-02 11:52:08 +09002094 pushSessionsChanged((int) msg.obj);
RoboErik2e7a9162014-06-04 16:53:45 -07002095 break;
Jaewan Kimd61a87b2017-02-17 23:14:10 +09002096 case MSG_VOLUME_INITIAL_DOWN:
2097 synchronized (mLock) {
Jaewan Kima7dce192017-02-16 17:10:54 +09002098 FullUserRecord user = mUserRecords.get((int) msg.arg1);
Jaewan Kimd61a87b2017-02-17 23:14:10 +09002099 if (user != null && user.mInitialDownVolumeKeyEvent != null) {
2100 dispatchVolumeKeyLongPressLocked(user.mInitialDownVolumeKeyEvent);
2101 // Mark that the key is already handled.
2102 user.mInitialDownVolumeKeyEvent = null;
2103 }
2104 }
2105 break;
Sungsoo Lim375efa12018-03-02 10:17:08 +09002106 case MSG_SESSIONS_TOKENS_CHANGED:
2107 pushSessionTokensChanged((int) msg.obj);
2108 break;
RoboErik2e7a9162014-06-04 16:53:45 -07002109 }
2110 }
2111
Jaewan Kim92dea332017-02-02 11:52:08 +09002112 public void postSessionsChanged(int userId) {
2113 // Use object instead of the arguments when posting message to remove pending requests.
2114 Integer userIdInteger = mIntegerCache.get(userId);
2115 if (userIdInteger == null) {
2116 userIdInteger = Integer.valueOf(userId);
2117 mIntegerCache.put(userId, userIdInteger);
2118 }
2119 removeMessages(MSG_SESSIONS_CHANGED, userIdInteger);
2120 obtainMessage(MSG_SESSIONS_CHANGED, userIdInteger).sendToTarget();
RoboErik2e7a9162014-06-04 16:53:45 -07002121 }
2122 }
Sungsoo Lim117c7f72018-02-13 16:02:24 +09002123
2124 private class ControllerCallback extends MediaController2.ControllerCallback {
2125
2126 private final SessionToken2 mToken;
2127
2128 ControllerCallback(SessionToken2 token) {
2129 mToken = token;
2130 }
2131
2132 @Override
Jaewan Kim3fb60d52018-03-02 19:23:18 +09002133 public void onDisconnected(MediaController2 controller) {
Sungsoo Lim117c7f72018-02-13 16:02:24 +09002134 destroySession2Internal(mToken);
2135 }
2136 };
Sungsoo Lim375efa12018-03-02 10:17:08 +09002137
2138 private final class SessionTokensListenerRecord implements IBinder.DeathRecipient {
2139 private final ISessionTokensListener mListener;
2140 private final int mUserId;
2141
2142 public SessionTokensListenerRecord(ISessionTokensListener listener, int userId) {
2143 mListener = listener;
Jaewan Kim525c88c2018-03-08 10:47:00 +09002144 // TODO(jaewan): should userId be mapped through mFullUserIds? (b/73597722)
Jaewan Kim005e2bb2018-03-02 15:55:12 +09002145 mUserId = userId;
Sungsoo Lim375efa12018-03-02 10:17:08 +09002146 }
2147
2148 @Override
2149 public void binderDied() {
2150 synchronized (mLock) {
2151 mSessionTokensListeners.remove(this);
2152 }
2153 }
2154 }
2155
2156 private void postSessionTokensUpdated(int userId) {
2157 mHandler.obtainMessage(MessageHandler.MSG_SESSIONS_TOKENS_CHANGED, userId).sendToTarget();
2158 }
2159
2160 private void pushSessionTokensChanged(int userId) {
2161 synchronized (mLock) {
2162 List<Bundle> tokens = new ArrayList<>();
2163 for (SessionToken2 token : mSessionRecords.keySet()) {
2164 // TODO(jaewan): Remove the check for UserHandle.USER_ALL (shouldn't happen).
2165 // This happens when called form buildMediaSessionService2List(...).
Jaewan Kim005e2bb2018-03-02 15:55:12 +09002166 // (b/73760382)
Sungsoo Lim375efa12018-03-02 10:17:08 +09002167 if (UserHandle.getUserId(token.getUid()) == userId
2168 || UserHandle.USER_ALL == userId) {
2169 tokens.add(token.toBundle());
2170 }
2171 }
2172
2173 for (SessionTokensListenerRecord record : mSessionTokensListeners) {
Jaewan Kim005e2bb2018-03-02 15:55:12 +09002174 // TODO(jaewan): Should userId be mapped through mFullUserIds? (b/73760382)
Sungsoo Lim375efa12018-03-02 10:17:08 +09002175 if (record.mUserId == userId || record.mUserId == UserHandle.USER_ALL) {
2176 try {
2177 record.mListener.onSessionTokensChanged(tokens);
2178 } catch (RemoteException e) {
2179 Log.w(TAG, "Failed to notify session tokens changed", e);
2180 }
2181 }
2182 }
2183 }
2184 }
2185
2186 private boolean addSessionRecordLocked(SessionToken2 token) {
2187 return addSessionRecordLocked(token, null);
2188 }
2189
2190 private boolean addSessionRecordLocked(SessionToken2 token, MediaController2 controller) {
2191 if (mSessionRecords.containsKey(token) && mSessionRecords.get(token) == controller) {
2192 // The key/value pair already exists, no need to update.
2193 return false;
2194 }
2195
2196 mSessionRecords.put(token, controller);
2197 return true;
2198 }
2199
2200 private boolean removeSessionRecordLocked(SessionToken2 token) {
2201 if (!mSessionRecords.containsKey(token)) {
2202 // The key is already removed, no need to remove.
2203 return false;
2204 }
2205
2206 mSessionRecords.remove(token);
2207 return true;
2208 }
RoboErik01fe6612014-02-13 14:19:04 -08002209}