blob: 94f289f2e38285c63f2b88637ac9ad02cca5682c [file] [log] [blame]
Sungsoo Limd8b978d2019-01-15 20:55:22 +09001/*
2 * Copyright 2019 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
19import static android.os.UserHandle.USER_ALL;
20
21import android.annotation.Nullable;
22import android.app.ActivityManager;
23import android.app.INotificationManager;
24import android.app.KeyguardManager;
25import android.app.PendingIntent;
26import android.app.PendingIntent.CanceledException;
27import android.content.ActivityNotFoundException;
28import android.content.BroadcastReceiver;
29import android.content.ComponentName;
30import android.content.ContentResolver;
31import android.content.Context;
32import android.content.Intent;
33import android.content.pm.ActivityInfo;
34import android.content.pm.PackageManager;
35import android.content.pm.PackageManager.NameNotFoundException;
36import android.content.pm.ServiceInfo;
37import android.content.pm.UserInfo;
38import android.database.ContentObserver;
39import android.media.AudioManager;
40import android.media.AudioManagerInternal;
41import android.media.AudioPlaybackConfiguration;
42import android.media.AudioSystem;
43import android.media.IAudioService;
44import android.media.IRemoteVolumeController;
Hyundo Moonff36c482019-01-31 02:28:20 +000045import android.media.MediaController2;
46import android.media.Session2CommandGroup;
Sungsoo Limd8b978d2019-01-15 20:55:22 +090047import android.media.Session2Token;
48import android.media.session.IActiveSessionsListener;
49import android.media.session.ICallback;
50import android.media.session.IOnMediaKeyListener;
51import android.media.session.IOnVolumeKeyLongPressListener;
Sungsoo Limd8b978d2019-01-15 20:55:22 +090052import android.media.session.ISession2TokensListener;
53import android.media.session.ISessionManager;
54import android.media.session.MediaSession;
55import android.media.session.MediaSessionManager;
56import android.media.session.SessionCallbackLink;
Sungsoo Lim39d07a32019-01-20 13:02:01 +090057import android.media.session.SessionLink;
Sungsoo Limd8b978d2019-01-15 20:55:22 +090058import android.net.Uri;
59import android.os.Binder;
60import android.os.Bundle;
61import android.os.Handler;
Hyundo Moonff36c482019-01-31 02:28:20 +000062import android.os.HandlerExecutor;
Sungsoo Limd8b978d2019-01-15 20:55:22 +090063import android.os.IBinder;
64import android.os.Message;
65import android.os.PowerManager;
66import android.os.Process;
Hyundo Moona676fdc2019-03-08 19:18:36 +090067import android.os.RemoteCallbackList;
Sungsoo Limd8b978d2019-01-15 20:55:22 +090068import android.os.RemoteException;
69import android.os.ResultReceiver;
70import android.os.ServiceManager;
71import android.os.UserHandle;
72import android.os.UserManager;
73import android.provider.Settings;
74import android.speech.RecognizerIntent;
75import android.text.TextUtils;
76import android.util.Log;
77import android.util.Slog;
78import android.util.SparseArray;
79import android.util.SparseIntArray;
80import android.view.KeyEvent;
81import android.view.ViewConfiguration;
82
83import com.android.internal.annotations.GuardedBy;
84import com.android.internal.util.DumpUtils;
85import com.android.server.LocalServices;
86
87import java.io.FileDescriptor;
88import java.io.PrintWriter;
89import java.util.ArrayList;
90import java.util.List;
91
92/**
93 * System implementation of MediaSessionManager
94 */
95public class MediaSessionServiceImpl extends MediaSessionService.ServiceImpl {
96 private static final String TAG = "MediaSessionService";
97 static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
98 // Leave log for key event always.
99 private static final boolean DEBUG_KEY_EVENT = true;
100
101 private static final int WAKELOCK_TIMEOUT = 5000;
102 private static final int MEDIA_KEY_LISTENER_TIMEOUT = 1000;
103
104 private final Context mContext;
105 private final SessionManagerImpl mSessionManagerImpl;
106 private final MessageHandler mHandler = new MessageHandler();
107 private final PowerManager.WakeLock mMediaEventWakeLock;
108 private final int mLongPressTimeout;
109 private final INotificationManager mNotificationManager;
110 private final Object mLock = new Object();
111 // Keeps the full user id for each user.
112 @GuardedBy("mLock")
113 private final SparseIntArray mFullUserIds = new SparseIntArray();
114 @GuardedBy("mLock")
115 private final SparseArray<FullUserRecord> mUserRecords = new SparseArray<FullUserRecord>();
116 @GuardedBy("mLock")
117 private final ArrayList<SessionsListenerRecord> mSessionsListeners =
118 new ArrayList<SessionsListenerRecord>();
119 // Map user id as index to list of Session2Tokens
120 // TODO: Keep session2 info in MediaSessionStack for prioritizing both session1 and session2 in
121 // one place.
122 @GuardedBy("mLock")
123 private final SparseArray<List<Session2Token>> mSession2TokensPerUser = new SparseArray<>();
124 @GuardedBy("mLock")
125 private final List<Session2TokensListenerRecord> mSession2TokensListenerRecords =
126 new ArrayList<>();
127
128 private KeyguardManager mKeyguardManager;
129 private IAudioService mAudioService;
130 private AudioManagerInternal mAudioManagerInternal;
131 private ActivityManager mActivityManager;
132 private ContentResolver mContentResolver;
133 private SettingsObserver mSettingsObserver;
134 private boolean mHasFeatureLeanback;
135
136 // The FullUserRecord of the current users. (i.e. The foreground user that isn't a profile)
137 // It's always not null after the MediaSessionService is started.
138 private FullUserRecord mCurrentFullUserRecord;
139 private MediaSessionRecord mGlobalPrioritySession;
140 private AudioPlayerStateMonitor mAudioPlayerStateMonitor;
141
Hyundo Moona676fdc2019-03-08 19:18:36 +0900142 // Used to notify System UI and Settings when remote volume was changed.
143 final RemoteCallbackList<IRemoteVolumeController> mRemoteVolumeControllers =
144 new RemoteCallbackList<>();
Sungsoo Limd8b978d2019-01-15 20:55:22 +0900145
146 public MediaSessionServiceImpl(Context context) {
147 mContext = context;
148 mSessionManagerImpl = new SessionManagerImpl();
149 PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
150 mMediaEventWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "handleMediaEvent");
151 mLongPressTimeout = ViewConfiguration.getLongPressTimeout();
152 mNotificationManager = INotificationManager.Stub.asInterface(
153 ServiceManager.getService(Context.NOTIFICATION_SERVICE));
154 }
155
156 Context getContext() {
157 return mContext;
158 }
159
160 IBinder getServiceBinder() {
161 return mSessionManagerImpl;
162 }
163
164 @Override
165 public void onStart() {
166 mKeyguardManager = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
167 mAudioService = getAudioService();
168 mAudioManagerInternal = LocalServices.getService(AudioManagerInternal.class);
169 mActivityManager =
170 (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
171 mAudioPlayerStateMonitor = AudioPlayerStateMonitor.getInstance();
172 mAudioPlayerStateMonitor.registerListener(
173 (config, isRemoved) -> {
174 if (isRemoved || !config.isActive() || config.getPlayerType()
175 == AudioPlaybackConfiguration.PLAYER_TYPE_JAM_SOUNDPOOL) {
176 return;
177 }
178 synchronized (mLock) {
179 FullUserRecord user = getFullUserRecordLocked(
180 UserHandle.getUserId(config.getClientUid()));
181 if (user != null) {
182 user.mPriorityStack.updateMediaButtonSessionIfNeeded();
183 }
184 }
185 }, null /* handler */);
186 mAudioPlayerStateMonitor.registerSelfIntoAudioServiceIfNeeded(mAudioService);
187 mContentResolver = mContext.getContentResolver();
188 mSettingsObserver = new SettingsObserver();
189 mSettingsObserver.observe();
190 mHasFeatureLeanback = mContext.getPackageManager().hasSystemFeature(
191 PackageManager.FEATURE_LEANBACK);
192
193 updateUser();
194 }
195
196 private IAudioService getAudioService() {
197 IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE);
198 return IAudioService.Stub.asInterface(b);
199 }
200
201 private boolean isGlobalPriorityActiveLocked() {
202 return mGlobalPrioritySession != null && mGlobalPrioritySession.isActive();
203 }
204
205 @Override
206 public void updateSession(MediaSessionRecord record) {
207 synchronized (mLock) {
208 FullUserRecord user = getFullUserRecordLocked(record.getUserId());
209 if (user == null) {
210 Log.w(TAG, "Unknown session updated. Ignoring.");
211 return;
212 }
213 if ((record.getFlags() & MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY) != 0) {
214 if (DEBUG_KEY_EVENT) {
215 Log.d(TAG, "Global priority session is updated, active=" + record.isActive());
216 }
217 user.pushAddressedPlayerChangedLocked();
218 } else {
219 if (!user.mPriorityStack.contains(record)) {
220 Log.w(TAG, "Unknown session updated. Ignoring.");
221 return;
222 }
223 user.mPriorityStack.onSessionStateChange(record);
224 }
225 mHandler.postSessionsChanged(record.getUserId());
226 }
227 }
228
229 @Override
230 public void setGlobalPrioritySession(MediaSessionRecord record) {
231 synchronized (mLock) {
232 FullUserRecord user = getFullUserRecordLocked(record.getUserId());
233 if (mGlobalPrioritySession != record) {
234 Log.d(TAG, "Global priority session is changed from " + mGlobalPrioritySession
235 + " to " + record);
236 mGlobalPrioritySession = record;
237 if (user != null && user.mPriorityStack.contains(record)) {
238 // Handle the global priority session separately.
239 // Otherwise, it can be the media button session regardless of the active state
240 // because it or other system components might have been the lastly played media
241 // app.
242 user.mPriorityStack.removeSession(record);
243 }
244 }
245 }
246 }
247
248 private List<MediaSessionRecord> getActiveSessionsLocked(int userId) {
249 List<MediaSessionRecord> records = new ArrayList<>();
250 if (userId == USER_ALL) {
251 int size = mUserRecords.size();
252 for (int i = 0; i < size; i++) {
253 records.addAll(mUserRecords.valueAt(i).mPriorityStack.getActiveSessions(userId));
254 }
255 } else {
256 FullUserRecord user = getFullUserRecordLocked(userId);
257 if (user == null) {
258 Log.w(TAG, "getSessions failed. Unknown user " + userId);
259 return records;
260 }
261 records.addAll(user.mPriorityStack.getActiveSessions(userId));
262 }
263
264 // Return global priority session at the first whenever it's asked.
265 if (isGlobalPriorityActiveLocked()
266 && (userId == USER_ALL || userId == mGlobalPrioritySession.getUserId())) {
267 records.add(0, mGlobalPrioritySession);
268 }
269 return records;
270 }
271
272 List<Session2Token> getSession2TokensLocked(int userId) {
273 List<Session2Token> list = new ArrayList<>();
274 if (userId == USER_ALL) {
275 for (int i = 0; i < mSession2TokensPerUser.size(); i++) {
276 list.addAll(mSession2TokensPerUser.valueAt(i));
277 }
278 } else {
279 list.addAll(mSession2TokensPerUser.get(userId));
280 }
281 return list;
282 }
283
284 /**
Hyundo Moona676fdc2019-03-08 19:18:36 +0900285 * Tells the System UI and Settings app that volume has changed on an active remote session.
Sungsoo Limd8b978d2019-01-15 20:55:22 +0900286 */
287 public void notifyRemoteVolumeChanged(int flags, MediaSessionRecord session) {
Hyundo Moona676fdc2019-03-08 19:18:36 +0900288 if (!session.isActive()) {
289 return;
290 }
291 int size = mRemoteVolumeControllers.beginBroadcast();
292 MediaSession.Token token = session.getSessionToken();
293 for (int i = size - 1; i >= 0; i--) {
Jaewan Kim32e90552019-02-12 23:06:46 +0900294 try {
Hyundo Moona676fdc2019-03-08 19:18:36 +0900295 IRemoteVolumeController cb = mRemoteVolumeControllers.getBroadcastItem(i);
296 cb.remoteVolumeChanged(token, flags);
Jaewan Kim32e90552019-02-12 23:06:46 +0900297 } catch (Exception e) {
Hyundo Moona676fdc2019-03-08 19:18:36 +0900298 Log.w(TAG, "Error sending volume change.", e);
Jaewan Kim32e90552019-02-12 23:06:46 +0900299 }
Sungsoo Limd8b978d2019-01-15 20:55:22 +0900300 }
Hyundo Moona676fdc2019-03-08 19:18:36 +0900301 mRemoteVolumeControllers.finishBroadcast();
Sungsoo Limd8b978d2019-01-15 20:55:22 +0900302 }
303
304 @Override
305 public void onSessionPlaystateChanged(MediaSessionRecord record, int oldState, int newState) {
306 synchronized (mLock) {
307 FullUserRecord user = getFullUserRecordLocked(record.getUserId());
308 if (user == null || !user.mPriorityStack.contains(record)) {
309 Log.d(TAG, "Unknown session changed playback state. Ignoring.");
310 return;
311 }
312 user.mPriorityStack.onPlaystateChanged(record, oldState, newState);
313 }
314 }
315
316 @Override
317 public void onSessionPlaybackTypeChanged(MediaSessionRecord record) {
318 synchronized (mLock) {
319 FullUserRecord user = getFullUserRecordLocked(record.getUserId());
320 if (user == null || !user.mPriorityStack.contains(record)) {
321 Log.d(TAG, "Unknown session changed playback type. Ignoring.");
322 return;
323 }
324 pushRemoteVolumeUpdateLocked(record.getUserId());
325 }
326 }
327
328 @Override
329 public void onStartUser(int userId) {
330 if (DEBUG) Log.d(TAG, "onStartUser: " + userId);
331 updateUser();
332 }
333
334 @Override
335 public void onSwitchUser(int userId) {
336 if (DEBUG) Log.d(TAG, "onSwitchUser: " + userId);
337 updateUser();
338 }
339
340 // Called when the user with the userId is removed.
341 @Override
342 public void onStopUser(int userId) {
343 if (DEBUG) Log.d(TAG, "onStopUser: " + userId);
344 synchronized (mLock) {
345 // TODO: Also handle removing user in updateUser() because adding/switching user is
346 // handled in updateUser().
347 FullUserRecord user = getFullUserRecordLocked(userId);
348 if (user != null) {
349 if (user.mFullUserId == userId) {
350 user.destroySessionsForUserLocked(USER_ALL);
351 mUserRecords.remove(userId);
352 } else {
353 user.destroySessionsForUserLocked(userId);
354 }
355 }
356 mSession2TokensPerUser.remove(userId);
357 updateUser();
358 }
359 }
360
361 @Override
362 public void monitor() {
363 synchronized (mLock) {
364 // Check for deadlock
365 }
366 }
367
368 protected void enforcePhoneStatePermission(int pid, int uid) {
369 if (mContext.checkPermission(android.Manifest.permission.MODIFY_PHONE_STATE, pid, uid)
370 != PackageManager.PERMISSION_GRANTED) {
371 throw new SecurityException("Must hold the MODIFY_PHONE_STATE permission.");
372 }
373 }
374
375 void sessionDied(MediaSessionRecord session) {
376 synchronized (mLock) {
377 destroySessionLocked(session);
378 }
379 }
380
381 void destroySession(MediaSessionRecord session) {
382 synchronized (mLock) {
383 destroySessionLocked(session);
384 }
385 }
386
387 private void updateUser() {
388 synchronized (mLock) {
389 UserManager manager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
390 mFullUserIds.clear();
391 List<UserInfo> allUsers = manager.getUsers();
392 if (allUsers != null) {
393 for (UserInfo userInfo : allUsers) {
394 if (userInfo.isManagedProfile()) {
395 mFullUserIds.put(userInfo.id, userInfo.profileGroupId);
396 } else {
397 mFullUserIds.put(userInfo.id, userInfo.id);
398 if (mUserRecords.get(userInfo.id) == null) {
399 mUserRecords.put(userInfo.id, new FullUserRecord(userInfo.id));
400 }
401 }
402 if (mSession2TokensPerUser.get(userInfo.id) == null) {
403 mSession2TokensPerUser.put(userInfo.id, new ArrayList<>());
404 }
405 }
406 }
407 // Ensure that the current full user exists.
408 int currentFullUserId = ActivityManager.getCurrentUser();
409 mCurrentFullUserRecord = mUserRecords.get(currentFullUserId);
410 if (mCurrentFullUserRecord == null) {
411 Log.w(TAG, "Cannot find FullUserInfo for the current user " + currentFullUserId);
412 mCurrentFullUserRecord = new FullUserRecord(currentFullUserId);
413 mUserRecords.put(currentFullUserId, mCurrentFullUserRecord);
414 if (mSession2TokensPerUser.get(currentFullUserId) == null) {
415 mSession2TokensPerUser.put(currentFullUserId, new ArrayList<>());
416 }
417 }
418 mFullUserIds.put(currentFullUserId, currentFullUserId);
419 }
420 }
421
422 private void updateActiveSessionListeners() {
423 synchronized (mLock) {
424 for (int i = mSessionsListeners.size() - 1; i >= 0; i--) {
425 SessionsListenerRecord listener = mSessionsListeners.get(i);
426 try {
427 enforceMediaPermissions(listener.componentName, listener.pid, listener.uid,
428 listener.userId);
429 } catch (SecurityException e) {
430 Log.i(TAG, "ActiveSessionsListener " + listener.componentName
431 + " is no longer authorized. Disconnecting.");
432 mSessionsListeners.remove(i);
433 try {
434 listener.listener
435 .onActiveSessionsChanged(new ArrayList<MediaSession.Token>());
436 } catch (Exception e1) {
437 // ignore
438 }
439 }
440 }
441 }
442 }
443
444 /*
445 * When a session is removed several things need to happen.
446 * 1. We need to remove it from the relevant user.
447 * 2. We need to remove it from the priority stack.
448 * 3. We need to remove it from all sessions.
449 * 4. If this is the system priority session we need to clear it.
450 * 5. We need to unlink to death from the cb binder
451 * 6. We need to tell the session to do any final cleanup (onDestroy)
452 */
453 private void destroySessionLocked(MediaSessionRecord session) {
454 if (DEBUG) {
455 Log.d(TAG, "Destroying " + session);
456 }
457 FullUserRecord user = getFullUserRecordLocked(session.getUserId());
458 if (mGlobalPrioritySession == session) {
459 mGlobalPrioritySession = null;
460 if (session.isActive() && user != null) {
461 user.pushAddressedPlayerChangedLocked();
462 }
463 } else {
464 if (user != null) {
465 user.mPriorityStack.removeSession(session);
466 }
467 }
468
469 try {
470 session.getCallback().getBinder().unlinkToDeath(session, 0);
471 } catch (Exception e) {
472 // ignore exceptions while destroying a session.
473 }
474 session.onDestroy();
475 mHandler.postSessionsChanged(session.getUserId());
476 }
477
478 private void enforcePackageName(String packageName, int uid) {
479 if (TextUtils.isEmpty(packageName)) {
480 throw new IllegalArgumentException("packageName may not be empty");
481 }
482 String[] packages = mContext.getPackageManager().getPackagesForUid(uid);
483 final int packageCount = packages.length;
484 for (int i = 0; i < packageCount; i++) {
485 if (packageName.equals(packages[i])) {
486 return;
487 }
488 }
489 throw new IllegalArgumentException("packageName is not owned by the calling process");
490 }
491
492 /**
493 * Checks a caller's authorization to register an IRemoteControlDisplay.
494 * Authorization is granted if one of the following is true:
495 * <ul>
496 * <li>the caller has android.Manifest.permission.MEDIA_CONTENT_CONTROL
497 * permission</li>
498 * <li>the caller's listener is one of the enabled notification listeners
499 * for the caller's user</li>
500 * </ul>
501 */
502 private void enforceMediaPermissions(ComponentName compName, int pid, int uid,
503 int resolvedUserId) {
Hyundo Moona676fdc2019-03-08 19:18:36 +0900504 if (hasStatusBarServicePermission(pid, uid)) return;
Sungsoo Limd8b978d2019-01-15 20:55:22 +0900505 if (mContext
506 .checkPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL, pid, uid)
507 != PackageManager.PERMISSION_GRANTED
508 && !isEnabledNotificationListener(compName, UserHandle.getUserId(uid),
509 resolvedUserId)) {
510 throw new SecurityException("Missing permission to control media.");
511 }
512 }
513
Hyundo Moona676fdc2019-03-08 19:18:36 +0900514 private boolean hasStatusBarServicePermission(int pid, int uid) {
Sungsoo Limd8b978d2019-01-15 20:55:22 +0900515 return mContext.checkPermission(android.Manifest.permission.STATUS_BAR_SERVICE,
516 pid, uid) == PackageManager.PERMISSION_GRANTED;
517 }
518
Hyundo Moona676fdc2019-03-08 19:18:36 +0900519 private void enforceStatusBarServicePermission(String action, int pid, int uid) {
520 if (!hasStatusBarServicePermission(pid, uid)) {
521 throw new SecurityException("Only System UI and Settings may " + action);
Sungsoo Limd8b978d2019-01-15 20:55:22 +0900522 }
523 }
524
525 /**
526 * This checks if the component is an enabled notification listener for the
527 * specified user. Enabled components may only operate on behalf of the user
528 * they're running as.
529 *
530 * @param compName The component that is enabled.
531 * @param userId The user id of the caller.
532 * @param forUserId The user id they're making the request on behalf of.
533 * @return True if the component is enabled, false otherwise
534 */
535 private boolean isEnabledNotificationListener(ComponentName compName, int userId,
536 int forUserId) {
537 if (userId != forUserId) {
538 // You may not access another user's content as an enabled listener.
539 return false;
540 }
541 if (DEBUG) {
542 Log.d(TAG, "Checking if enabled notification listener " + compName);
543 }
544 if (compName != null) {
545 try {
546 return mNotificationManager.isNotificationListenerAccessGrantedForUser(
547 compName, userId);
548 } catch (RemoteException e) {
549 Log.w(TAG, "Dead NotificationManager in isEnabledNotificationListener", e);
550 }
551 }
552 return false;
553 }
554
555 private MediaSessionRecord createSessionInternal(int callerPid, int callerUid, int userId,
Hyundo Moon5fc8db02019-02-20 15:51:56 +0900556 String callerPackageName, SessionCallbackLink cb, String tag, Bundle sessionInfo)
557 throws RemoteException {
Sungsoo Limd8b978d2019-01-15 20:55:22 +0900558 synchronized (mLock) {
Hyundo Moon5fc8db02019-02-20 15:51:56 +0900559 return createSessionLocked(callerPid, callerUid, userId, callerPackageName, cb,
560 tag, sessionInfo);
Sungsoo Limd8b978d2019-01-15 20:55:22 +0900561 }
562 }
563
564 /*
565 * When a session is created the following things need to happen.
566 * 1. Its callback binder needs a link to death
567 * 2. It needs to be added to all sessions.
568 * 3. It needs to be added to the priority stack.
569 * 4. It needs to be added to the relevant user record.
570 */
571 private MediaSessionRecord createSessionLocked(int callerPid, int callerUid, int userId,
Hyundo Moon5fc8db02019-02-20 15:51:56 +0900572 String callerPackageName, SessionCallbackLink cb, String tag, Bundle sessionInfo) {
Sungsoo Limd8b978d2019-01-15 20:55:22 +0900573 FullUserRecord user = getFullUserRecordLocked(userId);
574 if (user == null) {
Jaewan Kim32e90552019-02-12 23:06:46 +0900575 Log.w(TAG, "Request from invalid user: " + userId + ", pkg=" + callerPackageName);
Sungsoo Limd8b978d2019-01-15 20:55:22 +0900576 throw new RuntimeException("Session request from invalid user.");
577 }
578
579 final MediaSessionRecord session = new MediaSessionRecord(callerPid, callerUid, userId,
Hyundo Moon5fc8db02019-02-20 15:51:56 +0900580 callerPackageName, cb, tag, sessionInfo, this, mHandler.getLooper());
Sungsoo Limd8b978d2019-01-15 20:55:22 +0900581 try {
582 cb.getBinder().linkToDeath(session, 0);
583 } catch (RemoteException e) {
584 throw new RuntimeException("Media Session owner died prematurely.", e);
585 }
586
587 user.mPriorityStack.addSession(session);
588 mHandler.postSessionsChanged(userId);
589
590 if (DEBUG) {
591 Log.d(TAG, "Created session for " + callerPackageName + " with tag " + tag);
592 }
593 return session;
594 }
595
596 private int findIndexOfSessionsListenerLocked(IActiveSessionsListener listener) {
597 for (int i = mSessionsListeners.size() - 1; i >= 0; i--) {
598 if (mSessionsListeners.get(i).listener.asBinder() == listener.asBinder()) {
599 return i;
600 }
601 }
602 return -1;
603 }
604
605 private int findIndexOfSession2TokensListenerLocked(ISession2TokensListener listener) {
606 for (int i = mSession2TokensListenerRecords.size() - 1; i >= 0; i--) {
607 if (mSession2TokensListenerRecords.get(i).listener.asBinder() == listener.asBinder()) {
608 return i;
609 }
610 }
611 return -1;
612 }
613
614
615 private void pushSessionsChanged(int userId) {
616 synchronized (mLock) {
617 FullUserRecord user = getFullUserRecordLocked(userId);
618 if (user == null) {
619 Log.w(TAG, "pushSessionsChanged failed. No user with id=" + userId);
620 return;
621 }
622 List<MediaSessionRecord> records = getActiveSessionsLocked(userId);
623 int size = records.size();
624 ArrayList<MediaSession.Token> tokens = new ArrayList<MediaSession.Token>();
625 for (int i = 0; i < size; i++) {
Sungsoo Limaf7d46c2019-01-20 10:45:54 +0900626 tokens.add(records.get(i).getSessionToken());
Sungsoo Limd8b978d2019-01-15 20:55:22 +0900627 }
628 pushRemoteVolumeUpdateLocked(userId);
629 for (int i = mSessionsListeners.size() - 1; i >= 0; i--) {
630 SessionsListenerRecord record = mSessionsListeners.get(i);
631 if (record.userId == USER_ALL || record.userId == userId) {
632 try {
633 record.listener.onActiveSessionsChanged(tokens);
634 } catch (RemoteException e) {
635 Log.w(TAG, "Dead ActiveSessionsListener in pushSessionsChanged, removing",
636 e);
637 mSessionsListeners.remove(i);
638 }
639 }
640 }
641 }
642 }
643
644 private void pushRemoteVolumeUpdateLocked(int userId) {
Hyundo Moona676fdc2019-03-08 19:18:36 +0900645 FullUserRecord user = getFullUserRecordLocked(userId);
646 if (user == null) {
647 Log.w(TAG, "pushRemoteVolumeUpdateLocked failed. No user with id=" + userId);
648 return;
649 }
650
651 int size = mRemoteVolumeControllers.beginBroadcast();
652 MediaSessionRecord record = user.mPriorityStack.getDefaultRemoteSession(userId);
653 MediaSession.Token token = record == null ? null : record.getSessionToken();
654
655 for (int i = size - 1; i >= 0; i--) {
Sungsoo Limd8b978d2019-01-15 20:55:22 +0900656 try {
Hyundo Moona676fdc2019-03-08 19:18:36 +0900657 IRemoteVolumeController cb = mRemoteVolumeControllers.getBroadcastItem(i);
658 cb.updateRemoteController(token);
659 } catch (Exception e) {
660 Log.w(TAG, "Error sending default remote volume.", e);
Sungsoo Limd8b978d2019-01-15 20:55:22 +0900661 }
662 }
Hyundo Moona676fdc2019-03-08 19:18:36 +0900663 mRemoteVolumeControllers.finishBroadcast();
Sungsoo Limd8b978d2019-01-15 20:55:22 +0900664 }
665
666 void pushSession2TokensChangedLocked(int userId) {
667 List<Session2Token> allSession2Tokens = getSession2TokensLocked(USER_ALL);
668 List<Session2Token> session2Tokens = getSession2TokensLocked(userId);
669
670 for (int i = mSession2TokensListenerRecords.size() - 1; i >= 0; i--) {
671 Session2TokensListenerRecord listenerRecord = mSession2TokensListenerRecords.get(i);
672 try {
673 if (listenerRecord.userId == USER_ALL) {
674 listenerRecord.listener.onSession2TokensChanged(allSession2Tokens);
675 } else if (listenerRecord.userId == userId) {
676 listenerRecord.listener.onSession2TokensChanged(session2Tokens);
677 }
678 } catch (RemoteException e) {
679 Log.w(TAG, "Failed to notify Session2Token change. Removing listener.", e);
680 mSession2TokensListenerRecords.remove(i);
681 }
682 }
683 }
684
685 /**
686 * Called when the media button receiver for the {@code record} is changed.
687 *
688 * @param record the media session whose media button receiver is updated.
689 */
690 public void onMediaButtonReceiverChanged(MediaSessionRecord record) {
691 synchronized (mLock) {
692 FullUserRecord user = getFullUserRecordLocked(record.getUserId());
693 MediaSessionRecord mediaButtonSession =
694 user.mPriorityStack.getMediaButtonSession();
695 if (record == mediaButtonSession) {
696 user.rememberMediaButtonReceiverLocked(mediaButtonSession);
697 }
698 }
699 }
700
701 private String getCallingPackageName(int uid) {
702 String[] packages = mContext.getPackageManager().getPackagesForUid(uid);
703 if (packages != null && packages.length > 0) {
704 return packages[0];
705 }
706 return "";
707 }
708
709 private void dispatchVolumeKeyLongPressLocked(KeyEvent keyEvent) {
710 if (mCurrentFullUserRecord.mOnVolumeKeyLongPressListener == null) {
711 return;
712 }
713 try {
714 mCurrentFullUserRecord.mOnVolumeKeyLongPressListener.onVolumeKeyLongPress(keyEvent);
715 } catch (RemoteException e) {
716 Log.w(TAG, "Failed to send " + keyEvent + " to volume key long-press listener");
717 }
718 }
719
720 private FullUserRecord getFullUserRecordLocked(int userId) {
721 int fullUserId = mFullUserIds.get(userId, -1);
722 if (fullUserId < 0) {
723 return null;
724 }
725 return mUserRecords.get(fullUserId);
726 }
727
Jaewan Kim8be71c02019-01-29 01:22:13 +0900728 private MediaSessionRecord getMediaSessionRecordLocked(MediaSession.Token sessionToken) {
729 FullUserRecord user = getFullUserRecordLocked(UserHandle.getUserId(sessionToken.getUid()));
730 if (user != null) {
731 return user.mPriorityStack.getMediaSessionRecord(sessionToken);
732 }
733 return null;
734 }
735
Sungsoo Limd8b978d2019-01-15 20:55:22 +0900736 /**
737 * Information about a full user and its corresponding managed profiles.
738 *
739 * <p>Since the full user runs together with its managed profiles, a user wouldn't differentiate
740 * them when he/she presses a media/volume button. So keeping media sessions for them in one
741 * place makes more sense and increases the readability.</p>
742 * <p>The contents of this object is guarded by {@link #mLock}.
743 */
744 final class FullUserRecord implements MediaSessionStack.OnMediaButtonSessionChangedListener {
745 public static final int COMPONENT_TYPE_INVALID = 0;
746 public static final int COMPONENT_TYPE_BROADCAST = 1;
747 public static final int COMPONENT_TYPE_ACTIVITY = 2;
748 public static final int COMPONENT_TYPE_SERVICE = 3;
749 private static final String COMPONENT_NAME_USER_ID_DELIM = ",";
750
751 private final int mFullUserId;
752 private final MediaSessionStack mPriorityStack;
753 private PendingIntent mLastMediaButtonReceiver;
754 private ComponentName mRestoredMediaButtonReceiver;
755 private int mRestoredMediaButtonReceiverComponentType;
756 private int mRestoredMediaButtonReceiverUserId;
757
758 private IOnVolumeKeyLongPressListener mOnVolumeKeyLongPressListener;
759 private int mOnVolumeKeyLongPressListenerUid;
760 private KeyEvent mInitialDownVolumeKeyEvent;
761 private int mInitialDownVolumeStream;
762 private boolean mInitialDownMusicOnly;
763
764 private IOnMediaKeyListener mOnMediaKeyListener;
765 private int mOnMediaKeyListenerUid;
766 private ICallback mCallback;
767
768 FullUserRecord(int fullUserId) {
769 mFullUserId = fullUserId;
770 mPriorityStack = new MediaSessionStack(mAudioPlayerStateMonitor, this);
771 // Restore the remembered media button receiver before the boot.
772 String mediaButtonReceiverInfo = Settings.Secure.getStringForUser(mContentResolver,
773 Settings.System.MEDIA_BUTTON_RECEIVER, mFullUserId);
774 if (mediaButtonReceiverInfo == null) {
775 return;
776 }
777 String[] tokens = mediaButtonReceiverInfo.split(COMPONENT_NAME_USER_ID_DELIM);
778 if (tokens == null || (tokens.length != 2 && tokens.length != 3)) {
779 return;
780 }
781 mRestoredMediaButtonReceiver = ComponentName.unflattenFromString(tokens[0]);
782 mRestoredMediaButtonReceiverUserId = Integer.parseInt(tokens[1]);
783 if (tokens.length == 3) {
784 mRestoredMediaButtonReceiverComponentType = Integer.parseInt(tokens[2]);
785 } else {
786 mRestoredMediaButtonReceiverComponentType =
787 getComponentType(mRestoredMediaButtonReceiver);
788 }
789 }
790
791 public void destroySessionsForUserLocked(int userId) {
792 List<MediaSessionRecord> sessions = mPriorityStack.getPriorityList(false, userId);
793 for (MediaSessionRecord session : sessions) {
794 MediaSessionServiceImpl.this.destroySessionLocked(session);
795 }
796 }
797
798 public void dumpLocked(PrintWriter pw, String prefix) {
799 pw.print(prefix + "Record for full_user=" + mFullUserId);
800 // Dump managed profile user ids associated with this user.
801 int size = mFullUserIds.size();
802 for (int i = 0; i < size; i++) {
803 if (mFullUserIds.keyAt(i) != mFullUserIds.valueAt(i)
804 && mFullUserIds.valueAt(i) == mFullUserId) {
805 pw.print(", profile_user=" + mFullUserIds.keyAt(i));
806 }
807 }
808 pw.println();
809 String indent = prefix + " ";
810 pw.println(indent + "Volume key long-press listener: " + mOnVolumeKeyLongPressListener);
811 pw.println(indent + "Volume key long-press listener package: "
812 + getCallingPackageName(mOnVolumeKeyLongPressListenerUid));
813 pw.println(indent + "Media key listener: " + mOnMediaKeyListener);
814 pw.println(indent + "Media key listener package: "
815 + getCallingPackageName(mOnMediaKeyListenerUid));
816 pw.println(indent + "Callback: " + mCallback);
817 pw.println(indent + "Last MediaButtonReceiver: " + mLastMediaButtonReceiver);
818 pw.println(indent + "Restored MediaButtonReceiver: " + mRestoredMediaButtonReceiver);
819 pw.println(indent + "Restored MediaButtonReceiverComponentType: "
820 + mRestoredMediaButtonReceiverComponentType);
821 mPriorityStack.dump(pw, indent);
822 pw.println(indent + "Session2Tokens:");
823 for (int i = 0; i < mSession2TokensPerUser.size(); i++) {
824 List<Session2Token> list = mSession2TokensPerUser.valueAt(i);
825 if (list == null || list.size() == 0) {
826 continue;
827 }
828 for (Session2Token token : list) {
829 pw.println(indent + " " + token);
830 }
831 }
832 }
833
834 @Override
835 public void onMediaButtonSessionChanged(MediaSessionRecord oldMediaButtonSession,
836 MediaSessionRecord newMediaButtonSession) {
837 if (DEBUG_KEY_EVENT) {
838 Log.d(TAG, "Media button session is changed to " + newMediaButtonSession);
839 }
840 synchronized (mLock) {
841 if (oldMediaButtonSession != null) {
842 mHandler.postSessionsChanged(oldMediaButtonSession.getUserId());
843 }
844 if (newMediaButtonSession != null) {
845 rememberMediaButtonReceiverLocked(newMediaButtonSession);
846 mHandler.postSessionsChanged(newMediaButtonSession.getUserId());
847 }
848 pushAddressedPlayerChangedLocked();
849 }
850 }
851
852 // Remember media button receiver and keep it in the persistent storage.
853 public void rememberMediaButtonReceiverLocked(MediaSessionRecord record) {
854 PendingIntent receiver = record.getMediaButtonReceiver();
855 mLastMediaButtonReceiver = receiver;
856 mRestoredMediaButtonReceiver = null;
857 mRestoredMediaButtonReceiverComponentType = COMPONENT_TYPE_INVALID;
858
859 String mediaButtonReceiverInfo = "";
860 if (receiver != null) {
861 ComponentName component = receiver.getIntent().getComponent();
862 if (component != null
863 && record.getPackageName().equals(component.getPackageName())) {
864 String componentName = component.flattenToString();
865 int componentType = getComponentType(component);
866 mediaButtonReceiverInfo = String.join(COMPONENT_NAME_USER_ID_DELIM,
867 componentName, String.valueOf(record.getUserId()),
868 String.valueOf(componentType));
869 }
870 }
871 Settings.Secure.putStringForUser(mContentResolver,
872 Settings.System.MEDIA_BUTTON_RECEIVER, mediaButtonReceiverInfo,
873 mFullUserId);
874 }
875
876 private void pushAddressedPlayerChangedLocked() {
877 if (mCallback == null) {
878 return;
879 }
880 try {
881 MediaSessionRecord mediaButtonSession = getMediaButtonSessionLocked();
882 if (mediaButtonSession != null) {
883 mCallback.onAddressedPlayerChangedToMediaSession(
Sungsoo Limaf7d46c2019-01-20 10:45:54 +0900884 mediaButtonSession.getSessionToken());
Sungsoo Limd8b978d2019-01-15 20:55:22 +0900885 } else if (mCurrentFullUserRecord.mLastMediaButtonReceiver != null) {
886 mCallback.onAddressedPlayerChangedToMediaButtonReceiver(
887 mCurrentFullUserRecord.mLastMediaButtonReceiver
888 .getIntent().getComponent());
889 } else if (mCurrentFullUserRecord.mRestoredMediaButtonReceiver != null) {
890 mCallback.onAddressedPlayerChangedToMediaButtonReceiver(
891 mCurrentFullUserRecord.mRestoredMediaButtonReceiver);
892 }
893 } catch (RemoteException e) {
894 Log.w(TAG, "Failed to pushAddressedPlayerChangedLocked", e);
895 }
896 }
897
898 private MediaSessionRecord getMediaButtonSessionLocked() {
899 return isGlobalPriorityActiveLocked()
900 ? mGlobalPrioritySession : mPriorityStack.getMediaButtonSession();
901 }
902
903 private int getComponentType(@Nullable ComponentName componentName) {
904 if (componentName == null) {
905 return COMPONENT_TYPE_INVALID;
906 }
907 PackageManager pm = mContext.getPackageManager();
908 try {
909 ActivityInfo activityInfo = pm.getActivityInfo(componentName,
910 PackageManager.MATCH_DIRECT_BOOT_AWARE
911 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
912 | PackageManager.GET_ACTIVITIES);
913 if (activityInfo != null) {
914 return COMPONENT_TYPE_ACTIVITY;
915 }
916 } catch (NameNotFoundException e) {
917 }
918 try {
919 ServiceInfo serviceInfo = pm.getServiceInfo(componentName,
920 PackageManager.MATCH_DIRECT_BOOT_AWARE
921 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
922 | PackageManager.GET_SERVICES);
923 if (serviceInfo != null) {
924 return COMPONENT_TYPE_SERVICE;
925 }
926 } catch (NameNotFoundException e) {
927 }
928 // Pick legacy behavior for BroadcastReceiver or unknown.
929 return COMPONENT_TYPE_BROADCAST;
930 }
931 }
932
933 final class SessionsListenerRecord implements IBinder.DeathRecipient {
934 public final IActiveSessionsListener listener;
935 public final ComponentName componentName;
936 public final int userId;
937 public final int pid;
938 public final int uid;
939
940 SessionsListenerRecord(IActiveSessionsListener listener,
941 ComponentName componentName,
942 int userId, int pid, int uid) {
943 this.listener = listener;
944 this.componentName = componentName;
945 this.userId = userId;
946 this.pid = pid;
947 this.uid = uid;
948 }
949
950 @Override
951 public void binderDied() {
952 synchronized (mLock) {
953 mSessionsListeners.remove(this);
954 }
955 }
956 }
957
958 final class Session2TokensListenerRecord implements IBinder.DeathRecipient {
959 public final ISession2TokensListener listener;
960 public final int userId;
961
962 Session2TokensListenerRecord(ISession2TokensListener listener,
963 int userId) {
964 this.listener = listener;
965 this.userId = userId;
966 }
967
968 @Override
969 public void binderDied() {
970 synchronized (mLock) {
971 mSession2TokensListenerRecords.remove(this);
972 }
973 }
974 }
975
976 final class SettingsObserver extends ContentObserver {
977 private final Uri mSecureSettingsUri = Settings.Secure.getUriFor(
978 Settings.Secure.ENABLED_NOTIFICATION_LISTENERS);
979
980 private SettingsObserver() {
981 super(null);
982 }
983
984 private void observe() {
985 mContentResolver.registerContentObserver(mSecureSettingsUri,
986 false, this, USER_ALL);
987 }
988
989 @Override
990 public void onChange(boolean selfChange, Uri uri) {
991 updateActiveSessionListeners();
992 }
993 }
994
995 class SessionManagerImpl extends ISessionManager.Stub {
996 private static final String EXTRA_WAKELOCK_ACQUIRED =
997 "android.media.AudioService.WAKELOCK_ACQUIRED";
998 private static final int WAKELOCK_RELEASE_ON_FINISHED = 1980; // magic number
999
1000 private boolean mVoiceButtonDown = false;
1001 private boolean mVoiceButtonHandled = false;
1002
1003 @Override
Sungsoo Lim39d07a32019-01-20 13:02:01 +09001004 public SessionLink createSession(String packageName, SessionCallbackLink cb, String tag,
Hyundo Moon5fc8db02019-02-20 15:51:56 +09001005 Bundle sessionInfo, int userId) throws RemoteException {
Sungsoo Limd8b978d2019-01-15 20:55:22 +09001006 final int pid = Binder.getCallingPid();
1007 final int uid = Binder.getCallingUid();
1008 final long token = Binder.clearCallingIdentity();
1009 try {
1010 enforcePackageName(packageName, uid);
1011 int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId,
1012 false /* allowAll */, true /* requireFull */, "createSession", packageName);
1013 if (cb == null) {
1014 throw new IllegalArgumentException("Controller callback cannot be null");
1015 }
Hyundo Moon5fc8db02019-02-20 15:51:56 +09001016 return createSessionInternal(pid, uid, resolvedUserId, packageName, cb, tag,
1017 sessionInfo).getSessionBinder();
Sungsoo Limd8b978d2019-01-15 20:55:22 +09001018 } finally {
1019 Binder.restoreCallingIdentity(token);
1020 }
1021 }
1022
1023 @Override
1024 public void notifySession2Created(Session2Token sessionToken) throws RemoteException {
1025 final int pid = Binder.getCallingPid();
1026 final int uid = Binder.getCallingUid();
1027 final long token = Binder.clearCallingIdentity();
1028 try {
1029 if (DEBUG) {
1030 Log.d(TAG, "Session2 is created " + sessionToken);
1031 }
1032 if (uid != sessionToken.getUid()) {
1033 throw new SecurityException("Unexpected Session2Token's UID, expected=" + uid
1034 + " but actually=" + sessionToken.getUid());
1035 }
Hyundo Moonff36c482019-01-31 02:28:20 +00001036 Controller2Callback callback = new Controller2Callback(sessionToken);
1037 // Note: It's safe not to keep controller here because it wouldn't be GC'ed until
1038 // it's closed.
1039 // TODO: Keep controller as well for better readability
1040 // because the GC behavior isn't straightforward.
1041 MediaController2 controller = new MediaController2(mContext, sessionToken,
1042 new HandlerExecutor(mHandler), callback);
Sungsoo Limd8b978d2019-01-15 20:55:22 +09001043 } finally {
1044 Binder.restoreCallingIdentity(token);
1045 }
1046 }
1047
1048 @Override
Hyundo Moon817f5f82019-01-28 12:34:53 +09001049 public List<MediaSession.Token> getSessions(ComponentName componentName, int userId) {
Sungsoo Limd8b978d2019-01-15 20:55:22 +09001050 final int pid = Binder.getCallingPid();
1051 final int uid = Binder.getCallingUid();
1052 final long token = Binder.clearCallingIdentity();
1053
1054 try {
1055 int resolvedUserId = verifySessionsRequest(componentName, userId, pid, uid);
Hyundo Moon817f5f82019-01-28 12:34:53 +09001056 ArrayList<MediaSession.Token> tokens = new ArrayList<>();
Sungsoo Limd8b978d2019-01-15 20:55:22 +09001057 synchronized (mLock) {
1058 List<MediaSessionRecord> records = getActiveSessionsLocked(resolvedUserId);
1059 for (MediaSessionRecord record : records) {
Hyundo Moon817f5f82019-01-28 12:34:53 +09001060 tokens.add(record.getSessionToken());
Sungsoo Limd8b978d2019-01-15 20:55:22 +09001061 }
1062 }
Hyundo Moon817f5f82019-01-28 12:34:53 +09001063 return tokens;
Sungsoo Limd8b978d2019-01-15 20:55:22 +09001064 } finally {
1065 Binder.restoreCallingIdentity(token);
1066 }
1067 }
1068
1069 @Override
1070 public List<Session2Token> getSession2Tokens(int userId) {
1071 final int pid = Binder.getCallingPid();
1072 final int uid = Binder.getCallingUid();
1073 final long token = Binder.clearCallingIdentity();
1074
1075 try {
1076 // Check that they can make calls on behalf of the user and
1077 // get the final user id
1078 int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId,
1079 true /* allowAll */, true /* requireFull */, "getSession2Tokens",
1080 null /* optional packageName */);
1081 List<Session2Token> result;
1082 synchronized (mLock) {
1083 result = getSession2TokensLocked(resolvedUserId);
1084 }
1085 return result;
1086 } finally {
1087 Binder.restoreCallingIdentity(token);
1088 }
1089 }
1090
1091 @Override
1092 public void addSessionsListener(IActiveSessionsListener listener,
1093 ComponentName componentName, int userId) throws RemoteException {
1094 final int pid = Binder.getCallingPid();
1095 final int uid = Binder.getCallingUid();
1096 final long token = Binder.clearCallingIdentity();
1097
1098 try {
1099 int resolvedUserId = verifySessionsRequest(componentName, userId, pid, uid);
1100 synchronized (mLock) {
1101 int index = findIndexOfSessionsListenerLocked(listener);
1102 if (index != -1) {
1103 Log.w(TAG, "ActiveSessionsListener is already added, ignoring");
1104 return;
1105 }
1106 SessionsListenerRecord record = new SessionsListenerRecord(listener,
1107 componentName, resolvedUserId, pid, uid);
1108 try {
1109 listener.asBinder().linkToDeath(record, 0);
1110 } catch (RemoteException e) {
1111 Log.e(TAG, "ActiveSessionsListener is dead, ignoring it", e);
1112 return;
1113 }
1114 mSessionsListeners.add(record);
1115 }
1116 } finally {
1117 Binder.restoreCallingIdentity(token);
1118 }
1119 }
1120
1121 @Override
1122 public void removeSessionsListener(IActiveSessionsListener listener)
1123 throws RemoteException {
1124 synchronized (mLock) {
1125 int index = findIndexOfSessionsListenerLocked(listener);
1126 if (index != -1) {
1127 SessionsListenerRecord record = mSessionsListeners.remove(index);
1128 try {
1129 record.listener.asBinder().unlinkToDeath(record, 0);
1130 } catch (Exception e) {
1131 // ignore exceptions, the record is being removed
1132 }
1133 }
1134 }
1135 }
1136
1137 @Override
1138 public void addSession2TokensListener(ISession2TokensListener listener,
1139 int userId) {
1140 final int pid = Binder.getCallingPid();
1141 final int uid = Binder.getCallingUid();
1142 final long token = Binder.clearCallingIdentity();
1143
1144 try {
1145 // Check that they can make calls on behalf of the user and get the final user id.
1146 int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId,
1147 true /* allowAll */, true /* requireFull */, "addSession2TokensListener",
1148 null /* optional packageName */);
1149 synchronized (mLock) {
1150 int index = findIndexOfSession2TokensListenerLocked(listener);
1151 if (index >= 0) {
1152 Log.w(TAG, "addSession2TokensListener is already added, ignoring");
1153 return;
1154 }
1155 mSession2TokensListenerRecords.add(
1156 new Session2TokensListenerRecord(listener, resolvedUserId));
1157 }
1158 } finally {
1159 Binder.restoreCallingIdentity(token);
1160 }
1161 }
1162
1163 @Override
1164 public void removeSession2TokensListener(ISession2TokensListener listener) {
1165 final int pid = Binder.getCallingPid();
1166 final int uid = Binder.getCallingUid();
1167 final long token = Binder.clearCallingIdentity();
1168
1169 try {
1170 synchronized (mLock) {
1171 int index = findIndexOfSession2TokensListenerLocked(listener);
1172 if (index >= 0) {
1173 Session2TokensListenerRecord listenerRecord =
1174 mSession2TokensListenerRecords.remove(index);
1175 try {
1176 listenerRecord.listener.asBinder().unlinkToDeath(listenerRecord, 0);
1177 } catch (Exception e) {
1178 // Ignore exception.
1179 }
1180 }
1181 }
1182 } finally {
1183 Binder.restoreCallingIdentity(token);
1184 }
1185 }
1186
1187 /**
1188 * Handles the dispatching of the media button events to one of the
1189 * registered listeners, or if there was none, broadcast an
1190 * ACTION_MEDIA_BUTTON intent to the rest of the system.
1191 *
1192 * @param packageName The caller package
1193 * @param asSystemService {@code true} if the event sent to the session as if it was come
1194 * from the system service instead of the app process. This helps sessions to
1195 * distinguish between the key injection by the app and key events from the
1196 * hardware devices. Should be used only when the volume key events aren't handled
1197 * by foreground activity. {@code false} otherwise to tell session about the real
1198 * caller.
1199 * @param keyEvent a non-null KeyEvent whose key code is one of the
1200 * supported media buttons
1201 * @param needWakeLock true if a PARTIAL_WAKE_LOCK needs to be held
1202 * while this key event is dispatched.
1203 */
1204 @Override
1205 public void dispatchMediaKeyEvent(String packageName, boolean asSystemService,
1206 KeyEvent keyEvent, boolean needWakeLock) {
1207 if (keyEvent == null || !KeyEvent.isMediaSessionKey(keyEvent.getKeyCode())) {
1208 Log.w(TAG, "Attempted to dispatch null or non-media key event.");
1209 return;
1210 }
1211
1212 final int pid = Binder.getCallingPid();
1213 final int uid = Binder.getCallingUid();
1214 final long token = Binder.clearCallingIdentity();
1215 try {
1216 if (DEBUG) {
1217 Log.d(TAG, "dispatchMediaKeyEvent, pkg=" + packageName + " pid=" + pid
1218 + ", uid=" + uid + ", asSystem=" + asSystemService + ", event="
1219 + keyEvent);
1220 }
1221 if (!isUserSetupComplete()) {
1222 // Global media key handling can have the side-effect of starting new
1223 // activities which is undesirable while setup is in progress.
1224 Slog.i(TAG, "Not dispatching media key event because user "
1225 + "setup is in progress.");
1226 return;
1227 }
1228
1229 synchronized (mLock) {
1230 boolean isGlobalPriorityActive = isGlobalPriorityActiveLocked();
1231 if (isGlobalPriorityActive && uid != Process.SYSTEM_UID) {
1232 // Prevent dispatching key event through reflection while the global
1233 // priority session is active.
1234 Slog.i(TAG, "Only the system can dispatch media key event "
1235 + "to the global priority session.");
1236 return;
1237 }
1238 if (!isGlobalPriorityActive) {
1239 if (mCurrentFullUserRecord.mOnMediaKeyListener != null) {
1240 if (DEBUG_KEY_EVENT) {
1241 Log.d(TAG, "Send " + keyEvent + " to the media key listener");
1242 }
1243 try {
1244 mCurrentFullUserRecord.mOnMediaKeyListener.onMediaKey(keyEvent,
1245 new MediaKeyListenerResultReceiver(packageName, pid, uid,
1246 asSystemService, keyEvent, needWakeLock));
1247 return;
1248 } catch (RemoteException e) {
1249 Log.w(TAG, "Failed to send " + keyEvent
1250 + " to the media key listener");
1251 }
1252 }
1253 }
1254 if (!isGlobalPriorityActive && isVoiceKey(keyEvent.getKeyCode())) {
1255 handleVoiceKeyEventLocked(packageName, pid, uid, asSystemService, keyEvent,
1256 needWakeLock);
1257 } else {
1258 dispatchMediaKeyEventLocked(packageName, pid, uid, asSystemService,
1259 keyEvent, needWakeLock);
1260 }
1261 }
1262 } finally {
1263 Binder.restoreCallingIdentity(token);
1264 }
1265 }
1266
1267 @Override
Jaewan Kim8be71c02019-01-29 01:22:13 +09001268 public boolean dispatchMediaKeyEventToSessionAsSystemService(String packageName,
1269 MediaSession.Token sessionToken, KeyEvent keyEvent) {
1270 final int pid = Binder.getCallingPid();
1271 final int uid = Binder.getCallingUid();
1272 final long token = Binder.clearCallingIdentity();
1273 try {
1274 synchronized (mLock) {
1275 MediaSessionRecord record = getMediaSessionRecordLocked(sessionToken);
1276 if (record == null) {
1277 if (DEBUG) {
1278 Log.d(TAG, "Failed to find session to dispatch key event.");
1279 }
1280 return false;
1281 }
1282 if (DEBUG) {
1283 Log.d(TAG, "dispatchMediaKeyEventToSessionAsSystemService, pkg="
1284 + packageName + ", pid=" + pid + ", uid=" + uid + ", sessionToken="
1285 + sessionToken + ", event=" + keyEvent + ", session=" + record);
1286 }
1287 return record.sendMediaButton(packageName, pid, uid, true /* asSystemService */,
1288 keyEvent, 0, null);
1289 }
1290 } finally {
1291 Binder.restoreCallingIdentity(token);
1292 }
1293 }
1294
1295 @Override
Sungsoo Limd8b978d2019-01-15 20:55:22 +09001296 public void setCallback(ICallback callback) {
1297 final int pid = Binder.getCallingPid();
1298 final int uid = Binder.getCallingUid();
1299 final long token = Binder.clearCallingIdentity();
1300 try {
1301 if (!UserHandle.isSameApp(uid, Process.BLUETOOTH_UID)) {
1302 throw new SecurityException("Only Bluetooth service processes can set"
1303 + " Callback");
1304 }
1305 synchronized (mLock) {
1306 int userId = UserHandle.getUserId(uid);
1307 FullUserRecord user = getFullUserRecordLocked(userId);
1308 if (user == null || user.mFullUserId != userId) {
1309 Log.w(TAG, "Only the full user can set the callback"
1310 + ", userId=" + userId);
1311 return;
1312 }
1313 user.mCallback = callback;
1314 Log.d(TAG, "The callback " + user.mCallback
1315 + " is set by " + getCallingPackageName(uid));
1316 if (user.mCallback == null) {
1317 return;
1318 }
1319 try {
1320 user.mCallback.asBinder().linkToDeath(
1321 new IBinder.DeathRecipient() {
1322 @Override
1323 public void binderDied() {
1324 synchronized (mLock) {
1325 user.mCallback = null;
1326 }
1327 }
1328 }, 0);
1329 user.pushAddressedPlayerChangedLocked();
1330 } catch (RemoteException e) {
1331 Log.w(TAG, "Failed to set callback", e);
1332 user.mCallback = null;
1333 }
1334 }
1335 } finally {
1336 Binder.restoreCallingIdentity(token);
1337 }
1338 }
1339
1340 @Override
1341 public void setOnVolumeKeyLongPressListener(IOnVolumeKeyLongPressListener listener) {
1342 final int pid = Binder.getCallingPid();
1343 final int uid = Binder.getCallingUid();
1344 final long token = Binder.clearCallingIdentity();
1345 try {
1346 // Enforce SET_VOLUME_KEY_LONG_PRESS_LISTENER permission.
1347 if (mContext.checkPermission(
1348 android.Manifest.permission.SET_VOLUME_KEY_LONG_PRESS_LISTENER, pid, uid)
1349 != PackageManager.PERMISSION_GRANTED) {
1350 throw new SecurityException("Must hold the SET_VOLUME_KEY_LONG_PRESS_LISTENER"
1351 + " permission.");
1352 }
1353
1354 synchronized (mLock) {
1355 int userId = UserHandle.getUserId(uid);
1356 FullUserRecord user = getFullUserRecordLocked(userId);
1357 if (user == null || user.mFullUserId != userId) {
1358 Log.w(TAG, "Only the full user can set the volume key long-press listener"
1359 + ", userId=" + userId);
1360 return;
1361 }
1362 if (user.mOnVolumeKeyLongPressListener != null
1363 && user.mOnVolumeKeyLongPressListenerUid != uid) {
1364 Log.w(TAG, "The volume key long-press listener cannot be reset"
1365 + " by another app , mOnVolumeKeyLongPressListener="
1366 + user.mOnVolumeKeyLongPressListenerUid
1367 + ", uid=" + uid);
1368 return;
1369 }
1370
1371 user.mOnVolumeKeyLongPressListener = listener;
1372 user.mOnVolumeKeyLongPressListenerUid = uid;
1373
1374 Log.d(TAG, "The volume key long-press listener "
1375 + listener + " is set by " + getCallingPackageName(uid));
1376
1377 if (user.mOnVolumeKeyLongPressListener != null) {
1378 try {
1379 user.mOnVolumeKeyLongPressListener.asBinder().linkToDeath(
1380 new IBinder.DeathRecipient() {
1381 @Override
1382 public void binderDied() {
1383 synchronized (mLock) {
1384 user.mOnVolumeKeyLongPressListener = null;
1385 }
1386 }
1387 }, 0);
1388 } catch (RemoteException e) {
1389 Log.w(TAG, "Failed to set death recipient "
1390 + user.mOnVolumeKeyLongPressListener);
1391 user.mOnVolumeKeyLongPressListener = null;
1392 }
1393 }
1394 }
1395 } finally {
1396 Binder.restoreCallingIdentity(token);
1397 }
1398 }
1399
1400 @Override
1401 public void setOnMediaKeyListener(IOnMediaKeyListener listener) {
1402 final int pid = Binder.getCallingPid();
1403 final int uid = Binder.getCallingUid();
1404 final long token = Binder.clearCallingIdentity();
1405 try {
1406 // Enforce SET_MEDIA_KEY_LISTENER permission.
1407 if (mContext.checkPermission(
1408 android.Manifest.permission.SET_MEDIA_KEY_LISTENER, pid, uid)
1409 != PackageManager.PERMISSION_GRANTED) {
1410 throw new SecurityException("Must hold the SET_MEDIA_KEY_LISTENER permission.");
1411 }
1412
1413 synchronized (mLock) {
1414 int userId = UserHandle.getUserId(uid);
1415 FullUserRecord user = getFullUserRecordLocked(userId);
1416 if (user == null || user.mFullUserId != userId) {
1417 Log.w(TAG, "Only the full user can set the media key listener"
1418 + ", userId=" + userId);
1419 return;
1420 }
1421 if (user.mOnMediaKeyListener != null && user.mOnMediaKeyListenerUid != uid) {
1422 Log.w(TAG, "The media key listener cannot be reset by another app. "
1423 + ", mOnMediaKeyListenerUid=" + user.mOnMediaKeyListenerUid
1424 + ", uid=" + uid);
1425 return;
1426 }
1427
1428 user.mOnMediaKeyListener = listener;
1429 user.mOnMediaKeyListenerUid = uid;
1430
1431 Log.d(TAG, "The media key listener " + user.mOnMediaKeyListener
1432 + " is set by " + getCallingPackageName(uid));
1433
1434 if (user.mOnMediaKeyListener != null) {
1435 try {
1436 user.mOnMediaKeyListener.asBinder().linkToDeath(
1437 new IBinder.DeathRecipient() {
1438 @Override
1439 public void binderDied() {
1440 synchronized (mLock) {
1441 user.mOnMediaKeyListener = null;
1442 }
1443 }
1444 }, 0);
1445 } catch (RemoteException e) {
1446 Log.w(TAG, "Failed to set death recipient " + user.mOnMediaKeyListener);
1447 user.mOnMediaKeyListener = null;
1448 }
1449 }
1450 }
1451 } finally {
1452 Binder.restoreCallingIdentity(token);
1453 }
1454 }
1455
1456 /**
1457 * Handles the dispatching of the volume button events to one of the
1458 * registered listeners. If there's a volume key long-press listener and
1459 * there's no active global priority session, long-pressess will be sent to the
1460 * long-press listener instead of adjusting volume.
1461 *
1462 * @param packageName The caller's package name, obtained by Context#getPackageName()
1463 * @param opPackageName The caller's op package name, obtained by Context#getOpPackageName()
1464 * @param asSystemService {@code true} if the event sent to the session as if it was come
1465 * from the system service instead of the app process. This helps sessions to
1466 * distinguish between the key injection by the app and key events from the
1467 * hardware devices. Should be used only when the volume key events aren't handled
1468 * by foreground activity. {@code false} otherwise to tell session about the real
1469 * caller.
1470 * @param keyEvent a non-null KeyEvent whose key code is one of the
1471 * {@link KeyEvent#KEYCODE_VOLUME_UP},
1472 * {@link KeyEvent#KEYCODE_VOLUME_DOWN},
1473 * or {@link KeyEvent#KEYCODE_VOLUME_MUTE}.
1474 * @param stream stream type to adjust volume.
1475 * @param musicOnly true if both UI nor haptic feedback aren't needed when adjust volume.
1476 */
1477 @Override
1478 public void dispatchVolumeKeyEvent(String packageName, String opPackageName,
1479 boolean asSystemService, KeyEvent keyEvent, int stream, boolean musicOnly) {
1480 if (keyEvent == null
1481 || (keyEvent.getKeyCode() != KeyEvent.KEYCODE_VOLUME_UP
1482 && keyEvent.getKeyCode() != KeyEvent.KEYCODE_VOLUME_DOWN
1483 && keyEvent.getKeyCode() != KeyEvent.KEYCODE_VOLUME_MUTE)) {
1484 Log.w(TAG, "Attempted to dispatch null or non-volume key event.");
1485 return;
1486 }
1487
1488 final int pid = Binder.getCallingPid();
1489 final int uid = Binder.getCallingUid();
1490 final long token = Binder.clearCallingIdentity();
1491
1492 if (DEBUG_KEY_EVENT) {
1493 Log.d(TAG, "dispatchVolumeKeyEvent, pkg=" + packageName + ", pid=" + pid + ", uid="
1494 + uid + ", asSystem=" + asSystemService + ", event=" + keyEvent);
1495 }
1496
1497 try {
1498 synchronized (mLock) {
1499 if (isGlobalPriorityActiveLocked()
1500 || mCurrentFullUserRecord.mOnVolumeKeyLongPressListener == null) {
1501 dispatchVolumeKeyEventLocked(packageName, opPackageName, pid, uid,
1502 asSystemService, keyEvent, stream, musicOnly);
1503 } else {
1504 // TODO: Consider the case when both volume up and down keys are pressed
1505 // at the same time.
1506 if (keyEvent.getAction() == KeyEvent.ACTION_DOWN) {
1507 if (keyEvent.getRepeatCount() == 0) {
1508 // Keeps the copy of the KeyEvent because it can be reused.
1509 mCurrentFullUserRecord.mInitialDownVolumeKeyEvent =
1510 KeyEvent.obtain(keyEvent);
1511 mCurrentFullUserRecord.mInitialDownVolumeStream = stream;
1512 mCurrentFullUserRecord.mInitialDownMusicOnly = musicOnly;
1513 mHandler.sendMessageDelayed(
1514 mHandler.obtainMessage(
1515 MessageHandler.MSG_VOLUME_INITIAL_DOWN,
1516 mCurrentFullUserRecord.mFullUserId, 0),
1517 mLongPressTimeout);
1518 }
1519 if (keyEvent.getRepeatCount() > 0 || keyEvent.isLongPress()) {
1520 mHandler.removeMessages(MessageHandler.MSG_VOLUME_INITIAL_DOWN);
1521 if (mCurrentFullUserRecord.mInitialDownVolumeKeyEvent != null) {
1522 dispatchVolumeKeyLongPressLocked(
1523 mCurrentFullUserRecord.mInitialDownVolumeKeyEvent);
1524 // Mark that the key is already handled.
1525 mCurrentFullUserRecord.mInitialDownVolumeKeyEvent = null;
1526 }
1527 dispatchVolumeKeyLongPressLocked(keyEvent);
1528 }
1529 } else { // if up
1530 mHandler.removeMessages(MessageHandler.MSG_VOLUME_INITIAL_DOWN);
1531 if (mCurrentFullUserRecord.mInitialDownVolumeKeyEvent != null
1532 && mCurrentFullUserRecord.mInitialDownVolumeKeyEvent
1533 .getDownTime() == keyEvent.getDownTime()) {
1534 // Short-press. Should change volume.
1535 dispatchVolumeKeyEventLocked(packageName, opPackageName, pid, uid,
1536 asSystemService,
1537 mCurrentFullUserRecord.mInitialDownVolumeKeyEvent,
1538 mCurrentFullUserRecord.mInitialDownVolumeStream,
1539 mCurrentFullUserRecord.mInitialDownMusicOnly);
1540 dispatchVolumeKeyEventLocked(packageName, opPackageName, pid, uid,
1541 asSystemService, keyEvent, stream, musicOnly);
1542 } else {
1543 dispatchVolumeKeyLongPressLocked(keyEvent);
1544 }
1545 }
1546 }
1547 }
1548 } finally {
1549 Binder.restoreCallingIdentity(token);
1550 }
1551 }
1552
1553 private void dispatchVolumeKeyEventLocked(String packageName, String opPackageName, int pid,
1554 int uid, boolean asSystemService, KeyEvent keyEvent, int stream,
1555 boolean musicOnly) {
1556 boolean down = keyEvent.getAction() == KeyEvent.ACTION_DOWN;
1557 boolean up = keyEvent.getAction() == KeyEvent.ACTION_UP;
1558 int direction = 0;
1559 boolean isMute = false;
1560 switch (keyEvent.getKeyCode()) {
1561 case KeyEvent.KEYCODE_VOLUME_UP:
1562 direction = AudioManager.ADJUST_RAISE;
1563 break;
1564 case KeyEvent.KEYCODE_VOLUME_DOWN:
1565 direction = AudioManager.ADJUST_LOWER;
1566 break;
1567 case KeyEvent.KEYCODE_VOLUME_MUTE:
1568 isMute = true;
1569 break;
1570 }
1571 if (down || up) {
1572 int flags = AudioManager.FLAG_FROM_KEY;
1573 if (musicOnly) {
1574 // This flag is used when the screen is off to only affect active media.
1575 flags |= AudioManager.FLAG_ACTIVE_MEDIA_ONLY;
1576 } else {
1577 // These flags are consistent with the home screen
1578 if (up) {
1579 flags |= AudioManager.FLAG_PLAY_SOUND | AudioManager.FLAG_VIBRATE;
1580 } else {
1581 flags |= AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_VIBRATE;
1582 }
1583 }
1584 if (direction != 0) {
1585 // If this is action up we want to send a beep for non-music events
1586 if (up) {
1587 direction = 0;
1588 }
1589 dispatchAdjustVolumeLocked(packageName, opPackageName, pid, uid,
1590 asSystemService, stream, direction, flags);
1591 } else if (isMute) {
1592 if (down && keyEvent.getRepeatCount() == 0) {
1593 dispatchAdjustVolumeLocked(packageName, opPackageName, pid, uid,
1594 asSystemService, stream, AudioManager.ADJUST_TOGGLE_MUTE, flags);
1595 }
1596 }
1597 }
1598 }
1599
1600 @Override
Jaewan Kim8be71c02019-01-29 01:22:13 +09001601 public void dispatchVolumeKeyEventToSessionAsSystemService(String packageName,
1602 String opPackageName, MediaSession.Token sessionToken, KeyEvent keyEvent) {
1603 int pid = Binder.getCallingPid();
1604 int uid = Binder.getCallingUid();
1605 final long token = Binder.clearCallingIdentity();
1606 try {
1607 synchronized (mLock) {
1608 MediaSessionRecord record = getMediaSessionRecordLocked(sessionToken);
1609 if (record == null) {
1610 if (DEBUG) {
1611 Log.d(TAG, "Failed to find session to dispatch key event.");
1612 }
1613 return;
1614 }
1615 if (DEBUG) {
1616 Log.d(TAG, "dispatchVolumeKeyEventToSessionAsSystemService, pkg="
1617 + packageName + ", opPkg=" + opPackageName + ", pid=" + pid
1618 + ", uid=" + uid + ", sessionToken=" + sessionToken + ", event="
1619 + keyEvent + ", session=" + record);
1620 }
1621 switch (keyEvent.getAction()) {
1622 case KeyEvent.ACTION_DOWN: {
1623 int direction = 0;
1624 switch (keyEvent.getKeyCode()) {
1625 case KeyEvent.KEYCODE_VOLUME_UP:
1626 direction = AudioManager.ADJUST_RAISE;
1627 break;
1628 case KeyEvent.KEYCODE_VOLUME_DOWN:
1629 direction = AudioManager.ADJUST_LOWER;
1630 break;
1631 case KeyEvent.KEYCODE_VOLUME_MUTE:
1632 direction = AudioManager.ADJUST_TOGGLE_MUTE;
1633 break;
1634 }
1635 record.adjustVolume(packageName, opPackageName, pid, uid,
1636 null /* caller */, true /* asSystemService */, direction,
1637 AudioManager.FLAG_SHOW_UI, false /* useSuggested */);
1638 break;
1639 }
1640
1641 case KeyEvent.ACTION_UP: {
1642 final int flags =
1643 AudioManager.FLAG_PLAY_SOUND | AudioManager.FLAG_VIBRATE
1644 | AudioManager.FLAG_FROM_KEY;
1645 record.adjustVolume(packageName, opPackageName, pid, uid,
1646 null /* caller */, true /* asSystemService */, 0,
1647 flags, false /* useSuggested */);
1648 }
1649 }
1650 }
1651 } finally {
1652 Binder.restoreCallingIdentity(token);
1653 }
1654 }
1655
1656 @Override
Sungsoo Limd8b978d2019-01-15 20:55:22 +09001657 public void dispatchAdjustVolume(String packageName, String opPackageName,
1658 int suggestedStream, int delta, int flags) {
1659 final int pid = Binder.getCallingPid();
1660 final int uid = Binder.getCallingUid();
1661 final long token = Binder.clearCallingIdentity();
1662 try {
1663 synchronized (mLock) {
1664 dispatchAdjustVolumeLocked(packageName, opPackageName, pid, uid, false,
1665 suggestedStream, delta, flags);
1666 }
1667 } finally {
1668 Binder.restoreCallingIdentity(token);
1669 }
1670 }
1671
1672 @Override
Hyundo Moona676fdc2019-03-08 19:18:36 +09001673 public void registerRemoteVolumeController(IRemoteVolumeController rvc) {
Sungsoo Limd8b978d2019-01-15 20:55:22 +09001674 final int pid = Binder.getCallingPid();
1675 final int uid = Binder.getCallingUid();
1676 final long token = Binder.clearCallingIdentity();
1677 try {
Hyundo Moona676fdc2019-03-08 19:18:36 +09001678 enforceStatusBarServicePermission("listen for volume changes", pid, uid);
1679 mRemoteVolumeControllers.register(rvc);
1680 } finally {
1681 Binder.restoreCallingIdentity(token);
1682 }
1683 }
1684
1685 @Override
1686 public void unregisterRemoteVolumeController(IRemoteVolumeController rvc) {
1687 final int pid = Binder.getCallingPid();
1688 final int uid = Binder.getCallingUid();
1689 final long token = Binder.clearCallingIdentity();
1690 try {
1691 enforceStatusBarServicePermission("listen for volume changes", pid, uid);
1692 mRemoteVolumeControllers.unregister(rvc);
Sungsoo Limd8b978d2019-01-15 20:55:22 +09001693 } finally {
1694 Binder.restoreCallingIdentity(token);
1695 }
1696 }
1697
1698 @Override
1699 public boolean isGlobalPriorityActive() {
1700 synchronized (mLock) {
1701 return isGlobalPriorityActiveLocked();
1702 }
1703 }
1704
1705 @Override
1706 public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
1707 if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
1708
1709 pw.println("MEDIA SESSION SERVICE (dumpsys media_session)");
1710 pw.println();
1711
1712 synchronized (mLock) {
1713 pw.println(mSessionsListeners.size() + " sessions listeners.");
1714 pw.println("Global priority session is " + mGlobalPrioritySession);
1715 if (mGlobalPrioritySession != null) {
1716 mGlobalPrioritySession.dump(pw, " ");
1717 }
1718 pw.println("User Records:");
1719 int count = mUserRecords.size();
1720 for (int i = 0; i < count; i++) {
1721 mUserRecords.valueAt(i).dumpLocked(pw, "");
1722 }
1723 mAudioPlayerStateMonitor.dump(mContext, pw, "");
1724 }
1725 }
1726
1727 /**
1728 * Returns if the controller's package is trusted (i.e. has either MEDIA_CONTENT_CONTROL
1729 * permission or an enabled notification listener)
1730 *
1731 * @param controllerPackageName package name of the controller app
1732 * @param controllerPid pid of the controller app
1733 * @param controllerUid uid of the controller app
1734 */
1735 @Override
1736 public boolean isTrusted(String controllerPackageName, int controllerPid, int controllerUid)
1737 throws RemoteException {
1738 final int uid = Binder.getCallingUid();
1739 final long token = Binder.clearCallingIdentity();
1740 try {
1741 // Don't perform sanity check between controllerPackageName and controllerUid.
1742 // When an (activity|service) runs on the another apps process by specifying
1743 // android:process in the AndroidManifest.xml, then PID and UID would have the
1744 // running process' information instead of the (activity|service) that has created
1745 // MediaController.
1746 // Note that we can use Context#getOpPackageName() instead of
1747 // Context#getPackageName() for getting package name that matches with the PID/UID,
1748 // but it doesn't tell which package has created the MediaController, so useless.
1749 return hasMediaControlPermission(UserHandle.getUserId(uid), controllerPackageName,
1750 controllerPid, controllerUid);
1751 } finally {
1752 Binder.restoreCallingIdentity(token);
1753 }
1754 }
1755
1756 // For MediaSession
1757 private int verifySessionsRequest(ComponentName componentName, int userId, final int pid,
1758 final int uid) {
1759 String packageName = null;
1760 if (componentName != null) {
1761 // If they gave us a component name verify they own the
1762 // package
1763 packageName = componentName.getPackageName();
1764 enforcePackageName(packageName, uid);
1765 }
1766 // Check that they can make calls on behalf of the user and
1767 // get the final user id
1768 int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId,
1769 true /* allowAll */, true /* requireFull */, "getSessions", packageName);
1770 // Check if they have the permissions or their component is
1771 // enabled for the user they're calling from.
1772 enforceMediaPermissions(componentName, pid, uid, resolvedUserId);
1773 return resolvedUserId;
1774 }
1775
1776 private boolean hasMediaControlPermission(int resolvedUserId, String packageName,
1777 int pid, int uid) throws RemoteException {
Hyundo Moona676fdc2019-03-08 19:18:36 +09001778 // Allow API calls from the System UI and Settings
1779 if (hasStatusBarServicePermission(pid, uid)) {
Sungsoo Limd8b978d2019-01-15 20:55:22 +09001780 return true;
1781 }
1782
1783 // Check if it's system server or has MEDIA_CONTENT_CONTROL.
1784 // Note that system server doesn't have MEDIA_CONTENT_CONTROL, so we need extra
1785 // check here.
1786 if (uid == Process.SYSTEM_UID || mContext.checkPermission(
1787 android.Manifest.permission.MEDIA_CONTENT_CONTROL, pid, uid)
1788 == PackageManager.PERMISSION_GRANTED) {
1789 return true;
1790 } else if (DEBUG) {
1791 Log.d(TAG, packageName + " (uid=" + uid + ") hasn't granted MEDIA_CONTENT_CONTROL");
1792 }
1793
1794 // You may not access another user's content as an enabled listener.
1795 final int userId = UserHandle.getUserId(uid);
1796 if (resolvedUserId != userId) {
1797 return false;
1798 }
1799
1800 // TODO(jaewan): (Post-P) Propose NotificationManager#hasEnabledNotificationListener(
1801 // String pkgName) to notification team for optimization
1802 final List<ComponentName> enabledNotificationListeners =
1803 mNotificationManager.getEnabledNotificationListeners(userId);
1804 if (enabledNotificationListeners != null) {
1805 for (int i = 0; i < enabledNotificationListeners.size(); i++) {
1806 if (TextUtils.equals(packageName,
1807 enabledNotificationListeners.get(i).getPackageName())) {
1808 return true;
1809 }
1810 }
1811 }
1812 if (DEBUG) {
1813 Log.d(TAG, packageName + " (uid=" + uid + ") doesn't have an enabled "
1814 + "notification listener");
1815 }
1816 return false;
1817 }
1818
1819 private void dispatchAdjustVolumeLocked(String packageName, String opPackageName, int pid,
1820 int uid, boolean asSystemService, int suggestedStream, int direction, int flags) {
1821 MediaSessionRecord session = isGlobalPriorityActiveLocked() ? mGlobalPrioritySession
1822 : mCurrentFullUserRecord.mPriorityStack.getDefaultVolumeSession();
1823
1824 boolean preferSuggestedStream = false;
1825 if (isValidLocalStreamType(suggestedStream)
1826 && AudioSystem.isStreamActive(suggestedStream, 0)) {
1827 preferSuggestedStream = true;
1828 }
1829 if (DEBUG_KEY_EVENT) {
1830 Log.d(TAG, "Adjusting " + session + " by " + direction + ". flags="
1831 + flags + ", suggestedStream=" + suggestedStream
1832 + ", preferSuggestedStream=" + preferSuggestedStream);
1833 }
1834 if (session == null || preferSuggestedStream) {
1835 if ((flags & AudioManager.FLAG_ACTIVE_MEDIA_ONLY) != 0
1836 && !AudioSystem.isStreamActive(AudioManager.STREAM_MUSIC, 0)) {
1837 if (DEBUG) {
1838 Log.d(TAG, "No active session to adjust, skipping media only volume event");
1839 }
1840 return;
1841 }
1842
1843 // Execute mAudioService.adjustSuggestedStreamVolume() on
1844 // handler thread of MediaSessionService.
1845 // This will release the MediaSessionService.mLock sooner and avoid
1846 // a potential deadlock between MediaSessionService.mLock and
1847 // ActivityManagerService lock.
1848 mHandler.post(new Runnable() {
1849 @Override
1850 public void run() {
1851 final String callingOpPackageName;
1852 final int callingUid;
1853 if (asSystemService) {
1854 callingOpPackageName = mContext.getOpPackageName();
1855 callingUid = Process.myUid();
1856 } else {
1857 callingOpPackageName = opPackageName;
1858 callingUid = uid;
1859 }
1860 try {
1861 mAudioManagerInternal.adjustSuggestedStreamVolumeForUid(suggestedStream,
1862 direction, flags, callingOpPackageName, callingUid);
1863 } catch (SecurityException | IllegalArgumentException e) {
1864 Log.e(TAG, "Cannot adjust volume: direction=" + direction
1865 + ", suggestedStream=" + suggestedStream + ", flags=" + flags
1866 + ", packageName=" + packageName + ", uid=" + uid
1867 + ", asSystemService=" + asSystemService, e);
1868 }
1869 }
1870 });
1871 } else {
1872 session.adjustVolume(packageName, opPackageName, pid, uid, null, asSystemService,
1873 direction, flags, true);
1874 }
1875 }
1876
1877 private void handleVoiceKeyEventLocked(String packageName, int pid, int uid,
1878 boolean asSystemService, KeyEvent keyEvent, boolean needWakeLock) {
1879 int action = keyEvent.getAction();
1880 boolean isLongPress = (keyEvent.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0;
1881 if (action == KeyEvent.ACTION_DOWN) {
1882 if (keyEvent.getRepeatCount() == 0) {
1883 mVoiceButtonDown = true;
1884 mVoiceButtonHandled = false;
1885 } else if (mVoiceButtonDown && !mVoiceButtonHandled && isLongPress) {
1886 mVoiceButtonHandled = true;
1887 startVoiceInput(needWakeLock);
1888 }
1889 } else if (action == KeyEvent.ACTION_UP) {
1890 if (mVoiceButtonDown) {
1891 mVoiceButtonDown = false;
1892 if (!mVoiceButtonHandled && !keyEvent.isCanceled()) {
1893 // Resend the down then send this event through
1894 KeyEvent downEvent = KeyEvent.changeAction(keyEvent, KeyEvent.ACTION_DOWN);
1895 dispatchMediaKeyEventLocked(packageName, pid, uid, asSystemService,
1896 downEvent, needWakeLock);
1897 dispatchMediaKeyEventLocked(packageName, pid, uid, asSystemService,
1898 keyEvent, needWakeLock);
1899 }
1900 }
1901 }
1902 }
1903
1904 private void dispatchMediaKeyEventLocked(String packageName, int pid, int uid,
1905 boolean asSystemService, KeyEvent keyEvent, boolean needWakeLock) {
1906 MediaSessionRecord session = mCurrentFullUserRecord.getMediaButtonSessionLocked();
1907 if (session != null) {
1908 if (DEBUG_KEY_EVENT) {
1909 Log.d(TAG, "Sending " + keyEvent + " to " + session);
1910 }
1911 if (needWakeLock) {
1912 mKeyEventReceiver.aquireWakeLockLocked();
1913 }
1914 // If we don't need a wakelock use -1 as the id so we won't release it later.
1915 session.sendMediaButton(packageName, pid, uid, asSystemService, keyEvent,
1916 needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1,
1917 mKeyEventReceiver);
1918 if (mCurrentFullUserRecord.mCallback != null) {
1919 try {
1920 mCurrentFullUserRecord.mCallback.onMediaKeyEventDispatchedToMediaSession(
Sungsoo Limaf7d46c2019-01-20 10:45:54 +09001921 keyEvent, session.getSessionToken());
Sungsoo Limd8b978d2019-01-15 20:55:22 +09001922 } catch (RemoteException e) {
1923 Log.w(TAG, "Failed to send callback", e);
1924 }
1925 }
1926 } else if (mCurrentFullUserRecord.mLastMediaButtonReceiver != null
1927 || mCurrentFullUserRecord.mRestoredMediaButtonReceiver != null) {
1928 if (needWakeLock) {
1929 mKeyEventReceiver.aquireWakeLockLocked();
1930 }
1931 Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
1932 mediaButtonIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
1933 mediaButtonIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
1934 // TODO: Find a way to also send PID/UID in secure way.
1935 String callerPackageName =
1936 (asSystemService) ? mContext.getPackageName() : packageName;
1937 mediaButtonIntent.putExtra(Intent.EXTRA_PACKAGE_NAME, callerPackageName);
1938 try {
1939 if (mCurrentFullUserRecord.mLastMediaButtonReceiver != null) {
1940 PendingIntent receiver = mCurrentFullUserRecord.mLastMediaButtonReceiver;
1941 if (DEBUG_KEY_EVENT) {
1942 Log.d(TAG, "Sending " + keyEvent
1943 + " to the last known PendingIntent " + receiver);
1944 }
1945 receiver.send(mContext,
1946 needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1,
1947 mediaButtonIntent, mKeyEventReceiver, mHandler);
1948 if (mCurrentFullUserRecord.mCallback != null) {
1949 ComponentName componentName = mCurrentFullUserRecord
1950 .mLastMediaButtonReceiver.getIntent().getComponent();
1951 if (componentName != null) {
1952 mCurrentFullUserRecord.mCallback
1953 .onMediaKeyEventDispatchedToMediaButtonReceiver(
1954 keyEvent, componentName);
1955 }
1956 }
1957 } else {
1958 ComponentName receiver =
1959 mCurrentFullUserRecord.mRestoredMediaButtonReceiver;
1960 int componentType = mCurrentFullUserRecord
1961 .mRestoredMediaButtonReceiverComponentType;
1962 UserHandle userHandle = UserHandle.of(mCurrentFullUserRecord
1963 .mRestoredMediaButtonReceiverUserId);
1964 if (DEBUG_KEY_EVENT) {
1965 Log.d(TAG, "Sending " + keyEvent + " to the restored intent "
1966 + receiver + ", type=" + componentType);
1967 }
1968 mediaButtonIntent.setComponent(receiver);
1969 try {
1970 switch (componentType) {
1971 case FullUserRecord.COMPONENT_TYPE_ACTIVITY:
1972 mContext.startActivityAsUser(mediaButtonIntent, userHandle);
1973 break;
1974 case FullUserRecord.COMPONENT_TYPE_SERVICE:
1975 mContext.startForegroundServiceAsUser(mediaButtonIntent,
1976 userHandle);
1977 break;
1978 default:
1979 // Legacy behavior for other cases.
1980 mContext.sendBroadcastAsUser(mediaButtonIntent, userHandle);
1981 }
1982 } catch (Exception e) {
1983 Log.w(TAG, "Error sending media button to the restored intent "
1984 + receiver + ", type=" + componentType, e);
1985 }
1986 if (mCurrentFullUserRecord.mCallback != null) {
1987 mCurrentFullUserRecord.mCallback
1988 .onMediaKeyEventDispatchedToMediaButtonReceiver(
1989 keyEvent, receiver);
1990 }
1991 }
1992 } catch (CanceledException e) {
1993 Log.i(TAG, "Error sending key event to media button receiver "
1994 + mCurrentFullUserRecord.mLastMediaButtonReceiver, e);
1995 } catch (RemoteException e) {
1996 Log.w(TAG, "Failed to send callback", e);
1997 }
1998 }
1999 }
2000
2001 private void startVoiceInput(boolean needWakeLock) {
2002 Intent voiceIntent = null;
2003 // select which type of search to launch:
2004 // - screen on and device unlocked: action is ACTION_WEB_SEARCH
2005 // - device locked or screen off: action is
2006 // ACTION_VOICE_SEARCH_HANDS_FREE
2007 // with EXTRA_SECURE set to true if the device is securely locked
2008 PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
2009 boolean isLocked = mKeyguardManager != null && mKeyguardManager.isKeyguardLocked();
2010 if (!isLocked && pm.isScreenOn()) {
2011 voiceIntent = new Intent(android.speech.RecognizerIntent.ACTION_WEB_SEARCH);
2012 Log.i(TAG, "voice-based interactions: about to use ACTION_WEB_SEARCH");
2013 } else {
2014 voiceIntent = new Intent(RecognizerIntent.ACTION_VOICE_SEARCH_HANDS_FREE);
2015 voiceIntent.putExtra(RecognizerIntent.EXTRA_SECURE,
2016 isLocked && mKeyguardManager.isKeyguardSecure());
2017 Log.i(TAG, "voice-based interactions: about to use ACTION_VOICE_SEARCH_HANDS_FREE");
2018 }
2019 // start the search activity
2020 if (needWakeLock) {
2021 mMediaEventWakeLock.acquire();
2022 }
2023 try {
2024 if (voiceIntent != null) {
2025 voiceIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
2026 | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
2027 if (DEBUG) Log.d(TAG, "voiceIntent: " + voiceIntent);
2028 mContext.startActivityAsUser(voiceIntent, UserHandle.CURRENT);
2029 }
2030 } catch (ActivityNotFoundException e) {
2031 Log.w(TAG, "No activity for search: " + e);
2032 } finally {
2033 if (needWakeLock) {
2034 mMediaEventWakeLock.release();
2035 }
2036 }
2037 }
2038
2039 private boolean isVoiceKey(int keyCode) {
2040 return keyCode == KeyEvent.KEYCODE_HEADSETHOOK
2041 || (!mHasFeatureLeanback && keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
2042 }
2043
2044 private boolean isUserSetupComplete() {
2045 return Settings.Secure.getIntForUser(mContext.getContentResolver(),
2046 Settings.Secure.USER_SETUP_COMPLETE, 0, UserHandle.USER_CURRENT) != 0;
2047 }
2048
2049 // we only handle public stream types, which are 0-5
2050 private boolean isValidLocalStreamType(int streamType) {
2051 return streamType >= AudioManager.STREAM_VOICE_CALL
2052 && streamType <= AudioManager.STREAM_NOTIFICATION;
2053 }
2054
2055 private class MediaKeyListenerResultReceiver extends ResultReceiver implements Runnable {
2056 private final String mPackageName;
2057 private final int mPid;
2058 private final int mUid;
2059 private final boolean mAsSystemService;
2060 private final KeyEvent mKeyEvent;
2061 private final boolean mNeedWakeLock;
2062 private boolean mHandled;
2063
2064 private MediaKeyListenerResultReceiver(String packageName, int pid, int uid,
2065 boolean asSystemService, KeyEvent keyEvent, boolean needWakeLock) {
2066 super(mHandler);
2067 mHandler.postDelayed(this, MEDIA_KEY_LISTENER_TIMEOUT);
2068 mPackageName = packageName;
2069 mPid = pid;
2070 mUid = uid;
2071 mAsSystemService = asSystemService;
2072 mKeyEvent = keyEvent;
2073 mNeedWakeLock = needWakeLock;
2074 }
2075
2076 @Override
2077 public void run() {
2078 Log.d(TAG, "The media key listener is timed-out for " + mKeyEvent);
2079 dispatchMediaKeyEvent();
2080 }
2081
2082 @Override
2083 protected void onReceiveResult(int resultCode, Bundle resultData) {
2084 if (resultCode == MediaSessionManager.RESULT_MEDIA_KEY_HANDLED) {
2085 mHandled = true;
2086 mHandler.removeCallbacks(this);
2087 return;
2088 }
2089 dispatchMediaKeyEvent();
2090 }
2091
2092 private void dispatchMediaKeyEvent() {
2093 if (mHandled) {
2094 return;
2095 }
2096 mHandled = true;
2097 mHandler.removeCallbacks(this);
2098 synchronized (mLock) {
2099 if (!isGlobalPriorityActiveLocked()
2100 && isVoiceKey(mKeyEvent.getKeyCode())) {
2101 handleVoiceKeyEventLocked(mPackageName, mPid, mUid, mAsSystemService,
2102 mKeyEvent, mNeedWakeLock);
2103 } else {
2104 dispatchMediaKeyEventLocked(mPackageName, mPid, mUid, mAsSystemService,
2105 mKeyEvent, mNeedWakeLock);
2106 }
2107 }
2108 }
2109 }
2110
2111 private KeyEventWakeLockReceiver mKeyEventReceiver = new KeyEventWakeLockReceiver(mHandler);
2112
2113 class KeyEventWakeLockReceiver extends ResultReceiver implements Runnable,
2114 PendingIntent.OnFinished {
2115 private final Handler mHandler;
2116 private int mRefCount = 0;
2117 private int mLastTimeoutId = 0;
2118
2119 KeyEventWakeLockReceiver(Handler handler) {
2120 super(handler);
2121 mHandler = handler;
2122 }
2123
2124 public void onTimeout() {
2125 synchronized (mLock) {
2126 if (mRefCount == 0) {
2127 // We've already released it, so just return
2128 return;
2129 }
2130 mLastTimeoutId++;
2131 mRefCount = 0;
2132 releaseWakeLockLocked();
2133 }
2134 }
2135
2136 public void aquireWakeLockLocked() {
2137 if (mRefCount == 0) {
2138 mMediaEventWakeLock.acquire();
2139 }
2140 mRefCount++;
2141 mHandler.removeCallbacks(this);
2142 mHandler.postDelayed(this, WAKELOCK_TIMEOUT);
2143
2144 }
2145
2146 @Override
2147 public void run() {
2148 onTimeout();
2149 }
2150
2151 @Override
2152 protected void onReceiveResult(int resultCode, Bundle resultData) {
2153 if (resultCode < mLastTimeoutId) {
2154 // Ignore results from calls that were before the last
2155 // timeout, just in case.
2156 return;
2157 } else {
2158 synchronized (mLock) {
2159 if (mRefCount > 0) {
2160 mRefCount--;
2161 if (mRefCount == 0) {
2162 releaseWakeLockLocked();
2163 }
2164 }
2165 }
2166 }
2167 }
2168
2169 private void releaseWakeLockLocked() {
2170 mMediaEventWakeLock.release();
2171 mHandler.removeCallbacks(this);
2172 }
2173
2174 @Override
2175 public void onSendFinished(PendingIntent pendingIntent, Intent intent, int resultCode,
2176 String resultData, Bundle resultExtras) {
2177 onReceiveResult(resultCode, null);
2178 }
2179 };
2180
2181 BroadcastReceiver mKeyEventDone = new BroadcastReceiver() {
2182 @Override
2183 public void onReceive(Context context, Intent intent) {
2184 if (intent == null) {
2185 return;
2186 }
2187 Bundle extras = intent.getExtras();
2188 if (extras == null) {
2189 return;
2190 }
2191 synchronized (mLock) {
2192 if (extras.containsKey(EXTRA_WAKELOCK_ACQUIRED)
2193 && mMediaEventWakeLock.isHeld()) {
2194 mMediaEventWakeLock.release();
2195 }
2196 }
2197 }
2198 };
2199 }
2200
2201 final class MessageHandler extends Handler {
2202 private static final int MSG_SESSIONS_CHANGED = 1;
2203 private static final int MSG_VOLUME_INITIAL_DOWN = 2;
2204 private final SparseArray<Integer> mIntegerCache = new SparseArray<>();
2205
2206 @Override
2207 public void handleMessage(Message msg) {
2208 switch (msg.what) {
2209 case MSG_SESSIONS_CHANGED:
2210 pushSessionsChanged((int) msg.obj);
2211 break;
2212 case MSG_VOLUME_INITIAL_DOWN:
2213 synchronized (mLock) {
2214 FullUserRecord user = mUserRecords.get((int) msg.arg1);
2215 if (user != null && user.mInitialDownVolumeKeyEvent != null) {
2216 dispatchVolumeKeyLongPressLocked(user.mInitialDownVolumeKeyEvent);
2217 // Mark that the key is already handled.
2218 user.mInitialDownVolumeKeyEvent = null;
2219 }
2220 }
2221 break;
2222 }
2223 }
2224
2225 public void postSessionsChanged(int userId) {
2226 // Use object instead of the arguments when posting message to remove pending requests.
2227 Integer userIdInteger = mIntegerCache.get(userId);
2228 if (userIdInteger == null) {
2229 userIdInteger = Integer.valueOf(userId);
2230 mIntegerCache.put(userId, userIdInteger);
2231 }
2232 removeMessages(MSG_SESSIONS_CHANGED, userIdInteger);
2233 obtainMessage(MSG_SESSIONS_CHANGED, userIdInteger).sendToTarget();
2234 }
2235 }
Hyundo Moonff36c482019-01-31 02:28:20 +00002236
2237 private class Controller2Callback extends MediaController2.ControllerCallback {
2238 private final Session2Token mToken;
2239
2240 Controller2Callback(Session2Token token) {
2241 mToken = token;
2242 }
2243
2244 @Override
2245 public void onConnected(MediaController2 controller, Session2CommandGroup allowedCommands) {
2246 synchronized (mLock) {
2247 int userId = UserHandle.getUserId(mToken.getUid());
2248 mSession2TokensPerUser.get(userId).add(mToken);
2249 pushSession2TokensChangedLocked(userId);
2250 }
2251 }
2252
2253 @Override
2254 public void onDisconnected(MediaController2 controller) {
2255 synchronized (mLock) {
2256 int userId = UserHandle.getUserId(mToken.getUid());
2257 mSession2TokensPerUser.get(userId).remove(mToken);
2258 pushSession2TokensChangedLocked(userId);
2259 }
2260 }
2261 }
Sungsoo Limd8b978d2019-01-15 20:55:22 +09002262}