blob: dc4405fd012cffa23c71e2f997fa8bbd5ba2eef6 [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 Kim129a5622018-03-28 12:19:04 +0900104 static final boolean USE_MEDIA2_APIS = false; // TODO: Change this to true when we're ready.
Jaewan Kim92dea332017-02-02 11:52:08 +0900105 static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
Jaewan Kim50269362016-12-23 11:22:02 +0900106 // Leave log for key event always.
107 private static final boolean DEBUG_KEY_EVENT = true;
RoboErik01fe6612014-02-13 14:19:04 -0800108
RoboErik418c10c2014-05-19 09:25:25 -0700109 private static final int WAKELOCK_TIMEOUT = 5000;
Jaewan Kim6e2b01c2017-01-19 16:33:14 -0800110 private static final int MEDIA_KEY_LISTENER_TIMEOUT = 1000;
RoboErik418c10c2014-05-19 09:25:25 -0700111
RoboErik01fe6612014-02-13 14:19:04 -0800112 private final SessionManagerImpl mSessionManagerImpl;
113
Jaewan Kima7dce192017-02-16 17:10:54 +0900114 // Keeps the full user id for each user.
115 private final SparseIntArray mFullUserIds = new SparseIntArray();
116 private final SparseArray<FullUserRecord> mUserRecords = new SparseArray<FullUserRecord>();
RoboErik2e7a9162014-06-04 16:53:45 -0700117 private final ArrayList<SessionsListenerRecord> mSessionsListeners
118 = new ArrayList<SessionsListenerRecord>();
RoboErik01fe6612014-02-13 14:19:04 -0800119 private final Object mLock = new Object();
RoboErik2e7a9162014-06-04 16:53:45 -0700120 private final MessageHandler mHandler = new MessageHandler();
RoboErik8a2cfc32014-05-16 11:19:38 -0700121 private final PowerManager.WakeLock mMediaEventWakeLock;
Jaewan Kimd61a87b2017-02-17 23:14:10 +0900122 private final int mLongPressTimeout;
Jaewan Kimfed49502018-03-05 17:51:12 +0900123 private final INotificationManager mNotificationManager;
124 private final IPackageManager mPackageManager;
RoboErik01fe6612014-02-13 14:19:04 -0800125
RoboErik9a9d0b52014-05-20 14:53:39 -0700126 private KeyguardManager mKeyguardManager;
RoboErikb69ffd42014-05-30 14:57:59 -0700127 private IAudioService mAudioService;
RoboErik6f0e4dd2014-06-17 16:56:27 -0700128 private ContentResolver mContentResolver;
RoboErik7aef77b2014-08-08 15:56:54 -0700129 private SettingsObserver mSettingsObserver;
Jaewan Kimfdb612e2017-07-01 09:23:41 +0900130 private boolean mHasFeatureLeanback;
RoboErik9a9d0b52014-05-20 14:53:39 -0700131
Jaewan Kima7dce192017-02-16 17:10:54 +0900132 // The FullUserRecord of the current users. (i.e. The foreground user that isn't a profile)
133 // It's always not null after the MediaSessionService is started.
134 private FullUserRecord mCurrentFullUserRecord;
135 private MediaSessionRecord mGlobalPrioritySession;
Sungsoo Lim875e6972017-11-03 02:22:35 +0000136 private AudioPlayerStateMonitor mAudioPlayerStateMonitor;
RoboErike7880d82014-04-30 12:48:25 -0700137
RoboErik19c95182014-06-23 15:38:48 -0700138 // Used to notify system UI when remote volume was changed. TODO find a
139 // better way to handle this.
140 private IRemoteVolumeController mRvc;
141
Jaewan Kimceb6b6e2018-01-21 20:56:10 +0900142 // MediaSession2 support
Jaewan Kim005e2bb2018-03-02 15:55:12 +0900143 // TODO(jaewan): Support multi-user and managed profile. (b/73597722)
144 // TODO(jaewan): Make it priority list for handling volume/media key. (b/73760382)
Sungsoo Lim117c7f72018-02-13 16:02:24 +0900145 private final Map<SessionToken2, MediaController2> mSessionRecords = new ArrayMap<>();
Jaewan Kimceb6b6e2018-01-21 20:56:10 +0900146
Sungsoo Lim375efa12018-03-02 10:17:08 +0900147 private final List<SessionTokensListenerRecord> mSessionTokensListeners = new ArrayList<>();
148
RoboErik01fe6612014-02-13 14:19:04 -0800149 public MediaSessionService(Context context) {
150 super(context);
151 mSessionManagerImpl = new SessionManagerImpl();
RoboErik8a2cfc32014-05-16 11:19:38 -0700152 PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
153 mMediaEventWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "handleMediaEvent");
Jaewan Kimd61a87b2017-02-17 23:14:10 +0900154 mLongPressTimeout = ViewConfiguration.getLongPressTimeout();
Julia Reynoldsb852e562017-06-06 16:14:18 -0400155 mNotificationManager = INotificationManager.Stub.asInterface(
156 ServiceManager.getService(Context.NOTIFICATION_SERVICE));
Jaewan Kimfed49502018-03-05 17:51:12 +0900157 mPackageManager = AppGlobals.getPackageManager();
RoboErik01fe6612014-02-13 14:19:04 -0800158 }
159
160 @Override
161 public void onStart() {
162 publishBinderService(Context.MEDIA_SESSION_SERVICE, mSessionManagerImpl);
RoboErika278ea72014-04-24 14:49:01 -0700163 Watchdog.getInstance().addMonitor(this);
RoboErik9a9d0b52014-05-20 14:53:39 -0700164 mKeyguardManager =
165 (KeyguardManager) getContext().getSystemService(Context.KEYGUARD_SERVICE);
RoboErikb69ffd42014-05-30 14:57:59 -0700166 mAudioService = getAudioService();
Sungsoo Lim875e6972017-11-03 02:22:35 +0000167 mAudioPlayerStateMonitor = AudioPlayerStateMonitor.getInstance();
168 mAudioPlayerStateMonitor.registerListener(
Sungsoo Lim2afdbc42017-11-01 13:45:59 +0900169 (config, isRemoved) -> {
170 if (isRemoved || !config.isActive() || config.getPlayerType()
171 == AudioPlaybackConfiguration.PLAYER_TYPE_JAM_SOUNDPOOL) {
172 return;
Jaewan Kim92dea332017-02-02 11:52:08 +0900173 }
Sungsoo Lim2afdbc42017-11-01 13:45:59 +0900174 synchronized (mLock) {
175 FullUserRecord user = getFullUserRecordLocked(
176 UserHandle.getUserId(config.getClientUid()));
177 if (user != null) {
178 user.mPriorityStack.updateMediaButtonSessionIfNeeded();
179 }
180 }
181 }, null /* handler */);
Sungsoo Lim875e6972017-11-03 02:22:35 +0000182 mAudioPlayerStateMonitor.registerSelfIntoAudioServiceIfNeeded(mAudioService);
RoboErik6f0e4dd2014-06-17 16:56:27 -0700183 mContentResolver = getContext().getContentResolver();
RoboErik7aef77b2014-08-08 15:56:54 -0700184 mSettingsObserver = new SettingsObserver();
185 mSettingsObserver.observe();
Jaewan Kimfdb612e2017-07-01 09:23:41 +0900186 mHasFeatureLeanback = getContext().getPackageManager().hasSystemFeature(
187 PackageManager.FEATURE_LEANBACK);
RoboErikc8f92d12015-01-05 16:48:07 -0800188
189 updateUser();
Jaewan Kimceb6b6e2018-01-21 20:56:10 +0900190
Jaewan Kim66d451b2018-02-12 21:23:06 +0900191 registerPackageBroadcastReceivers();
Jaewan Kim005e2bb2018-03-02 15:55:12 +0900192 // TODO(jaewan): Query per users (b/73597722)
Jaewan Kimceb6b6e2018-01-21 20:56:10 +0900193 buildMediaSessionService2List();
RoboErikb69ffd42014-05-30 14:57:59 -0700194 }
195
196 private IAudioService getAudioService() {
197 IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE);
198 return IAudioService.Stub.asInterface(b);
RoboErik07c70772014-03-20 13:33:52 -0700199 }
200
Jaewan Kima7dce192017-02-16 17:10:54 +0900201 private boolean isGlobalPriorityActiveLocked() {
202 return mGlobalPrioritySession != null && mGlobalPrioritySession.isActive();
203 }
204
RoboErika8f95142014-05-05 14:23:49 -0700205 public void updateSession(MediaSessionRecord record) {
RoboErike7880d82014-04-30 12:48:25 -0700206 synchronized (mLock) {
Jaewan Kima7dce192017-02-16 17:10:54 +0900207 FullUserRecord user = getFullUserRecordLocked(record.getUserId());
Jaewan Kim101b4d52017-05-18 13:23:11 +0900208 if (user == null) {
209 Log.w(TAG, "Unknown session updated. Ignoring.");
RoboErik4646d282014-05-13 10:13:04 -0700210 return;
211 }
Jaewan Kima7dce192017-02-16 17:10:54 +0900212 if ((record.getFlags() & MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY) != 0) {
Jaewan Kim101b4d52017-05-18 13:23:11 +0900213 if (DEBUG_KEY_EVENT) {
214 Log.d(TAG, "Global priority session is updated, active=" + record.isActive());
215 }
Jaewan Kim92dea332017-02-02 11:52:08 +0900216 user.pushAddressedPlayerChangedLocked();
Jaewan Kim101b4d52017-05-18 13:23:11 +0900217 } else {
218 if (!user.mPriorityStack.contains(record)) {
219 Log.w(TAG, "Unknown session updated. Ignoring.");
220 return;
221 }
222 user.mPriorityStack.onSessionStateChange(record);
Jaewan Kima7dce192017-02-16 17:10:54 +0900223 }
Jaewan Kim92dea332017-02-02 11:52:08 +0900224 mHandler.postSessionsChanged(record.getUserId());
RoboErike7880d82014-04-30 12:48:25 -0700225 }
226 }
227
Jaewan Kimfa85b602017-10-10 16:49:58 +0900228 public void setGlobalPrioritySession(MediaSessionRecord record) {
229 synchronized (mLock) {
230 FullUserRecord user = getFullUserRecordLocked(record.getUserId());
231 if (mGlobalPrioritySession != record) {
232 Log.d(TAG, "Global priority session is changed from " + mGlobalPrioritySession
233 + " to " + record);
234 mGlobalPrioritySession = record;
235 if (user != null && user.mPriorityStack.contains(record)) {
236 // Handle the global priority session separately.
237 // Otherwise, it can be the media button session regardless of the active state
238 // because it or other system components might have been the lastly played media
239 // app.
240 user.mPriorityStack.removeSession(record);
241 }
242 }
243 }
244 }
245
Jaewan Kim101b4d52017-05-18 13:23:11 +0900246 private List<MediaSessionRecord> getActiveSessionsLocked(int userId) {
Jaewan Kimda74a152017-10-03 23:58:11 +0900247 List<MediaSessionRecord> records = new ArrayList<>();
Jaewan Kim101b4d52017-05-18 13:23:11 +0900248 if (userId == UserHandle.USER_ALL) {
Jaewan Kim101b4d52017-05-18 13:23:11 +0900249 int size = mUserRecords.size();
250 for (int i = 0; i < size; i++) {
251 records.addAll(mUserRecords.valueAt(i).mPriorityStack.getActiveSessions(userId));
252 }
253 } else {
254 FullUserRecord user = getFullUserRecordLocked(userId);
255 if (user == null) {
256 Log.w(TAG, "getSessions failed. Unknown user " + userId);
Jaewan Kimda74a152017-10-03 23:58:11 +0900257 return records;
Jaewan Kim101b4d52017-05-18 13:23:11 +0900258 }
Jaewan Kimda74a152017-10-03 23:58:11 +0900259 records.addAll(user.mPriorityStack.getActiveSessions(userId));
Jaewan Kim101b4d52017-05-18 13:23:11 +0900260 }
261
262 // Return global priority session at the first whenever it's asked.
263 if (isGlobalPriorityActiveLocked()
264 && (userId == UserHandle.USER_ALL
265 || userId == mGlobalPrioritySession.getUserId())) {
266 records.add(0, mGlobalPrioritySession);
267 }
268 return records;
269 }
270
RoboErik9c5b7cb2015-01-15 15:09:09 -0800271 /**
Hyundo Moona055f132017-01-13 15:31:06 +0900272 * Tells the system UI that volume has changed on an active remote session.
RoboErik9c5b7cb2015-01-15 15:09:09 -0800273 */
274 public void notifyRemoteVolumeChanged(int flags, MediaSessionRecord session) {
Hyundo Moona055f132017-01-13 15:31:06 +0900275 if (mRvc == null || !session.isActive()) {
RoboErik9c5b7cb2015-01-15 15:09:09 -0800276 return;
277 }
278 try {
279 mRvc.remoteVolumeChanged(session.getControllerBinder(), flags);
280 } catch (Exception e) {
281 Log.wtf(TAG, "Error sending volume change to system UI.", e);
282 }
283 }
284
Jaewan Kim92dea332017-02-02 11:52:08 +0900285 public void onSessionPlaystateChanged(MediaSessionRecord record, int oldState, int newState) {
RoboErika8f95142014-05-05 14:23:49 -0700286 synchronized (mLock) {
Jaewan Kima7dce192017-02-16 17:10:54 +0900287 FullUserRecord user = getFullUserRecordLocked(record.getUserId());
288 if (user == null || !user.mPriorityStack.contains(record)) {
RoboErik4646d282014-05-13 10:13:04 -0700289 Log.d(TAG, "Unknown session changed playback state. Ignoring.");
290 return;
291 }
Jaewan Kim92dea332017-02-02 11:52:08 +0900292 user.mPriorityStack.onPlaystateChanged(record, oldState, newState);
RoboErika8f95142014-05-05 14:23:49 -0700293 }
294 }
295
RoboErik19c95182014-06-23 15:38:48 -0700296 public void onSessionPlaybackTypeChanged(MediaSessionRecord record) {
297 synchronized (mLock) {
Jaewan Kima7dce192017-02-16 17:10:54 +0900298 FullUserRecord user = getFullUserRecordLocked(record.getUserId());
299 if (user == null || !user.mPriorityStack.contains(record)) {
RoboErik19c95182014-06-23 15:38:48 -0700300 Log.d(TAG, "Unknown session changed playback type. Ignoring.");
301 return;
302 }
303 pushRemoteVolumeUpdateLocked(record.getUserId());
304 }
305 }
306
RoboErika278ea72014-04-24 14:49:01 -0700307 @Override
Jaewan Kim8f729082016-06-21 12:36:26 +0900308 public void onStartUser(int userId) {
309 if (DEBUG) Log.d(TAG, "onStartUser: " + userId);
RoboErik4646d282014-05-13 10:13:04 -0700310 updateUser();
311 }
312
313 @Override
Jaewan Kim8f729082016-06-21 12:36:26 +0900314 public void onSwitchUser(int userId) {
315 if (DEBUG) Log.d(TAG, "onSwitchUser: " + userId);
RoboErik4646d282014-05-13 10:13:04 -0700316 updateUser();
317 }
318
319 @Override
Jaewan Kim8f729082016-06-21 12:36:26 +0900320 public void onStopUser(int userId) {
321 if (DEBUG) Log.d(TAG, "onStopUser: " + userId);
RoboErik4646d282014-05-13 10:13:04 -0700322 synchronized (mLock) {
Jaewan Kima7dce192017-02-16 17:10:54 +0900323 FullUserRecord user = getFullUserRecordLocked(userId);
RoboErik4646d282014-05-13 10:13:04 -0700324 if (user != null) {
Jaewan Kima7dce192017-02-16 17:10:54 +0900325 if (user.mFullUserId == userId) {
326 user.destroySessionsForUserLocked(UserHandle.USER_ALL);
327 mUserRecords.remove(userId);
328 } else {
329 user.destroySessionsForUserLocked(userId);
330 }
RoboErik4646d282014-05-13 10:13:04 -0700331 }
Jaewan Kim8f729082016-06-21 12:36:26 +0900332 updateUser();
RoboErik4646d282014-05-13 10:13:04 -0700333 }
334 }
335
336 @Override
RoboErika278ea72014-04-24 14:49:01 -0700337 public void monitor() {
338 synchronized (mLock) {
339 // Check for deadlock
340 }
341 }
342
RoboErik4646d282014-05-13 10:13:04 -0700343 protected void enforcePhoneStatePermission(int pid, int uid) {
344 if (getContext().checkPermission(android.Manifest.permission.MODIFY_PHONE_STATE, pid, uid)
345 != PackageManager.PERMISSION_GRANTED) {
346 throw new SecurityException("Must hold the MODIFY_PHONE_STATE permission.");
347 }
348 }
349
RoboErik01fe6612014-02-13 14:19:04 -0800350 void sessionDied(MediaSessionRecord session) {
RoboErika278ea72014-04-24 14:49:01 -0700351 synchronized (mLock) {
RoboErik01fe6612014-02-13 14:19:04 -0800352 destroySessionLocked(session);
353 }
354 }
355
356 void destroySession(MediaSessionRecord session) {
RoboErika278ea72014-04-24 14:49:01 -0700357 synchronized (mLock) {
RoboErik01fe6612014-02-13 14:19:04 -0800358 destroySessionLocked(session);
359 }
360 }
361
RoboErik4646d282014-05-13 10:13:04 -0700362 private void updateUser() {
363 synchronized (mLock) {
Jaewan Kim8f729082016-06-21 12:36:26 +0900364 UserManager manager = (UserManager) getContext().getSystemService(Context.USER_SERVICE);
Jaewan Kima7dce192017-02-16 17:10:54 +0900365 mFullUserIds.clear();
366 List<UserInfo> allUsers = manager.getUsers();
367 if (allUsers != null) {
368 for (UserInfo userInfo : allUsers) {
369 if (userInfo.isManagedProfile()) {
370 mFullUserIds.put(userInfo.id, userInfo.profileGroupId);
371 } else {
372 mFullUserIds.put(userInfo.id, userInfo.id);
373 if (mUserRecords.get(userInfo.id) == null) {
374 mUserRecords.put(userInfo.id, new FullUserRecord(userInfo.id));
375 }
376 }
Jaewan Kim8f729082016-06-21 12:36:26 +0900377 }
RoboErik4646d282014-05-13 10:13:04 -0700378 }
Jaewan Kima7dce192017-02-16 17:10:54 +0900379 // Ensure that the current full user exists.
380 int currentFullUserId = ActivityManager.getCurrentUser();
381 mCurrentFullUserRecord = mUserRecords.get(currentFullUserId);
382 if (mCurrentFullUserRecord == null) {
383 Log.w(TAG, "Cannot find FullUserInfo for the current user " + currentFullUserId);
384 mCurrentFullUserRecord = new FullUserRecord(currentFullUserId);
385 mUserRecords.put(currentFullUserId, mCurrentFullUserRecord);
386 }
387 mFullUserIds.put(currentFullUserId, currentFullUserId);
RoboErik4646d282014-05-13 10:13:04 -0700388 }
389 }
390
RoboErik7aef77b2014-08-08 15:56:54 -0700391 private void updateActiveSessionListeners() {
392 synchronized (mLock) {
393 for (int i = mSessionsListeners.size() - 1; i >= 0; i--) {
394 SessionsListenerRecord listener = mSessionsListeners.get(i);
395 try {
396 enforceMediaPermissions(listener.mComponentName, listener.mPid, listener.mUid,
397 listener.mUserId);
398 } catch (SecurityException e) {
399 Log.i(TAG, "ActiveSessionsListener " + listener.mComponentName
400 + " is no longer authorized. Disconnecting.");
401 mSessionsListeners.remove(i);
402 try {
403 listener.mListener
404 .onActiveSessionsChanged(new ArrayList<MediaSession.Token>());
405 } catch (Exception e1) {
406 // ignore
407 }
408 }
409 }
410 }
411 }
412
RoboErik4646d282014-05-13 10:13:04 -0700413 /*
414 * When a session is removed several things need to happen.
415 * 1. We need to remove it from the relevant user.
416 * 2. We need to remove it from the priority stack.
417 * 3. We need to remove it from all sessions.
418 * 4. If this is the system priority session we need to clear it.
419 * 5. We need to unlink to death from the cb binder
420 * 6. We need to tell the session to do any final cleanup (onDestroy)
421 */
RoboErik01fe6612014-02-13 14:19:04 -0800422 private void destroySessionLocked(MediaSessionRecord session) {
Insun Kang30be970a2015-11-26 15:35:44 +0900423 if (DEBUG) {
Jaewan Kim5e1476e2016-07-19 22:25:39 +0900424 Log.d(TAG, "Destroying " + session);
Insun Kang30be970a2015-11-26 15:35:44 +0900425 }
Jaewan Kim101b4d52017-05-18 13:23:11 +0900426 FullUserRecord user = getFullUserRecordLocked(session.getUserId());
Jaewan Kima7dce192017-02-16 17:10:54 +0900427 if (mGlobalPrioritySession == session) {
428 mGlobalPrioritySession = null;
Jaewan Kim92dea332017-02-02 11:52:08 +0900429 if (session.isActive() && user != null) {
430 user.pushAddressedPlayerChangedLocked();
431 }
Jaewan Kim101b4d52017-05-18 13:23:11 +0900432 } else {
433 if (user != null) {
434 user.mPriorityStack.removeSession(session);
435 }
Jaewan Kima7dce192017-02-16 17:10:54 +0900436 }
RoboErik4646d282014-05-13 10:13:04 -0700437
438 try {
439 session.getCallback().asBinder().unlinkToDeath(session, 0);
440 } catch (Exception e) {
441 // ignore exceptions while destroying a session.
442 }
443 session.onDestroy();
Jaewan Kim92dea332017-02-02 11:52:08 +0900444 mHandler.postSessionsChanged(session.getUserId());
RoboErik01fe6612014-02-13 14:19:04 -0800445 }
446
Jaewan Kim66d451b2018-02-12 21:23:06 +0900447 private void registerPackageBroadcastReceivers() {
448 // TODO(jaewan): Only consider changed packages when building session service list
449 // when we make this multi-user aware. At that time,
450 // use PackageMonitor.getChangingUserId() to know which user has changed.
Jaewan Kim005e2bb2018-03-02 15:55:12 +0900451 // (b/73597722)
Jaewan Kim66d451b2018-02-12 21:23:06 +0900452 IntentFilter filter = new IntentFilter();
453 filter.addDataScheme("package");
454 filter.addAction(Intent.ACTION_PACKAGE_ADDED);
455 filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
456 filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
457 filter.addAction(Intent.ACTION_PACKAGES_SUSPENDED);
458 filter.addAction(Intent.ACTION_PACKAGES_UNSUSPENDED);
459 filter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
460 filter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
461 filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
462
463 getContext().registerReceiverAsUser(new BroadcastReceiver() {
464 @Override
465 public void onReceive(Context context, Intent intent) {
466 final int changeUserId = intent.getIntExtra(
467 Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
468 if (changeUserId == UserHandle.USER_NULL) {
469 Log.w(TAG, "Intent broadcast does not contain user handle: "+ intent);
470 return;
471 }
472 // Check if the package is replacing (i.e. reinstalling)
473 final boolean isReplacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
Jaewan Kim005e2bb2018-03-02 15:55:12 +0900474 // TODO(jaewan): Add multi-user support with this. (b/73597722)
Jaewan Kim66d451b2018-02-12 21:23:06 +0900475 // final int uid = intent.getIntExtra(Intent.EXTRA_UID, 0);
476
477 if (DEBUG) {
478 Log.d(TAG, "Received change in packages, intent=" + intent);
479 }
480 switch (intent.getAction()) {
481 case Intent.ACTION_PACKAGE_ADDED:
482 case Intent.ACTION_PACKAGE_REMOVED:
483 case Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE:
484 case Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE:
485 if (isReplacing) {
486 // Ignore if the package(s) are replacing. In that case, followings will
487 // happen in order.
488 // 1. ACTION_PACKAGE_REMOVED with isReplacing=true
489 // 2. ACTION_PACKAGE_ADDED with isReplacing=true
490 // 3. ACTION_PACKAGE_REPLACED
491 // (Note that ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE and
492 // ACTION_EXTERNAL_APPLICATIONS_AVAILABLE will be also called with
493 // isReplacing=true for both ASEC hosted packages and packages in
494 // external storage)
495 // Since we only want to update session service list once, ignore
496 // actions above when replacing.
497 // Replacing will be handled only once with the ACTION_PACKAGE_REPLACED.
498 break;
499 }
500 // pass-through
501 case Intent.ACTION_PACKAGE_CHANGED:
502 case Intent.ACTION_PACKAGES_SUSPENDED:
503 case Intent.ACTION_PACKAGES_UNSUSPENDED:
504 case Intent.ACTION_PACKAGE_REPLACED:
505 buildMediaSessionService2List();
506 }
507 }
508 }, UserHandle.ALL, filter, null, BackgroundThread.getHandler());
509 }
510
Jaewan Kimceb6b6e2018-01-21 20:56:10 +0900511 private void buildMediaSessionService2List() {
Jaewan Kim129a5622018-03-28 12:19:04 +0900512 if (!USE_MEDIA2_APIS) {
513 return;
514 }
Jaewan Kimceb6b6e2018-01-21 20:56:10 +0900515 if (DEBUG) {
516 Log.d(TAG, "buildMediaSessionService2List");
517 }
Jaewan Kim005e2bb2018-03-02 15:55:12 +0900518 // TODO(jaewan): Also query for managed profile users. (b/73597722)
Jaewan Kimf7a77062018-01-27 01:34:24 +0900519 PackageManager manager = getContext().getPackageManager();
Jaewan Kimbcecf312018-01-23 19:30:42 +0900520 List<ResolveInfo> services = new ArrayList<>();
521 // If multiple actions are declared for a service, browser gets higher priority.
Jaewan Kimf7a77062018-01-27 01:34:24 +0900522 List<ResolveInfo> libraryServices = manager.queryIntentServices(
Jaewan Kimbcecf312018-01-23 19:30:42 +0900523 new Intent(MediaLibraryService2.SERVICE_INTERFACE), PackageManager.GET_META_DATA);
524 if (libraryServices != null) {
525 services.addAll(libraryServices);
526 }
Jaewan Kimf7a77062018-01-27 01:34:24 +0900527 List<ResolveInfo> sessionServices = manager.queryIntentServices(
Jaewan Kimbcecf312018-01-23 19:30:42 +0900528 new Intent(MediaSessionService2.SERVICE_INTERFACE), PackageManager.GET_META_DATA);
529 if (sessionServices != null) {
530 services.addAll(sessionServices);
531 }
Jaewan Kimceb6b6e2018-01-21 20:56:10 +0900532 synchronized (mLock) {
Jaewan Kim66d451b2018-02-12 21:23:06 +0900533 // List to keep the session services that need be removed because they don't exist
534 // in the 'services' above.
Sungsoo Lim375efa12018-03-02 10:17:08 +0900535 boolean notifySessionTokensUpdated = false;
Sungsoo Limc0d54a22018-02-27 09:05:21 +0900536 Set<SessionToken2> sessionTokensToRemove = new HashSet<>();
537 for (SessionToken2 token : mSessionRecords.keySet()) {
538 if (token.getType() != TYPE_SESSION) {
539 sessionTokensToRemove.add(token);
Jaewan Kim66d451b2018-02-12 21:23:06 +0900540 }
Jaewan Kimceb6b6e2018-01-21 20:56:10 +0900541 }
Sungsoo Lim117c7f72018-02-13 16:02:24 +0900542
Jaewan Kimceb6b6e2018-01-21 20:56:10 +0900543 for (int i = 0; i < services.size(); i++) {
544 if (services.get(i) == null || services.get(i).serviceInfo == null) {
545 continue;
546 }
547 ServiceInfo serviceInfo = services.get(i).serviceInfo;
Jaewan Kimf7a77062018-01-27 01:34:24 +0900548 int uid;
549 try {
Jaewan Kim005e2bb2018-03-02 15:55:12 +0900550 // TODO(jaewan): Do this per user. (b/73597722)
Jaewan Kimf7a77062018-01-27 01:34:24 +0900551 uid = manager.getPackageUid(serviceInfo.packageName,
552 PackageManager.GET_META_DATA);
553 } catch (NameNotFoundException e) {
554 continue;
555 }
Jaewan Kim66d451b2018-02-12 21:23:06 +0900556 SessionToken2 token;
Jaewan Kim44fec2d2018-01-29 21:49:41 +0900557 try {
Jaewan Kim66d451b2018-02-12 21:23:06 +0900558 token = new SessionToken2(getContext(),
Jaewan Kim44fec2d2018-01-29 21:49:41 +0900559 serviceInfo.packageName, serviceInfo.name, uid);
Jaewan Kim66d451b2018-02-12 21:23:06 +0900560 } catch (IllegalArgumentException e) {
561 Log.w(TAG, "Invalid session service", e);
562 continue;
563 }
Sungsoo Lim117c7f72018-02-13 16:02:24 +0900564 // If the token already exists, keep it in the mSessions by removing from
565 // sessionTokensToRemove.
566 if (!sessionTokensToRemove.remove(token)) {
Jaewan Kim66d451b2018-02-12 21:23:06 +0900567 // New session service is found.
Sungsoo Lim375efa12018-03-02 10:17:08 +0900568 notifySessionTokensUpdated |= addSessionRecordLocked(token);
Jaewan Kimceb6b6e2018-01-21 20:56:10 +0900569 }
570 }
Sungsoo Lim117c7f72018-02-13 16:02:24 +0900571 for (SessionToken2 token : sessionTokensToRemove) {
572 mSessionRecords.remove(token);
Sungsoo Lim375efa12018-03-02 10:17:08 +0900573 notifySessionTokensUpdated |= removeSessionRecordLocked(token);
574 }
575
576 if (notifySessionTokensUpdated) {
577 // TODO(jaewan): Pass proper user id to postSessionTokensUpdated(...)
578 postSessionTokensUpdated(UserHandle.USER_ALL);
Jaewan Kim66d451b2018-02-12 21:23:06 +0900579 }
Jaewan Kimceb6b6e2018-01-21 20:56:10 +0900580 }
581 if (DEBUG) {
Sungsoo Lim117c7f72018-02-13 16:02:24 +0900582 Log.d(TAG, "Found " + mSessionRecords.size() + " session services");
583 for (SessionToken2 token : mSessionRecords.keySet()) {
584 Log.d(TAG, " " + token);
Jaewan Kimceb6b6e2018-01-21 20:56:10 +0900585 }
586 }
587 }
588
RoboErik01fe6612014-02-13 14:19:04 -0800589 private void enforcePackageName(String packageName, int uid) {
590 if (TextUtils.isEmpty(packageName)) {
591 throw new IllegalArgumentException("packageName may not be empty");
592 }
593 String[] packages = getContext().getPackageManager().getPackagesForUid(uid);
594 final int packageCount = packages.length;
595 for (int i = 0; i < packageCount; i++) {
596 if (packageName.equals(packages[i])) {
597 return;
598 }
599 }
600 throw new IllegalArgumentException("packageName is not owned by the calling process");
601 }
602
RoboErike7880d82014-04-30 12:48:25 -0700603 /**
604 * Checks a caller's authorization to register an IRemoteControlDisplay.
605 * Authorization is granted if one of the following is true:
606 * <ul>
607 * <li>the caller has android.Manifest.permission.MEDIA_CONTENT_CONTROL
608 * permission</li>
RoboErika5b02322014-05-07 17:05:49 -0700609 * <li>the caller's listener is one of the enabled notification listeners
610 * for the caller's user</li>
RoboErike7880d82014-04-30 12:48:25 -0700611 * </ul>
612 */
RoboErika5b02322014-05-07 17:05:49 -0700613 private void enforceMediaPermissions(ComponentName compName, int pid, int uid,
614 int resolvedUserId) {
Hyundo Moonf84c1c02018-03-19 20:33:27 +0900615 if (isCurrentVolumeController(pid, uid)) return;
RoboErike7880d82014-04-30 12:48:25 -0700616 if (getContext()
617 .checkPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL, pid, uid)
618 != PackageManager.PERMISSION_GRANTED
RoboErika5b02322014-05-07 17:05:49 -0700619 && !isEnabledNotificationListener(compName, UserHandle.getUserId(uid),
620 resolvedUserId)) {
RoboErike7880d82014-04-30 12:48:25 -0700621 throw new SecurityException("Missing permission to control media.");
622 }
623 }
624
Hyundo Moonf84c1c02018-03-19 20:33:27 +0900625 private boolean isCurrentVolumeController(int pid, int uid) {
Julia Reynoldsbb983d202017-01-06 09:54:20 -0500626 return getContext().checkPermission(android.Manifest.permission.STATUS_BAR_SERVICE,
627 pid, uid) == PackageManager.PERMISSION_GRANTED;
John Spurlockbe19ed02015-02-22 10:57:55 -0500628 }
629
630 private void enforceSystemUiPermission(String action, int pid, int uid) {
Hyundo Moonf84c1c02018-03-19 20:33:27 +0900631 if (!isCurrentVolumeController(pid, uid)) {
RoboErik19c95182014-06-23 15:38:48 -0700632 throw new SecurityException("Only system ui may " + action);
633 }
634 }
635
RoboErika5b02322014-05-07 17:05:49 -0700636 /**
637 * This checks if the component is an enabled notification listener for the
638 * specified user. Enabled components may only operate on behalf of the user
639 * they're running as.
640 *
641 * @param compName The component that is enabled.
642 * @param userId The user id of the caller.
643 * @param forUserId The user id they're making the request on behalf of.
644 * @return True if the component is enabled, false otherwise
645 */
646 private boolean isEnabledNotificationListener(ComponentName compName, int userId,
647 int forUserId) {
648 if (userId != forUserId) {
649 // You may not access another user's content as an enabled listener.
650 return false;
651 }
RoboErik51fa6bc2014-06-20 14:59:58 -0700652 if (DEBUG) {
653 Log.d(TAG, "Checking if enabled notification listener " + compName);
654 }
RoboErike7880d82014-04-30 12:48:25 -0700655 if (compName != null) {
Julia Reynoldsb852e562017-06-06 16:14:18 -0400656 try {
657 return mNotificationManager.isNotificationListenerAccessGrantedForUser(
658 compName, userId);
659 } catch(RemoteException e) {
660 Log.w(TAG, "Dead NotificationManager in isEnabledNotificationListener", e);
RoboErike7880d82014-04-30 12:48:25 -0700661 }
662 }
663 return false;
664 }
665
RoboErika5b02322014-05-07 17:05:49 -0700666 private MediaSessionRecord createSessionInternal(int callerPid, int callerUid, int userId,
RoboErik4646d282014-05-13 10:13:04 -0700667 String callerPackageName, ISessionCallback cb, String tag) throws RemoteException {
RoboErik01fe6612014-02-13 14:19:04 -0800668 synchronized (mLock) {
RoboErika5b02322014-05-07 17:05:49 -0700669 return createSessionLocked(callerPid, callerUid, userId, callerPackageName, cb, tag);
RoboErik01fe6612014-02-13 14:19:04 -0800670 }
671 }
672
RoboErik4646d282014-05-13 10:13:04 -0700673 /*
674 * When a session is created the following things need to happen.
RoboErik8a2cfc32014-05-16 11:19:38 -0700675 * 1. Its callback binder needs a link to death
RoboErik4646d282014-05-13 10:13:04 -0700676 * 2. It needs to be added to all sessions.
677 * 3. It needs to be added to the priority stack.
678 * 4. It needs to be added to the relevant user record.
679 */
RoboErika5b02322014-05-07 17:05:49 -0700680 private MediaSessionRecord createSessionLocked(int callerPid, int callerUid, int userId,
681 String callerPackageName, ISessionCallback cb, String tag) {
Jaewan Kima7dce192017-02-16 17:10:54 +0900682 FullUserRecord user = getFullUserRecordLocked(userId);
Dongwon Kang8cf39c52016-07-29 13:20:39 -0700683 if (user == null) {
684 Log.wtf(TAG, "Request from invalid user: " + userId);
685 throw new RuntimeException("Session request from invalid user.");
686 }
687
RoboErika5b02322014-05-07 17:05:49 -0700688 final MediaSessionRecord session = new MediaSessionRecord(callerPid, callerUid, userId,
Jaewan Kim92dea332017-02-02 11:52:08 +0900689 callerPackageName, cb, tag, this, mHandler.getLooper());
RoboErik01fe6612014-02-13 14:19:04 -0800690 try {
691 cb.asBinder().linkToDeath(session, 0);
692 } catch (RemoteException e) {
693 throw new RuntimeException("Media Session owner died prematurely.", e);
694 }
RoboErik4646d282014-05-13 10:13:04 -0700695
Jaewan Kim101b4d52017-05-18 13:23:11 +0900696 user.mPriorityStack.addSession(session);
Jaewan Kim92dea332017-02-02 11:52:08 +0900697 mHandler.postSessionsChanged(userId);
RoboErik2e7a9162014-06-04 16:53:45 -0700698
RoboErik01fe6612014-02-13 14:19:04 -0800699 if (DEBUG) {
Jaewan Kim5e1476e2016-07-19 22:25:39 +0900700 Log.d(TAG, "Created session for " + callerPackageName + " with tag " + tag);
RoboErik01fe6612014-02-13 14:19:04 -0800701 }
702 return session;
703 }
704
RoboErik2e7a9162014-06-04 16:53:45 -0700705 private int findIndexOfSessionsListenerLocked(IActiveSessionsListener listener) {
706 for (int i = mSessionsListeners.size() - 1; i >= 0; i--) {
RoboErika08adb242014-11-21 18:28:18 -0800707 if (mSessionsListeners.get(i).mListener.asBinder() == listener.asBinder()) {
RoboErik2e7a9162014-06-04 16:53:45 -0700708 return i;
709 }
710 }
711 return -1;
712 }
713
RoboErik2e7a9162014-06-04 16:53:45 -0700714 private void pushSessionsChanged(int userId) {
715 synchronized (mLock) {
Jaewan Kima7dce192017-02-16 17:10:54 +0900716 FullUserRecord user = getFullUserRecordLocked(userId);
717 if (user == null) {
718 Log.w(TAG, "pushSessionsChanged failed. No user with id=" + userId);
719 return;
720 }
Jaewan Kim101b4d52017-05-18 13:23:11 +0900721 List<MediaSessionRecord> records = getActiveSessionsLocked(userId);
RoboErik2e7a9162014-06-04 16:53:45 -0700722 int size = records.size();
Jeff Browndba34ba2014-06-24 20:46:03 -0700723 ArrayList<MediaSession.Token> tokens = new ArrayList<MediaSession.Token>();
RoboErik2e7a9162014-06-04 16:53:45 -0700724 for (int i = 0; i < size; i++) {
Jeff Browndba34ba2014-06-24 20:46:03 -0700725 tokens.add(new MediaSession.Token(records.get(i).getControllerBinder()));
RoboErik2e7a9162014-06-04 16:53:45 -0700726 }
RoboErik19c95182014-06-23 15:38:48 -0700727 pushRemoteVolumeUpdateLocked(userId);
RoboErik2e7a9162014-06-04 16:53:45 -0700728 for (int i = mSessionsListeners.size() - 1; i >= 0; i--) {
729 SessionsListenerRecord record = mSessionsListeners.get(i);
730 if (record.mUserId == UserHandle.USER_ALL || record.mUserId == userId) {
731 try {
732 record.mListener.onActiveSessionsChanged(tokens);
733 } catch (RemoteException e) {
734 Log.w(TAG, "Dead ActiveSessionsListener in pushSessionsChanged, removing",
735 e);
736 mSessionsListeners.remove(i);
737 }
738 }
739 }
740 }
741 }
742
RoboErik19c95182014-06-23 15:38:48 -0700743 private void pushRemoteVolumeUpdateLocked(int userId) {
744 if (mRvc != null) {
745 try {
Jaewan Kima7dce192017-02-16 17:10:54 +0900746 FullUserRecord user = getFullUserRecordLocked(userId);
747 if (user == null) {
748 Log.w(TAG, "pushRemoteVolumeUpdateLocked failed. No user with id=" + userId);
749 return;
750 }
751 MediaSessionRecord record = user.mPriorityStack.getDefaultRemoteSession(userId);
RoboErik19c95182014-06-23 15:38:48 -0700752 mRvc.updateRemoteController(record == null ? null : record.getControllerBinder());
753 } catch (RemoteException e) {
754 Log.wtf(TAG, "Error sending default remote volume to sys ui.", e);
755 }
756 }
757 }
758
Jaewan Kim92dea332017-02-02 11:52:08 +0900759 /**
760 * Called when the media button receiver for the {@param record} is changed.
761 *
762 * @param record the media session whose media button receiver is updated.
763 */
764 public void onMediaButtonReceiverChanged(MediaSessionRecord record) {
765 synchronized (mLock) {
766 FullUserRecord user = getFullUserRecordLocked(record.getUserId());
767 MediaSessionRecord mediaButtonSession =
768 user.mPriorityStack.getMediaButtonSession();
769 if (record == mediaButtonSession) {
770 user.rememberMediaButtonReceiverLocked(mediaButtonSession);
771 }
772 }
773 }
774
Jaewan Kim50269362016-12-23 11:22:02 +0900775 private String getCallingPackageName(int uid) {
776 String[] packages = getContext().getPackageManager().getPackagesForUid(uid);
777 if (packages != null && packages.length > 0) {
778 return packages[0];
779 }
780 return "";
781 }
782
Jaewan Kimd61a87b2017-02-17 23:14:10 +0900783 private void dispatchVolumeKeyLongPressLocked(KeyEvent keyEvent) {
Jaewan Kim7b3cae32018-06-07 10:16:22 +0900784 if (mCurrentFullUserRecord.mOnVolumeKeyLongPressListener == null) {
785 return;
786 }
Jaewan Kimd61a87b2017-02-17 23:14:10 +0900787 try {
Jaewan Kima7dce192017-02-16 17:10:54 +0900788 mCurrentFullUserRecord.mOnVolumeKeyLongPressListener.onVolumeKeyLongPress(keyEvent);
Jaewan Kimd61a87b2017-02-17 23:14:10 +0900789 } catch (RemoteException e) {
790 Log.w(TAG, "Failed to send " + keyEvent + " to volume key long-press listener");
791 }
792 }
793
Jaewan Kima7dce192017-02-16 17:10:54 +0900794 private FullUserRecord getFullUserRecordLocked(int userId) {
795 int fullUserId = mFullUserIds.get(userId, -1);
796 if (fullUserId < 0) {
797 return null;
798 }
799 return mUserRecords.get(fullUserId);
800 }
801
Sungsoo Lim117c7f72018-02-13 16:02:24 +0900802 void destroySession2Internal(SessionToken2 token) {
803 synchronized (mLock) {
Sungsoo Lim375efa12018-03-02 10:17:08 +0900804 boolean notifySessionTokensUpdated = false;
Sungsoo Lim117c7f72018-02-13 16:02:24 +0900805 if (token.getType() == SessionToken2.TYPE_SESSION) {
Sungsoo Lim375efa12018-03-02 10:17:08 +0900806 notifySessionTokensUpdated |= removeSessionRecordLocked(token);
Sungsoo Lim117c7f72018-02-13 16:02:24 +0900807 } else {
Sungsoo Lim375efa12018-03-02 10:17:08 +0900808 notifySessionTokensUpdated |= addSessionRecordLocked(token);
809 }
810 if (notifySessionTokensUpdated) {
811 postSessionTokensUpdated(UserHandle.getUserId(token.getUid()));
Sungsoo Lim117c7f72018-02-13 16:02:24 +0900812 }
813 }
814 }
815
RoboErik4646d282014-05-13 10:13:04 -0700816 /**
Jaewan Kima7dce192017-02-16 17:10:54 +0900817 * Information about a full user and its corresponding managed profiles.
818 *
819 * <p>Since the full user runs together with its managed profiles, a user wouldn't differentiate
820 * them when he/she presses a media/volume button. So keeping media sessions for them in one
821 * place makes more sense and increases the readability.</p>
822 * <p>The contents of this object is guarded by {@link #mLock}.
RoboErik4646d282014-05-13 10:13:04 -0700823 */
Jaewan Kim92dea332017-02-02 11:52:08 +0900824 final class FullUserRecord implements MediaSessionStack.OnMediaButtonSessionChangedListener {
Jaewan Kima7dce192017-02-16 17:10:54 +0900825 private static final String COMPONENT_NAME_USER_ID_DELIM = ",";
826 private final int mFullUserId;
Jaewan Kim92dea332017-02-02 11:52:08 +0900827 private final MediaSessionStack mPriorityStack;
RoboErikb214efb2014-07-24 13:20:30 -0700828 private PendingIntent mLastMediaButtonReceiver;
RoboErikc8f92d12015-01-05 16:48:07 -0800829 private ComponentName mRestoredMediaButtonReceiver;
Jaewan Kima7dce192017-02-16 17:10:54 +0900830 private int mRestoredMediaButtonReceiverUserId;
RoboErik4646d282014-05-13 10:13:04 -0700831
Jaewan Kim50269362016-12-23 11:22:02 +0900832 private IOnVolumeKeyLongPressListener mOnVolumeKeyLongPressListener;
833 private int mOnVolumeKeyLongPressListenerUid;
834 private KeyEvent mInitialDownVolumeKeyEvent;
835 private int mInitialDownVolumeStream;
836 private boolean mInitialDownMusicOnly;
837
Jaewan Kim6e2b01c2017-01-19 16:33:14 -0800838 private IOnMediaKeyListener mOnMediaKeyListener;
839 private int mOnMediaKeyListenerUid;
Jaewan Kima7dce192017-02-16 17:10:54 +0900840 private ICallback mCallback;
Jaewan Kim6e2b01c2017-01-19 16:33:14 -0800841
Jaewan Kima7dce192017-02-16 17:10:54 +0900842 public FullUserRecord(int fullUserId) {
843 mFullUserId = fullUserId;
Sungsoo Lim875e6972017-11-03 02:22:35 +0000844 mPriorityStack = new MediaSessionStack(mAudioPlayerStateMonitor, this);
Jaewan Kima7dce192017-02-16 17:10:54 +0900845 // Restore the remembered media button receiver before the boot.
846 String mediaButtonReceiver = Settings.Secure.getStringForUser(mContentResolver,
847 Settings.System.MEDIA_BUTTON_RECEIVER, mFullUserId);
848 if (mediaButtonReceiver == null) {
849 return;
850 }
851 String[] tokens = mediaButtonReceiver.split(COMPONENT_NAME_USER_ID_DELIM);
852 if (tokens == null || tokens.length != 2) {
853 return;
854 }
855 mRestoredMediaButtonReceiver = ComponentName.unflattenFromString(tokens[0]);
856 mRestoredMediaButtonReceiverUserId = Integer.parseInt(tokens[1]);
RoboErik4646d282014-05-13 10:13:04 -0700857 }
858
Jaewan Kima7dce192017-02-16 17:10:54 +0900859 public void destroySessionsForUserLocked(int userId) {
Jaewan Kim92dea332017-02-02 11:52:08 +0900860 List<MediaSessionRecord> sessions = mPriorityStack.getPriorityList(false, userId);
Jaewan Kima7dce192017-02-16 17:10:54 +0900861 for (MediaSessionRecord session : sessions) {
RoboErik4646d282014-05-13 10:13:04 -0700862 MediaSessionService.this.destroySessionLocked(session);
RoboErik4646d282014-05-13 10:13:04 -0700863 }
864 }
865
RoboErik4646d282014-05-13 10:13:04 -0700866 public void dumpLocked(PrintWriter pw, String prefix) {
Jaewan Kima7dce192017-02-16 17:10:54 +0900867 pw.print(prefix + "Record for full_user=" + mFullUserId);
868 // Dump managed profile user ids associated with this user.
869 int size = mFullUserIds.size();
870 for (int i = 0; i < size; i++) {
871 if (mFullUserIds.keyAt(i) != mFullUserIds.valueAt(i)
872 && mFullUserIds.valueAt(i) == mFullUserId) {
873 pw.print(", profile_user=" + mFullUserIds.keyAt(i));
874 }
875 }
876 pw.println();
RoboErik4646d282014-05-13 10:13:04 -0700877 String indent = prefix + " ";
Jaewan Kima7dce192017-02-16 17:10:54 +0900878 pw.println(indent + "Volume key long-press listener: " + mOnVolumeKeyLongPressListener);
879 pw.println(indent + "Volume key long-press listener package: " +
Jaewan Kim50269362016-12-23 11:22:02 +0900880 getCallingPackageName(mOnVolumeKeyLongPressListenerUid));
Jaewan Kim6e2b01c2017-01-19 16:33:14 -0800881 pw.println(indent + "Media key listener: " + mOnMediaKeyListener);
882 pw.println(indent + "Media key listener package: " +
883 getCallingPackageName(mOnMediaKeyListenerUid));
Jaewan Kima7dce192017-02-16 17:10:54 +0900884 pw.println(indent + "Callback: " + mCallback);
885 pw.println(indent + "Last MediaButtonReceiver: " + mLastMediaButtonReceiver);
886 pw.println(indent + "Restored MediaButtonReceiver: " + mRestoredMediaButtonReceiver);
887 mPriorityStack.dump(pw, indent);
888 }
889
Jaewan Kim92dea332017-02-02 11:52:08 +0900890 @Override
891 public void onMediaButtonSessionChanged(MediaSessionRecord oldMediaButtonSession,
892 MediaSessionRecord newMediaButtonSession) {
893 if (DEBUG_KEY_EVENT) {
Jaewan Kim98e4aaf2017-05-12 17:06:47 +0900894 Log.d(TAG, "Media button session is changed to " + newMediaButtonSession);
Jaewan Kim92dea332017-02-02 11:52:08 +0900895 }
896 synchronized (mLock) {
897 if (oldMediaButtonSession != null) {
898 mHandler.postSessionsChanged(oldMediaButtonSession.getUserId());
899 }
900 if (newMediaButtonSession != null) {
901 rememberMediaButtonReceiverLocked(newMediaButtonSession);
902 mHandler.postSessionsChanged(newMediaButtonSession.getUserId());
903 }
904 pushAddressedPlayerChangedLocked();
905 }
906 }
907
908 // Remember media button receiver and keep it in the persistent storage.
909 public void rememberMediaButtonReceiverLocked(MediaSessionRecord record) {
Jaewan Kima7dce192017-02-16 17:10:54 +0900910 PendingIntent receiver = record.getMediaButtonReceiver();
Jaewan Kima7dce192017-02-16 17:10:54 +0900911 mLastMediaButtonReceiver = receiver;
Jaewan Kim92dea332017-02-02 11:52:08 +0900912 mRestoredMediaButtonReceiver = null;
913 String componentName = "";
914 if (receiver != null) {
915 ComponentName component = receiver.getIntent().getComponent();
916 if (component != null
917 && record.getPackageName().equals(component.getPackageName())) {
918 componentName = component.flattenToString();
919 }
RoboErik4646d282014-05-13 10:13:04 -0700920 }
Jaewan Kim92dea332017-02-02 11:52:08 +0900921 Settings.Secure.putStringForUser(mContentResolver,
922 Settings.System.MEDIA_BUTTON_RECEIVER,
923 componentName + COMPONENT_NAME_USER_ID_DELIM + record.getUserId(),
924 mFullUserId);
RoboErik4646d282014-05-13 10:13:04 -0700925 }
RoboErikc8f92d12015-01-05 16:48:07 -0800926
Jaewan Kima7dce192017-02-16 17:10:54 +0900927 private void pushAddressedPlayerChangedLocked() {
928 if (mCallback == null) {
929 return;
RoboErikc8f92d12015-01-05 16:48:07 -0800930 }
Jaewan Kima7dce192017-02-16 17:10:54 +0900931 try {
932 MediaSessionRecord mediaButtonSession = getMediaButtonSessionLocked();
933 if (mediaButtonSession != null) {
934 mCallback.onAddressedPlayerChangedToMediaSession(
935 new MediaSession.Token(mediaButtonSession.getControllerBinder()));
936 } else if (mCurrentFullUserRecord.mLastMediaButtonReceiver != null) {
937 mCallback.onAddressedPlayerChangedToMediaButtonReceiver(
938 mCurrentFullUserRecord.mLastMediaButtonReceiver
939 .getIntent().getComponent());
940 } else if (mCurrentFullUserRecord.mRestoredMediaButtonReceiver != null) {
941 mCallback.onAddressedPlayerChangedToMediaButtonReceiver(
942 mCurrentFullUserRecord.mRestoredMediaButtonReceiver);
943 }
944 } catch (RemoteException e) {
945 Log.w(TAG, "Failed to pushAddressedPlayerChangedLocked", e);
946 }
947 }
948
949 private MediaSessionRecord getMediaButtonSessionLocked() {
Jaewan Kim92dea332017-02-02 11:52:08 +0900950 return isGlobalPriorityActiveLocked()
951 ? mGlobalPrioritySession : mPriorityStack.getMediaButtonSession();
RoboErikc8f92d12015-01-05 16:48:07 -0800952 }
RoboErik4646d282014-05-13 10:13:04 -0700953 }
954
RoboErik2e7a9162014-06-04 16:53:45 -0700955 final class SessionsListenerRecord implements IBinder.DeathRecipient {
956 private final IActiveSessionsListener mListener;
RoboErik7aef77b2014-08-08 15:56:54 -0700957 private final ComponentName mComponentName;
RoboErik2e7a9162014-06-04 16:53:45 -0700958 private final int mUserId;
RoboErik7aef77b2014-08-08 15:56:54 -0700959 private final int mPid;
960 private final int mUid;
RoboErik2e7a9162014-06-04 16:53:45 -0700961
RoboErik7aef77b2014-08-08 15:56:54 -0700962 public SessionsListenerRecord(IActiveSessionsListener listener,
963 ComponentName componentName,
964 int userId, int pid, int uid) {
RoboErik2e7a9162014-06-04 16:53:45 -0700965 mListener = listener;
RoboErik7aef77b2014-08-08 15:56:54 -0700966 mComponentName = componentName;
RoboErik2e7a9162014-06-04 16:53:45 -0700967 mUserId = userId;
RoboErik7aef77b2014-08-08 15:56:54 -0700968 mPid = pid;
969 mUid = uid;
RoboErik2e7a9162014-06-04 16:53:45 -0700970 }
971
972 @Override
973 public void binderDied() {
974 synchronized (mLock) {
975 mSessionsListeners.remove(this);
976 }
977 }
978 }
979
RoboErik7aef77b2014-08-08 15:56:54 -0700980 final class SettingsObserver extends ContentObserver {
981 private final Uri mSecureSettingsUri = Settings.Secure.getUriFor(
982 Settings.Secure.ENABLED_NOTIFICATION_LISTENERS);
983
984 private SettingsObserver() {
985 super(null);
986 }
987
988 private void observe() {
989 mContentResolver.registerContentObserver(mSecureSettingsUri,
990 false, this, UserHandle.USER_ALL);
991 }
992
993 @Override
994 public void onChange(boolean selfChange, Uri uri) {
995 updateActiveSessionListeners();
996 }
997 }
998
RoboErik07c70772014-03-20 13:33:52 -0700999 class SessionManagerImpl extends ISessionManager.Stub {
RoboErik8a2cfc32014-05-16 11:19:38 -07001000 private static final String EXTRA_WAKELOCK_ACQUIRED =
1001 "android.media.AudioService.WAKELOCK_ACQUIRED";
1002 private static final int WAKELOCK_RELEASE_ON_FINISHED = 1980; // magic number
1003
RoboErik9a9d0b52014-05-20 14:53:39 -07001004 private boolean mVoiceButtonDown = false;
1005 private boolean mVoiceButtonHandled = false;
1006
RoboErik07c70772014-03-20 13:33:52 -07001007 @Override
RoboErika5b02322014-05-07 17:05:49 -07001008 public ISession createSession(String packageName, ISessionCallback cb, String tag,
1009 int userId) throws RemoteException {
RoboErik01fe6612014-02-13 14:19:04 -08001010 final int pid = Binder.getCallingPid();
1011 final int uid = Binder.getCallingUid();
1012 final long token = Binder.clearCallingIdentity();
1013 try {
1014 enforcePackageName(packageName, uid);
RoboErika5b02322014-05-07 17:05:49 -07001015 int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId,
1016 false /* allowAll */, true /* requireFull */, "createSession", packageName);
RoboErik01fe6612014-02-13 14:19:04 -08001017 if (cb == null) {
1018 throw new IllegalArgumentException("Controller callback cannot be null");
1019 }
RoboErika5b02322014-05-07 17:05:49 -07001020 return createSessionInternal(pid, uid, resolvedUserId, packageName, cb, tag)
1021 .getSessionBinder();
RoboErike7880d82014-04-30 12:48:25 -07001022 } finally {
1023 Binder.restoreCallingIdentity(token);
1024 }
1025 }
1026
1027 @Override
RoboErika5b02322014-05-07 17:05:49 -07001028 public List<IBinder> getSessions(ComponentName componentName, int userId) {
RoboErike7880d82014-04-30 12:48:25 -07001029 final int pid = Binder.getCallingPid();
1030 final int uid = Binder.getCallingUid();
1031 final long token = Binder.clearCallingIdentity();
1032
1033 try {
RoboErik2e7a9162014-06-04 16:53:45 -07001034 int resolvedUserId = verifySessionsRequest(componentName, userId, pid, uid);
RoboErike7880d82014-04-30 12:48:25 -07001035 ArrayList<IBinder> binders = new ArrayList<IBinder>();
1036 synchronized (mLock) {
Jaewan Kim101b4d52017-05-18 13:23:11 +09001037 List<MediaSessionRecord> records = getActiveSessionsLocked(resolvedUserId);
1038 for (MediaSessionRecord record : records) {
1039 binders.add(record.getControllerBinder().asBinder());
RoboErike7880d82014-04-30 12:48:25 -07001040 }
1041 }
1042 return binders;
RoboErik01fe6612014-02-13 14:19:04 -08001043 } finally {
1044 Binder.restoreCallingIdentity(token);
1045 }
1046 }
RoboErika278ea72014-04-24 14:49:01 -07001047
RoboErik2e7a9162014-06-04 16:53:45 -07001048 @Override
1049 public void addSessionsListener(IActiveSessionsListener listener,
1050 ComponentName componentName, int userId) throws RemoteException {
1051 final int pid = Binder.getCallingPid();
1052 final int uid = Binder.getCallingUid();
1053 final long token = Binder.clearCallingIdentity();
1054
1055 try {
1056 int resolvedUserId = verifySessionsRequest(componentName, userId, pid, uid);
1057 synchronized (mLock) {
1058 int index = findIndexOfSessionsListenerLocked(listener);
1059 if (index != -1) {
1060 Log.w(TAG, "ActiveSessionsListener is already added, ignoring");
1061 return;
1062 }
1063 SessionsListenerRecord record = new SessionsListenerRecord(listener,
RoboErik7aef77b2014-08-08 15:56:54 -07001064 componentName, resolvedUserId, pid, uid);
RoboErik2e7a9162014-06-04 16:53:45 -07001065 try {
1066 listener.asBinder().linkToDeath(record, 0);
1067 } catch (RemoteException e) {
1068 Log.e(TAG, "ActiveSessionsListener is dead, ignoring it", e);
1069 return;
1070 }
1071 mSessionsListeners.add(record);
1072 }
1073 } finally {
1074 Binder.restoreCallingIdentity(token);
1075 }
1076 }
1077
1078 @Override
1079 public void removeSessionsListener(IActiveSessionsListener listener)
1080 throws RemoteException {
1081 synchronized (mLock) {
1082 int index = findIndexOfSessionsListenerLocked(listener);
1083 if (index != -1) {
1084 SessionsListenerRecord record = mSessionsListeners.remove(index);
1085 try {
1086 record.mListener.asBinder().unlinkToDeath(record, 0);
1087 } catch (Exception e) {
1088 // ignore exceptions, the record is being removed
1089 }
1090 }
1091 }
1092 }
1093
RoboErik8a2cfc32014-05-16 11:19:38 -07001094 /**
1095 * Handles the dispatching of the media button events to one of the
1096 * registered listeners, or if there was none, broadcast an
1097 * ACTION_MEDIA_BUTTON intent to the rest of the system.
1098 *
Jaewan Kim77748b62018-05-03 19:43:33 +09001099 * @param packageName The caller package
1100 * @param asSystemService {@code true} if the event sent to the session as if it was come
1101 * from the system service instead of the app process. This helps sessions to
1102 * distinguish between the key injection by the app and key events from the
1103 * hardware devices. Should be used only when the volume key events aren't handled
1104 * by foreground activity. {@code false} otherwise to tell session about the real
1105 * caller.
RoboErik8a2cfc32014-05-16 11:19:38 -07001106 * @param keyEvent a non-null KeyEvent whose key code is one of the
1107 * supported media buttons
1108 * @param needWakeLock true if a PARTIAL_WAKE_LOCK needs to be held
1109 * while this key event is dispatched.
1110 */
1111 @Override
Jaewan Kim77748b62018-05-03 19:43:33 +09001112 public void dispatchMediaKeyEvent(String packageName, boolean asSystemService,
1113 KeyEvent keyEvent, boolean needWakeLock) {
RoboErik8a2cfc32014-05-16 11:19:38 -07001114 if (keyEvent == null || !KeyEvent.isMediaKey(keyEvent.getKeyCode())) {
1115 Log.w(TAG, "Attempted to dispatch null or non-media key event.");
1116 return;
1117 }
Jeff Brown38d3feb2015-03-19 18:26:30 -07001118
RoboErik8a2cfc32014-05-16 11:19:38 -07001119 final int pid = Binder.getCallingPid();
1120 final int uid = Binder.getCallingUid();
1121 final long token = Binder.clearCallingIdentity();
RoboErik8a2cfc32014-05-16 11:19:38 -07001122 try {
Jeff Brown221a8272015-03-23 13:53:09 -07001123 if (DEBUG) {
Jaewan Kim77748b62018-05-03 19:43:33 +09001124 Log.d(TAG, "dispatchMediaKeyEvent, pkg=" + packageName + " pid=" + pid
1125 + ", uid=" + uid + ", asSystem=" + asSystemService + ", event="
Jeff Brown221a8272015-03-23 13:53:09 -07001126 + keyEvent);
1127 }
Jeff Brown38d3feb2015-03-19 18:26:30 -07001128 if (!isUserSetupComplete()) {
1129 // Global media key handling can have the side-effect of starting new
1130 // activities which is undesirable while setup is in progress.
1131 Slog.i(TAG, "Not dispatching media key event because user "
1132 + "setup is in progress.");
1133 return;
1134 }
1135
RoboErik8a2cfc32014-05-16 11:19:38 -07001136 synchronized (mLock) {
Jaewan Kima7dce192017-02-16 17:10:54 +09001137 boolean isGlobalPriorityActive = isGlobalPriorityActiveLocked();
Jaewan Kim51255012017-02-24 16:19:14 +09001138 if (isGlobalPriorityActive && uid != Process.SYSTEM_UID) {
1139 // Prevent dispatching key event through reflection while the global
1140 // priority session is active.
1141 Slog.i(TAG, "Only the system can dispatch media key event "
1142 + "to the global priority session.");
1143 return;
1144 }
Jaewan Kim98003d32017-02-24 18:33:04 +09001145 if (!isGlobalPriorityActive) {
Jaewan Kima7dce192017-02-16 17:10:54 +09001146 if (mCurrentFullUserRecord.mOnMediaKeyListener != null) {
Jaewan Kim98003d32017-02-24 18:33:04 +09001147 if (DEBUG_KEY_EVENT) {
Jaewan Kima7dce192017-02-16 17:10:54 +09001148 Log.d(TAG, "Send " + keyEvent + " to the media key listener");
Jaewan Kim98003d32017-02-24 18:33:04 +09001149 }
1150 try {
Jaewan Kima7dce192017-02-16 17:10:54 +09001151 mCurrentFullUserRecord.mOnMediaKeyListener.onMediaKey(keyEvent,
Jaewan Kim77748b62018-05-03 19:43:33 +09001152 new MediaKeyListenerResultReceiver(packageName, pid, uid,
1153 asSystemService, keyEvent, needWakeLock));
Jaewan Kim98003d32017-02-24 18:33:04 +09001154 return;
1155 } catch (RemoteException e) {
Jaewan Kima7dce192017-02-16 17:10:54 +09001156 Log.w(TAG, "Failed to send " + keyEvent
1157 + " to the media key listener");
Jaewan Kim98003d32017-02-24 18:33:04 +09001158 }
1159 }
1160 }
Jaewan Kim51255012017-02-24 16:19:14 +09001161 if (!isGlobalPriorityActive && isVoiceKey(keyEvent.getKeyCode())) {
Jaewan Kim77748b62018-05-03 19:43:33 +09001162 handleVoiceKeyEventLocked(packageName, pid, uid, asSystemService, keyEvent,
1163 needWakeLock);
RoboErik8a2cfc32014-05-16 11:19:38 -07001164 } else {
Jaewan Kim77748b62018-05-03 19:43:33 +09001165 dispatchMediaKeyEventLocked(packageName, pid, uid, asSystemService,
1166 keyEvent, needWakeLock);
RoboErik8a2cfc32014-05-16 11:19:38 -07001167 }
1168 }
1169 } finally {
1170 Binder.restoreCallingIdentity(token);
1171 }
1172 }
1173
RoboErika278ea72014-04-24 14:49:01 -07001174 @Override
Jaewan Kimbd16f452017-02-03 16:21:38 +09001175 public void setCallback(ICallback callback) {
1176 final int pid = Binder.getCallingPid();
1177 final int uid = Binder.getCallingUid();
1178 final long token = Binder.clearCallingIdentity();
1179 try {
Amith Yamasanie259ad22017-04-24 11:30:19 -07001180 if (!UserHandle.isSameApp(uid, Process.BLUETOOTH_UID)) {
Jaewan Kimbd16f452017-02-03 16:21:38 +09001181 throw new SecurityException("Only Bluetooth service processes can set"
1182 + " Callback");
1183 }
1184 synchronized (mLock) {
Jaewan Kima7dce192017-02-16 17:10:54 +09001185 int userId = UserHandle.getUserId(uid);
1186 FullUserRecord user = getFullUserRecordLocked(userId);
1187 if (user == null || user.mFullUserId != userId) {
1188 Log.w(TAG, "Only the full user can set the callback"
1189 + ", userId=" + userId);
1190 return;
1191 }
1192 user.mCallback = callback;
1193 Log.d(TAG, "The callback " + user.mCallback
Jaewan Kimbd16f452017-02-03 16:21:38 +09001194 + " is set by " + getCallingPackageName(uid));
Jaewan Kima7dce192017-02-16 17:10:54 +09001195 if (user.mCallback == null) {
Jaewan Kimbd16f452017-02-03 16:21:38 +09001196 return;
1197 }
1198 try {
Jaewan Kima7dce192017-02-16 17:10:54 +09001199 user.mCallback.asBinder().linkToDeath(
Jaewan Kimbd16f452017-02-03 16:21:38 +09001200 new IBinder.DeathRecipient() {
1201 @Override
1202 public void binderDied() {
1203 synchronized (mLock) {
Jaewan Kima7dce192017-02-16 17:10:54 +09001204 user.mCallback = null;
Jaewan Kimbd16f452017-02-03 16:21:38 +09001205 }
1206 }
1207 }, 0);
Jaewan Kima7dce192017-02-16 17:10:54 +09001208 user.pushAddressedPlayerChangedLocked();
Jaewan Kimbd16f452017-02-03 16:21:38 +09001209 } catch (RemoteException e) {
1210 Log.w(TAG, "Failed to set callback", e);
Jaewan Kima7dce192017-02-16 17:10:54 +09001211 user.mCallback = null;
Jaewan Kimbd16f452017-02-03 16:21:38 +09001212 }
1213 }
1214 } finally {
1215 Binder.restoreCallingIdentity(token);
1216 }
1217 }
1218
1219 @Override
Jaewan Kim50269362016-12-23 11:22:02 +09001220 public void setOnVolumeKeyLongPressListener(IOnVolumeKeyLongPressListener listener) {
1221 final int pid = Binder.getCallingPid();
1222 final int uid = Binder.getCallingUid();
1223 final long token = Binder.clearCallingIdentity();
1224 try {
1225 // Enforce SET_VOLUME_KEY_LONG_PRESS_LISTENER permission.
1226 if (getContext().checkPermission(
1227 android.Manifest.permission.SET_VOLUME_KEY_LONG_PRESS_LISTENER, pid, uid)
1228 != PackageManager.PERMISSION_GRANTED) {
1229 throw new SecurityException("Must hold the SET_VOLUME_KEY_LONG_PRESS_LISTENER" +
1230 " permission.");
1231 }
1232
1233 synchronized (mLock) {
Jaewan Kima7dce192017-02-16 17:10:54 +09001234 int userId = UserHandle.getUserId(uid);
1235 FullUserRecord user = getFullUserRecordLocked(userId);
1236 if (user == null || user.mFullUserId != userId) {
1237 Log.w(TAG, "Only the full user can set the volume key long-press listener"
1238 + ", userId=" + userId);
1239 return;
1240 }
Jaewan Kim50269362016-12-23 11:22:02 +09001241 if (user.mOnVolumeKeyLongPressListener != null &&
1242 user.mOnVolumeKeyLongPressListenerUid != uid) {
Jaewan Kima7dce192017-02-16 17:10:54 +09001243 Log.w(TAG, "The volume key long-press listener cannot be reset"
1244 + " by another app , mOnVolumeKeyLongPressListener="
1245 + user.mOnVolumeKeyLongPressListenerUid
1246 + ", uid=" + uid);
Jaewan Kim50269362016-12-23 11:22:02 +09001247 return;
1248 }
1249
1250 user.mOnVolumeKeyLongPressListener = listener;
1251 user.mOnVolumeKeyLongPressListenerUid = uid;
1252
Jaewan Kima7dce192017-02-16 17:10:54 +09001253 Log.d(TAG, "The volume key long-press listener "
Jaewan Kim50269362016-12-23 11:22:02 +09001254 + listener + " is set by " + getCallingPackageName(uid));
1255
1256 if (user.mOnVolumeKeyLongPressListener != null) {
1257 try {
1258 user.mOnVolumeKeyLongPressListener.asBinder().linkToDeath(
1259 new IBinder.DeathRecipient() {
1260 @Override
1261 public void binderDied() {
1262 synchronized (mLock) {
1263 user.mOnVolumeKeyLongPressListener = null;
1264 }
1265 }
1266 }, 0);
1267 } catch (RemoteException e) {
1268 Log.w(TAG, "Failed to set death recipient "
1269 + user.mOnVolumeKeyLongPressListener);
1270 user.mOnVolumeKeyLongPressListener = null;
1271 }
1272 }
1273 }
1274 } finally {
1275 Binder.restoreCallingIdentity(token);
1276 }
1277 }
1278
Jaewan Kim6e2b01c2017-01-19 16:33:14 -08001279 @Override
1280 public void setOnMediaKeyListener(IOnMediaKeyListener listener) {
1281 final int pid = Binder.getCallingPid();
1282 final int uid = Binder.getCallingUid();
1283 final long token = Binder.clearCallingIdentity();
1284 try {
1285 // Enforce SET_MEDIA_KEY_LISTENER permission.
1286 if (getContext().checkPermission(
1287 android.Manifest.permission.SET_MEDIA_KEY_LISTENER, pid, uid)
1288 != PackageManager.PERMISSION_GRANTED) {
1289 throw new SecurityException("Must hold the SET_MEDIA_KEY_LISTENER" +
1290 " permission.");
1291 }
1292
1293 synchronized (mLock) {
1294 int userId = UserHandle.getUserId(uid);
Jaewan Kima7dce192017-02-16 17:10:54 +09001295 FullUserRecord user = getFullUserRecordLocked(userId);
1296 if (user == null || user.mFullUserId != userId) {
1297 Log.w(TAG, "Only the full user can set the media key listener"
1298 + ", userId=" + userId);
1299 return;
1300 }
Jaewan Kim6e2b01c2017-01-19 16:33:14 -08001301 if (user.mOnMediaKeyListener != null && user.mOnMediaKeyListenerUid != uid) {
Jaewan Kima7dce192017-02-16 17:10:54 +09001302 Log.w(TAG, "The media key listener cannot be reset by another app. "
1303 + ", mOnMediaKeyListenerUid=" + user.mOnMediaKeyListenerUid
1304 + ", uid=" + uid);
Jaewan Kim6e2b01c2017-01-19 16:33:14 -08001305 return;
1306 }
1307
1308 user.mOnMediaKeyListener = listener;
1309 user.mOnMediaKeyListenerUid = uid;
1310
Jaewan Kima7dce192017-02-16 17:10:54 +09001311 Log.d(TAG, "The media key listener " + user.mOnMediaKeyListener
Jaewan Kim6e2b01c2017-01-19 16:33:14 -08001312 + " is set by " + getCallingPackageName(uid));
1313
1314 if (user.mOnMediaKeyListener != null) {
1315 try {
1316 user.mOnMediaKeyListener.asBinder().linkToDeath(
1317 new IBinder.DeathRecipient() {
1318 @Override
1319 public void binderDied() {
1320 synchronized (mLock) {
1321 user.mOnMediaKeyListener = null;
1322 }
1323 }
1324 }, 0);
1325 } catch (RemoteException e) {
1326 Log.w(TAG, "Failed to set death recipient " + user.mOnMediaKeyListener);
1327 user.mOnMediaKeyListener = null;
1328 }
1329 }
1330 }
1331 } finally {
1332 Binder.restoreCallingIdentity(token);
1333 }
1334 }
1335
Jaewan Kim50269362016-12-23 11:22:02 +09001336 /**
1337 * Handles the dispatching of the volume button events to one of the
1338 * registered listeners. If there's a volume key long-press listener and
1339 * there's no active global priority session, long-pressess will be sent to the
1340 * long-press listener instead of adjusting volume.
1341 *
Jaewan Kim77748b62018-05-03 19:43:33 +09001342 * @param packageName The caller package.
1343 * @param asSystemService {@code true} if the event sent to the session as if it was come
1344 * from the system service instead of the app process. This helps sessions to
1345 * distinguish between the key injection by the app and key events from the
1346 * hardware devices. Should be used only when the volume key events aren't handled
1347 * by foreground activity. {@code false} otherwise to tell session about the real
1348 * caller.
Jaewan Kim50269362016-12-23 11:22:02 +09001349 * @param keyEvent a non-null KeyEvent whose key code is one of the
1350 * {@link KeyEvent#KEYCODE_VOLUME_UP},
1351 * {@link KeyEvent#KEYCODE_VOLUME_DOWN},
1352 * or {@link KeyEvent#KEYCODE_VOLUME_MUTE}.
1353 * @param stream stream type to adjust volume.
1354 * @param musicOnly true if both UI nor haptic feedback aren't needed when adjust volume.
1355 */
1356 @Override
Jaewan Kim77748b62018-05-03 19:43:33 +09001357 public void dispatchVolumeKeyEvent(String packageName, boolean asSystemService,
1358 KeyEvent keyEvent, int stream, boolean musicOnly) {
Jaewan Kim50269362016-12-23 11:22:02 +09001359 if (keyEvent == null ||
1360 (keyEvent.getKeyCode() != KeyEvent.KEYCODE_VOLUME_UP
1361 && keyEvent.getKeyCode() != KeyEvent.KEYCODE_VOLUME_DOWN
1362 && keyEvent.getKeyCode() != KeyEvent.KEYCODE_VOLUME_MUTE)) {
1363 Log.w(TAG, "Attempted to dispatch null or non-volume key event.");
1364 return;
1365 }
1366
1367 final int pid = Binder.getCallingPid();
1368 final int uid = Binder.getCallingUid();
1369 final long token = Binder.clearCallingIdentity();
1370
Jaewan Kimb2781e72017-03-02 09:57:09 +09001371 if (DEBUG_KEY_EVENT) {
Jaewan Kim77748b62018-05-03 19:43:33 +09001372 Log.d(TAG, "dispatchVolumeKeyEvent, pkg=" + packageName + ", pid=" + pid + ", uid="
1373 + uid + ", asSystem=" + asSystemService + ", event=" + keyEvent);
Jaewan Kim50269362016-12-23 11:22:02 +09001374 }
1375
1376 try {
1377 synchronized (mLock) {
Jaewan Kima7dce192017-02-16 17:10:54 +09001378 if (isGlobalPriorityActiveLocked()
1379 || mCurrentFullUserRecord.mOnVolumeKeyLongPressListener == null) {
Jaewan Kim77748b62018-05-03 19:43:33 +09001380 dispatchVolumeKeyEventLocked(packageName, pid, uid, asSystemService,
1381 keyEvent, stream, musicOnly);
Jaewan Kim50269362016-12-23 11:22:02 +09001382 } else {
1383 // TODO: Consider the case when both volume up and down keys are pressed
1384 // at the same time.
1385 if (keyEvent.getAction() == KeyEvent.ACTION_DOWN) {
1386 if (keyEvent.getRepeatCount() == 0) {
Jaewan Kimd61a87b2017-02-17 23:14:10 +09001387 // Keeps the copy of the KeyEvent because it can be reused.
Jaewan Kima7dce192017-02-16 17:10:54 +09001388 mCurrentFullUserRecord.mInitialDownVolumeKeyEvent =
1389 KeyEvent.obtain(keyEvent);
1390 mCurrentFullUserRecord.mInitialDownVolumeStream = stream;
1391 mCurrentFullUserRecord.mInitialDownMusicOnly = musicOnly;
Jaewan Kimd61a87b2017-02-17 23:14:10 +09001392 mHandler.sendMessageDelayed(
1393 mHandler.obtainMessage(
Jaewan Kima7dce192017-02-16 17:10:54 +09001394 MessageHandler.MSG_VOLUME_INITIAL_DOWN,
1395 mCurrentFullUserRecord.mFullUserId, 0),
Jaewan Kimd61a87b2017-02-17 23:14:10 +09001396 mLongPressTimeout);
Jaewan Kim50269362016-12-23 11:22:02 +09001397 }
1398 if (keyEvent.getRepeatCount() > 0 || keyEvent.isLongPress()) {
Jaewan Kimd61a87b2017-02-17 23:14:10 +09001399 mHandler.removeMessages(MessageHandler.MSG_VOLUME_INITIAL_DOWN);
Jaewan Kima7dce192017-02-16 17:10:54 +09001400 if (mCurrentFullUserRecord.mInitialDownVolumeKeyEvent != null) {
Jaewan Kim50269362016-12-23 11:22:02 +09001401 dispatchVolumeKeyLongPressLocked(
Jaewan Kima7dce192017-02-16 17:10:54 +09001402 mCurrentFullUserRecord.mInitialDownVolumeKeyEvent);
Jaewan Kim50269362016-12-23 11:22:02 +09001403 // Mark that the key is already handled.
Jaewan Kima7dce192017-02-16 17:10:54 +09001404 mCurrentFullUserRecord.mInitialDownVolumeKeyEvent = null;
Jaewan Kim50269362016-12-23 11:22:02 +09001405 }
1406 dispatchVolumeKeyLongPressLocked(keyEvent);
1407 }
1408 } else { // if up
Jaewan Kimd61a87b2017-02-17 23:14:10 +09001409 mHandler.removeMessages(MessageHandler.MSG_VOLUME_INITIAL_DOWN);
Jaewan Kima7dce192017-02-16 17:10:54 +09001410 if (mCurrentFullUserRecord.mInitialDownVolumeKeyEvent != null
1411 && mCurrentFullUserRecord.mInitialDownVolumeKeyEvent
1412 .getDownTime() == keyEvent.getDownTime()) {
Jaewan Kim50269362016-12-23 11:22:02 +09001413 // Short-press. Should change volume.
Jaewan Kim77748b62018-05-03 19:43:33 +09001414 dispatchVolumeKeyEventLocked(packageName, pid, uid, asSystemService,
Jaewan Kima7dce192017-02-16 17:10:54 +09001415 mCurrentFullUserRecord.mInitialDownVolumeKeyEvent,
1416 mCurrentFullUserRecord.mInitialDownVolumeStream,
1417 mCurrentFullUserRecord.mInitialDownMusicOnly);
Jaewan Kim77748b62018-05-03 19:43:33 +09001418 dispatchVolumeKeyEventLocked(packageName, pid, uid, asSystemService,
1419 keyEvent, stream, musicOnly);
Jaewan Kim50269362016-12-23 11:22:02 +09001420 } else {
1421 dispatchVolumeKeyLongPressLocked(keyEvent);
1422 }
1423 }
1424 }
1425 }
1426 } finally {
1427 Binder.restoreCallingIdentity(token);
1428 }
1429 }
1430
Jaewan Kim77748b62018-05-03 19:43:33 +09001431 private void dispatchVolumeKeyEventLocked(String packageName, int pid, int uid,
1432 boolean asSystemService, KeyEvent keyEvent, int stream, boolean musicOnly) {
Jaewan Kim50269362016-12-23 11:22:02 +09001433 boolean down = keyEvent.getAction() == KeyEvent.ACTION_DOWN;
1434 boolean up = keyEvent.getAction() == KeyEvent.ACTION_UP;
1435 int direction = 0;
1436 boolean isMute = false;
1437 switch (keyEvent.getKeyCode()) {
1438 case KeyEvent.KEYCODE_VOLUME_UP:
1439 direction = AudioManager.ADJUST_RAISE;
1440 break;
1441 case KeyEvent.KEYCODE_VOLUME_DOWN:
1442 direction = AudioManager.ADJUST_LOWER;
1443 break;
1444 case KeyEvent.KEYCODE_VOLUME_MUTE:
1445 isMute = true;
1446 break;
1447 }
1448 if (down || up) {
1449 int flags = AudioManager.FLAG_FROM_KEY;
1450 if (musicOnly) {
1451 // This flag is used when the screen is off to only affect active media.
1452 flags |= AudioManager.FLAG_ACTIVE_MEDIA_ONLY;
1453 } else {
1454 // These flags are consistent with the home screen
1455 if (up) {
1456 flags |= AudioManager.FLAG_PLAY_SOUND | AudioManager.FLAG_VIBRATE;
1457 } else {
1458 flags |= AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_VIBRATE;
1459 }
1460 }
1461 if (direction != 0) {
1462 // If this is action up we want to send a beep for non-music events
1463 if (up) {
1464 direction = 0;
1465 }
Jaewan Kim77748b62018-05-03 19:43:33 +09001466 dispatchAdjustVolumeLocked(packageName, pid, uid, asSystemService, stream,
1467 direction, flags);
Jaewan Kim50269362016-12-23 11:22:02 +09001468 } else if (isMute) {
1469 if (down && keyEvent.getRepeatCount() == 0) {
Jaewan Kim77748b62018-05-03 19:43:33 +09001470 dispatchAdjustVolumeLocked(packageName, pid, uid, asSystemService, stream,
1471 AudioManager.ADJUST_TOGGLE_MUTE, flags);
Jaewan Kim50269362016-12-23 11:22:02 +09001472 }
1473 }
1474 }
1475 }
1476
1477 @Override
Jaewan Kim77748b62018-05-03 19:43:33 +09001478 public void dispatchAdjustVolume(String packageName, int suggestedStream, int delta,
1479 int flags) {
1480 final int pid = Binder.getCallingPid();
1481 final int uid = Binder.getCallingUid();
RoboErikb69ffd42014-05-30 14:57:59 -07001482 final long token = Binder.clearCallingIdentity();
1483 try {
1484 synchronized (mLock) {
Jaewan Kim77748b62018-05-03 19:43:33 +09001485 dispatchAdjustVolumeLocked(packageName, pid, uid, false,
1486 suggestedStream, delta, flags);
RoboErikb69ffd42014-05-30 14:57:59 -07001487 }
1488 } finally {
1489 Binder.restoreCallingIdentity(token);
1490 }
1491 }
1492
1493 @Override
RoboErik19c95182014-06-23 15:38:48 -07001494 public void setRemoteVolumeController(IRemoteVolumeController rvc) {
1495 final int pid = Binder.getCallingPid();
1496 final int uid = Binder.getCallingUid();
1497 final long token = Binder.clearCallingIdentity();
1498 try {
John Spurlockeb69e242015-02-17 17:15:04 -05001499 enforceSystemUiPermission("listen for volume changes", pid, uid);
RoboErik19c95182014-06-23 15:38:48 -07001500 mRvc = rvc;
1501 } finally {
1502 Binder.restoreCallingIdentity(token);
1503 }
1504 }
1505
1506 @Override
RoboErikde9ba392014-09-26 12:51:01 -07001507 public boolean isGlobalPriorityActive() {
Jaewan Kim51255012017-02-24 16:19:14 +09001508 synchronized (mLock) {
Jaewan Kima7dce192017-02-16 17:10:54 +09001509 return isGlobalPriorityActiveLocked();
Jaewan Kim51255012017-02-24 16:19:14 +09001510 }
RoboErikde9ba392014-09-26 12:51:01 -07001511 }
1512
1513 @Override
RoboErika278ea72014-04-24 14:49:01 -07001514 public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
Jeff Sharkeyfe9a53b2017-03-31 14:08:23 -06001515 if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) return;
RoboErika278ea72014-04-24 14:49:01 -07001516
1517 pw.println("MEDIA SESSION SERVICE (dumpsys media_session)");
1518 pw.println();
1519
1520 synchronized (mLock) {
RoboErika08adb242014-11-21 18:28:18 -08001521 pw.println(mSessionsListeners.size() + " sessions listeners.");
Jaewan Kima7dce192017-02-16 17:10:54 +09001522 pw.println("Global priority session is " + mGlobalPrioritySession);
Jaewan Kim101b4d52017-05-18 13:23:11 +09001523 if (mGlobalPrioritySession != null) {
1524 mGlobalPrioritySession.dump(pw, " ");
1525 }
RoboErik4646d282014-05-13 10:13:04 -07001526 pw.println("User Records:");
Jaewan Kime0ca3f32017-02-16 15:52:39 +09001527 int count = mUserRecords.size();
RoboErika278ea72014-04-24 14:49:01 -07001528 for (int i = 0; i < count; i++) {
Jaewan Kima7dce192017-02-16 17:10:54 +09001529 mUserRecords.valueAt(i).dumpLocked(pw, "");
RoboErika278ea72014-04-24 14:49:01 -07001530 }
Sungsoo Lim875e6972017-11-03 02:22:35 +00001531 mAudioPlayerStateMonitor.dump(getContext(), pw, "");
RoboErika278ea72014-04-24 14:49:01 -07001532 }
1533 }
RoboErik8a2cfc32014-05-16 11:19:38 -07001534
Jaewan Kim7e9e4d92018-02-13 22:20:41 +09001535 /**
Jaewan Kimfed49502018-03-05 17:51:12 +09001536 * Returns if the controller's package is trusted (i.e. has either MEDIA_CONTENT_CONTROL
1537 * permission or an enabled notification listener)
1538 *
Hyundo Moonf84c1c02018-03-19 20:33:27 +09001539 * @param controllerPackageName package name of the controller app
1540 * @param controllerPid pid of the controller app
1541 * @param controllerUid uid of the controller app
Jaewan Kimfed49502018-03-05 17:51:12 +09001542 */
1543 @Override
Hyundo Moonf84c1c02018-03-19 20:33:27 +09001544 public boolean isTrusted(String controllerPackageName, int controllerPid, int controllerUid)
1545 throws RemoteException {
1546 final int uid = Binder.getCallingUid();
Jaewan Kimfed49502018-03-05 17:51:12 +09001547 final long token = Binder.clearCallingIdentity();
1548 try {
Jaewan Kim03cba652018-04-04 16:25:35 +09001549 int controllerUserId = UserHandle.getUserId(controllerUid);
1550 int controllerUidFromPackageName;
1551 try {
1552 controllerUidFromPackageName = getContext().getPackageManager()
1553 .getPackageUidAsUser(controllerPackageName, controllerUserId);
1554 } catch (NameNotFoundException e) {
1555 if (DEBUG) {
1556 Log.d(TAG, "Package " + controllerPackageName + " doesn't exist");
1557 }
1558 return false;
1559 }
1560 if (controllerUidFromPackageName != controllerUid) {
1561 if (DEBUG) {
1562 Log.d(TAG, "Package name " + controllerPackageName
1563 + " doesn't match with the uid " + controllerUid);
1564 }
1565 return false;
1566 }
Hyundo Moonf84c1c02018-03-19 20:33:27 +09001567 return hasMediaControlPermission(UserHandle.getUserId(uid), controllerPackageName,
1568 controllerPid, controllerUid);
Jaewan Kimfed49502018-03-05 17:51:12 +09001569 } finally {
1570 Binder.restoreCallingIdentity(token);
1571 }
Jaewan Kimfed49502018-03-05 17:51:12 +09001572 }
1573
1574 /**
Jaewan Kim7e9e4d92018-02-13 22:20:41 +09001575 * Called when a {@link android.media.MediaSession2} instance is created.
1576 * <p>
1577 * This does two things.
1578 * 1. Keep the newly created session in the service
1579 * 2. Do sanity check to ensure unique id per package, and return result
1580 *
1581 * @param sessionToken SessionToken2 object in bundled form
1582 * @return {@code true} if the session's id isn't used by the package now. {@code false}
1583 * otherwise.
1584 */
Jaewan Kimceb6b6e2018-01-21 20:56:10 +09001585 @Override
Sungsoo Lim117c7f72018-02-13 16:02:24 +09001586 public boolean createSession2(Bundle sessionToken) {
Jaewan Kim129a5622018-03-28 12:19:04 +09001587 if (!USE_MEDIA2_APIS) {
1588 return false;
1589 }
Jaewan Kimf7a77062018-01-27 01:34:24 +09001590 final int uid = Binder.getCallingUid();
Christofer Ã…kersten6823d812018-03-22 23:20:39 +09001591 final SessionToken2 token = SessionToken2.fromBundle(sessionToken);
Jaewan Kimf7a77062018-01-27 01:34:24 +09001592 if (token == null || token.getUid() != uid) {
1593 Log.w(TAG, "onSessionCreated failed, expected caller uid=" + token.getUid()
1594 + " but from uid=" + uid);
1595 }
1596 if (DEBUG) {
Sungsoo Lim117c7f72018-02-13 16:02:24 +09001597 Log.d(TAG, "createSession2: " + token);
Jaewan Kimf7a77062018-01-27 01:34:24 +09001598 }
Jaewan Kimceb6b6e2018-01-21 20:56:10 +09001599 synchronized (mLock) {
Sungsoo Lim117c7f72018-02-13 16:02:24 +09001600 MediaController2 controller = mSessionRecords.get(token);
1601 if (controller != null && controller.isConnected()) {
1602 return false;
Jaewan Kimceb6b6e2018-01-21 20:56:10 +09001603 }
Sungsoo Lim117c7f72018-02-13 16:02:24 +09001604 Context context = getContext();
Sungsoo Lim375efa12018-03-02 10:17:08 +09001605 controller = new MediaController2(context, token, context.getMainExecutor(),
1606 new ControllerCallback(token));
1607 if (addSessionRecordLocked(token, controller)) {
1608 postSessionTokensUpdated(UserHandle.getUserId(token.getUid()));
1609 }
Sungsoo Lim117c7f72018-02-13 16:02:24 +09001610 return true;
Jaewan Kimceb6b6e2018-01-21 20:56:10 +09001611 }
Jaewan Kimf7a77062018-01-27 01:34:24 +09001612 }
1613
Jaewan Kim7e9e4d92018-02-13 22:20:41 +09001614 /**
1615 * Called when a {@link android.media.MediaSession2} instance is closed. (i.e. destroyed)
1616 * <p>
1617 * Ideally service should know that a session is destroyed through the
1618 * {@link android.media.MediaController2.ControllerCallback#onDisconnected()}, which is
1619 * asynchronous call. However, we also need synchronous way together to address timing
1620 * issue. If the package recreates the session almost immediately, which happens commonly
1621 * for tests, service will reject the creation through {@link #onSessionCreated(Bundle)}
1622 * if the service hasn't notified previous destroy yet. This synchronous API will address
1623 * the issue.
1624 *
1625 * @param sessionToken SessionToken2 object in bundled form
1626 */
Jaewan Kimf7a77062018-01-27 01:34:24 +09001627 @Override
Sungsoo Lim117c7f72018-02-13 16:02:24 +09001628 public void destroySession2(Bundle sessionToken) {
Jaewan Kim129a5622018-03-28 12:19:04 +09001629 if (!USE_MEDIA2_APIS) {
1630 return;
1631 }
Jaewan Kimf7a77062018-01-27 01:34:24 +09001632 final int uid = Binder.getCallingUid();
Christofer Ã…kersten6823d812018-03-22 23:20:39 +09001633 final SessionToken2 token = SessionToken2.fromBundle(sessionToken);
Jaewan Kimf7a77062018-01-27 01:34:24 +09001634 if (token == null || token.getUid() != uid) {
1635 Log.w(TAG, "onSessionDestroyed failed, expected caller uid=" + token.getUid()
1636 + " but from uid=" + uid);
1637 }
1638 if (DEBUG) {
Sungsoo Lim117c7f72018-02-13 16:02:24 +09001639 Log.d(TAG, "destroySession2 " + token);
Jaewan Kimf7a77062018-01-27 01:34:24 +09001640 }
Sungsoo Lim117c7f72018-02-13 16:02:24 +09001641 destroySession2Internal(token);
Jaewan Kimceb6b6e2018-01-21 20:56:10 +09001642 }
1643
Hyundo Moonf84c1c02018-03-19 20:33:27 +09001644 // TODO(jaewan): Make this API take userId as an argument (b/73597722)
Jaewan Kimceb6b6e2018-01-21 20:56:10 +09001645 @Override
1646 public List<Bundle> getSessionTokens(boolean activeSessionOnly,
Hyundo Moonf84c1c02018-03-19 20:33:27 +09001647 boolean sessionServiceOnly, String packageName) throws RemoteException {
Jaewan Kim129a5622018-03-28 12:19:04 +09001648 if (!USE_MEDIA2_APIS) {
1649 return null;
1650 }
Hyundo Moonf84c1c02018-03-19 20:33:27 +09001651 final int pid = Binder.getCallingPid();
1652 final int uid = Binder.getCallingUid();
1653 final long token = Binder.clearCallingIdentity();
1654
Jaewan Kimceb6b6e2018-01-21 20:56:10 +09001655 List<Bundle> tokens = new ArrayList<>();
Hyundo Moonf84c1c02018-03-19 20:33:27 +09001656 try {
1657 verifySessionsRequest2(UserHandle.getUserId(uid), packageName, pid, uid);
1658 synchronized (mLock) {
1659 for (Map.Entry<SessionToken2, MediaController2> record
1660 : mSessionRecords.entrySet()) {
1661 boolean isSessionService = (record.getKey().getType() != TYPE_SESSION);
1662 boolean isActive = record.getValue() != null;
1663 if ((activeSessionOnly && !isActive)
1664 || (sessionServiceOnly && !isSessionService)) {
1665 continue;
1666 }
1667 tokens.add(record.getKey().toBundle());
Jaewan Kimceb6b6e2018-01-21 20:56:10 +09001668 }
1669 }
Hyundo Moonf84c1c02018-03-19 20:33:27 +09001670 } finally {
1671 Binder.restoreCallingIdentity(token);
Jaewan Kimceb6b6e2018-01-21 20:56:10 +09001672 }
1673 return tokens;
1674 }
1675
Jaewan Kim379e30d2018-01-29 11:57:04 +09001676 @Override
1677 public void addSessionTokensListener(ISessionTokensListener listener, int userId,
Hyundo Moonf84c1c02018-03-19 20:33:27 +09001678 String packageName) throws RemoteException {
Jaewan Kim129a5622018-03-28 12:19:04 +09001679 if (!USE_MEDIA2_APIS) {
1680 return;
1681 }
Hyundo Moonf84c1c02018-03-19 20:33:27 +09001682 final int pid = Binder.getCallingPid();
1683 final int uid = Binder.getCallingUid();
1684 final long token = Binder.clearCallingIdentity();
1685 try {
1686 int resolvedUserId = verifySessionsRequest2(userId, packageName, pid, uid);
1687 synchronized (mLock) {
1688 final SessionTokensListenerRecord record =
1689 new SessionTokensListenerRecord(listener, resolvedUserId);
1690 try {
1691 listener.asBinder().linkToDeath(record, 0);
1692 } catch (RemoteException e) {
1693 }
1694 mSessionTokensListeners.add(record);
Sungsoo Lim375efa12018-03-02 10:17:08 +09001695 }
Hyundo Moonf84c1c02018-03-19 20:33:27 +09001696 } finally {
1697 Binder.restoreCallingIdentity(token);
Sungsoo Lim375efa12018-03-02 10:17:08 +09001698 }
Jaewan Kim379e30d2018-01-29 11:57:04 +09001699 }
1700
Hyundo Moonf84c1c02018-03-19 20:33:27 +09001701 // TODO(jaewan): Make this API take userId as an argument (b/73597722)
Jaewan Kim379e30d2018-01-29 11:57:04 +09001702 @Override
Hyundo Moonf84c1c02018-03-19 20:33:27 +09001703 public void removeSessionTokensListener(ISessionTokensListener listener,
1704 String packageName) throws RemoteException {
Jaewan Kim129a5622018-03-28 12:19:04 +09001705 if (!USE_MEDIA2_APIS) {
1706 return;
1707 }
Hyundo Moonf84c1c02018-03-19 20:33:27 +09001708 final int pid = Binder.getCallingPid();
1709 final int uid = Binder.getCallingUid();
1710 final long token = Binder.clearCallingIdentity();
1711 try {
1712 verifySessionsRequest2(UserHandle.getUserId(uid), packageName, pid, uid);
1713 synchronized (mLock) {
1714 IBinder listenerBinder = listener.asBinder();
1715 for (SessionTokensListenerRecord record : mSessionTokensListeners) {
1716 if (listenerBinder.equals(record.mListener.asBinder())) {
1717 try {
1718 listenerBinder.unlinkToDeath(record, 0);
1719 } catch (NoSuchElementException e) {
1720 }
1721 mSessionTokensListeners.remove(record);
1722 break;
Sungsoo Lim375efa12018-03-02 10:17:08 +09001723 }
Sungsoo Lim375efa12018-03-02 10:17:08 +09001724 }
1725 }
Hyundo Moonf84c1c02018-03-19 20:33:27 +09001726 } finally {
1727 Binder.restoreCallingIdentity(token);
Sungsoo Lim375efa12018-03-02 10:17:08 +09001728 }
Jaewan Kim379e30d2018-01-29 11:57:04 +09001729 }
1730
Hyundo Moonf84c1c02018-03-19 20:33:27 +09001731 // For MediaSession
RoboErik2e7a9162014-06-04 16:53:45 -07001732 private int verifySessionsRequest(ComponentName componentName, int userId, final int pid,
1733 final int uid) {
1734 String packageName = null;
1735 if (componentName != null) {
1736 // If they gave us a component name verify they own the
1737 // package
1738 packageName = componentName.getPackageName();
1739 enforcePackageName(packageName, uid);
1740 }
1741 // Check that they can make calls on behalf of the user and
1742 // get the final user id
1743 int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId,
1744 true /* allowAll */, true /* requireFull */, "getSessions", packageName);
1745 // Check if they have the permissions or their component is
1746 // enabled for the user they're calling from.
1747 enforceMediaPermissions(componentName, pid, uid, resolvedUserId);
1748 return resolvedUserId;
1749 }
1750
Hyundo Moonf84c1c02018-03-19 20:33:27 +09001751 // For MediaSession2
1752 private int verifySessionsRequest2(int targetUserId, String callerPackageName,
1753 int callerPid, int callerUid) throws RemoteException {
1754 // Check that they can make calls on behalf of the user and get the final user id.
1755 int resolvedUserId = ActivityManager.handleIncomingUser(callerPid, callerUid,
1756 targetUserId, true /* allowAll */, true /* requireFull */, "getSessionTokens",
1757 callerPackageName);
1758 // Check if they have the permissions or their component is
1759 // enabled for the user they're calling from.
1760 if (!hasMediaControlPermission(
1761 resolvedUserId, callerPackageName, callerPid, callerUid)) {
1762 throw new SecurityException("Missing permission to control media.");
1763 }
1764 return resolvedUserId;
1765 }
1766
1767 // For MediaSession2
1768 private boolean hasMediaControlPermission(int resolvedUserId, String packageName,
1769 int pid, int uid) throws RemoteException {
1770 // Allow API calls from the System UI
1771 if (isCurrentVolumeController(pid, uid)) {
1772 return true;
1773 }
1774
1775 // Check if it's system server or has MEDIA_CONTENT_CONTROL.
1776 // Note that system server doesn't have MEDIA_CONTENT_CONTROL, so we need extra
1777 // check here.
1778 if (uid == Process.SYSTEM_UID || getContext().checkPermission(
1779 android.Manifest.permission.MEDIA_CONTENT_CONTROL, pid, uid)
1780 == PackageManager.PERMISSION_GRANTED) {
1781 return true;
1782 } else if (DEBUG) {
1783 Log.d(TAG, packageName + " (uid=" + uid + ") hasn't granted MEDIA_CONTENT_CONTROL");
1784 }
1785
1786 // You may not access another user's content as an enabled listener.
1787 final int userId = UserHandle.getUserId(uid);
1788 if (resolvedUserId != userId) {
1789 return false;
1790 }
1791
1792 // TODO(jaewan): (Post-P) Propose NotificationManager#hasEnabledNotificationListener(
1793 // String pkgName) to notification team for optimization
1794 final List<ComponentName> enabledNotificationListeners =
1795 mNotificationManager.getEnabledNotificationListeners(userId);
1796 if (enabledNotificationListeners != null) {
1797 for (int i = 0; i < enabledNotificationListeners.size(); i++) {
1798 if (TextUtils.equals(packageName,
1799 enabledNotificationListeners.get(i).getPackageName())) {
1800 return true;
1801 }
1802 }
1803 }
1804 if (DEBUG) {
1805 Log.d(TAG, packageName + " (uid=" + uid + ") doesn't have an enabled "
1806 + "notification listener");
1807 }
1808 return false;
1809 }
1810
Jaewan Kim77748b62018-05-03 19:43:33 +09001811 private void dispatchAdjustVolumeLocked(String packageName, int pid, int uid,
1812 boolean asSystemService, int suggestedStream, int direction, int flags) {
Jaewan Kima7dce192017-02-16 17:10:54 +09001813 MediaSessionRecord session = isGlobalPriorityActiveLocked() ? mGlobalPrioritySession
1814 : mCurrentFullUserRecord.mPriorityStack.getDefaultVolumeSession();
Jaewan Kim50269362016-12-23 11:22:02 +09001815
RoboErik9c785402014-11-11 16:52:26 -08001816 boolean preferSuggestedStream = false;
1817 if (isValidLocalStreamType(suggestedStream)
1818 && AudioSystem.isStreamActive(suggestedStream, 0)) {
1819 preferSuggestedStream = true;
1820 }
Jaewan Kimb2781e72017-03-02 09:57:09 +09001821 if (DEBUG_KEY_EVENT) {
Jaewan Kim5e1476e2016-07-19 22:25:39 +09001822 Log.d(TAG, "Adjusting " + session + " by " + direction + ". flags="
1823 + flags + ", suggestedStream=" + suggestedStream
1824 + ", preferSuggestedStream=" + preferSuggestedStream);
1825 }
RoboErik9c785402014-11-11 16:52:26 -08001826 if (session == null || preferSuggestedStream) {
RoboErik94c716e2014-09-14 13:54:31 -07001827 if ((flags & AudioManager.FLAG_ACTIVE_MEDIA_ONLY) != 0
1828 && !AudioSystem.isStreamActive(AudioManager.STREAM_MUSIC, 0)) {
RoboErik3c45c292014-07-08 16:47:31 -07001829 if (DEBUG) {
1830 Log.d(TAG, "No active session to adjust, skipping media only volume event");
RoboErik3c45c292014-07-08 16:47:31 -07001831 }
RoboErikb7c014c2014-07-22 15:58:22 -07001832 return;
RoboErik3c45c292014-07-08 16:47:31 -07001833 }
Shibin George19e84042016-06-14 20:42:13 +05301834
1835 // Execute mAudioService.adjustSuggestedStreamVolume() on
1836 // handler thread of MediaSessionService.
1837 // This will release the MediaSessionService.mLock sooner and avoid
1838 // a potential deadlock between MediaSessionService.mLock and
1839 // ActivityManagerService lock.
1840 mHandler.post(new Runnable() {
1841 @Override
1842 public void run() {
1843 try {
1844 String packageName = getContext().getOpPackageName();
1845 mAudioService.adjustSuggestedStreamVolume(direction, suggestedStream,
1846 flags, packageName, TAG);
Jaewan Kim4f5e8052018-07-24 17:31:10 +09001847 } catch (RemoteException|SecurityException e) {
Shibin George19e84042016-06-14 20:42:13 +05301848 Log.e(TAG, "Error adjusting default volume.", e);
Hyundo Moon739d6c22017-09-18 17:01:48 +09001849 } catch (IllegalArgumentException e) {
1850 Log.e(TAG, "Cannot adjust volume: direction=" + direction
1851 + ", suggestedStream=" + suggestedStream + ", flags=" + flags,
1852 e);
Shibin George19e84042016-06-14 20:42:13 +05301853 }
1854 }
1855 });
RoboErikb69ffd42014-05-30 14:57:59 -07001856 } else {
Jaewan Kim21c23e32018-05-17 16:47:31 +09001857 session.adjustVolume(packageName, pid, uid, null, asSystemService,
Jaewan Kim77748b62018-05-03 19:43:33 +09001858 direction, flags, true);
RoboErikb69ffd42014-05-30 14:57:59 -07001859 }
1860 }
1861
Jaewan Kim77748b62018-05-03 19:43:33 +09001862 private void handleVoiceKeyEventLocked(String packageName, int pid, int uid,
1863 boolean asSystemService, KeyEvent keyEvent, boolean needWakeLock) {
RoboErik9a9d0b52014-05-20 14:53:39 -07001864 int action = keyEvent.getAction();
1865 boolean isLongPress = (keyEvent.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0;
1866 if (action == KeyEvent.ACTION_DOWN) {
1867 if (keyEvent.getRepeatCount() == 0) {
1868 mVoiceButtonDown = true;
1869 mVoiceButtonHandled = false;
1870 } else if (mVoiceButtonDown && !mVoiceButtonHandled && isLongPress) {
1871 mVoiceButtonHandled = true;
1872 startVoiceInput(needWakeLock);
1873 }
1874 } else if (action == KeyEvent.ACTION_UP) {
1875 if (mVoiceButtonDown) {
1876 mVoiceButtonDown = false;
1877 if (!mVoiceButtonHandled && !keyEvent.isCanceled()) {
1878 // Resend the down then send this event through
1879 KeyEvent downEvent = KeyEvent.changeAction(keyEvent, KeyEvent.ACTION_DOWN);
Jaewan Kim77748b62018-05-03 19:43:33 +09001880 dispatchMediaKeyEventLocked(packageName, pid, uid, asSystemService,
1881 downEvent, needWakeLock);
1882 dispatchMediaKeyEventLocked(packageName, pid, uid, asSystemService,
1883 keyEvent, needWakeLock);
RoboErik9a9d0b52014-05-20 14:53:39 -07001884 }
1885 }
1886 }
1887 }
1888
Jaewan Kim77748b62018-05-03 19:43:33 +09001889 private void dispatchMediaKeyEventLocked(String packageName, int pid, int uid,
1890 boolean asSystemService, KeyEvent keyEvent, boolean needWakeLock) {
Jaewan Kima7dce192017-02-16 17:10:54 +09001891 MediaSessionRecord session = mCurrentFullUserRecord.getMediaButtonSessionLocked();
RoboErik9a9d0b52014-05-20 14:53:39 -07001892 if (session != null) {
Jaewan Kim50269362016-12-23 11:22:02 +09001893 if (DEBUG_KEY_EVENT) {
Jaewan Kim5e1476e2016-07-19 22:25:39 +09001894 Log.d(TAG, "Sending " + keyEvent + " to " + session);
RoboErik9a9d0b52014-05-20 14:53:39 -07001895 }
1896 if (needWakeLock) {
1897 mKeyEventReceiver.aquireWakeLockLocked();
1898 }
Jaewan Kim50269362016-12-23 11:22:02 +09001899 // If we don't need a wakelock use -1 as the id so we won't release it later.
Jaewan Kim77748b62018-05-03 19:43:33 +09001900 session.sendMediaButton(packageName, pid, uid, asSystemService, keyEvent,
RoboErik9a9d0b52014-05-20 14:53:39 -07001901 needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1,
Hyundo Moonb1e344e2018-03-22 17:22:14 +09001902 mKeyEventReceiver);
Jaewan Kima7dce192017-02-16 17:10:54 +09001903 if (mCurrentFullUserRecord.mCallback != null) {
Jaewan Kimbd16f452017-02-03 16:21:38 +09001904 try {
Jaewan Kima7dce192017-02-16 17:10:54 +09001905 mCurrentFullUserRecord.mCallback.onMediaKeyEventDispatchedToMediaSession(
Jaewan Kim77748b62018-05-03 19:43:33 +09001906 keyEvent, new MediaSession.Token(session.getControllerBinder()));
Jaewan Kimbd16f452017-02-03 16:21:38 +09001907 } catch (RemoteException e) {
1908 Log.w(TAG, "Failed to send callback", e);
1909 }
1910 }
Jaewan Kima7dce192017-02-16 17:10:54 +09001911 } else if (mCurrentFullUserRecord.mLastMediaButtonReceiver != null
1912 || mCurrentFullUserRecord.mRestoredMediaButtonReceiver != null) {
1913 if (needWakeLock) {
1914 mKeyEventReceiver.aquireWakeLockLocked();
1915 }
1916 Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
1917 mediaButtonIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
1918 mediaButtonIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
Jaewan Kim77748b62018-05-03 19:43:33 +09001919 // TODO: Find a way to also send PID/UID in secure way.
1920 String callerPackageName =
1921 (asSystemService) ? getContext().getPackageName() : packageName;
1922 mediaButtonIntent.putExtra(Intent.EXTRA_PACKAGE_NAME, callerPackageName);
Jaewan Kima7dce192017-02-16 17:10:54 +09001923 try {
1924 if (mCurrentFullUserRecord.mLastMediaButtonReceiver != null) {
1925 PendingIntent receiver = mCurrentFullUserRecord.mLastMediaButtonReceiver;
1926 if (DEBUG_KEY_EVENT) {
1927 Log.d(TAG, "Sending " + keyEvent
Jaewan Kim92dea332017-02-02 11:52:08 +09001928 + " to the last known PendingIntent " + receiver);
Jaewan Kima7dce192017-02-16 17:10:54 +09001929 }
1930 receiver.send(getContext(),
1931 needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1,
1932 mediaButtonIntent, mKeyEventReceiver, mHandler);
1933 if (mCurrentFullUserRecord.mCallback != null) {
1934 ComponentName componentName = mCurrentFullUserRecord
1935 .mLastMediaButtonReceiver.getIntent().getComponent();
1936 if (componentName != null) {
1937 mCurrentFullUserRecord.mCallback
1938 .onMediaKeyEventDispatchedToMediaButtonReceiver(
1939 keyEvent, componentName);
Jaewan Kimbd16f452017-02-03 16:21:38 +09001940 }
RoboErikc8f92d12015-01-05 16:48:07 -08001941 }
Jaewan Kima7dce192017-02-16 17:10:54 +09001942 } else {
1943 ComponentName receiver =
1944 mCurrentFullUserRecord.mRestoredMediaButtonReceiver;
1945 if (DEBUG_KEY_EVENT) {
1946 Log.d(TAG, "Sending " + keyEvent + " to the restored intent "
1947 + receiver);
1948 }
1949 mediaButtonIntent.setComponent(receiver);
1950 getContext().sendBroadcastAsUser(mediaButtonIntent,
Jaewan Kim92dea332017-02-02 11:52:08 +09001951 UserHandle.of(mCurrentFullUserRecord
1952 .mRestoredMediaButtonReceiverUserId));
Jaewan Kima7dce192017-02-16 17:10:54 +09001953 if (mCurrentFullUserRecord.mCallback != null) {
1954 mCurrentFullUserRecord.mCallback
1955 .onMediaKeyEventDispatchedToMediaButtonReceiver(
1956 keyEvent, receiver);
1957 }
RoboErikb214efb2014-07-24 13:20:30 -07001958 }
Jaewan Kima7dce192017-02-16 17:10:54 +09001959 } catch (CanceledException e) {
1960 Log.i(TAG, "Error sending key event to media button receiver "
1961 + mCurrentFullUserRecord.mLastMediaButtonReceiver, e);
1962 } catch (RemoteException e) {
1963 Log.w(TAG, "Failed to send callback", e);
RoboErik9a9d0b52014-05-20 14:53:39 -07001964 }
RoboErik9a9d0b52014-05-20 14:53:39 -07001965 }
1966 }
1967
1968 private void startVoiceInput(boolean needWakeLock) {
1969 Intent voiceIntent = null;
1970 // select which type of search to launch:
1971 // - screen on and device unlocked: action is ACTION_WEB_SEARCH
1972 // - device locked or screen off: action is
1973 // ACTION_VOICE_SEARCH_HANDS_FREE
1974 // with EXTRA_SECURE set to true if the device is securely locked
1975 PowerManager pm = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE);
1976 boolean isLocked = mKeyguardManager != null && mKeyguardManager.isKeyguardLocked();
1977 if (!isLocked && pm.isScreenOn()) {
1978 voiceIntent = new Intent(android.speech.RecognizerIntent.ACTION_WEB_SEARCH);
1979 Log.i(TAG, "voice-based interactions: about to use ACTION_WEB_SEARCH");
1980 } else {
1981 voiceIntent = new Intent(RecognizerIntent.ACTION_VOICE_SEARCH_HANDS_FREE);
1982 voiceIntent.putExtra(RecognizerIntent.EXTRA_SECURE,
1983 isLocked && mKeyguardManager.isKeyguardSecure());
1984 Log.i(TAG, "voice-based interactions: about to use ACTION_VOICE_SEARCH_HANDS_FREE");
1985 }
1986 // start the search activity
1987 if (needWakeLock) {
1988 mMediaEventWakeLock.acquire();
1989 }
1990 try {
1991 if (voiceIntent != null) {
1992 voiceIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
1993 | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
Jaewan Kim8f729082016-06-21 12:36:26 +09001994 if (DEBUG) Log.d(TAG, "voiceIntent: " + voiceIntent);
RoboErik9a9d0b52014-05-20 14:53:39 -07001995 getContext().startActivityAsUser(voiceIntent, UserHandle.CURRENT);
1996 }
1997 } catch (ActivityNotFoundException e) {
1998 Log.w(TAG, "No activity for search: " + e);
1999 } finally {
2000 if (needWakeLock) {
2001 mMediaEventWakeLock.release();
2002 }
2003 }
2004 }
2005
2006 private boolean isVoiceKey(int keyCode) {
Jaewan Kimba18d8e2017-05-12 17:37:57 +09002007 return keyCode == KeyEvent.KEYCODE_HEADSETHOOK
Jaewan Kimfdb612e2017-07-01 09:23:41 +09002008 || (!mHasFeatureLeanback && keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
RoboErik9a9d0b52014-05-20 14:53:39 -07002009 }
2010
Jeff Brown38d3feb2015-03-19 18:26:30 -07002011 private boolean isUserSetupComplete() {
2012 return Settings.Secure.getIntForUser(getContext().getContentResolver(),
2013 Settings.Secure.USER_SETUP_COMPLETE, 0, UserHandle.USER_CURRENT) != 0;
2014 }
2015
RoboErik9c785402014-11-11 16:52:26 -08002016 // we only handle public stream types, which are 0-5
2017 private boolean isValidLocalStreamType(int streamType) {
2018 return streamType >= AudioManager.STREAM_VOICE_CALL
2019 && streamType <= AudioManager.STREAM_NOTIFICATION;
2020 }
2021
Jaewan Kim6e2b01c2017-01-19 16:33:14 -08002022 private class MediaKeyListenerResultReceiver extends ResultReceiver implements Runnable {
Jaewan Kim77748b62018-05-03 19:43:33 +09002023 private final String mPackageName;
2024 private final int mPid;
2025 private final int mUid;
2026 private final boolean mAsSystemService;
2027 private final KeyEvent mKeyEvent;
2028 private final boolean mNeedWakeLock;
Jaewan Kim6e2b01c2017-01-19 16:33:14 -08002029 private boolean mHandled;
2030
Jaewan Kim77748b62018-05-03 19:43:33 +09002031 private MediaKeyListenerResultReceiver(String packageName, int pid, int uid,
2032 boolean asSystemService, KeyEvent keyEvent, boolean needWakeLock) {
Jaewan Kim6e2b01c2017-01-19 16:33:14 -08002033 super(mHandler);
2034 mHandler.postDelayed(this, MEDIA_KEY_LISTENER_TIMEOUT);
Jaewan Kim77748b62018-05-03 19:43:33 +09002035 mPackageName = packageName;
2036 mPid = pid;
2037 mUid = uid;
2038 mAsSystemService = asSystemService;
Jaewan Kim6e2b01c2017-01-19 16:33:14 -08002039 mKeyEvent = keyEvent;
2040 mNeedWakeLock = needWakeLock;
2041 }
2042
2043 @Override
2044 public void run() {
2045 Log.d(TAG, "The media key listener is timed-out for " + mKeyEvent);
2046 dispatchMediaKeyEvent();
2047 }
2048
2049 @Override
2050 protected void onReceiveResult(int resultCode, Bundle resultData) {
2051 if (resultCode == MediaSessionManager.RESULT_MEDIA_KEY_HANDLED) {
2052 mHandled = true;
2053 mHandler.removeCallbacks(this);
2054 return;
2055 }
2056 dispatchMediaKeyEvent();
2057 }
2058
2059 private void dispatchMediaKeyEvent() {
2060 if (mHandled) {
2061 return;
2062 }
2063 mHandled = true;
2064 mHandler.removeCallbacks(this);
2065 synchronized (mLock) {
Jaewan Kima7dce192017-02-16 17:10:54 +09002066 if (!isGlobalPriorityActiveLocked()
Jaewan Kim98003d32017-02-24 18:33:04 +09002067 && isVoiceKey(mKeyEvent.getKeyCode())) {
Jaewan Kim77748b62018-05-03 19:43:33 +09002068 handleVoiceKeyEventLocked(mPackageName, mPid, mUid, mAsSystemService,
2069 mKeyEvent, mNeedWakeLock);
Jaewan Kim98003d32017-02-24 18:33:04 +09002070 } else {
Jaewan Kim77748b62018-05-03 19:43:33 +09002071 dispatchMediaKeyEventLocked(mPackageName, mPid, mUid, mAsSystemService,
2072 mKeyEvent, mNeedWakeLock);
Jaewan Kim98003d32017-02-24 18:33:04 +09002073 }
Jaewan Kim6e2b01c2017-01-19 16:33:14 -08002074 }
2075 }
2076 }
2077
RoboErik418c10c2014-05-19 09:25:25 -07002078 private KeyEventWakeLockReceiver mKeyEventReceiver = new KeyEventWakeLockReceiver(mHandler);
2079
RoboErikb214efb2014-07-24 13:20:30 -07002080 class KeyEventWakeLockReceiver extends ResultReceiver implements Runnable,
2081 PendingIntent.OnFinished {
RoboErik418c10c2014-05-19 09:25:25 -07002082 private final Handler mHandler;
2083 private int mRefCount = 0;
2084 private int mLastTimeoutId = 0;
2085
2086 public KeyEventWakeLockReceiver(Handler handler) {
2087 super(handler);
2088 mHandler = handler;
2089 }
2090
2091 public void onTimeout() {
2092 synchronized (mLock) {
2093 if (mRefCount == 0) {
2094 // We've already released it, so just return
2095 return;
2096 }
2097 mLastTimeoutId++;
2098 mRefCount = 0;
2099 releaseWakeLockLocked();
2100 }
2101 }
2102
2103 public void aquireWakeLockLocked() {
2104 if (mRefCount == 0) {
2105 mMediaEventWakeLock.acquire();
2106 }
2107 mRefCount++;
2108 mHandler.removeCallbacks(this);
2109 mHandler.postDelayed(this, WAKELOCK_TIMEOUT);
2110
2111 }
2112
2113 @Override
2114 public void run() {
2115 onTimeout();
2116 }
2117
RoboErik8a2cfc32014-05-16 11:19:38 -07002118 @Override
2119 protected void onReceiveResult(int resultCode, Bundle resultData) {
RoboErik418c10c2014-05-19 09:25:25 -07002120 if (resultCode < mLastTimeoutId) {
2121 // Ignore results from calls that were before the last
2122 // timeout, just in case.
2123 return;
2124 } else {
2125 synchronized (mLock) {
2126 if (mRefCount > 0) {
2127 mRefCount--;
2128 if (mRefCount == 0) {
2129 releaseWakeLockLocked();
2130 }
2131 }
RoboErik8a2cfc32014-05-16 11:19:38 -07002132 }
2133 }
2134 }
RoboErik418c10c2014-05-19 09:25:25 -07002135
2136 private void releaseWakeLockLocked() {
2137 mMediaEventWakeLock.release();
2138 mHandler.removeCallbacks(this);
2139 }
RoboErikb214efb2014-07-24 13:20:30 -07002140
2141 @Override
2142 public void onSendFinished(PendingIntent pendingIntent, Intent intent, int resultCode,
2143 String resultData, Bundle resultExtras) {
2144 onReceiveResult(resultCode, null);
2145 }
RoboErik8a2cfc32014-05-16 11:19:38 -07002146 };
2147
2148 BroadcastReceiver mKeyEventDone = new BroadcastReceiver() {
2149 @Override
2150 public void onReceive(Context context, Intent intent) {
2151 if (intent == null) {
2152 return;
2153 }
2154 Bundle extras = intent.getExtras();
2155 if (extras == null) {
2156 return;
2157 }
2158 synchronized (mLock) {
2159 if (extras.containsKey(EXTRA_WAKELOCK_ACQUIRED)
2160 && mMediaEventWakeLock.isHeld()) {
2161 mMediaEventWakeLock.release();
2162 }
2163 }
2164 }
2165 };
RoboErik01fe6612014-02-13 14:19:04 -08002166 }
2167
RoboErik2e7a9162014-06-04 16:53:45 -07002168 final class MessageHandler extends Handler {
2169 private static final int MSG_SESSIONS_CHANGED = 1;
Jaewan Kimd61a87b2017-02-17 23:14:10 +09002170 private static final int MSG_VOLUME_INITIAL_DOWN = 2;
Sungsoo Lim375efa12018-03-02 10:17:08 +09002171 private static final int MSG_SESSIONS_TOKENS_CHANGED = 3;
Jaewan Kim92dea332017-02-02 11:52:08 +09002172 private final SparseArray<Integer> mIntegerCache = new SparseArray<>();
RoboErik2e7a9162014-06-04 16:53:45 -07002173
2174 @Override
2175 public void handleMessage(Message msg) {
2176 switch (msg.what) {
2177 case MSG_SESSIONS_CHANGED:
Jaewan Kim92dea332017-02-02 11:52:08 +09002178 pushSessionsChanged((int) msg.obj);
RoboErik2e7a9162014-06-04 16:53:45 -07002179 break;
Jaewan Kimd61a87b2017-02-17 23:14:10 +09002180 case MSG_VOLUME_INITIAL_DOWN:
2181 synchronized (mLock) {
Jaewan Kima7dce192017-02-16 17:10:54 +09002182 FullUserRecord user = mUserRecords.get((int) msg.arg1);
Jaewan Kimd61a87b2017-02-17 23:14:10 +09002183 if (user != null && user.mInitialDownVolumeKeyEvent != null) {
2184 dispatchVolumeKeyLongPressLocked(user.mInitialDownVolumeKeyEvent);
2185 // Mark that the key is already handled.
2186 user.mInitialDownVolumeKeyEvent = null;
2187 }
2188 }
2189 break;
Sungsoo Lim375efa12018-03-02 10:17:08 +09002190 case MSG_SESSIONS_TOKENS_CHANGED:
2191 pushSessionTokensChanged((int) msg.obj);
2192 break;
RoboErik2e7a9162014-06-04 16:53:45 -07002193 }
2194 }
2195
Jaewan Kim92dea332017-02-02 11:52:08 +09002196 public void postSessionsChanged(int userId) {
2197 // Use object instead of the arguments when posting message to remove pending requests.
2198 Integer userIdInteger = mIntegerCache.get(userId);
2199 if (userIdInteger == null) {
2200 userIdInteger = Integer.valueOf(userId);
2201 mIntegerCache.put(userId, userIdInteger);
2202 }
2203 removeMessages(MSG_SESSIONS_CHANGED, userIdInteger);
2204 obtainMessage(MSG_SESSIONS_CHANGED, userIdInteger).sendToTarget();
RoboErik2e7a9162014-06-04 16:53:45 -07002205 }
2206 }
Sungsoo Lim117c7f72018-02-13 16:02:24 +09002207
2208 private class ControllerCallback extends MediaController2.ControllerCallback {
2209
2210 private final SessionToken2 mToken;
2211
2212 ControllerCallback(SessionToken2 token) {
2213 mToken = token;
2214 }
2215
2216 @Override
Jaewan Kim3fb60d52018-03-02 19:23:18 +09002217 public void onDisconnected(MediaController2 controller) {
Sungsoo Lim117c7f72018-02-13 16:02:24 +09002218 destroySession2Internal(mToken);
2219 }
2220 };
Sungsoo Lim375efa12018-03-02 10:17:08 +09002221
2222 private final class SessionTokensListenerRecord implements IBinder.DeathRecipient {
2223 private final ISessionTokensListener mListener;
2224 private final int mUserId;
2225
2226 public SessionTokensListenerRecord(ISessionTokensListener listener, int userId) {
2227 mListener = listener;
Jaewan Kim525c88c2018-03-08 10:47:00 +09002228 // TODO(jaewan): should userId be mapped through mFullUserIds? (b/73597722)
Jaewan Kim005e2bb2018-03-02 15:55:12 +09002229 mUserId = userId;
Sungsoo Lim375efa12018-03-02 10:17:08 +09002230 }
2231
2232 @Override
2233 public void binderDied() {
2234 synchronized (mLock) {
2235 mSessionTokensListeners.remove(this);
2236 }
2237 }
2238 }
2239
2240 private void postSessionTokensUpdated(int userId) {
2241 mHandler.obtainMessage(MessageHandler.MSG_SESSIONS_TOKENS_CHANGED, userId).sendToTarget();
2242 }
2243
2244 private void pushSessionTokensChanged(int userId) {
2245 synchronized (mLock) {
2246 List<Bundle> tokens = new ArrayList<>();
2247 for (SessionToken2 token : mSessionRecords.keySet()) {
2248 // TODO(jaewan): Remove the check for UserHandle.USER_ALL (shouldn't happen).
2249 // This happens when called form buildMediaSessionService2List(...).
Jaewan Kim005e2bb2018-03-02 15:55:12 +09002250 // (b/73760382)
Sungsoo Lim375efa12018-03-02 10:17:08 +09002251 if (UserHandle.getUserId(token.getUid()) == userId
2252 || UserHandle.USER_ALL == userId) {
2253 tokens.add(token.toBundle());
2254 }
2255 }
2256
2257 for (SessionTokensListenerRecord record : mSessionTokensListeners) {
Jaewan Kim005e2bb2018-03-02 15:55:12 +09002258 // TODO(jaewan): Should userId be mapped through mFullUserIds? (b/73760382)
Sungsoo Lim375efa12018-03-02 10:17:08 +09002259 if (record.mUserId == userId || record.mUserId == UserHandle.USER_ALL) {
2260 try {
2261 record.mListener.onSessionTokensChanged(tokens);
2262 } catch (RemoteException e) {
2263 Log.w(TAG, "Failed to notify session tokens changed", e);
2264 }
2265 }
2266 }
2267 }
2268 }
2269
2270 private boolean addSessionRecordLocked(SessionToken2 token) {
2271 return addSessionRecordLocked(token, null);
2272 }
2273
2274 private boolean addSessionRecordLocked(SessionToken2 token, MediaController2 controller) {
2275 if (mSessionRecords.containsKey(token) && mSessionRecords.get(token) == controller) {
2276 // The key/value pair already exists, no need to update.
2277 return false;
2278 }
2279
2280 mSessionRecords.put(token, controller);
2281 return true;
2282 }
2283
2284 private boolean removeSessionRecordLocked(SessionToken2 token) {
2285 if (!mSessionRecords.containsKey(token)) {
2286 // The key is already removed, no need to remove.
2287 return false;
2288 }
2289
2290 mSessionRecords.remove(token);
2291 return true;
2292 }
RoboErik01fe6612014-02-13 14:19:04 -08002293}