blob: ecff334edc0c3a6cb37899976c7cdcf4d9da64fa [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;
Julia Reynoldsb852e562017-06-06 16:14:18 -040022import android.app.INotificationManager;
RoboErik9a9d0b52014-05-20 14:53:39 -070023import android.app.KeyguardManager;
RoboErikb214efb2014-07-24 13:20:30 -070024import android.app.PendingIntent;
25import android.app.PendingIntent.CanceledException;
RoboErik9a9d0b52014-05-20 14:53:39 -070026import android.content.ActivityNotFoundException;
RoboErik8a2cfc32014-05-16 11:19:38 -070027import android.content.BroadcastReceiver;
RoboErike7880d82014-04-30 12:48:25 -070028import android.content.ComponentName;
RoboErik6f0e4dd2014-06-17 16:56:27 -070029import android.content.ContentResolver;
RoboErik01fe6612014-02-13 14:19:04 -080030import android.content.Context;
RoboErik8a2cfc32014-05-16 11:19:38 -070031import android.content.Intent;
Jaewan Kim66d451b2018-02-12 21:23:06 +090032import android.content.IntentFilter;
RoboErika278ea72014-04-24 14:49:01 -070033import android.content.pm.PackageManager;
Jaewan Kimf7a77062018-01-27 01:34:24 +090034import android.content.pm.PackageManager.NameNotFoundException;
Jaewan Kimceb6b6e2018-01-21 20:56:10 +090035import android.content.pm.ResolveInfo;
36import android.content.pm.ServiceInfo;
Jaewan Kima7dce192017-02-16 17:10:54 +090037import android.content.pm.UserInfo;
RoboErik7aef77b2014-08-08 15:56:54 -070038import android.database.ContentObserver;
RoboErik3c45c292014-07-08 16:47:31 -070039import android.media.AudioManager;
Sungsoo Lim875e6972017-11-03 02:22:35 +000040import android.media.AudioPlaybackConfiguration;
RoboErik94c716e2014-09-14 13:54:31 -070041import android.media.AudioSystem;
RoboErikb69ffd42014-05-30 14:57:59 -070042import android.media.IAudioService;
RoboErik19c95182014-06-23 15:38:48 -070043import android.media.IRemoteVolumeController;
Jaewan Kim379e30d2018-01-29 11:57:04 +090044import android.media.ISessionTokensListener;
Jaewan Kimbcecf312018-01-23 19:30:42 +090045import android.media.MediaLibraryService2;
Jaewan Kimceb6b6e2018-01-21 20:56:10 +090046import android.media.MediaSessionService2;
Jaewan Kim04de5de2018-01-25 02:24:03 +090047import android.media.SessionToken2;
RoboErik2e7a9162014-06-04 16:53:45 -070048import android.media.session.IActiveSessionsListener;
Jaewan Kimbd16f452017-02-03 16:21:38 +090049import android.media.session.ICallback;
Jaewan Kim6e2b01c2017-01-19 16:33:14 -080050import android.media.session.IOnMediaKeyListener;
Jaewan Kim50269362016-12-23 11:22:02 +090051import android.media.session.IOnVolumeKeyLongPressListener;
RoboErik07c70772014-03-20 13:33:52 -070052import android.media.session.ISession;
53import android.media.session.ISessionCallback;
54import android.media.session.ISessionManager;
Jeff Browndba34ba2014-06-24 20:46:03 -070055import android.media.session.MediaSession;
Jaewan Kim6e2b01c2017-01-19 16:33:14 -080056import android.media.session.MediaSessionManager;
RoboErik7aef77b2014-08-08 15:56:54 -070057import android.net.Uri;
RoboErik01fe6612014-02-13 14:19:04 -080058import android.os.Binder;
RoboErik8a2cfc32014-05-16 11:19:38 -070059import android.os.Bundle;
RoboErik8ae0f342014-02-24 18:02:08 -080060import android.os.Handler;
RoboErike7880d82014-04-30 12:48:25 -070061import android.os.IBinder;
RoboErik2e7a9162014-06-04 16:53:45 -070062import android.os.Message;
RoboErik8a2cfc32014-05-16 11:19:38 -070063import android.os.PowerManager;
Jaewan Kim8f729082016-06-21 12:36:26 +090064import android.os.Process;
RoboErik01fe6612014-02-13 14:19:04 -080065import android.os.RemoteException;
RoboErik8a2cfc32014-05-16 11:19:38 -070066import android.os.ResultReceiver;
RoboErikb69ffd42014-05-30 14:57:59 -070067import android.os.ServiceManager;
RoboErike7880d82014-04-30 12:48:25 -070068import android.os.UserHandle;
Jaewan Kim8f729082016-06-21 12:36:26 +090069import android.os.UserManager;
RoboErike7880d82014-04-30 12:48:25 -070070import android.provider.Settings;
RoboErik9a9d0b52014-05-20 14:53:39 -070071import android.speech.RecognizerIntent;
RoboErik01fe6612014-02-13 14:19:04 -080072import android.text.TextUtils;
73import android.util.Log;
Jeff Brown38d3feb2015-03-19 18:26:30 -070074import android.util.Slog;
RoboErik4646d282014-05-13 10:13:04 -070075import android.util.SparseArray;
Jaewan Kima7dce192017-02-16 17:10:54 +090076import android.util.SparseIntArray;
RoboErik8a2cfc32014-05-16 11:19:38 -070077import android.view.KeyEvent;
Jaewan Kimd61a87b2017-02-17 23:14:10 +090078import android.view.ViewConfiguration;
RoboErik01fe6612014-02-13 14:19:04 -080079
Jaewan Kim66d451b2018-02-12 21:23:06 +090080import com.android.internal.os.BackgroundThread;
Jeff Sharkeyfe9a53b2017-03-31 14:08:23 -060081import com.android.internal.util.DumpUtils;
RoboErik01fe6612014-02-13 14:19:04 -080082import com.android.server.SystemService;
RoboErika278ea72014-04-24 14:49:01 -070083import com.android.server.Watchdog;
84import com.android.server.Watchdog.Monitor;
RoboErik01fe6612014-02-13 14:19:04 -080085
RoboErika278ea72014-04-24 14:49:01 -070086import java.io.FileDescriptor;
87import java.io.PrintWriter;
RoboErik01fe6612014-02-13 14:19:04 -080088import java.util.ArrayList;
RoboErike7880d82014-04-30 12:48:25 -070089import java.util.List;
RoboErik01fe6612014-02-13 14:19:04 -080090
91/**
92 * System implementation of MediaSessionManager
93 */
RoboErika278ea72014-04-24 14:49:01 -070094public class MediaSessionService extends SystemService implements Monitor {
RoboErik01fe6612014-02-13 14:19:04 -080095 private static final String TAG = "MediaSessionService";
Jaewan Kim92dea332017-02-02 11:52:08 +090096 static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
Jaewan Kim50269362016-12-23 11:22:02 +090097 // Leave log for key event always.
98 private static final boolean DEBUG_KEY_EVENT = true;
RoboErik01fe6612014-02-13 14:19:04 -080099
RoboErik418c10c2014-05-19 09:25:25 -0700100 private static final int WAKELOCK_TIMEOUT = 5000;
Jaewan Kim6e2b01c2017-01-19 16:33:14 -0800101 private static final int MEDIA_KEY_LISTENER_TIMEOUT = 1000;
RoboErik418c10c2014-05-19 09:25:25 -0700102
RoboErik01fe6612014-02-13 14:19:04 -0800103 private final SessionManagerImpl mSessionManagerImpl;
104
Jaewan Kima7dce192017-02-16 17:10:54 +0900105 // Keeps the full user id for each user.
106 private final SparseIntArray mFullUserIds = new SparseIntArray();
107 private final SparseArray<FullUserRecord> mUserRecords = new SparseArray<FullUserRecord>();
RoboErik2e7a9162014-06-04 16:53:45 -0700108 private final ArrayList<SessionsListenerRecord> mSessionsListeners
109 = new ArrayList<SessionsListenerRecord>();
RoboErik01fe6612014-02-13 14:19:04 -0800110 private final Object mLock = new Object();
RoboErik2e7a9162014-06-04 16:53:45 -0700111 private final MessageHandler mHandler = new MessageHandler();
RoboErik8a2cfc32014-05-16 11:19:38 -0700112 private final PowerManager.WakeLock mMediaEventWakeLock;
Jaewan Kimd61a87b2017-02-17 23:14:10 +0900113 private final int mLongPressTimeout;
RoboErik01fe6612014-02-13 14:19:04 -0800114
RoboErik9a9d0b52014-05-20 14:53:39 -0700115 private KeyguardManager mKeyguardManager;
RoboErikb69ffd42014-05-30 14:57:59 -0700116 private IAudioService mAudioService;
RoboErik6f0e4dd2014-06-17 16:56:27 -0700117 private ContentResolver mContentResolver;
RoboErik7aef77b2014-08-08 15:56:54 -0700118 private SettingsObserver mSettingsObserver;
Julia Reynoldsb852e562017-06-06 16:14:18 -0400119 private INotificationManager mNotificationManager;
Jaewan Kimfdb612e2017-07-01 09:23:41 +0900120 private boolean mHasFeatureLeanback;
RoboErik9a9d0b52014-05-20 14:53:39 -0700121
Jaewan Kima7dce192017-02-16 17:10:54 +0900122 // The FullUserRecord of the current users. (i.e. The foreground user that isn't a profile)
123 // It's always not null after the MediaSessionService is started.
124 private FullUserRecord mCurrentFullUserRecord;
125 private MediaSessionRecord mGlobalPrioritySession;
Sungsoo Lim875e6972017-11-03 02:22:35 +0000126 private AudioPlayerStateMonitor mAudioPlayerStateMonitor;
RoboErike7880d82014-04-30 12:48:25 -0700127
RoboErik19c95182014-06-23 15:38:48 -0700128 // Used to notify system UI when remote volume was changed. TODO find a
129 // better way to handle this.
130 private IRemoteVolumeController mRvc;
131
Jaewan Kimceb6b6e2018-01-21 20:56:10 +0900132 // MediaSession2 support
133 // TODO(jaewan): Support multi-user and managed profile.
134 // TODO(jaewan): Make it priority list for handling volume/media key.
135 private final List<MediaSession2Record> mSessions = new ArrayList<>();
136
137 private final MediaSession2Record.SessionDestroyedListener mSessionDestroyedListener =
Jaewan Kimf7a77062018-01-27 01:34:24 +0900138 (record) -> {
Jaewan Kimceb6b6e2018-01-21 20:56:10 +0900139 synchronized (mLock) {
Jaewan Kimf7a77062018-01-27 01:34:24 +0900140 destroySessionLocked(record);
Jaewan Kimceb6b6e2018-01-21 20:56:10 +0900141 }
142 };
143
RoboErik01fe6612014-02-13 14:19:04 -0800144 public MediaSessionService(Context context) {
145 super(context);
146 mSessionManagerImpl = new SessionManagerImpl();
RoboErik8a2cfc32014-05-16 11:19:38 -0700147 PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
148 mMediaEventWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "handleMediaEvent");
Jaewan Kimd61a87b2017-02-17 23:14:10 +0900149 mLongPressTimeout = ViewConfiguration.getLongPressTimeout();
Julia Reynoldsb852e562017-06-06 16:14:18 -0400150 mNotificationManager = INotificationManager.Stub.asInterface(
151 ServiceManager.getService(Context.NOTIFICATION_SERVICE));
RoboErik01fe6612014-02-13 14:19:04 -0800152 }
153
154 @Override
155 public void onStart() {
156 publishBinderService(Context.MEDIA_SESSION_SERVICE, mSessionManagerImpl);
RoboErika278ea72014-04-24 14:49:01 -0700157 Watchdog.getInstance().addMonitor(this);
RoboErik9a9d0b52014-05-20 14:53:39 -0700158 mKeyguardManager =
159 (KeyguardManager) getContext().getSystemService(Context.KEYGUARD_SERVICE);
RoboErikb69ffd42014-05-30 14:57:59 -0700160 mAudioService = getAudioService();
Sungsoo Lim875e6972017-11-03 02:22:35 +0000161 mAudioPlayerStateMonitor = AudioPlayerStateMonitor.getInstance();
162 mAudioPlayerStateMonitor.registerListener(
Sungsoo Lim2afdbc42017-11-01 13:45:59 +0900163 (config, isRemoved) -> {
164 if (isRemoved || !config.isActive() || config.getPlayerType()
165 == AudioPlaybackConfiguration.PLAYER_TYPE_JAM_SOUNDPOOL) {
166 return;
Jaewan Kim92dea332017-02-02 11:52:08 +0900167 }
Sungsoo Lim2afdbc42017-11-01 13:45:59 +0900168 synchronized (mLock) {
169 FullUserRecord user = getFullUserRecordLocked(
170 UserHandle.getUserId(config.getClientUid()));
171 if (user != null) {
172 user.mPriorityStack.updateMediaButtonSessionIfNeeded();
173 }
174 }
175 }, null /* handler */);
Sungsoo Lim875e6972017-11-03 02:22:35 +0000176 mAudioPlayerStateMonitor.registerSelfIntoAudioServiceIfNeeded(mAudioService);
RoboErik6f0e4dd2014-06-17 16:56:27 -0700177 mContentResolver = getContext().getContentResolver();
RoboErik7aef77b2014-08-08 15:56:54 -0700178 mSettingsObserver = new SettingsObserver();
179 mSettingsObserver.observe();
Jaewan Kimfdb612e2017-07-01 09:23:41 +0900180 mHasFeatureLeanback = getContext().getPackageManager().hasSystemFeature(
181 PackageManager.FEATURE_LEANBACK);
RoboErikc8f92d12015-01-05 16:48:07 -0800182
183 updateUser();
Jaewan Kimceb6b6e2018-01-21 20:56:10 +0900184
Jaewan Kim66d451b2018-02-12 21:23:06 +0900185 registerPackageBroadcastReceivers();
Jaewan Kimceb6b6e2018-01-21 20:56:10 +0900186 // TODO(jaewan): Query per users
Jaewan Kimceb6b6e2018-01-21 20:56:10 +0900187 buildMediaSessionService2List();
RoboErikb69ffd42014-05-30 14:57:59 -0700188 }
189
190 private IAudioService getAudioService() {
191 IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE);
192 return IAudioService.Stub.asInterface(b);
RoboErik07c70772014-03-20 13:33:52 -0700193 }
194
Jaewan Kima7dce192017-02-16 17:10:54 +0900195 private boolean isGlobalPriorityActiveLocked() {
196 return mGlobalPrioritySession != null && mGlobalPrioritySession.isActive();
197 }
198
RoboErika8f95142014-05-05 14:23:49 -0700199 public void updateSession(MediaSessionRecord record) {
RoboErike7880d82014-04-30 12:48:25 -0700200 synchronized (mLock) {
Jaewan Kima7dce192017-02-16 17:10:54 +0900201 FullUserRecord user = getFullUserRecordLocked(record.getUserId());
Jaewan Kim101b4d52017-05-18 13:23:11 +0900202 if (user == null) {
203 Log.w(TAG, "Unknown session updated. Ignoring.");
RoboErik4646d282014-05-13 10:13:04 -0700204 return;
205 }
Jaewan Kima7dce192017-02-16 17:10:54 +0900206 if ((record.getFlags() & MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY) != 0) {
Jaewan Kim101b4d52017-05-18 13:23:11 +0900207 if (DEBUG_KEY_EVENT) {
208 Log.d(TAG, "Global priority session is updated, active=" + record.isActive());
209 }
Jaewan Kim92dea332017-02-02 11:52:08 +0900210 user.pushAddressedPlayerChangedLocked();
Jaewan Kim101b4d52017-05-18 13:23:11 +0900211 } else {
212 if (!user.mPriorityStack.contains(record)) {
213 Log.w(TAG, "Unknown session updated. Ignoring.");
214 return;
215 }
216 user.mPriorityStack.onSessionStateChange(record);
Jaewan Kima7dce192017-02-16 17:10:54 +0900217 }
Jaewan Kim92dea332017-02-02 11:52:08 +0900218 mHandler.postSessionsChanged(record.getUserId());
RoboErike7880d82014-04-30 12:48:25 -0700219 }
220 }
221
Jaewan Kimfa85b602017-10-10 16:49:58 +0900222 public void setGlobalPrioritySession(MediaSessionRecord record) {
223 synchronized (mLock) {
224 FullUserRecord user = getFullUserRecordLocked(record.getUserId());
225 if (mGlobalPrioritySession != record) {
226 Log.d(TAG, "Global priority session is changed from " + mGlobalPrioritySession
227 + " to " + record);
228 mGlobalPrioritySession = record;
229 if (user != null && user.mPriorityStack.contains(record)) {
230 // Handle the global priority session separately.
231 // Otherwise, it can be the media button session regardless of the active state
232 // because it or other system components might have been the lastly played media
233 // app.
234 user.mPriorityStack.removeSession(record);
235 }
236 }
237 }
238 }
239
Jaewan Kim101b4d52017-05-18 13:23:11 +0900240 private List<MediaSessionRecord> getActiveSessionsLocked(int userId) {
Jaewan Kimda74a152017-10-03 23:58:11 +0900241 List<MediaSessionRecord> records = new ArrayList<>();
Jaewan Kim101b4d52017-05-18 13:23:11 +0900242 if (userId == UserHandle.USER_ALL) {
Jaewan Kim101b4d52017-05-18 13:23:11 +0900243 int size = mUserRecords.size();
244 for (int i = 0; i < size; i++) {
245 records.addAll(mUserRecords.valueAt(i).mPriorityStack.getActiveSessions(userId));
246 }
247 } else {
248 FullUserRecord user = getFullUserRecordLocked(userId);
249 if (user == null) {
250 Log.w(TAG, "getSessions failed. Unknown user " + userId);
Jaewan Kimda74a152017-10-03 23:58:11 +0900251 return records;
Jaewan Kim101b4d52017-05-18 13:23:11 +0900252 }
Jaewan Kimda74a152017-10-03 23:58:11 +0900253 records.addAll(user.mPriorityStack.getActiveSessions(userId));
Jaewan Kim101b4d52017-05-18 13:23:11 +0900254 }
255
256 // Return global priority session at the first whenever it's asked.
257 if (isGlobalPriorityActiveLocked()
258 && (userId == UserHandle.USER_ALL
259 || userId == mGlobalPrioritySession.getUserId())) {
260 records.add(0, mGlobalPrioritySession);
261 }
262 return records;
263 }
264
RoboErik9c5b7cb2015-01-15 15:09:09 -0800265 /**
Hyundo Moona055f132017-01-13 15:31:06 +0900266 * Tells the system UI that volume has changed on an active remote session.
RoboErik9c5b7cb2015-01-15 15:09:09 -0800267 */
268 public void notifyRemoteVolumeChanged(int flags, MediaSessionRecord session) {
Hyundo Moona055f132017-01-13 15:31:06 +0900269 if (mRvc == null || !session.isActive()) {
RoboErik9c5b7cb2015-01-15 15:09:09 -0800270 return;
271 }
272 try {
273 mRvc.remoteVolumeChanged(session.getControllerBinder(), flags);
274 } catch (Exception e) {
275 Log.wtf(TAG, "Error sending volume change to system UI.", e);
276 }
277 }
278
Jaewan Kim92dea332017-02-02 11:52:08 +0900279 public void onSessionPlaystateChanged(MediaSessionRecord record, int oldState, int newState) {
RoboErika8f95142014-05-05 14:23:49 -0700280 synchronized (mLock) {
Jaewan Kima7dce192017-02-16 17:10:54 +0900281 FullUserRecord user = getFullUserRecordLocked(record.getUserId());
282 if (user == null || !user.mPriorityStack.contains(record)) {
RoboErik4646d282014-05-13 10:13:04 -0700283 Log.d(TAG, "Unknown session changed playback state. Ignoring.");
284 return;
285 }
Jaewan Kim92dea332017-02-02 11:52:08 +0900286 user.mPriorityStack.onPlaystateChanged(record, oldState, newState);
RoboErika8f95142014-05-05 14:23:49 -0700287 }
288 }
289
RoboErik19c95182014-06-23 15:38:48 -0700290 public void onSessionPlaybackTypeChanged(MediaSessionRecord record) {
291 synchronized (mLock) {
Jaewan Kima7dce192017-02-16 17:10:54 +0900292 FullUserRecord user = getFullUserRecordLocked(record.getUserId());
293 if (user == null || !user.mPriorityStack.contains(record)) {
RoboErik19c95182014-06-23 15:38:48 -0700294 Log.d(TAG, "Unknown session changed playback type. Ignoring.");
295 return;
296 }
297 pushRemoteVolumeUpdateLocked(record.getUserId());
298 }
299 }
300
RoboErika278ea72014-04-24 14:49:01 -0700301 @Override
Jaewan Kim8f729082016-06-21 12:36:26 +0900302 public void onStartUser(int userId) {
303 if (DEBUG) Log.d(TAG, "onStartUser: " + userId);
RoboErik4646d282014-05-13 10:13:04 -0700304 updateUser();
305 }
306
307 @Override
Jaewan Kim8f729082016-06-21 12:36:26 +0900308 public void onSwitchUser(int userId) {
309 if (DEBUG) Log.d(TAG, "onSwitchUser: " + userId);
RoboErik4646d282014-05-13 10:13:04 -0700310 updateUser();
311 }
312
313 @Override
Jaewan Kim8f729082016-06-21 12:36:26 +0900314 public void onStopUser(int userId) {
315 if (DEBUG) Log.d(TAG, "onStopUser: " + userId);
RoboErik4646d282014-05-13 10:13:04 -0700316 synchronized (mLock) {
Jaewan Kima7dce192017-02-16 17:10:54 +0900317 FullUserRecord user = getFullUserRecordLocked(userId);
RoboErik4646d282014-05-13 10:13:04 -0700318 if (user != null) {
Jaewan Kima7dce192017-02-16 17:10:54 +0900319 if (user.mFullUserId == userId) {
320 user.destroySessionsForUserLocked(UserHandle.USER_ALL);
321 mUserRecords.remove(userId);
322 } else {
323 user.destroySessionsForUserLocked(userId);
324 }
RoboErik4646d282014-05-13 10:13:04 -0700325 }
Jaewan Kim8f729082016-06-21 12:36:26 +0900326 updateUser();
RoboErik4646d282014-05-13 10:13:04 -0700327 }
328 }
329
330 @Override
RoboErika278ea72014-04-24 14:49:01 -0700331 public void monitor() {
332 synchronized (mLock) {
333 // Check for deadlock
334 }
335 }
336
RoboErik4646d282014-05-13 10:13:04 -0700337 protected void enforcePhoneStatePermission(int pid, int uid) {
338 if (getContext().checkPermission(android.Manifest.permission.MODIFY_PHONE_STATE, pid, uid)
339 != PackageManager.PERMISSION_GRANTED) {
340 throw new SecurityException("Must hold the MODIFY_PHONE_STATE permission.");
341 }
342 }
343
RoboErik01fe6612014-02-13 14:19:04 -0800344 void sessionDied(MediaSessionRecord session) {
RoboErika278ea72014-04-24 14:49:01 -0700345 synchronized (mLock) {
RoboErik01fe6612014-02-13 14:19:04 -0800346 destroySessionLocked(session);
347 }
348 }
349
350 void destroySession(MediaSessionRecord session) {
RoboErika278ea72014-04-24 14:49:01 -0700351 synchronized (mLock) {
RoboErik01fe6612014-02-13 14:19:04 -0800352 destroySessionLocked(session);
353 }
354 }
355
RoboErik4646d282014-05-13 10:13:04 -0700356 private void updateUser() {
357 synchronized (mLock) {
Jaewan Kim8f729082016-06-21 12:36:26 +0900358 UserManager manager = (UserManager) getContext().getSystemService(Context.USER_SERVICE);
Jaewan Kima7dce192017-02-16 17:10:54 +0900359 mFullUserIds.clear();
360 List<UserInfo> allUsers = manager.getUsers();
361 if (allUsers != null) {
362 for (UserInfo userInfo : allUsers) {
363 if (userInfo.isManagedProfile()) {
364 mFullUserIds.put(userInfo.id, userInfo.profileGroupId);
365 } else {
366 mFullUserIds.put(userInfo.id, userInfo.id);
367 if (mUserRecords.get(userInfo.id) == null) {
368 mUserRecords.put(userInfo.id, new FullUserRecord(userInfo.id));
369 }
370 }
Jaewan Kim8f729082016-06-21 12:36:26 +0900371 }
RoboErik4646d282014-05-13 10:13:04 -0700372 }
Jaewan Kima7dce192017-02-16 17:10:54 +0900373 // Ensure that the current full user exists.
374 int currentFullUserId = ActivityManager.getCurrentUser();
375 mCurrentFullUserRecord = mUserRecords.get(currentFullUserId);
376 if (mCurrentFullUserRecord == null) {
377 Log.w(TAG, "Cannot find FullUserInfo for the current user " + currentFullUserId);
378 mCurrentFullUserRecord = new FullUserRecord(currentFullUserId);
379 mUserRecords.put(currentFullUserId, mCurrentFullUserRecord);
380 }
381 mFullUserIds.put(currentFullUserId, currentFullUserId);
RoboErik4646d282014-05-13 10:13:04 -0700382 }
383 }
384
RoboErik7aef77b2014-08-08 15:56:54 -0700385 private void updateActiveSessionListeners() {
386 synchronized (mLock) {
387 for (int i = mSessionsListeners.size() - 1; i >= 0; i--) {
388 SessionsListenerRecord listener = mSessionsListeners.get(i);
389 try {
390 enforceMediaPermissions(listener.mComponentName, listener.mPid, listener.mUid,
391 listener.mUserId);
392 } catch (SecurityException e) {
393 Log.i(TAG, "ActiveSessionsListener " + listener.mComponentName
394 + " is no longer authorized. Disconnecting.");
395 mSessionsListeners.remove(i);
396 try {
397 listener.mListener
398 .onActiveSessionsChanged(new ArrayList<MediaSession.Token>());
399 } catch (Exception e1) {
400 // ignore
401 }
402 }
403 }
404 }
405 }
406
RoboErik4646d282014-05-13 10:13:04 -0700407 /*
408 * When a session is removed several things need to happen.
409 * 1. We need to remove it from the relevant user.
410 * 2. We need to remove it from the priority stack.
411 * 3. We need to remove it from all sessions.
412 * 4. If this is the system priority session we need to clear it.
413 * 5. We need to unlink to death from the cb binder
414 * 6. We need to tell the session to do any final cleanup (onDestroy)
415 */
RoboErik01fe6612014-02-13 14:19:04 -0800416 private void destroySessionLocked(MediaSessionRecord session) {
Insun Kang30be970a2015-11-26 15:35:44 +0900417 if (DEBUG) {
Jaewan Kim5e1476e2016-07-19 22:25:39 +0900418 Log.d(TAG, "Destroying " + session);
Insun Kang30be970a2015-11-26 15:35:44 +0900419 }
Jaewan Kim101b4d52017-05-18 13:23:11 +0900420 FullUserRecord user = getFullUserRecordLocked(session.getUserId());
Jaewan Kima7dce192017-02-16 17:10:54 +0900421 if (mGlobalPrioritySession == session) {
422 mGlobalPrioritySession = null;
Jaewan Kim92dea332017-02-02 11:52:08 +0900423 if (session.isActive() && user != null) {
424 user.pushAddressedPlayerChangedLocked();
425 }
Jaewan Kim101b4d52017-05-18 13:23:11 +0900426 } else {
427 if (user != null) {
428 user.mPriorityStack.removeSession(session);
429 }
Jaewan Kima7dce192017-02-16 17:10:54 +0900430 }
RoboErik4646d282014-05-13 10:13:04 -0700431
432 try {
433 session.getCallback().asBinder().unlinkToDeath(session, 0);
434 } catch (Exception e) {
435 // ignore exceptions while destroying a session.
436 }
437 session.onDestroy();
Jaewan Kim92dea332017-02-02 11:52:08 +0900438 mHandler.postSessionsChanged(session.getUserId());
RoboErik01fe6612014-02-13 14:19:04 -0800439 }
440
Jaewan Kim66d451b2018-02-12 21:23:06 +0900441 private void registerPackageBroadcastReceivers() {
442 // TODO(jaewan): Only consider changed packages when building session service list
443 // when we make this multi-user aware. At that time,
444 // use PackageMonitor.getChangingUserId() to know which user has changed.
445 IntentFilter filter = new IntentFilter();
446 filter.addDataScheme("package");
447 filter.addAction(Intent.ACTION_PACKAGE_ADDED);
448 filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
449 filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
450 filter.addAction(Intent.ACTION_PACKAGES_SUSPENDED);
451 filter.addAction(Intent.ACTION_PACKAGES_UNSUSPENDED);
452 filter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
453 filter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
454 filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
455
456 getContext().registerReceiverAsUser(new BroadcastReceiver() {
457 @Override
458 public void onReceive(Context context, Intent intent) {
459 final int changeUserId = intent.getIntExtra(
460 Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
461 if (changeUserId == UserHandle.USER_NULL) {
462 Log.w(TAG, "Intent broadcast does not contain user handle: "+ intent);
463 return;
464 }
465 // Check if the package is replacing (i.e. reinstalling)
466 final boolean isReplacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
467 // TODO(jaewan): Add multi-user support with this.
468 // final int uid = intent.getIntExtra(Intent.EXTRA_UID, 0);
469
470 if (DEBUG) {
471 Log.d(TAG, "Received change in packages, intent=" + intent);
472 }
473 switch (intent.getAction()) {
474 case Intent.ACTION_PACKAGE_ADDED:
475 case Intent.ACTION_PACKAGE_REMOVED:
476 case Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE:
477 case Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE:
478 if (isReplacing) {
479 // Ignore if the package(s) are replacing. In that case, followings will
480 // happen in order.
481 // 1. ACTION_PACKAGE_REMOVED with isReplacing=true
482 // 2. ACTION_PACKAGE_ADDED with isReplacing=true
483 // 3. ACTION_PACKAGE_REPLACED
484 // (Note that ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE and
485 // ACTION_EXTERNAL_APPLICATIONS_AVAILABLE will be also called with
486 // isReplacing=true for both ASEC hosted packages and packages in
487 // external storage)
488 // Since we only want to update session service list once, ignore
489 // actions above when replacing.
490 // Replacing will be handled only once with the ACTION_PACKAGE_REPLACED.
491 break;
492 }
493 // pass-through
494 case Intent.ACTION_PACKAGE_CHANGED:
495 case Intent.ACTION_PACKAGES_SUSPENDED:
496 case Intent.ACTION_PACKAGES_UNSUSPENDED:
497 case Intent.ACTION_PACKAGE_REPLACED:
498 buildMediaSessionService2List();
499 }
500 }
501 }, UserHandle.ALL, filter, null, BackgroundThread.getHandler());
502 }
503
Jaewan Kimceb6b6e2018-01-21 20:56:10 +0900504 private void buildMediaSessionService2List() {
505 if (DEBUG) {
506 Log.d(TAG, "buildMediaSessionService2List");
507 }
Jaewan Kim66d451b2018-02-12 21:23:06 +0900508 // TODO(jaewan): Also query for managed profile users.
Jaewan Kimf7a77062018-01-27 01:34:24 +0900509 // TODO(jaewan): Similar codes are also at the updatable. Can't we share codes?
510 PackageManager manager = getContext().getPackageManager();
Jaewan Kimbcecf312018-01-23 19:30:42 +0900511 List<ResolveInfo> services = new ArrayList<>();
512 // If multiple actions are declared for a service, browser gets higher priority.
Jaewan Kimf7a77062018-01-27 01:34:24 +0900513 List<ResolveInfo> libraryServices = manager.queryIntentServices(
Jaewan Kimbcecf312018-01-23 19:30:42 +0900514 new Intent(MediaLibraryService2.SERVICE_INTERFACE), PackageManager.GET_META_DATA);
515 if (libraryServices != null) {
516 services.addAll(libraryServices);
517 }
Jaewan Kimf7a77062018-01-27 01:34:24 +0900518 List<ResolveInfo> sessionServices = manager.queryIntentServices(
Jaewan Kimbcecf312018-01-23 19:30:42 +0900519 new Intent(MediaSessionService2.SERVICE_INTERFACE), PackageManager.GET_META_DATA);
520 if (sessionServices != null) {
521 services.addAll(sessionServices);
522 }
Jaewan Kimceb6b6e2018-01-21 20:56:10 +0900523 synchronized (mLock) {
Jaewan Kim66d451b2018-02-12 21:23:06 +0900524 // List to keep the session services that need be removed because they don't exist
525 // in the 'services' above.
526 List<MediaSession2Record> removeCandidates = new ArrayList<>();
527 for (int i = 0; i < mSessions.size(); i++) {
528 if (mSessions.get(i).getToken().getType() != TYPE_SESSION) {
529 removeCandidates.add(mSessions.get(i));
530 }
Jaewan Kimceb6b6e2018-01-21 20:56:10 +0900531 }
532 for (int i = 0; i < services.size(); i++) {
533 if (services.get(i) == null || services.get(i).serviceInfo == null) {
534 continue;
535 }
536 ServiceInfo serviceInfo = services.get(i).serviceInfo;
Jaewan Kimf7a77062018-01-27 01:34:24 +0900537 int uid;
538 try {
Jaewan Kim44fec2d2018-01-29 21:49:41 +0900539 // TODO(jaewan): Do this per user.
Jaewan Kimf7a77062018-01-27 01:34:24 +0900540 uid = manager.getPackageUid(serviceInfo.packageName,
541 PackageManager.GET_META_DATA);
542 } catch (NameNotFoundException e) {
543 continue;
544 }
Jaewan Kim66d451b2018-02-12 21:23:06 +0900545 SessionToken2 token;
Jaewan Kim44fec2d2018-01-29 21:49:41 +0900546 try {
Jaewan Kim66d451b2018-02-12 21:23:06 +0900547 token = new SessionToken2(getContext(),
Jaewan Kim44fec2d2018-01-29 21:49:41 +0900548 serviceInfo.packageName, serviceInfo.name, uid);
Jaewan Kim66d451b2018-02-12 21:23:06 +0900549 } catch (IllegalArgumentException e) {
550 Log.w(TAG, "Invalid session service", e);
551 continue;
552 }
553 boolean found = false;
554 for (int j = 0; j < mSessions.size(); j++) {
555 if (token.equals(mSessions.get(j).getToken())) {
556 // If the token already exists, keep it in the mSessions.
557 removeCandidates.remove(mSessions.get(j));
558 found = true;
559 break;
560 }
561 }
562 if (!found) {
563 // New session service is found.
Jaewan Kimf7a77062018-01-27 01:34:24 +0900564 MediaSession2Record record = new MediaSession2Record(getContext(),
565 token, mSessionDestroyedListener);
Jaewan Kimceb6b6e2018-01-21 20:56:10 +0900566 mSessions.add(record);
567 }
568 }
Jaewan Kim66d451b2018-02-12 21:23:06 +0900569 for (int i = 0; i < removeCandidates.size(); i++) {
570 removeCandidates.get(i).onSessionDestroyed();
571 mSessions.remove(removeCandidates.get(i));
572 }
573 removeCandidates.clear();
Jaewan Kimceb6b6e2018-01-21 20:56:10 +0900574 }
575 if (DEBUG) {
576 Log.d(TAG, "Found " + mSessions.size() + " session services");
577 for (int i = 0; i < mSessions.size(); i++) {
578 Log.d(TAG, " " + mSessions.get(i).getToken());
579 }
580 }
581 }
582
Jaewan Kimf7a77062018-01-27 01:34:24 +0900583 private MediaSession2Record getSessionRecordLocked(int uid, String packageName, String id) {
Jaewan Kimceb6b6e2018-01-21 20:56:10 +0900584 for (int i = 0; i < mSessions.size(); i++) {
Jaewan Kimf7a77062018-01-27 01:34:24 +0900585 SessionToken2 token = mSessions.get(i).getToken();
586 if (token.getUid() == uid && token.getPackageName().equals(packageName)
587 && token.getId().equals(id)) {
588 return mSessions.get(i);
Jaewan Kimceb6b6e2018-01-21 20:56:10 +0900589 }
590 }
591 return null;
592 }
593
Jaewan Kimf7a77062018-01-27 01:34:24 +0900594 private void destroySessionLocked(MediaSession2Record record) {
595 if (DEBUG) {
596 Log.d(TAG, record.toString() + " becomes inactive");
597 }
598 record.onSessionDestroyed();
599 if (record.getToken().getType() == TYPE_SESSION) {
600 mSessions.remove(record);
601 }
602 }
603
RoboErik01fe6612014-02-13 14:19:04 -0800604 private void enforcePackageName(String packageName, int uid) {
605 if (TextUtils.isEmpty(packageName)) {
606 throw new IllegalArgumentException("packageName may not be empty");
607 }
608 String[] packages = getContext().getPackageManager().getPackagesForUid(uid);
609 final int packageCount = packages.length;
610 for (int i = 0; i < packageCount; i++) {
611 if (packageName.equals(packages[i])) {
612 return;
613 }
614 }
615 throw new IllegalArgumentException("packageName is not owned by the calling process");
616 }
617
RoboErike7880d82014-04-30 12:48:25 -0700618 /**
619 * Checks a caller's authorization to register an IRemoteControlDisplay.
620 * Authorization is granted if one of the following is true:
621 * <ul>
622 * <li>the caller has android.Manifest.permission.MEDIA_CONTENT_CONTROL
623 * permission</li>
RoboErika5b02322014-05-07 17:05:49 -0700624 * <li>the caller's listener is one of the enabled notification listeners
625 * for the caller's user</li>
RoboErike7880d82014-04-30 12:48:25 -0700626 * </ul>
627 */
RoboErika5b02322014-05-07 17:05:49 -0700628 private void enforceMediaPermissions(ComponentName compName, int pid, int uid,
629 int resolvedUserId) {
Julia Reynoldsbb983d202017-01-06 09:54:20 -0500630 if (isCurrentVolumeController(uid, pid)) return;
RoboErike7880d82014-04-30 12:48:25 -0700631 if (getContext()
632 .checkPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL, pid, uid)
633 != PackageManager.PERMISSION_GRANTED
RoboErika5b02322014-05-07 17:05:49 -0700634 && !isEnabledNotificationListener(compName, UserHandle.getUserId(uid),
635 resolvedUserId)) {
RoboErike7880d82014-04-30 12:48:25 -0700636 throw new SecurityException("Missing permission to control media.");
637 }
638 }
639
Julia Reynoldsbb983d202017-01-06 09:54:20 -0500640 private boolean isCurrentVolumeController(int uid, int pid) {
641 return getContext().checkPermission(android.Manifest.permission.STATUS_BAR_SERVICE,
642 pid, uid) == PackageManager.PERMISSION_GRANTED;
John Spurlockbe19ed02015-02-22 10:57:55 -0500643 }
644
645 private void enforceSystemUiPermission(String action, int pid, int uid) {
Julia Reynoldsbb983d202017-01-06 09:54:20 -0500646 if (!isCurrentVolumeController(uid, pid)) {
RoboErik19c95182014-06-23 15:38:48 -0700647 throw new SecurityException("Only system ui may " + action);
648 }
649 }
650
RoboErika5b02322014-05-07 17:05:49 -0700651 /**
652 * This checks if the component is an enabled notification listener for the
653 * specified user. Enabled components may only operate on behalf of the user
654 * they're running as.
655 *
656 * @param compName The component that is enabled.
657 * @param userId The user id of the caller.
658 * @param forUserId The user id they're making the request on behalf of.
659 * @return True if the component is enabled, false otherwise
660 */
661 private boolean isEnabledNotificationListener(ComponentName compName, int userId,
662 int forUserId) {
663 if (userId != forUserId) {
664 // You may not access another user's content as an enabled listener.
665 return false;
666 }
RoboErik51fa6bc2014-06-20 14:59:58 -0700667 if (DEBUG) {
668 Log.d(TAG, "Checking if enabled notification listener " + compName);
669 }
RoboErike7880d82014-04-30 12:48:25 -0700670 if (compName != null) {
Julia Reynoldsb852e562017-06-06 16:14:18 -0400671 try {
672 return mNotificationManager.isNotificationListenerAccessGrantedForUser(
673 compName, userId);
674 } catch(RemoteException e) {
675 Log.w(TAG, "Dead NotificationManager in isEnabledNotificationListener", e);
RoboErike7880d82014-04-30 12:48:25 -0700676 }
677 }
678 return false;
679 }
680
RoboErika5b02322014-05-07 17:05:49 -0700681 private MediaSessionRecord createSessionInternal(int callerPid, int callerUid, int userId,
RoboErik4646d282014-05-13 10:13:04 -0700682 String callerPackageName, ISessionCallback cb, String tag) throws RemoteException {
RoboErik01fe6612014-02-13 14:19:04 -0800683 synchronized (mLock) {
RoboErika5b02322014-05-07 17:05:49 -0700684 return createSessionLocked(callerPid, callerUid, userId, callerPackageName, cb, tag);
RoboErik01fe6612014-02-13 14:19:04 -0800685 }
686 }
687
RoboErik4646d282014-05-13 10:13:04 -0700688 /*
689 * When a session is created the following things need to happen.
RoboErik8a2cfc32014-05-16 11:19:38 -0700690 * 1. Its callback binder needs a link to death
RoboErik4646d282014-05-13 10:13:04 -0700691 * 2. It needs to be added to all sessions.
692 * 3. It needs to be added to the priority stack.
693 * 4. It needs to be added to the relevant user record.
694 */
RoboErika5b02322014-05-07 17:05:49 -0700695 private MediaSessionRecord createSessionLocked(int callerPid, int callerUid, int userId,
696 String callerPackageName, ISessionCallback cb, String tag) {
Jaewan Kima7dce192017-02-16 17:10:54 +0900697 FullUserRecord user = getFullUserRecordLocked(userId);
Dongwon Kang8cf39c52016-07-29 13:20:39 -0700698 if (user == null) {
699 Log.wtf(TAG, "Request from invalid user: " + userId);
700 throw new RuntimeException("Session request from invalid user.");
701 }
702
RoboErika5b02322014-05-07 17:05:49 -0700703 final MediaSessionRecord session = new MediaSessionRecord(callerPid, callerUid, userId,
Jaewan Kim92dea332017-02-02 11:52:08 +0900704 callerPackageName, cb, tag, this, mHandler.getLooper());
RoboErik01fe6612014-02-13 14:19:04 -0800705 try {
706 cb.asBinder().linkToDeath(session, 0);
707 } catch (RemoteException e) {
708 throw new RuntimeException("Media Session owner died prematurely.", e);
709 }
RoboErik4646d282014-05-13 10:13:04 -0700710
Jaewan Kim101b4d52017-05-18 13:23:11 +0900711 user.mPriorityStack.addSession(session);
Jaewan Kim92dea332017-02-02 11:52:08 +0900712 mHandler.postSessionsChanged(userId);
RoboErik2e7a9162014-06-04 16:53:45 -0700713
RoboErik01fe6612014-02-13 14:19:04 -0800714 if (DEBUG) {
Jaewan Kim5e1476e2016-07-19 22:25:39 +0900715 Log.d(TAG, "Created session for " + callerPackageName + " with tag " + tag);
RoboErik01fe6612014-02-13 14:19:04 -0800716 }
717 return session;
718 }
719
RoboErik2e7a9162014-06-04 16:53:45 -0700720 private int findIndexOfSessionsListenerLocked(IActiveSessionsListener listener) {
721 for (int i = mSessionsListeners.size() - 1; i >= 0; i--) {
RoboErika08adb242014-11-21 18:28:18 -0800722 if (mSessionsListeners.get(i).mListener.asBinder() == listener.asBinder()) {
RoboErik2e7a9162014-06-04 16:53:45 -0700723 return i;
724 }
725 }
726 return -1;
727 }
728
RoboErik2e7a9162014-06-04 16:53:45 -0700729 private void pushSessionsChanged(int userId) {
730 synchronized (mLock) {
Jaewan Kima7dce192017-02-16 17:10:54 +0900731 FullUserRecord user = getFullUserRecordLocked(userId);
732 if (user == null) {
733 Log.w(TAG, "pushSessionsChanged failed. No user with id=" + userId);
734 return;
735 }
Jaewan Kim101b4d52017-05-18 13:23:11 +0900736 List<MediaSessionRecord> records = getActiveSessionsLocked(userId);
RoboErik2e7a9162014-06-04 16:53:45 -0700737 int size = records.size();
Jeff Browndba34ba2014-06-24 20:46:03 -0700738 ArrayList<MediaSession.Token> tokens = new ArrayList<MediaSession.Token>();
RoboErik2e7a9162014-06-04 16:53:45 -0700739 for (int i = 0; i < size; i++) {
Jeff Browndba34ba2014-06-24 20:46:03 -0700740 tokens.add(new MediaSession.Token(records.get(i).getControllerBinder()));
RoboErik2e7a9162014-06-04 16:53:45 -0700741 }
RoboErik19c95182014-06-23 15:38:48 -0700742 pushRemoteVolumeUpdateLocked(userId);
RoboErik2e7a9162014-06-04 16:53:45 -0700743 for (int i = mSessionsListeners.size() - 1; i >= 0; i--) {
744 SessionsListenerRecord record = mSessionsListeners.get(i);
745 if (record.mUserId == UserHandle.USER_ALL || record.mUserId == userId) {
746 try {
747 record.mListener.onActiveSessionsChanged(tokens);
748 } catch (RemoteException e) {
749 Log.w(TAG, "Dead ActiveSessionsListener in pushSessionsChanged, removing",
750 e);
751 mSessionsListeners.remove(i);
752 }
753 }
754 }
755 }
756 }
757
RoboErik19c95182014-06-23 15:38:48 -0700758 private void pushRemoteVolumeUpdateLocked(int userId) {
759 if (mRvc != null) {
760 try {
Jaewan Kima7dce192017-02-16 17:10:54 +0900761 FullUserRecord user = getFullUserRecordLocked(userId);
762 if (user == null) {
763 Log.w(TAG, "pushRemoteVolumeUpdateLocked failed. No user with id=" + userId);
764 return;
765 }
766 MediaSessionRecord record = user.mPriorityStack.getDefaultRemoteSession(userId);
RoboErik19c95182014-06-23 15:38:48 -0700767 mRvc.updateRemoteController(record == null ? null : record.getControllerBinder());
768 } catch (RemoteException e) {
769 Log.wtf(TAG, "Error sending default remote volume to sys ui.", e);
770 }
771 }
772 }
773
Jaewan Kim92dea332017-02-02 11:52:08 +0900774 /**
775 * Called when the media button receiver for the {@param record} is changed.
776 *
777 * @param record the media session whose media button receiver is updated.
778 */
779 public void onMediaButtonReceiverChanged(MediaSessionRecord record) {
780 synchronized (mLock) {
781 FullUserRecord user = getFullUserRecordLocked(record.getUserId());
782 MediaSessionRecord mediaButtonSession =
783 user.mPriorityStack.getMediaButtonSession();
784 if (record == mediaButtonSession) {
785 user.rememberMediaButtonReceiverLocked(mediaButtonSession);
786 }
787 }
788 }
789
Jaewan Kim50269362016-12-23 11:22:02 +0900790 private String getCallingPackageName(int uid) {
791 String[] packages = getContext().getPackageManager().getPackagesForUid(uid);
792 if (packages != null && packages.length > 0) {
793 return packages[0];
794 }
795 return "";
796 }
797
Jaewan Kimd61a87b2017-02-17 23:14:10 +0900798 private void dispatchVolumeKeyLongPressLocked(KeyEvent keyEvent) {
Jaewan Kimd61a87b2017-02-17 23:14:10 +0900799 try {
Jaewan Kima7dce192017-02-16 17:10:54 +0900800 mCurrentFullUserRecord.mOnVolumeKeyLongPressListener.onVolumeKeyLongPress(keyEvent);
Jaewan Kimd61a87b2017-02-17 23:14:10 +0900801 } catch (RemoteException e) {
802 Log.w(TAG, "Failed to send " + keyEvent + " to volume key long-press listener");
803 }
804 }
805
Jaewan Kima7dce192017-02-16 17:10:54 +0900806 private FullUserRecord getFullUserRecordLocked(int userId) {
807 int fullUserId = mFullUserIds.get(userId, -1);
808 if (fullUserId < 0) {
809 return null;
810 }
811 return mUserRecords.get(fullUserId);
812 }
813
RoboErik4646d282014-05-13 10:13:04 -0700814 /**
Jaewan Kima7dce192017-02-16 17:10:54 +0900815 * Information about a full user and its corresponding managed profiles.
816 *
817 * <p>Since the full user runs together with its managed profiles, a user wouldn't differentiate
818 * them when he/she presses a media/volume button. So keeping media sessions for them in one
819 * place makes more sense and increases the readability.</p>
820 * <p>The contents of this object is guarded by {@link #mLock}.
RoboErik4646d282014-05-13 10:13:04 -0700821 */
Jaewan Kim92dea332017-02-02 11:52:08 +0900822 final class FullUserRecord implements MediaSessionStack.OnMediaButtonSessionChangedListener {
Jaewan Kima7dce192017-02-16 17:10:54 +0900823 private static final String COMPONENT_NAME_USER_ID_DELIM = ",";
824 private final int mFullUserId;
Jaewan Kim92dea332017-02-02 11:52:08 +0900825 private final MediaSessionStack mPriorityStack;
RoboErikb214efb2014-07-24 13:20:30 -0700826 private PendingIntent mLastMediaButtonReceiver;
RoboErikc8f92d12015-01-05 16:48:07 -0800827 private ComponentName mRestoredMediaButtonReceiver;
Jaewan Kima7dce192017-02-16 17:10:54 +0900828 private int mRestoredMediaButtonReceiverUserId;
RoboErik4646d282014-05-13 10:13:04 -0700829
Jaewan Kim50269362016-12-23 11:22:02 +0900830 private IOnVolumeKeyLongPressListener mOnVolumeKeyLongPressListener;
831 private int mOnVolumeKeyLongPressListenerUid;
832 private KeyEvent mInitialDownVolumeKeyEvent;
833 private int mInitialDownVolumeStream;
834 private boolean mInitialDownMusicOnly;
835
Jaewan Kim6e2b01c2017-01-19 16:33:14 -0800836 private IOnMediaKeyListener mOnMediaKeyListener;
837 private int mOnMediaKeyListenerUid;
Jaewan Kima7dce192017-02-16 17:10:54 +0900838 private ICallback mCallback;
Jaewan Kim6e2b01c2017-01-19 16:33:14 -0800839
Jaewan Kima7dce192017-02-16 17:10:54 +0900840 public FullUserRecord(int fullUserId) {
841 mFullUserId = fullUserId;
Sungsoo Lim875e6972017-11-03 02:22:35 +0000842 mPriorityStack = new MediaSessionStack(mAudioPlayerStateMonitor, this);
Jaewan Kima7dce192017-02-16 17:10:54 +0900843 // Restore the remembered media button receiver before the boot.
844 String mediaButtonReceiver = Settings.Secure.getStringForUser(mContentResolver,
845 Settings.System.MEDIA_BUTTON_RECEIVER, mFullUserId);
846 if (mediaButtonReceiver == null) {
847 return;
848 }
849 String[] tokens = mediaButtonReceiver.split(COMPONENT_NAME_USER_ID_DELIM);
850 if (tokens == null || tokens.length != 2) {
851 return;
852 }
853 mRestoredMediaButtonReceiver = ComponentName.unflattenFromString(tokens[0]);
854 mRestoredMediaButtonReceiverUserId = Integer.parseInt(tokens[1]);
RoboErik4646d282014-05-13 10:13:04 -0700855 }
856
Jaewan Kima7dce192017-02-16 17:10:54 +0900857 public void destroySessionsForUserLocked(int userId) {
Jaewan Kim92dea332017-02-02 11:52:08 +0900858 List<MediaSessionRecord> sessions = mPriorityStack.getPriorityList(false, userId);
Jaewan Kima7dce192017-02-16 17:10:54 +0900859 for (MediaSessionRecord session : sessions) {
RoboErik4646d282014-05-13 10:13:04 -0700860 MediaSessionService.this.destroySessionLocked(session);
RoboErik4646d282014-05-13 10:13:04 -0700861 }
862 }
863
RoboErik4646d282014-05-13 10:13:04 -0700864 public void dumpLocked(PrintWriter pw, String prefix) {
Jaewan Kima7dce192017-02-16 17:10:54 +0900865 pw.print(prefix + "Record for full_user=" + mFullUserId);
866 // Dump managed profile user ids associated with this user.
867 int size = mFullUserIds.size();
868 for (int i = 0; i < size; i++) {
869 if (mFullUserIds.keyAt(i) != mFullUserIds.valueAt(i)
870 && mFullUserIds.valueAt(i) == mFullUserId) {
871 pw.print(", profile_user=" + mFullUserIds.keyAt(i));
872 }
873 }
874 pw.println();
RoboErik4646d282014-05-13 10:13:04 -0700875 String indent = prefix + " ";
Jaewan Kima7dce192017-02-16 17:10:54 +0900876 pw.println(indent + "Volume key long-press listener: " + mOnVolumeKeyLongPressListener);
877 pw.println(indent + "Volume key long-press listener package: " +
Jaewan Kim50269362016-12-23 11:22:02 +0900878 getCallingPackageName(mOnVolumeKeyLongPressListenerUid));
Jaewan Kim6e2b01c2017-01-19 16:33:14 -0800879 pw.println(indent + "Media key listener: " + mOnMediaKeyListener);
880 pw.println(indent + "Media key listener package: " +
881 getCallingPackageName(mOnMediaKeyListenerUid));
Jaewan Kima7dce192017-02-16 17:10:54 +0900882 pw.println(indent + "Callback: " + mCallback);
883 pw.println(indent + "Last MediaButtonReceiver: " + mLastMediaButtonReceiver);
884 pw.println(indent + "Restored MediaButtonReceiver: " + mRestoredMediaButtonReceiver);
885 mPriorityStack.dump(pw, indent);
886 }
887
Jaewan Kim92dea332017-02-02 11:52:08 +0900888 @Override
889 public void onMediaButtonSessionChanged(MediaSessionRecord oldMediaButtonSession,
890 MediaSessionRecord newMediaButtonSession) {
891 if (DEBUG_KEY_EVENT) {
Jaewan Kim98e4aaf2017-05-12 17:06:47 +0900892 Log.d(TAG, "Media button session is changed to " + newMediaButtonSession);
Jaewan Kim92dea332017-02-02 11:52:08 +0900893 }
894 synchronized (mLock) {
895 if (oldMediaButtonSession != null) {
896 mHandler.postSessionsChanged(oldMediaButtonSession.getUserId());
897 }
898 if (newMediaButtonSession != null) {
899 rememberMediaButtonReceiverLocked(newMediaButtonSession);
900 mHandler.postSessionsChanged(newMediaButtonSession.getUserId());
901 }
902 pushAddressedPlayerChangedLocked();
903 }
904 }
905
906 // Remember media button receiver and keep it in the persistent storage.
907 public void rememberMediaButtonReceiverLocked(MediaSessionRecord record) {
Jaewan Kima7dce192017-02-16 17:10:54 +0900908 PendingIntent receiver = record.getMediaButtonReceiver();
Jaewan Kima7dce192017-02-16 17:10:54 +0900909 mLastMediaButtonReceiver = receiver;
Jaewan Kim92dea332017-02-02 11:52:08 +0900910 mRestoredMediaButtonReceiver = null;
911 String componentName = "";
912 if (receiver != null) {
913 ComponentName component = receiver.getIntent().getComponent();
914 if (component != null
915 && record.getPackageName().equals(component.getPackageName())) {
916 componentName = component.flattenToString();
917 }
RoboErik4646d282014-05-13 10:13:04 -0700918 }
Jaewan Kim92dea332017-02-02 11:52:08 +0900919 Settings.Secure.putStringForUser(mContentResolver,
920 Settings.System.MEDIA_BUTTON_RECEIVER,
921 componentName + COMPONENT_NAME_USER_ID_DELIM + record.getUserId(),
922 mFullUserId);
RoboErik4646d282014-05-13 10:13:04 -0700923 }
RoboErikc8f92d12015-01-05 16:48:07 -0800924
Jaewan Kima7dce192017-02-16 17:10:54 +0900925 private void pushAddressedPlayerChangedLocked() {
926 if (mCallback == null) {
927 return;
RoboErikc8f92d12015-01-05 16:48:07 -0800928 }
Jaewan Kima7dce192017-02-16 17:10:54 +0900929 try {
930 MediaSessionRecord mediaButtonSession = getMediaButtonSessionLocked();
931 if (mediaButtonSession != null) {
932 mCallback.onAddressedPlayerChangedToMediaSession(
933 new MediaSession.Token(mediaButtonSession.getControllerBinder()));
934 } else if (mCurrentFullUserRecord.mLastMediaButtonReceiver != null) {
935 mCallback.onAddressedPlayerChangedToMediaButtonReceiver(
936 mCurrentFullUserRecord.mLastMediaButtonReceiver
937 .getIntent().getComponent());
938 } else if (mCurrentFullUserRecord.mRestoredMediaButtonReceiver != null) {
939 mCallback.onAddressedPlayerChangedToMediaButtonReceiver(
940 mCurrentFullUserRecord.mRestoredMediaButtonReceiver);
941 }
942 } catch (RemoteException e) {
943 Log.w(TAG, "Failed to pushAddressedPlayerChangedLocked", e);
944 }
945 }
946
947 private MediaSessionRecord getMediaButtonSessionLocked() {
Jaewan Kim92dea332017-02-02 11:52:08 +0900948 return isGlobalPriorityActiveLocked()
949 ? mGlobalPrioritySession : mPriorityStack.getMediaButtonSession();
RoboErikc8f92d12015-01-05 16:48:07 -0800950 }
RoboErik4646d282014-05-13 10:13:04 -0700951 }
952
RoboErik2e7a9162014-06-04 16:53:45 -0700953 final class SessionsListenerRecord implements IBinder.DeathRecipient {
954 private final IActiveSessionsListener mListener;
RoboErik7aef77b2014-08-08 15:56:54 -0700955 private final ComponentName mComponentName;
RoboErik2e7a9162014-06-04 16:53:45 -0700956 private final int mUserId;
RoboErik7aef77b2014-08-08 15:56:54 -0700957 private final int mPid;
958 private final int mUid;
RoboErik2e7a9162014-06-04 16:53:45 -0700959
RoboErik7aef77b2014-08-08 15:56:54 -0700960 public SessionsListenerRecord(IActiveSessionsListener listener,
961 ComponentName componentName,
962 int userId, int pid, int uid) {
RoboErik2e7a9162014-06-04 16:53:45 -0700963 mListener = listener;
RoboErik7aef77b2014-08-08 15:56:54 -0700964 mComponentName = componentName;
RoboErik2e7a9162014-06-04 16:53:45 -0700965 mUserId = userId;
RoboErik7aef77b2014-08-08 15:56:54 -0700966 mPid = pid;
967 mUid = uid;
RoboErik2e7a9162014-06-04 16:53:45 -0700968 }
969
970 @Override
971 public void binderDied() {
972 synchronized (mLock) {
973 mSessionsListeners.remove(this);
974 }
975 }
976 }
977
RoboErik7aef77b2014-08-08 15:56:54 -0700978 final class SettingsObserver extends ContentObserver {
979 private final Uri mSecureSettingsUri = Settings.Secure.getUriFor(
980 Settings.Secure.ENABLED_NOTIFICATION_LISTENERS);
981
982 private SettingsObserver() {
983 super(null);
984 }
985
986 private void observe() {
987 mContentResolver.registerContentObserver(mSecureSettingsUri,
988 false, this, UserHandle.USER_ALL);
989 }
990
991 @Override
992 public void onChange(boolean selfChange, Uri uri) {
993 updateActiveSessionListeners();
994 }
995 }
996
RoboErik07c70772014-03-20 13:33:52 -0700997 class SessionManagerImpl extends ISessionManager.Stub {
RoboErik8a2cfc32014-05-16 11:19:38 -0700998 private static final String EXTRA_WAKELOCK_ACQUIRED =
999 "android.media.AudioService.WAKELOCK_ACQUIRED";
1000 private static final int WAKELOCK_RELEASE_ON_FINISHED = 1980; // magic number
1001
RoboErik9a9d0b52014-05-20 14:53:39 -07001002 private boolean mVoiceButtonDown = false;
1003 private boolean mVoiceButtonHandled = false;
1004
RoboErik07c70772014-03-20 13:33:52 -07001005 @Override
RoboErika5b02322014-05-07 17:05:49 -07001006 public ISession createSession(String packageName, ISessionCallback cb, String tag,
1007 int userId) throws RemoteException {
RoboErik01fe6612014-02-13 14:19:04 -08001008 final int pid = Binder.getCallingPid();
1009 final int uid = Binder.getCallingUid();
1010 final long token = Binder.clearCallingIdentity();
1011 try {
1012 enforcePackageName(packageName, uid);
RoboErika5b02322014-05-07 17:05:49 -07001013 int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId,
1014 false /* allowAll */, true /* requireFull */, "createSession", packageName);
RoboErik01fe6612014-02-13 14:19:04 -08001015 if (cb == null) {
1016 throw new IllegalArgumentException("Controller callback cannot be null");
1017 }
RoboErika5b02322014-05-07 17:05:49 -07001018 return createSessionInternal(pid, uid, resolvedUserId, packageName, cb, tag)
1019 .getSessionBinder();
RoboErike7880d82014-04-30 12:48:25 -07001020 } finally {
1021 Binder.restoreCallingIdentity(token);
1022 }
1023 }
1024
1025 @Override
RoboErika5b02322014-05-07 17:05:49 -07001026 public List<IBinder> getSessions(ComponentName componentName, int userId) {
RoboErike7880d82014-04-30 12:48:25 -07001027 final int pid = Binder.getCallingPid();
1028 final int uid = Binder.getCallingUid();
1029 final long token = Binder.clearCallingIdentity();
1030
1031 try {
RoboErik2e7a9162014-06-04 16:53:45 -07001032 int resolvedUserId = verifySessionsRequest(componentName, userId, pid, uid);
RoboErike7880d82014-04-30 12:48:25 -07001033 ArrayList<IBinder> binders = new ArrayList<IBinder>();
1034 synchronized (mLock) {
Jaewan Kim101b4d52017-05-18 13:23:11 +09001035 List<MediaSessionRecord> records = getActiveSessionsLocked(resolvedUserId);
1036 for (MediaSessionRecord record : records) {
1037 binders.add(record.getControllerBinder().asBinder());
RoboErike7880d82014-04-30 12:48:25 -07001038 }
1039 }
1040 return binders;
RoboErik01fe6612014-02-13 14:19:04 -08001041 } finally {
1042 Binder.restoreCallingIdentity(token);
1043 }
1044 }
RoboErika278ea72014-04-24 14:49:01 -07001045
RoboErik2e7a9162014-06-04 16:53:45 -07001046 @Override
1047 public void addSessionsListener(IActiveSessionsListener listener,
1048 ComponentName componentName, int userId) throws RemoteException {
1049 final int pid = Binder.getCallingPid();
1050 final int uid = Binder.getCallingUid();
1051 final long token = Binder.clearCallingIdentity();
1052
1053 try {
1054 int resolvedUserId = verifySessionsRequest(componentName, userId, pid, uid);
1055 synchronized (mLock) {
1056 int index = findIndexOfSessionsListenerLocked(listener);
1057 if (index != -1) {
1058 Log.w(TAG, "ActiveSessionsListener is already added, ignoring");
1059 return;
1060 }
1061 SessionsListenerRecord record = new SessionsListenerRecord(listener,
RoboErik7aef77b2014-08-08 15:56:54 -07001062 componentName, resolvedUserId, pid, uid);
RoboErik2e7a9162014-06-04 16:53:45 -07001063 try {
1064 listener.asBinder().linkToDeath(record, 0);
1065 } catch (RemoteException e) {
1066 Log.e(TAG, "ActiveSessionsListener is dead, ignoring it", e);
1067 return;
1068 }
1069 mSessionsListeners.add(record);
1070 }
1071 } finally {
1072 Binder.restoreCallingIdentity(token);
1073 }
1074 }
1075
1076 @Override
1077 public void removeSessionsListener(IActiveSessionsListener listener)
1078 throws RemoteException {
1079 synchronized (mLock) {
1080 int index = findIndexOfSessionsListenerLocked(listener);
1081 if (index != -1) {
1082 SessionsListenerRecord record = mSessionsListeners.remove(index);
1083 try {
1084 record.mListener.asBinder().unlinkToDeath(record, 0);
1085 } catch (Exception e) {
1086 // ignore exceptions, the record is being removed
1087 }
1088 }
1089 }
1090 }
1091
RoboErik8a2cfc32014-05-16 11:19:38 -07001092 /**
1093 * Handles the dispatching of the media button events to one of the
1094 * registered listeners, or if there was none, broadcast an
1095 * ACTION_MEDIA_BUTTON intent to the rest of the system.
1096 *
1097 * @param keyEvent a non-null KeyEvent whose key code is one of the
1098 * supported media buttons
1099 * @param needWakeLock true if a PARTIAL_WAKE_LOCK needs to be held
1100 * while this key event is dispatched.
1101 */
1102 @Override
1103 public void dispatchMediaKeyEvent(KeyEvent keyEvent, boolean needWakeLock) {
1104 if (keyEvent == null || !KeyEvent.isMediaKey(keyEvent.getKeyCode())) {
1105 Log.w(TAG, "Attempted to dispatch null or non-media key event.");
1106 return;
1107 }
Jeff Brown38d3feb2015-03-19 18:26:30 -07001108
RoboErik8a2cfc32014-05-16 11:19:38 -07001109 final int pid = Binder.getCallingPid();
1110 final int uid = Binder.getCallingUid();
1111 final long token = Binder.clearCallingIdentity();
RoboErik8a2cfc32014-05-16 11:19:38 -07001112 try {
Jeff Brown221a8272015-03-23 13:53:09 -07001113 if (DEBUG) {
1114 Log.d(TAG, "dispatchMediaKeyEvent, pid=" + pid + ", uid=" + uid + ", event="
1115 + keyEvent);
1116 }
Jeff Brown38d3feb2015-03-19 18:26:30 -07001117 if (!isUserSetupComplete()) {
1118 // Global media key handling can have the side-effect of starting new
1119 // activities which is undesirable while setup is in progress.
1120 Slog.i(TAG, "Not dispatching media key event because user "
1121 + "setup is in progress.");
1122 return;
1123 }
1124
RoboErik8a2cfc32014-05-16 11:19:38 -07001125 synchronized (mLock) {
Jaewan Kima7dce192017-02-16 17:10:54 +09001126 boolean isGlobalPriorityActive = isGlobalPriorityActiveLocked();
Jaewan Kim51255012017-02-24 16:19:14 +09001127 if (isGlobalPriorityActive && uid != Process.SYSTEM_UID) {
1128 // Prevent dispatching key event through reflection while the global
1129 // priority session is active.
1130 Slog.i(TAG, "Only the system can dispatch media key event "
1131 + "to the global priority session.");
1132 return;
1133 }
Jaewan Kim98003d32017-02-24 18:33:04 +09001134 if (!isGlobalPriorityActive) {
Jaewan Kima7dce192017-02-16 17:10:54 +09001135 if (mCurrentFullUserRecord.mOnMediaKeyListener != null) {
Jaewan Kim98003d32017-02-24 18:33:04 +09001136 if (DEBUG_KEY_EVENT) {
Jaewan Kima7dce192017-02-16 17:10:54 +09001137 Log.d(TAG, "Send " + keyEvent + " to the media key listener");
Jaewan Kim98003d32017-02-24 18:33:04 +09001138 }
1139 try {
Jaewan Kima7dce192017-02-16 17:10:54 +09001140 mCurrentFullUserRecord.mOnMediaKeyListener.onMediaKey(keyEvent,
Jaewan Kim98003d32017-02-24 18:33:04 +09001141 new MediaKeyListenerResultReceiver(keyEvent, needWakeLock));
1142 return;
1143 } catch (RemoteException e) {
Jaewan Kima7dce192017-02-16 17:10:54 +09001144 Log.w(TAG, "Failed to send " + keyEvent
1145 + " to the media key listener");
Jaewan Kim98003d32017-02-24 18:33:04 +09001146 }
1147 }
1148 }
Jaewan Kim51255012017-02-24 16:19:14 +09001149 if (!isGlobalPriorityActive && isVoiceKey(keyEvent.getKeyCode())) {
Jaewan Kim6e2b01c2017-01-19 16:33:14 -08001150 handleVoiceKeyEventLocked(keyEvent, needWakeLock);
RoboErik8a2cfc32014-05-16 11:19:38 -07001151 } else {
Jaewan Kim98003d32017-02-24 18:33:04 +09001152 dispatchMediaKeyEventLocked(keyEvent, needWakeLock);
RoboErik8a2cfc32014-05-16 11:19:38 -07001153 }
1154 }
1155 } finally {
1156 Binder.restoreCallingIdentity(token);
1157 }
1158 }
1159
RoboErika278ea72014-04-24 14:49:01 -07001160 @Override
Jaewan Kimbd16f452017-02-03 16:21:38 +09001161 public void setCallback(ICallback callback) {
1162 final int pid = Binder.getCallingPid();
1163 final int uid = Binder.getCallingUid();
1164 final long token = Binder.clearCallingIdentity();
1165 try {
Amith Yamasanie259ad22017-04-24 11:30:19 -07001166 if (!UserHandle.isSameApp(uid, Process.BLUETOOTH_UID)) {
Jaewan Kimbd16f452017-02-03 16:21:38 +09001167 throw new SecurityException("Only Bluetooth service processes can set"
1168 + " Callback");
1169 }
1170 synchronized (mLock) {
Jaewan Kima7dce192017-02-16 17:10:54 +09001171 int userId = UserHandle.getUserId(uid);
1172 FullUserRecord user = getFullUserRecordLocked(userId);
1173 if (user == null || user.mFullUserId != userId) {
1174 Log.w(TAG, "Only the full user can set the callback"
1175 + ", userId=" + userId);
1176 return;
1177 }
1178 user.mCallback = callback;
1179 Log.d(TAG, "The callback " + user.mCallback
Jaewan Kimbd16f452017-02-03 16:21:38 +09001180 + " is set by " + getCallingPackageName(uid));
Jaewan Kima7dce192017-02-16 17:10:54 +09001181 if (user.mCallback == null) {
Jaewan Kimbd16f452017-02-03 16:21:38 +09001182 return;
1183 }
1184 try {
Jaewan Kima7dce192017-02-16 17:10:54 +09001185 user.mCallback.asBinder().linkToDeath(
Jaewan Kimbd16f452017-02-03 16:21:38 +09001186 new IBinder.DeathRecipient() {
1187 @Override
1188 public void binderDied() {
1189 synchronized (mLock) {
Jaewan Kima7dce192017-02-16 17:10:54 +09001190 user.mCallback = null;
Jaewan Kimbd16f452017-02-03 16:21:38 +09001191 }
1192 }
1193 }, 0);
Jaewan Kima7dce192017-02-16 17:10:54 +09001194 user.pushAddressedPlayerChangedLocked();
Jaewan Kimbd16f452017-02-03 16:21:38 +09001195 } catch (RemoteException e) {
1196 Log.w(TAG, "Failed to set callback", e);
Jaewan Kima7dce192017-02-16 17:10:54 +09001197 user.mCallback = null;
Jaewan Kimbd16f452017-02-03 16:21:38 +09001198 }
1199 }
1200 } finally {
1201 Binder.restoreCallingIdentity(token);
1202 }
1203 }
1204
1205 @Override
Jaewan Kim50269362016-12-23 11:22:02 +09001206 public void setOnVolumeKeyLongPressListener(IOnVolumeKeyLongPressListener listener) {
1207 final int pid = Binder.getCallingPid();
1208 final int uid = Binder.getCallingUid();
1209 final long token = Binder.clearCallingIdentity();
1210 try {
1211 // Enforce SET_VOLUME_KEY_LONG_PRESS_LISTENER permission.
1212 if (getContext().checkPermission(
1213 android.Manifest.permission.SET_VOLUME_KEY_LONG_PRESS_LISTENER, pid, uid)
1214 != PackageManager.PERMISSION_GRANTED) {
1215 throw new SecurityException("Must hold the SET_VOLUME_KEY_LONG_PRESS_LISTENER" +
1216 " permission.");
1217 }
1218
1219 synchronized (mLock) {
Jaewan Kima7dce192017-02-16 17:10:54 +09001220 int userId = UserHandle.getUserId(uid);
1221 FullUserRecord user = getFullUserRecordLocked(userId);
1222 if (user == null || user.mFullUserId != userId) {
1223 Log.w(TAG, "Only the full user can set the volume key long-press listener"
1224 + ", userId=" + userId);
1225 return;
1226 }
Jaewan Kim50269362016-12-23 11:22:02 +09001227 if (user.mOnVolumeKeyLongPressListener != null &&
1228 user.mOnVolumeKeyLongPressListenerUid != uid) {
Jaewan Kima7dce192017-02-16 17:10:54 +09001229 Log.w(TAG, "The volume key long-press listener cannot be reset"
1230 + " by another app , mOnVolumeKeyLongPressListener="
1231 + user.mOnVolumeKeyLongPressListenerUid
1232 + ", uid=" + uid);
Jaewan Kim50269362016-12-23 11:22:02 +09001233 return;
1234 }
1235
1236 user.mOnVolumeKeyLongPressListener = listener;
1237 user.mOnVolumeKeyLongPressListenerUid = uid;
1238
Jaewan Kima7dce192017-02-16 17:10:54 +09001239 Log.d(TAG, "The volume key long-press listener "
Jaewan Kim50269362016-12-23 11:22:02 +09001240 + listener + " is set by " + getCallingPackageName(uid));
1241
1242 if (user.mOnVolumeKeyLongPressListener != null) {
1243 try {
1244 user.mOnVolumeKeyLongPressListener.asBinder().linkToDeath(
1245 new IBinder.DeathRecipient() {
1246 @Override
1247 public void binderDied() {
1248 synchronized (mLock) {
1249 user.mOnVolumeKeyLongPressListener = null;
1250 }
1251 }
1252 }, 0);
1253 } catch (RemoteException e) {
1254 Log.w(TAG, "Failed to set death recipient "
1255 + user.mOnVolumeKeyLongPressListener);
1256 user.mOnVolumeKeyLongPressListener = null;
1257 }
1258 }
1259 }
1260 } finally {
1261 Binder.restoreCallingIdentity(token);
1262 }
1263 }
1264
Jaewan Kim6e2b01c2017-01-19 16:33:14 -08001265 @Override
1266 public void setOnMediaKeyListener(IOnMediaKeyListener listener) {
1267 final int pid = Binder.getCallingPid();
1268 final int uid = Binder.getCallingUid();
1269 final long token = Binder.clearCallingIdentity();
1270 try {
1271 // Enforce SET_MEDIA_KEY_LISTENER permission.
1272 if (getContext().checkPermission(
1273 android.Manifest.permission.SET_MEDIA_KEY_LISTENER, pid, uid)
1274 != PackageManager.PERMISSION_GRANTED) {
1275 throw new SecurityException("Must hold the SET_MEDIA_KEY_LISTENER" +
1276 " permission.");
1277 }
1278
1279 synchronized (mLock) {
1280 int userId = UserHandle.getUserId(uid);
Jaewan Kima7dce192017-02-16 17:10:54 +09001281 FullUserRecord user = getFullUserRecordLocked(userId);
1282 if (user == null || user.mFullUserId != userId) {
1283 Log.w(TAG, "Only the full user can set the media key listener"
1284 + ", userId=" + userId);
1285 return;
1286 }
Jaewan Kim6e2b01c2017-01-19 16:33:14 -08001287 if (user.mOnMediaKeyListener != null && user.mOnMediaKeyListenerUid != uid) {
Jaewan Kima7dce192017-02-16 17:10:54 +09001288 Log.w(TAG, "The media key listener cannot be reset by another app. "
1289 + ", mOnMediaKeyListenerUid=" + user.mOnMediaKeyListenerUid
1290 + ", uid=" + uid);
Jaewan Kim6e2b01c2017-01-19 16:33:14 -08001291 return;
1292 }
1293
1294 user.mOnMediaKeyListener = listener;
1295 user.mOnMediaKeyListenerUid = uid;
1296
Jaewan Kima7dce192017-02-16 17:10:54 +09001297 Log.d(TAG, "The media key listener " + user.mOnMediaKeyListener
Jaewan Kim6e2b01c2017-01-19 16:33:14 -08001298 + " is set by " + getCallingPackageName(uid));
1299
1300 if (user.mOnMediaKeyListener != null) {
1301 try {
1302 user.mOnMediaKeyListener.asBinder().linkToDeath(
1303 new IBinder.DeathRecipient() {
1304 @Override
1305 public void binderDied() {
1306 synchronized (mLock) {
1307 user.mOnMediaKeyListener = null;
1308 }
1309 }
1310 }, 0);
1311 } catch (RemoteException e) {
1312 Log.w(TAG, "Failed to set death recipient " + user.mOnMediaKeyListener);
1313 user.mOnMediaKeyListener = null;
1314 }
1315 }
1316 }
1317 } finally {
1318 Binder.restoreCallingIdentity(token);
1319 }
1320 }
1321
Jaewan Kim50269362016-12-23 11:22:02 +09001322 /**
1323 * Handles the dispatching of the volume button events to one of the
1324 * registered listeners. If there's a volume key long-press listener and
1325 * there's no active global priority session, long-pressess will be sent to the
1326 * long-press listener instead of adjusting volume.
1327 *
1328 * @param keyEvent a non-null KeyEvent whose key code is one of the
1329 * {@link KeyEvent#KEYCODE_VOLUME_UP},
1330 * {@link KeyEvent#KEYCODE_VOLUME_DOWN},
1331 * or {@link KeyEvent#KEYCODE_VOLUME_MUTE}.
1332 * @param stream stream type to adjust volume.
1333 * @param musicOnly true if both UI nor haptic feedback aren't needed when adjust volume.
1334 */
1335 @Override
1336 public void dispatchVolumeKeyEvent(KeyEvent keyEvent, int stream, boolean musicOnly) {
1337 if (keyEvent == null ||
1338 (keyEvent.getKeyCode() != KeyEvent.KEYCODE_VOLUME_UP
1339 && keyEvent.getKeyCode() != KeyEvent.KEYCODE_VOLUME_DOWN
1340 && keyEvent.getKeyCode() != KeyEvent.KEYCODE_VOLUME_MUTE)) {
1341 Log.w(TAG, "Attempted to dispatch null or non-volume key event.");
1342 return;
1343 }
1344
1345 final int pid = Binder.getCallingPid();
1346 final int uid = Binder.getCallingUid();
1347 final long token = Binder.clearCallingIdentity();
1348
Jaewan Kimb2781e72017-03-02 09:57:09 +09001349 if (DEBUG_KEY_EVENT) {
Jaewan Kim50269362016-12-23 11:22:02 +09001350 Log.d(TAG, "dispatchVolumeKeyEvent, pid=" + pid + ", uid=" + uid + ", event="
1351 + keyEvent);
1352 }
1353
1354 try {
1355 synchronized (mLock) {
Jaewan Kima7dce192017-02-16 17:10:54 +09001356 if (isGlobalPriorityActiveLocked()
1357 || mCurrentFullUserRecord.mOnVolumeKeyLongPressListener == null) {
Jaewan Kim50269362016-12-23 11:22:02 +09001358 dispatchVolumeKeyEventLocked(keyEvent, stream, musicOnly);
1359 } else {
1360 // TODO: Consider the case when both volume up and down keys are pressed
1361 // at the same time.
1362 if (keyEvent.getAction() == KeyEvent.ACTION_DOWN) {
1363 if (keyEvent.getRepeatCount() == 0) {
Jaewan Kimd61a87b2017-02-17 23:14:10 +09001364 // Keeps the copy of the KeyEvent because it can be reused.
Jaewan Kima7dce192017-02-16 17:10:54 +09001365 mCurrentFullUserRecord.mInitialDownVolumeKeyEvent =
1366 KeyEvent.obtain(keyEvent);
1367 mCurrentFullUserRecord.mInitialDownVolumeStream = stream;
1368 mCurrentFullUserRecord.mInitialDownMusicOnly = musicOnly;
Jaewan Kimd61a87b2017-02-17 23:14:10 +09001369 mHandler.sendMessageDelayed(
1370 mHandler.obtainMessage(
Jaewan Kima7dce192017-02-16 17:10:54 +09001371 MessageHandler.MSG_VOLUME_INITIAL_DOWN,
1372 mCurrentFullUserRecord.mFullUserId, 0),
Jaewan Kimd61a87b2017-02-17 23:14:10 +09001373 mLongPressTimeout);
Jaewan Kim50269362016-12-23 11:22:02 +09001374 }
1375 if (keyEvent.getRepeatCount() > 0 || keyEvent.isLongPress()) {
Jaewan Kimd61a87b2017-02-17 23:14:10 +09001376 mHandler.removeMessages(MessageHandler.MSG_VOLUME_INITIAL_DOWN);
Jaewan Kima7dce192017-02-16 17:10:54 +09001377 if (mCurrentFullUserRecord.mInitialDownVolumeKeyEvent != null) {
Jaewan Kim50269362016-12-23 11:22:02 +09001378 dispatchVolumeKeyLongPressLocked(
Jaewan Kima7dce192017-02-16 17:10:54 +09001379 mCurrentFullUserRecord.mInitialDownVolumeKeyEvent);
Jaewan Kim50269362016-12-23 11:22:02 +09001380 // Mark that the key is already handled.
Jaewan Kima7dce192017-02-16 17:10:54 +09001381 mCurrentFullUserRecord.mInitialDownVolumeKeyEvent = null;
Jaewan Kim50269362016-12-23 11:22:02 +09001382 }
1383 dispatchVolumeKeyLongPressLocked(keyEvent);
1384 }
1385 } else { // if up
Jaewan Kimd61a87b2017-02-17 23:14:10 +09001386 mHandler.removeMessages(MessageHandler.MSG_VOLUME_INITIAL_DOWN);
Jaewan Kima7dce192017-02-16 17:10:54 +09001387 if (mCurrentFullUserRecord.mInitialDownVolumeKeyEvent != null
1388 && mCurrentFullUserRecord.mInitialDownVolumeKeyEvent
1389 .getDownTime() == keyEvent.getDownTime()) {
Jaewan Kim50269362016-12-23 11:22:02 +09001390 // Short-press. Should change volume.
1391 dispatchVolumeKeyEventLocked(
Jaewan Kima7dce192017-02-16 17:10:54 +09001392 mCurrentFullUserRecord.mInitialDownVolumeKeyEvent,
1393 mCurrentFullUserRecord.mInitialDownVolumeStream,
1394 mCurrentFullUserRecord.mInitialDownMusicOnly);
Jaewan Kim50269362016-12-23 11:22:02 +09001395 dispatchVolumeKeyEventLocked(keyEvent, stream, musicOnly);
1396 } else {
1397 dispatchVolumeKeyLongPressLocked(keyEvent);
1398 }
1399 }
1400 }
1401 }
1402 } finally {
1403 Binder.restoreCallingIdentity(token);
1404 }
1405 }
1406
Jaewan Kim50269362016-12-23 11:22:02 +09001407 private void dispatchVolumeKeyEventLocked(
1408 KeyEvent keyEvent, int stream, boolean musicOnly) {
1409 boolean down = keyEvent.getAction() == KeyEvent.ACTION_DOWN;
1410 boolean up = keyEvent.getAction() == KeyEvent.ACTION_UP;
1411 int direction = 0;
1412 boolean isMute = false;
1413 switch (keyEvent.getKeyCode()) {
1414 case KeyEvent.KEYCODE_VOLUME_UP:
1415 direction = AudioManager.ADJUST_RAISE;
1416 break;
1417 case KeyEvent.KEYCODE_VOLUME_DOWN:
1418 direction = AudioManager.ADJUST_LOWER;
1419 break;
1420 case KeyEvent.KEYCODE_VOLUME_MUTE:
1421 isMute = true;
1422 break;
1423 }
1424 if (down || up) {
1425 int flags = AudioManager.FLAG_FROM_KEY;
1426 if (musicOnly) {
1427 // This flag is used when the screen is off to only affect active media.
1428 flags |= AudioManager.FLAG_ACTIVE_MEDIA_ONLY;
1429 } else {
1430 // These flags are consistent with the home screen
1431 if (up) {
1432 flags |= AudioManager.FLAG_PLAY_SOUND | AudioManager.FLAG_VIBRATE;
1433 } else {
1434 flags |= AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_VIBRATE;
1435 }
1436 }
1437 if (direction != 0) {
1438 // If this is action up we want to send a beep for non-music events
1439 if (up) {
1440 direction = 0;
1441 }
1442 dispatchAdjustVolumeLocked(stream, direction, flags);
1443 } else if (isMute) {
1444 if (down && keyEvent.getRepeatCount() == 0) {
1445 dispatchAdjustVolumeLocked(stream, AudioManager.ADJUST_TOGGLE_MUTE, flags);
1446 }
1447 }
1448 }
1449 }
1450
1451 @Override
RoboErik7c82ced2014-12-04 17:39:08 -08001452 public void dispatchAdjustVolume(int suggestedStream, int delta, int flags) {
RoboErikb69ffd42014-05-30 14:57:59 -07001453 final long token = Binder.clearCallingIdentity();
1454 try {
1455 synchronized (mLock) {
Jaewan Kim50269362016-12-23 11:22:02 +09001456 dispatchAdjustVolumeLocked(suggestedStream, delta, flags);
RoboErikb69ffd42014-05-30 14:57:59 -07001457 }
1458 } finally {
1459 Binder.restoreCallingIdentity(token);
1460 }
1461 }
1462
1463 @Override
RoboErik19c95182014-06-23 15:38:48 -07001464 public void setRemoteVolumeController(IRemoteVolumeController rvc) {
1465 final int pid = Binder.getCallingPid();
1466 final int uid = Binder.getCallingUid();
1467 final long token = Binder.clearCallingIdentity();
1468 try {
John Spurlockeb69e242015-02-17 17:15:04 -05001469 enforceSystemUiPermission("listen for volume changes", pid, uid);
RoboErik19c95182014-06-23 15:38:48 -07001470 mRvc = rvc;
1471 } finally {
1472 Binder.restoreCallingIdentity(token);
1473 }
1474 }
1475
1476 @Override
RoboErikde9ba392014-09-26 12:51:01 -07001477 public boolean isGlobalPriorityActive() {
Jaewan Kim51255012017-02-24 16:19:14 +09001478 synchronized (mLock) {
Jaewan Kima7dce192017-02-16 17:10:54 +09001479 return isGlobalPriorityActiveLocked();
Jaewan Kim51255012017-02-24 16:19:14 +09001480 }
RoboErikde9ba392014-09-26 12:51:01 -07001481 }
1482
1483 @Override
RoboErika278ea72014-04-24 14:49:01 -07001484 public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
Jeff Sharkeyfe9a53b2017-03-31 14:08:23 -06001485 if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) return;
RoboErika278ea72014-04-24 14:49:01 -07001486
1487 pw.println("MEDIA SESSION SERVICE (dumpsys media_session)");
1488 pw.println();
1489
1490 synchronized (mLock) {
RoboErika08adb242014-11-21 18:28:18 -08001491 pw.println(mSessionsListeners.size() + " sessions listeners.");
Jaewan Kima7dce192017-02-16 17:10:54 +09001492 pw.println("Global priority session is " + mGlobalPrioritySession);
Jaewan Kim101b4d52017-05-18 13:23:11 +09001493 if (mGlobalPrioritySession != null) {
1494 mGlobalPrioritySession.dump(pw, " ");
1495 }
RoboErik4646d282014-05-13 10:13:04 -07001496 pw.println("User Records:");
Jaewan Kime0ca3f32017-02-16 15:52:39 +09001497 int count = mUserRecords.size();
RoboErika278ea72014-04-24 14:49:01 -07001498 for (int i = 0; i < count; i++) {
Jaewan Kima7dce192017-02-16 17:10:54 +09001499 mUserRecords.valueAt(i).dumpLocked(pw, "");
RoboErika278ea72014-04-24 14:49:01 -07001500 }
Sungsoo Lim875e6972017-11-03 02:22:35 +00001501 mAudioPlayerStateMonitor.dump(getContext(), pw, "");
Jaewan Kima70b3e52018-01-29 19:36:30 +09001502
1503 // TODO(jaewan): Remove this debug command before ship.
1504 if (args != null && args.length > 0 && "--purge".equals(args[0])) {
1505 mSessions.clear();
1506 }
1507 pw.println();
1508 pw.println("Session2: size=" + mSessions.size());
1509 for (int i = 0; i < mSessions.size(); i++) {
1510 pw.println(" " + mSessions.get(i));
1511 }
RoboErika278ea72014-04-24 14:49:01 -07001512 }
1513 }
RoboErik8a2cfc32014-05-16 11:19:38 -07001514
Jaewan Kim7e9e4d92018-02-13 22:20:41 +09001515 /**
1516 * Called when a {@link android.media.MediaSession2} instance is created.
1517 * <p>
1518 * This does two things.
1519 * 1. Keep the newly created session in the service
1520 * 2. Do sanity check to ensure unique id per package, and return result
1521 *
1522 * @param sessionToken SessionToken2 object in bundled form
1523 * @return {@code true} if the session's id isn't used by the package now. {@code false}
1524 * otherwise.
1525 */
Jaewan Kimceb6b6e2018-01-21 20:56:10 +09001526 @Override
Jaewan Kimf7a77062018-01-27 01:34:24 +09001527 public boolean onSessionCreated(Bundle sessionToken) {
1528 final int uid = Binder.getCallingUid();
1529 final int pid = Binder.getCallingPid();
1530 final SessionToken2 token = SessionToken2.fromBundle(getContext(), sessionToken);
1531 if (token == null || token.getUid() != uid) {
1532 Log.w(TAG, "onSessionCreated failed, expected caller uid=" + token.getUid()
1533 + " but from uid=" + uid);
1534 }
1535 if (DEBUG) {
1536 Log.d(TAG, "onSessionCreated " + token);
1537 }
Jaewan Kimceb6b6e2018-01-21 20:56:10 +09001538 synchronized (mLock) {
Jaewan Kimf7a77062018-01-27 01:34:24 +09001539 MediaSession2Record record = getSessionRecordLocked(
1540 uid, token.getPackageName(), token.getId());
1541 if (record != null) {
1542 return record.onSessionCreated(token);
Jaewan Kimceb6b6e2018-01-21 20:56:10 +09001543 } else {
Jaewan Kimf7a77062018-01-27 01:34:24 +09001544 record = new MediaSession2Record(
1545 getContext(), token, mSessionDestroyedListener);
1546 mSessions.add(record);
1547 return record.onSessionCreated(token);
Jaewan Kimceb6b6e2018-01-21 20:56:10 +09001548 }
1549 }
Jaewan Kimf7a77062018-01-27 01:34:24 +09001550 }
1551
Jaewan Kim7e9e4d92018-02-13 22:20:41 +09001552 /**
1553 * Called when a {@link android.media.MediaSession2} instance is closed. (i.e. destroyed)
1554 * <p>
1555 * Ideally service should know that a session is destroyed through the
1556 * {@link android.media.MediaController2.ControllerCallback#onDisconnected()}, which is
1557 * asynchronous call. However, we also need synchronous way together to address timing
1558 * issue. If the package recreates the session almost immediately, which happens commonly
1559 * for tests, service will reject the creation through {@link #onSessionCreated(Bundle)}
1560 * if the service hasn't notified previous destroy yet. This synchronous API will address
1561 * the issue.
1562 *
1563 * @param sessionToken SessionToken2 object in bundled form
1564 */
Jaewan Kimf7a77062018-01-27 01:34:24 +09001565 @Override
1566 public void onSessionDestroyed(Bundle sessionToken) {
1567 final int uid = Binder.getCallingUid();
1568 final int pid = Binder.getCallingPid();
1569 final SessionToken2 token = SessionToken2.fromBundle(getContext(), sessionToken);
1570 if (token == null || token.getUid() != uid) {
1571 Log.w(TAG, "onSessionDestroyed failed, expected caller uid=" + token.getUid()
1572 + " but from uid=" + uid);
1573 }
1574 if (DEBUG) {
1575 Log.d(TAG, "onSessionDestroyed " + token);
1576 }
1577 synchronized (mLock) {
1578 MediaSession2Record record = getSessionRecordLocked(
1579 uid, token.getPackageName(), token.getId());
1580 if (record != null) {
1581 record.onSessionDestroyed();
1582 } else {
1583 if (DEBUG) {
1584 Log.d(TAG, "Cannot find a session record to destroy. uid=" + uid
1585 + ", pkg=" + token.getPackageName() + ", id=" + token.getId());
1586 }
1587 }
1588 }
Jaewan Kimceb6b6e2018-01-21 20:56:10 +09001589 }
1590
1591 // TODO(jaewan): Protect this API with permission
Jaewan Kimceb6b6e2018-01-21 20:56:10 +09001592 @Override
1593 public List<Bundle> getSessionTokens(boolean activeSessionOnly,
1594 boolean sessionServiceOnly) throws RemoteException {
1595 List<Bundle> tokens = new ArrayList<>();
1596 synchronized (mLock) {
1597 for (int i = 0; i < mSessions.size(); i++) {
1598 MediaSession2Record record = mSessions.get(i);
Jaewan Kimf7a77062018-01-27 01:34:24 +09001599 boolean isSessionService = (record.getToken().getType() != TYPE_SESSION);
1600 boolean isActive = record.getController() != null;
Jaewan Kimceb6b6e2018-01-21 20:56:10 +09001601 if ((!activeSessionOnly && isSessionService)
1602 || (!sessionServiceOnly && isActive)) {
Jaewan Kim04de5de2018-01-25 02:24:03 +09001603 SessionToken2 token = record.getToken();
Jaewan Kimceb6b6e2018-01-21 20:56:10 +09001604 if (token != null) {
1605 tokens.add(token.toBundle());
1606 } else {
1607 Log.wtf(TAG, "Null token for record=" + record);
1608 }
1609 }
1610 }
1611 }
1612 return tokens;
1613 }
1614
Jaewan Kim66d451b2018-02-12 21:23:06 +09001615 // TODO(jaewan): Protect this API with permission
Jaewan Kim379e30d2018-01-29 11:57:04 +09001616 @Override
1617 public void addSessionTokensListener(ISessionTokensListener listener, int userId,
1618 String packageName) {
1619 // TODO(jaewan): Implement.
1620 }
1621
Jaewan Kim66d451b2018-02-12 21:23:06 +09001622 // TODO(jaewan): Protect this API with permission
Jaewan Kim379e30d2018-01-29 11:57:04 +09001623 @Override
1624 public void removeSessionTokensListener(ISessionTokensListener listener) {
1625 // TODO(jaewan): Implement
1626 }
1627
RoboErik2e7a9162014-06-04 16:53:45 -07001628 private int verifySessionsRequest(ComponentName componentName, int userId, final int pid,
1629 final int uid) {
1630 String packageName = null;
1631 if (componentName != null) {
1632 // If they gave us a component name verify they own the
1633 // package
1634 packageName = componentName.getPackageName();
1635 enforcePackageName(packageName, uid);
1636 }
1637 // Check that they can make calls on behalf of the user and
1638 // get the final user id
1639 int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId,
1640 true /* allowAll */, true /* requireFull */, "getSessions", packageName);
1641 // Check if they have the permissions or their component is
1642 // enabled for the user they're calling from.
1643 enforceMediaPermissions(componentName, pid, uid, resolvedUserId);
1644 return resolvedUserId;
1645 }
1646
Jaewan Kim50269362016-12-23 11:22:02 +09001647 private void dispatchAdjustVolumeLocked(int suggestedStream, int direction, int flags) {
Jaewan Kima7dce192017-02-16 17:10:54 +09001648 MediaSessionRecord session = isGlobalPriorityActiveLocked() ? mGlobalPrioritySession
1649 : mCurrentFullUserRecord.mPriorityStack.getDefaultVolumeSession();
Jaewan Kim50269362016-12-23 11:22:02 +09001650
RoboErik9c785402014-11-11 16:52:26 -08001651 boolean preferSuggestedStream = false;
1652 if (isValidLocalStreamType(suggestedStream)
1653 && AudioSystem.isStreamActive(suggestedStream, 0)) {
1654 preferSuggestedStream = true;
1655 }
Jaewan Kimb2781e72017-03-02 09:57:09 +09001656 if (DEBUG_KEY_EVENT) {
Jaewan Kim5e1476e2016-07-19 22:25:39 +09001657 Log.d(TAG, "Adjusting " + session + " by " + direction + ". flags="
1658 + flags + ", suggestedStream=" + suggestedStream
1659 + ", preferSuggestedStream=" + preferSuggestedStream);
1660 }
RoboErik9c785402014-11-11 16:52:26 -08001661 if (session == null || preferSuggestedStream) {
RoboErik94c716e2014-09-14 13:54:31 -07001662 if ((flags & AudioManager.FLAG_ACTIVE_MEDIA_ONLY) != 0
1663 && !AudioSystem.isStreamActive(AudioManager.STREAM_MUSIC, 0)) {
RoboErik3c45c292014-07-08 16:47:31 -07001664 if (DEBUG) {
1665 Log.d(TAG, "No active session to adjust, skipping media only volume event");
RoboErik3c45c292014-07-08 16:47:31 -07001666 }
RoboErikb7c014c2014-07-22 15:58:22 -07001667 return;
RoboErik3c45c292014-07-08 16:47:31 -07001668 }
Shibin George19e84042016-06-14 20:42:13 +05301669
1670 // Execute mAudioService.adjustSuggestedStreamVolume() on
1671 // handler thread of MediaSessionService.
1672 // This will release the MediaSessionService.mLock sooner and avoid
1673 // a potential deadlock between MediaSessionService.mLock and
1674 // ActivityManagerService lock.
1675 mHandler.post(new Runnable() {
1676 @Override
1677 public void run() {
1678 try {
1679 String packageName = getContext().getOpPackageName();
1680 mAudioService.adjustSuggestedStreamVolume(direction, suggestedStream,
1681 flags, packageName, TAG);
1682 } catch (RemoteException e) {
1683 Log.e(TAG, "Error adjusting default volume.", e);
Hyundo Moon739d6c22017-09-18 17:01:48 +09001684 } catch (IllegalArgumentException e) {
1685 Log.e(TAG, "Cannot adjust volume: direction=" + direction
1686 + ", suggestedStream=" + suggestedStream + ", flags=" + flags,
1687 e);
Shibin George19e84042016-06-14 20:42:13 +05301688 }
1689 }
1690 });
RoboErikb69ffd42014-05-30 14:57:59 -07001691 } else {
RoboErik0dac35a2014-08-12 15:48:49 -07001692 session.adjustVolume(direction, flags, getContext().getPackageName(),
Jaewan Kim8f729082016-06-21 12:36:26 +09001693 Process.SYSTEM_UID, true);
RoboErikb69ffd42014-05-30 14:57:59 -07001694 }
1695 }
1696
Jaewan Kim6e2b01c2017-01-19 16:33:14 -08001697 private void handleVoiceKeyEventLocked(KeyEvent keyEvent, boolean needWakeLock) {
RoboErik9a9d0b52014-05-20 14:53:39 -07001698 int action = keyEvent.getAction();
1699 boolean isLongPress = (keyEvent.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0;
1700 if (action == KeyEvent.ACTION_DOWN) {
1701 if (keyEvent.getRepeatCount() == 0) {
1702 mVoiceButtonDown = true;
1703 mVoiceButtonHandled = false;
1704 } else if (mVoiceButtonDown && !mVoiceButtonHandled && isLongPress) {
1705 mVoiceButtonHandled = true;
1706 startVoiceInput(needWakeLock);
1707 }
1708 } else if (action == KeyEvent.ACTION_UP) {
1709 if (mVoiceButtonDown) {
1710 mVoiceButtonDown = false;
1711 if (!mVoiceButtonHandled && !keyEvent.isCanceled()) {
1712 // Resend the down then send this event through
1713 KeyEvent downEvent = KeyEvent.changeAction(keyEvent, KeyEvent.ACTION_DOWN);
Jaewan Kim98003d32017-02-24 18:33:04 +09001714 dispatchMediaKeyEventLocked(downEvent, needWakeLock);
1715 dispatchMediaKeyEventLocked(keyEvent, needWakeLock);
RoboErik9a9d0b52014-05-20 14:53:39 -07001716 }
1717 }
1718 }
1719 }
1720
Jaewan Kim98003d32017-02-24 18:33:04 +09001721 private void dispatchMediaKeyEventLocked(KeyEvent keyEvent, boolean needWakeLock) {
Jaewan Kima7dce192017-02-16 17:10:54 +09001722 MediaSessionRecord session = mCurrentFullUserRecord.getMediaButtonSessionLocked();
RoboErik9a9d0b52014-05-20 14:53:39 -07001723 if (session != null) {
Jaewan Kim50269362016-12-23 11:22:02 +09001724 if (DEBUG_KEY_EVENT) {
Jaewan Kim5e1476e2016-07-19 22:25:39 +09001725 Log.d(TAG, "Sending " + keyEvent + " to " + session);
RoboErik9a9d0b52014-05-20 14:53:39 -07001726 }
1727 if (needWakeLock) {
1728 mKeyEventReceiver.aquireWakeLockLocked();
1729 }
Jaewan Kim50269362016-12-23 11:22:02 +09001730 // If we don't need a wakelock use -1 as the id so we won't release it later.
RoboErik9a9d0b52014-05-20 14:53:39 -07001731 session.sendMediaButton(keyEvent,
1732 needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1,
Jaewan Kim8f729082016-06-21 12:36:26 +09001733 mKeyEventReceiver, Process.SYSTEM_UID,
Donghyun Cho1ea56832016-02-23 16:30:07 +09001734 getContext().getPackageName());
Jaewan Kima7dce192017-02-16 17:10:54 +09001735 if (mCurrentFullUserRecord.mCallback != null) {
Jaewan Kimbd16f452017-02-03 16:21:38 +09001736 try {
Jaewan Kima7dce192017-02-16 17:10:54 +09001737 mCurrentFullUserRecord.mCallback.onMediaKeyEventDispatchedToMediaSession(
1738 keyEvent,
Jaewan Kimbd16f452017-02-03 16:21:38 +09001739 new MediaSession.Token(session.getControllerBinder()));
1740 } catch (RemoteException e) {
1741 Log.w(TAG, "Failed to send callback", e);
1742 }
1743 }
Jaewan Kima7dce192017-02-16 17:10:54 +09001744 } else if (mCurrentFullUserRecord.mLastMediaButtonReceiver != null
1745 || mCurrentFullUserRecord.mRestoredMediaButtonReceiver != null) {
1746 if (needWakeLock) {
1747 mKeyEventReceiver.aquireWakeLockLocked();
1748 }
1749 Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
1750 mediaButtonIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
1751 mediaButtonIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
1752 try {
1753 if (mCurrentFullUserRecord.mLastMediaButtonReceiver != null) {
1754 PendingIntent receiver = mCurrentFullUserRecord.mLastMediaButtonReceiver;
1755 if (DEBUG_KEY_EVENT) {
1756 Log.d(TAG, "Sending " + keyEvent
Jaewan Kim92dea332017-02-02 11:52:08 +09001757 + " to the last known PendingIntent " + receiver);
Jaewan Kima7dce192017-02-16 17:10:54 +09001758 }
1759 receiver.send(getContext(),
1760 needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1,
1761 mediaButtonIntent, mKeyEventReceiver, mHandler);
1762 if (mCurrentFullUserRecord.mCallback != null) {
1763 ComponentName componentName = mCurrentFullUserRecord
1764 .mLastMediaButtonReceiver.getIntent().getComponent();
1765 if (componentName != null) {
1766 mCurrentFullUserRecord.mCallback
1767 .onMediaKeyEventDispatchedToMediaButtonReceiver(
1768 keyEvent, componentName);
Jaewan Kimbd16f452017-02-03 16:21:38 +09001769 }
RoboErikc8f92d12015-01-05 16:48:07 -08001770 }
Jaewan Kima7dce192017-02-16 17:10:54 +09001771 } else {
1772 ComponentName receiver =
1773 mCurrentFullUserRecord.mRestoredMediaButtonReceiver;
1774 if (DEBUG_KEY_EVENT) {
1775 Log.d(TAG, "Sending " + keyEvent + " to the restored intent "
1776 + receiver);
1777 }
1778 mediaButtonIntent.setComponent(receiver);
1779 getContext().sendBroadcastAsUser(mediaButtonIntent,
Jaewan Kim92dea332017-02-02 11:52:08 +09001780 UserHandle.of(mCurrentFullUserRecord
1781 .mRestoredMediaButtonReceiverUserId));
Jaewan Kima7dce192017-02-16 17:10:54 +09001782 if (mCurrentFullUserRecord.mCallback != null) {
1783 mCurrentFullUserRecord.mCallback
1784 .onMediaKeyEventDispatchedToMediaButtonReceiver(
1785 keyEvent, receiver);
1786 }
RoboErikb214efb2014-07-24 13:20:30 -07001787 }
Jaewan Kima7dce192017-02-16 17:10:54 +09001788 } catch (CanceledException e) {
1789 Log.i(TAG, "Error sending key event to media button receiver "
1790 + mCurrentFullUserRecord.mLastMediaButtonReceiver, e);
1791 } catch (RemoteException e) {
1792 Log.w(TAG, "Failed to send callback", e);
RoboErik9a9d0b52014-05-20 14:53:39 -07001793 }
RoboErik9a9d0b52014-05-20 14:53:39 -07001794 }
1795 }
1796
1797 private void startVoiceInput(boolean needWakeLock) {
1798 Intent voiceIntent = null;
1799 // select which type of search to launch:
1800 // - screen on and device unlocked: action is ACTION_WEB_SEARCH
1801 // - device locked or screen off: action is
1802 // ACTION_VOICE_SEARCH_HANDS_FREE
1803 // with EXTRA_SECURE set to true if the device is securely locked
1804 PowerManager pm = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE);
1805 boolean isLocked = mKeyguardManager != null && mKeyguardManager.isKeyguardLocked();
1806 if (!isLocked && pm.isScreenOn()) {
1807 voiceIntent = new Intent(android.speech.RecognizerIntent.ACTION_WEB_SEARCH);
1808 Log.i(TAG, "voice-based interactions: about to use ACTION_WEB_SEARCH");
1809 } else {
1810 voiceIntent = new Intent(RecognizerIntent.ACTION_VOICE_SEARCH_HANDS_FREE);
1811 voiceIntent.putExtra(RecognizerIntent.EXTRA_SECURE,
1812 isLocked && mKeyguardManager.isKeyguardSecure());
1813 Log.i(TAG, "voice-based interactions: about to use ACTION_VOICE_SEARCH_HANDS_FREE");
1814 }
1815 // start the search activity
1816 if (needWakeLock) {
1817 mMediaEventWakeLock.acquire();
1818 }
1819 try {
1820 if (voiceIntent != null) {
1821 voiceIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
1822 | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
Jaewan Kim8f729082016-06-21 12:36:26 +09001823 if (DEBUG) Log.d(TAG, "voiceIntent: " + voiceIntent);
RoboErik9a9d0b52014-05-20 14:53:39 -07001824 getContext().startActivityAsUser(voiceIntent, UserHandle.CURRENT);
1825 }
1826 } catch (ActivityNotFoundException e) {
1827 Log.w(TAG, "No activity for search: " + e);
1828 } finally {
1829 if (needWakeLock) {
1830 mMediaEventWakeLock.release();
1831 }
1832 }
1833 }
1834
1835 private boolean isVoiceKey(int keyCode) {
Jaewan Kimba18d8e2017-05-12 17:37:57 +09001836 return keyCode == KeyEvent.KEYCODE_HEADSETHOOK
Jaewan Kimfdb612e2017-07-01 09:23:41 +09001837 || (!mHasFeatureLeanback && keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
RoboErik9a9d0b52014-05-20 14:53:39 -07001838 }
1839
Jeff Brown38d3feb2015-03-19 18:26:30 -07001840 private boolean isUserSetupComplete() {
1841 return Settings.Secure.getIntForUser(getContext().getContentResolver(),
1842 Settings.Secure.USER_SETUP_COMPLETE, 0, UserHandle.USER_CURRENT) != 0;
1843 }
1844
RoboErik9c785402014-11-11 16:52:26 -08001845 // we only handle public stream types, which are 0-5
1846 private boolean isValidLocalStreamType(int streamType) {
1847 return streamType >= AudioManager.STREAM_VOICE_CALL
1848 && streamType <= AudioManager.STREAM_NOTIFICATION;
1849 }
1850
Jaewan Kim6e2b01c2017-01-19 16:33:14 -08001851 private class MediaKeyListenerResultReceiver extends ResultReceiver implements Runnable {
1852 private KeyEvent mKeyEvent;
1853 private boolean mNeedWakeLock;
1854 private boolean mHandled;
1855
1856 private MediaKeyListenerResultReceiver(KeyEvent keyEvent, boolean needWakeLock) {
1857 super(mHandler);
1858 mHandler.postDelayed(this, MEDIA_KEY_LISTENER_TIMEOUT);
1859 mKeyEvent = keyEvent;
1860 mNeedWakeLock = needWakeLock;
1861 }
1862
1863 @Override
1864 public void run() {
1865 Log.d(TAG, "The media key listener is timed-out for " + mKeyEvent);
1866 dispatchMediaKeyEvent();
1867 }
1868
1869 @Override
1870 protected void onReceiveResult(int resultCode, Bundle resultData) {
1871 if (resultCode == MediaSessionManager.RESULT_MEDIA_KEY_HANDLED) {
1872 mHandled = true;
1873 mHandler.removeCallbacks(this);
1874 return;
1875 }
1876 dispatchMediaKeyEvent();
1877 }
1878
1879 private void dispatchMediaKeyEvent() {
1880 if (mHandled) {
1881 return;
1882 }
1883 mHandled = true;
1884 mHandler.removeCallbacks(this);
1885 synchronized (mLock) {
Jaewan Kima7dce192017-02-16 17:10:54 +09001886 if (!isGlobalPriorityActiveLocked()
Jaewan Kim98003d32017-02-24 18:33:04 +09001887 && isVoiceKey(mKeyEvent.getKeyCode())) {
1888 handleVoiceKeyEventLocked(mKeyEvent, mNeedWakeLock);
1889 } else {
1890 dispatchMediaKeyEventLocked(mKeyEvent, mNeedWakeLock);
1891 }
Jaewan Kim6e2b01c2017-01-19 16:33:14 -08001892 }
1893 }
1894 }
1895
RoboErik418c10c2014-05-19 09:25:25 -07001896 private KeyEventWakeLockReceiver mKeyEventReceiver = new KeyEventWakeLockReceiver(mHandler);
1897
RoboErikb214efb2014-07-24 13:20:30 -07001898 class KeyEventWakeLockReceiver extends ResultReceiver implements Runnable,
1899 PendingIntent.OnFinished {
RoboErik418c10c2014-05-19 09:25:25 -07001900 private final Handler mHandler;
1901 private int mRefCount = 0;
1902 private int mLastTimeoutId = 0;
1903
1904 public KeyEventWakeLockReceiver(Handler handler) {
1905 super(handler);
1906 mHandler = handler;
1907 }
1908
1909 public void onTimeout() {
1910 synchronized (mLock) {
1911 if (mRefCount == 0) {
1912 // We've already released it, so just return
1913 return;
1914 }
1915 mLastTimeoutId++;
1916 mRefCount = 0;
1917 releaseWakeLockLocked();
1918 }
1919 }
1920
1921 public void aquireWakeLockLocked() {
1922 if (mRefCount == 0) {
1923 mMediaEventWakeLock.acquire();
1924 }
1925 mRefCount++;
1926 mHandler.removeCallbacks(this);
1927 mHandler.postDelayed(this, WAKELOCK_TIMEOUT);
1928
1929 }
1930
1931 @Override
1932 public void run() {
1933 onTimeout();
1934 }
1935
RoboErik8a2cfc32014-05-16 11:19:38 -07001936 @Override
1937 protected void onReceiveResult(int resultCode, Bundle resultData) {
RoboErik418c10c2014-05-19 09:25:25 -07001938 if (resultCode < mLastTimeoutId) {
1939 // Ignore results from calls that were before the last
1940 // timeout, just in case.
1941 return;
1942 } else {
1943 synchronized (mLock) {
1944 if (mRefCount > 0) {
1945 mRefCount--;
1946 if (mRefCount == 0) {
1947 releaseWakeLockLocked();
1948 }
1949 }
RoboErik8a2cfc32014-05-16 11:19:38 -07001950 }
1951 }
1952 }
RoboErik418c10c2014-05-19 09:25:25 -07001953
1954 private void releaseWakeLockLocked() {
1955 mMediaEventWakeLock.release();
1956 mHandler.removeCallbacks(this);
1957 }
RoboErikb214efb2014-07-24 13:20:30 -07001958
1959 @Override
1960 public void onSendFinished(PendingIntent pendingIntent, Intent intent, int resultCode,
1961 String resultData, Bundle resultExtras) {
1962 onReceiveResult(resultCode, null);
1963 }
RoboErik8a2cfc32014-05-16 11:19:38 -07001964 };
1965
1966 BroadcastReceiver mKeyEventDone = new BroadcastReceiver() {
1967 @Override
1968 public void onReceive(Context context, Intent intent) {
1969 if (intent == null) {
1970 return;
1971 }
1972 Bundle extras = intent.getExtras();
1973 if (extras == null) {
1974 return;
1975 }
1976 synchronized (mLock) {
1977 if (extras.containsKey(EXTRA_WAKELOCK_ACQUIRED)
1978 && mMediaEventWakeLock.isHeld()) {
1979 mMediaEventWakeLock.release();
1980 }
1981 }
1982 }
1983 };
RoboErik01fe6612014-02-13 14:19:04 -08001984 }
1985
RoboErik2e7a9162014-06-04 16:53:45 -07001986 final class MessageHandler extends Handler {
1987 private static final int MSG_SESSIONS_CHANGED = 1;
Jaewan Kimd61a87b2017-02-17 23:14:10 +09001988 private static final int MSG_VOLUME_INITIAL_DOWN = 2;
Jaewan Kim92dea332017-02-02 11:52:08 +09001989 private final SparseArray<Integer> mIntegerCache = new SparseArray<>();
RoboErik2e7a9162014-06-04 16:53:45 -07001990
1991 @Override
1992 public void handleMessage(Message msg) {
1993 switch (msg.what) {
1994 case MSG_SESSIONS_CHANGED:
Jaewan Kim92dea332017-02-02 11:52:08 +09001995 pushSessionsChanged((int) msg.obj);
RoboErik2e7a9162014-06-04 16:53:45 -07001996 break;
Jaewan Kimd61a87b2017-02-17 23:14:10 +09001997 case MSG_VOLUME_INITIAL_DOWN:
1998 synchronized (mLock) {
Jaewan Kima7dce192017-02-16 17:10:54 +09001999 FullUserRecord user = mUserRecords.get((int) msg.arg1);
Jaewan Kimd61a87b2017-02-17 23:14:10 +09002000 if (user != null && user.mInitialDownVolumeKeyEvent != null) {
2001 dispatchVolumeKeyLongPressLocked(user.mInitialDownVolumeKeyEvent);
2002 // Mark that the key is already handled.
2003 user.mInitialDownVolumeKeyEvent = null;
2004 }
2005 }
2006 break;
RoboErik2e7a9162014-06-04 16:53:45 -07002007 }
2008 }
2009
Jaewan Kim92dea332017-02-02 11:52:08 +09002010 public void postSessionsChanged(int userId) {
2011 // Use object instead of the arguments when posting message to remove pending requests.
2012 Integer userIdInteger = mIntegerCache.get(userId);
2013 if (userIdInteger == null) {
2014 userIdInteger = Integer.valueOf(userId);
2015 mIntegerCache.put(userId, userIdInteger);
2016 }
2017 removeMessages(MSG_SESSIONS_CHANGED, userIdInteger);
2018 obtainMessage(MSG_SESSIONS_CHANGED, userIdInteger).sendToTarget();
RoboErik2e7a9162014-06-04 16:53:45 -07002019 }
2020 }
RoboErik01fe6612014-02-13 14:19:04 -08002021}