1 /* 2 * Copyright 2020 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 package com.android.server.media; 17 18 import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL; 19 import static android.os.UserHandle.ALL; 20 import static android.os.UserHandle.getUserHandleForUid; 21 22 import android.annotation.NonNull; 23 import android.annotation.Nullable; 24 import android.app.ActivityManager; 25 import android.app.NotificationManager; 26 import android.content.Context; 27 import android.content.pm.PackageManager; 28 import android.media.IMediaCommunicationService; 29 import android.media.IMediaCommunicationServiceCallback; 30 import android.media.MediaController2; 31 import android.media.MediaParceledListSlice; 32 import android.media.Session2CommandGroup; 33 import android.media.Session2Token; 34 import android.media.session.MediaSessionManager; 35 import android.os.Binder; 36 import android.os.Build; 37 import android.os.Handler; 38 import android.os.IBinder; 39 import android.os.Looper; 40 import android.os.Process; 41 import android.os.RemoteException; 42 import android.os.UserHandle; 43 import android.os.UserManager; 44 import android.util.Log; 45 import android.util.SparseArray; 46 import android.util.SparseIntArray; 47 import android.view.KeyEvent; 48 49 import androidx.annotation.RequiresApi; 50 51 import com.android.internal.annotations.GuardedBy; 52 import com.android.modules.annotation.MinSdk; 53 import com.android.server.SystemService; 54 55 import java.lang.ref.WeakReference; 56 import java.util.ArrayList; 57 import java.util.List; 58 import java.util.Objects; 59 import java.util.concurrent.Executor; 60 import java.util.concurrent.Executors; 61 62 /** 63 * A system service that manages {@link android.media.MediaSession2} creations 64 * and their ongoing media playback state. 65 * @hide 66 */ 67 @MinSdk(Build.VERSION_CODES.S) 68 @RequiresApi(Build.VERSION_CODES.S) 69 public class MediaCommunicationService extends SystemService { 70 private static final String TAG = "MediaCommunicationSrv"; 71 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); 72 73 final Context mContext; 74 75 final Object mLock = new Object(); 76 final Handler mHandler = new Handler(Looper.getMainLooper()); 77 78 @GuardedBy("mLock") 79 private final SparseIntArray mFullUserIds = new SparseIntArray(); 80 @GuardedBy("mLock") 81 private final SparseArray<FullUserRecord> mUserRecords = new SparseArray<>(); 82 83 final Executor mRecordExecutor = Executors.newSingleThreadExecutor(); 84 @GuardedBy("mLock") 85 final ArrayList<CallbackRecord> mCallbackRecords = new ArrayList<>(); 86 final NotificationManager mNotificationManager; 87 MediaSessionManager mSessionManager; 88 MediaCommunicationService(Context context)89 public MediaCommunicationService(Context context) { 90 super(context); 91 mContext = context; 92 mNotificationManager = context.getSystemService(NotificationManager.class); 93 } 94 95 @Override onStart()96 public void onStart() { 97 publishBinderService(Context.MEDIA_COMMUNICATION_SERVICE, new Stub()); 98 updateUser(); 99 } 100 101 @Override onBootPhase(int phase)102 public void onBootPhase(int phase) { 103 super.onBootPhase(phase); 104 switch (phase) { 105 // This ensures MediaSessionService is started 106 case PHASE_BOOT_COMPLETED: 107 mSessionManager = mContext.getSystemService(MediaSessionManager.class); 108 break; 109 } 110 } 111 112 @Override onUserStarting(@onNull TargetUser user)113 public void onUserStarting(@NonNull TargetUser user) { 114 if (DEBUG) Log.d(TAG, "onUserStarting: " + user); 115 updateUser(); 116 } 117 118 @Override onUserSwitching(@ullable TargetUser from, @NonNull TargetUser to)119 public void onUserSwitching(@Nullable TargetUser from, @NonNull TargetUser to) { 120 if (DEBUG) Log.d(TAG, "onUserSwitching: " + to); 121 updateUser(); 122 } 123 124 @Override onUserStopped(@onNull TargetUser targetUser)125 public void onUserStopped(@NonNull TargetUser targetUser) { 126 int userId = targetUser.getUserHandle().getIdentifier(); 127 128 if (DEBUG) Log.d(TAG, "onUserStopped: " + userId); 129 synchronized (mLock) { 130 FullUserRecord user = getFullUserRecordLocked(userId); 131 if (user != null) { 132 if (user.getFullUserId() == userId) { 133 user.destroyAllSessions(); 134 mUserRecords.remove(userId); 135 } else { 136 user.destroySessionsForUser(userId); 137 } 138 } 139 } 140 updateUser(); 141 } 142 143 @Nullable findCallbackRecordLocked(@ullable IMediaCommunicationServiceCallback callback)144 CallbackRecord findCallbackRecordLocked(@Nullable IMediaCommunicationServiceCallback callback) { 145 if (callback == null) { 146 return null; 147 } 148 for (CallbackRecord record : mCallbackRecords) { 149 if (Objects.equals(callback.asBinder(), record.mCallback.asBinder())) { 150 return record; 151 } 152 } 153 return null; 154 } 155 getSession2TokensLocked(int userId)156 ArrayList<Session2Token> getSession2TokensLocked(int userId) { 157 ArrayList<Session2Token> list = new ArrayList<>(); 158 if (userId == ALL.getIdentifier()) { 159 int size = mUserRecords.size(); 160 for (int i = 0; i < size; i++) { 161 list.addAll(mUserRecords.valueAt(i).getAllSession2Tokens()); 162 } 163 } else { 164 FullUserRecord user = getFullUserRecordLocked(userId); 165 if (user != null) { 166 list.addAll(user.getSession2Tokens(userId)); 167 } 168 } 169 return list; 170 } 171 getFullUserRecordLocked(int userId)172 private FullUserRecord getFullUserRecordLocked(int userId) { 173 int fullUserId = mFullUserIds.get(userId, -1); 174 if (fullUserId < 0) { 175 return null; 176 } 177 return mUserRecords.get(fullUserId); 178 } 179 hasMediaControlPermission(int pid, int uid)180 private boolean hasMediaControlPermission(int pid, int uid) { 181 // Check if it's system server or has MEDIA_CONTENT_CONTROL. 182 // Note that system server doesn't have MEDIA_CONTENT_CONTROL, so we need extra 183 // check here. 184 if (uid == Process.SYSTEM_UID || mContext.checkPermission( 185 android.Manifest.permission.MEDIA_CONTENT_CONTROL, pid, uid) 186 == PackageManager.PERMISSION_GRANTED) { 187 return true; 188 } else if (DEBUG) { 189 Log.d(TAG, "uid(" + uid + ") hasn't granted MEDIA_CONTENT_CONTROL"); 190 } 191 return false; 192 } 193 updateUser()194 private void updateUser() { 195 UserManager manager = mContext.getSystemService(UserManager.class); 196 List<UserHandle> allUsers = manager.getUserHandles(/*excludeDying=*/false); 197 198 synchronized (mLock) { 199 mFullUserIds.clear(); 200 if (allUsers != null) { 201 for (UserHandle user : allUsers) { 202 UserHandle parent = manager.getProfileParent(user); 203 if (parent != null) { 204 mFullUserIds.put(user.getIdentifier(), parent.getIdentifier()); 205 } else { 206 mFullUserIds.put(user.getIdentifier(), user.getIdentifier()); 207 if (mUserRecords.get(user.getIdentifier()) == null) { 208 mUserRecords.put(user.getIdentifier(), 209 new FullUserRecord(user.getIdentifier())); 210 } 211 } 212 } 213 } 214 // Ensure that the current full user exists. 215 int currentFullUserId = ActivityManager.getCurrentUser(); 216 FullUserRecord currentFullUserRecord = mUserRecords.get(currentFullUserId); 217 if (currentFullUserRecord == null) { 218 Log.w(TAG, "Cannot find FullUserInfo for the current user " + currentFullUserId); 219 currentFullUserRecord = new FullUserRecord(currentFullUserId); 220 mUserRecords.put(currentFullUserId, currentFullUserRecord); 221 } 222 mFullUserIds.put(currentFullUserId, currentFullUserId); 223 } 224 } 225 dispatchSession2Created(Session2Token token)226 void dispatchSession2Created(Session2Token token) { 227 synchronized (mLock) { 228 for (CallbackRecord record : mCallbackRecords) { 229 if (record.mUserId != ALL.getIdentifier() 230 && record.mUserId != getUserHandleForUid(token.getUid()).getIdentifier()) { 231 continue; 232 } 233 try { 234 record.mCallback.onSession2Created(token); 235 } catch (RemoteException e) { 236 Log.w(TAG, "Failed to notify session2 token created " + record); 237 } 238 } 239 } 240 } 241 dispatchSession2Changed(int userId)242 void dispatchSession2Changed(int userId) { 243 ArrayList<Session2Token> allSession2Tokens; 244 ArrayList<Session2Token> userSession2Tokens; 245 246 synchronized (mLock) { 247 allSession2Tokens = getSession2TokensLocked(ALL.getIdentifier()); 248 userSession2Tokens = getSession2TokensLocked(userId); 249 250 for (CallbackRecord record : mCallbackRecords) { 251 if (record.mUserId == ALL.getIdentifier()) { 252 try { 253 MediaParceledListSlice<Session2Token> toSend = 254 new MediaParceledListSlice<>(allSession2Tokens); 255 toSend.setInlineCountLimit(0); 256 record.mCallback.onSession2Changed(toSend); 257 } catch (RemoteException e) { 258 Log.w(TAG, "Failed to notify session2 tokens changed " + record); 259 } 260 } else if (record.mUserId == userId) { 261 try { 262 MediaParceledListSlice<Session2Token> toSend = 263 new MediaParceledListSlice<>(userSession2Tokens); 264 toSend.setInlineCountLimit(0); 265 record.mCallback.onSession2Changed(toSend); 266 } catch (RemoteException e) { 267 Log.w(TAG, "Failed to notify session2 tokens changed " + record); 268 } 269 } 270 } 271 } 272 } 273 onSessionDied(Session2Record session)274 void onSessionDied(Session2Record session) { 275 if (DEBUG) { 276 Log.d(TAG, "Destroying " + session); 277 } 278 if (session.isClosed()) { 279 Log.w(TAG, "Destroying already destroyed session. Ignoring."); 280 return; 281 } 282 283 FullUserRecord user = session.getFullUser(); 284 if (user != null) { 285 user.removeSession(session); 286 } 287 session.close(); 288 } 289 onSessionPlaybackStateChanged(Session2Record session, boolean promotePriority)290 void onSessionPlaybackStateChanged(Session2Record session, boolean promotePriority) { 291 FullUserRecord user = session.getFullUser(); 292 if (user == null || !user.containsSession(session)) { 293 Log.d(TAG, "Unknown session changed playback state. Ignoring."); 294 return; 295 } 296 user.onPlaybackStateChanged(session, promotePriority); 297 } 298 299 isMediaSessionKey(int keyCode)300 static boolean isMediaSessionKey(int keyCode) { 301 switch (keyCode) { 302 case KeyEvent.KEYCODE_MEDIA_PLAY: 303 case KeyEvent.KEYCODE_MEDIA_PAUSE: 304 case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: 305 case KeyEvent.KEYCODE_MUTE: 306 case KeyEvent.KEYCODE_HEADSETHOOK: 307 case KeyEvent.KEYCODE_MEDIA_STOP: 308 case KeyEvent.KEYCODE_MEDIA_NEXT: 309 case KeyEvent.KEYCODE_MEDIA_PREVIOUS: 310 case KeyEvent.KEYCODE_MEDIA_REWIND: 311 case KeyEvent.KEYCODE_MEDIA_RECORD: 312 case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD: 313 return true; 314 } 315 return false; 316 } 317 318 private class Stub extends IMediaCommunicationService.Stub { 319 @Override notifySession2Created(Session2Token sessionToken)320 public void notifySession2Created(Session2Token sessionToken) { 321 final int pid = Binder.getCallingPid(); 322 final int uid = Binder.getCallingUid(); 323 final long token = Binder.clearCallingIdentity(); 324 325 try { 326 if (DEBUG) { 327 Log.d(TAG, "Session2 is created " + sessionToken); 328 } 329 if (uid != sessionToken.getUid()) { 330 throw new SecurityException("Unexpected Session2Token's UID, expected=" + uid 331 + " but actually=" + sessionToken.getUid()); 332 } 333 FullUserRecord user; 334 int userId = getUserHandleForUid(sessionToken.getUid()).getIdentifier(); 335 synchronized (mLock) { 336 user = getFullUserRecordLocked(userId); 337 } 338 if (user == null) { 339 Log.w(TAG, "notifySession2Created: Ignore session of an unknown user"); 340 return; 341 } 342 user.addSession(new Session2Record(MediaCommunicationService.this, 343 user, sessionToken, mRecordExecutor)); 344 } finally { 345 Binder.restoreCallingIdentity(token); 346 } 347 } 348 349 /** 350 * Returns if the controller's package is trusted (i.e. has either MEDIA_CONTENT_CONTROL 351 * permission or an enabled notification listener) 352 * 353 * @param controllerPackageName package name of the controller app 354 * @param controllerPid pid of the controller app 355 * @param controllerUid uid of the controller app 356 */ 357 @Override isTrusted(String controllerPackageName, int controllerPid, int controllerUid)358 public boolean isTrusted(String controllerPackageName, int controllerPid, 359 int controllerUid) { 360 final int uid = Binder.getCallingUid(); 361 final int userId = UserHandle.getUserHandleForUid(uid).getIdentifier(); 362 final long token = Binder.clearCallingIdentity(); 363 try { 364 // Don't perform check between controllerPackageName and controllerUid. 365 // When an (activity|service) runs on the another apps process by specifying 366 // android:process in the AndroidManifest.xml, then PID and UID would have the 367 // running process' information instead of the (activity|service) that has created 368 // MediaController. 369 // Note that we can use Context#getOpPackageName() instead of 370 // Context#getPackageName() for getting package name that matches with the PID/UID, 371 // but it doesn't tell which package has created the MediaController, so useless. 372 return hasMediaControlPermission(controllerPid, controllerUid) 373 || hasEnabledNotificationListener( 374 userId, controllerPackageName, controllerUid); 375 } finally { 376 Binder.restoreCallingIdentity(token); 377 } 378 } 379 380 @Override getSession2Tokens(int userId)381 public MediaParceledListSlice getSession2Tokens(int userId) { 382 final int pid = Binder.getCallingPid(); 383 final int uid = Binder.getCallingUid(); 384 final long token = Binder.clearCallingIdentity(); 385 386 try { 387 // Check that they can make calls on behalf of the user and get the final user id 388 int resolvedUserId = handleIncomingUser(pid, uid, userId, null); 389 ArrayList<Session2Token> result; 390 synchronized (mLock) { 391 result = getSession2TokensLocked(resolvedUserId); 392 } 393 MediaParceledListSlice parceledListSlice = new MediaParceledListSlice<>(result); 394 parceledListSlice.setInlineCountLimit(1); 395 return parceledListSlice; 396 } finally { 397 Binder.restoreCallingIdentity(token); 398 } 399 } 400 401 @Override dispatchMediaKeyEvent(String packageName, KeyEvent keyEvent, boolean asSystemService)402 public void dispatchMediaKeyEvent(String packageName, KeyEvent keyEvent, 403 boolean asSystemService) { 404 if (keyEvent == null || !isMediaSessionKey(keyEvent.getKeyCode())) { 405 Log.w(TAG, "Attempted to dispatch null or non-media key event."); 406 return; 407 } 408 409 final int pid = Binder.getCallingPid(); 410 final int uid = Binder.getCallingUid(); 411 final long token = Binder.clearCallingIdentity(); 412 try { 413 //TODO: Dispatch key event to media session 2 if required 414 mSessionManager.dispatchMediaKeyEvent(keyEvent, asSystemService); 415 } finally { 416 Binder.restoreCallingIdentity(token); 417 } 418 } 419 420 @Override registerCallback(IMediaCommunicationServiceCallback callback, String packageName)421 public void registerCallback(IMediaCommunicationServiceCallback callback, 422 String packageName) throws RemoteException { 423 Objects.requireNonNull(callback, "callback should not be null"); 424 Objects.requireNonNull(packageName, "packageName should not be null"); 425 426 synchronized (mLock) { 427 if (findCallbackRecordLocked(callback) == null) { 428 429 CallbackRecord record = new CallbackRecord(callback, packageName, 430 Binder.getCallingUid(), Binder.getCallingPid()); 431 mCallbackRecords.add(record); 432 try { 433 callback.asBinder().linkToDeath(record, 0); 434 } catch (RemoteException e) { 435 Log.w(TAG, "Failed to register callback", e); 436 mCallbackRecords.remove(record); 437 } 438 } else { 439 Log.e(TAG, "registerCallback is called with already registered callback. " 440 + "packageName=" + packageName); 441 } 442 } 443 } 444 445 @Override unregisterCallback(IMediaCommunicationServiceCallback callback)446 public void unregisterCallback(IMediaCommunicationServiceCallback callback) 447 throws RemoteException { 448 synchronized (mLock) { 449 CallbackRecord existingRecord = findCallbackRecordLocked(callback); 450 if (existingRecord != null) { 451 mCallbackRecords.remove(existingRecord); 452 callback.asBinder().unlinkToDeath(existingRecord, 0); 453 } else { 454 Log.e(TAG, "unregisterCallback is called with unregistered callback."); 455 } 456 } 457 } 458 hasEnabledNotificationListener(int callingUserId, String controllerPackageName, int controllerUid)459 private boolean hasEnabledNotificationListener(int callingUserId, 460 String controllerPackageName, int controllerUid) { 461 int controllerUserId = UserHandle.getUserHandleForUid(controllerUid).getIdentifier(); 462 if (callingUserId != controllerUserId) { 463 // Enabled notification listener only works within the same user. 464 return false; 465 } 466 467 if (mNotificationManager.hasEnabledNotificationListener(controllerPackageName, 468 UserHandle.getUserHandleForUid(controllerUid))) { 469 return true; 470 } 471 if (DEBUG) { 472 Log.d(TAG, controllerPackageName + " (uid=" + controllerUid 473 + ") doesn't have an enabled notification listener"); 474 } 475 return false; 476 } 477 478 // Handles incoming user by checking whether the caller has permission to access the 479 // given user id's information or not. Permission is not necessary if the given user id is 480 // equal to the caller's user id, but if not, the caller needs to have the 481 // INTERACT_ACROSS_USERS_FULL permission. Otherwise, a security exception will be thrown. 482 // The return value will be the given user id, unless the given user id is 483 // UserHandle.CURRENT, which will return the ActivityManager.getCurrentUser() value instead. handleIncomingUser(int pid, int uid, int userId, String packageName)484 private int handleIncomingUser(int pid, int uid, int userId, String packageName) { 485 int callingUserId = UserHandle.getUserHandleForUid(uid).getIdentifier(); 486 if (userId == callingUserId) { 487 return userId; 488 } 489 490 boolean canInteractAcrossUsersFull = mContext.checkPermission( 491 INTERACT_ACROSS_USERS_FULL, pid, uid) == PackageManager.PERMISSION_GRANTED; 492 if (canInteractAcrossUsersFull) { 493 if (userId == UserHandle.CURRENT.getIdentifier()) { 494 return ActivityManager.getCurrentUser(); 495 } 496 return userId; 497 } 498 499 throw new SecurityException("Permission denied while calling from " + packageName 500 + " with user id: " + userId + "; Need to run as either the calling user id (" 501 + callingUserId + "), or with " + INTERACT_ACROSS_USERS_FULL + " permission"); 502 } 503 } 504 505 final class CallbackRecord implements IBinder.DeathRecipient { 506 private final IMediaCommunicationServiceCallback mCallback; 507 private final String mPackageName; 508 private final int mUid; 509 private int mPid; 510 private final int mUserId; 511 CallbackRecord(IMediaCommunicationServiceCallback callback, String packageName, int uid, int pid)512 CallbackRecord(IMediaCommunicationServiceCallback callback, 513 String packageName, int uid, int pid) { 514 mCallback = callback; 515 mPackageName = packageName; 516 mUid = uid; 517 mPid = pid; 518 mUserId = (mContext.checkPermission( 519 INTERACT_ACROSS_USERS_FULL, pid, uid) == PackageManager.PERMISSION_GRANTED) 520 ? ALL.getIdentifier() : UserHandle.getUserHandleForUid(mUid).getIdentifier(); 521 } 522 523 @Override toString()524 public String toString() { 525 return "CallbackRecord[callback=" + mCallback + ", pkg=" + mPackageName 526 + ", uid=" + mUid + ", pid=" + mPid + "]"; 527 } 528 529 @Override binderDied()530 public void binderDied() { 531 synchronized (mLock) { 532 mCallbackRecords.remove(this); 533 } 534 } 535 } 536 537 final class FullUserRecord { 538 private final int mFullUserId; 539 private final SessionPriorityList mSessionPriorityList = new SessionPriorityList(); 540 FullUserRecord(int fullUserId)541 FullUserRecord(int fullUserId) { 542 mFullUserId = fullUserId; 543 } 544 addSession(Session2Record record)545 public void addSession(Session2Record record) { 546 mSessionPriorityList.addSession(record); 547 mHandler.post(() -> dispatchSession2Created(record.mSessionToken)); 548 mHandler.post(() -> dispatchSession2Changed(mFullUserId)); 549 } 550 removeSession(Session2Record record)551 private void removeSession(Session2Record record) { 552 mSessionPriorityList.removeSession(record); 553 mHandler.post(() -> dispatchSession2Changed(mFullUserId)); 554 //TODO: Handle if the removed session was the media button session. 555 } 556 getFullUserId()557 public int getFullUserId() { 558 return mFullUserId; 559 } 560 getAllSession2Tokens()561 public List<Session2Token> getAllSession2Tokens() { 562 return mSessionPriorityList.getAllTokens(); 563 } 564 getSession2Tokens(int userId)565 public List<Session2Token> getSession2Tokens(int userId) { 566 return mSessionPriorityList.getTokensByUserId(userId); 567 } 568 destroyAllSessions()569 public void destroyAllSessions() { 570 mSessionPriorityList.destroyAllSessions(); 571 mHandler.post(() -> dispatchSession2Changed(mFullUserId)); 572 } 573 destroySessionsForUser(int userId)574 public void destroySessionsForUser(int userId) { 575 if (mSessionPriorityList.destroySessionsByUserId(userId)) { 576 mHandler.post(() -> dispatchSession2Changed(mFullUserId)); 577 } 578 } 579 containsSession(Session2Record session)580 public boolean containsSession(Session2Record session) { 581 return mSessionPriorityList.contains(session); 582 } 583 onPlaybackStateChanged(Session2Record session, boolean promotePriority)584 public void onPlaybackStateChanged(Session2Record session, boolean promotePriority) { 585 mSessionPriorityList.onPlaybackStateChanged(session, promotePriority); 586 } 587 } 588 589 static final class Session2Record { 590 final Session2Token mSessionToken; 591 final Object mSession2RecordLock = new Object(); 592 final WeakReference<MediaCommunicationService> mServiceRef; 593 final WeakReference<FullUserRecord> mFullUserRef; 594 @GuardedBy("mSession2RecordLock") 595 private final MediaController2 mController; 596 597 @GuardedBy("mSession2RecordLock") 598 boolean mIsConnected; 599 @GuardedBy("mSession2RecordLock") 600 private boolean mIsClosed; 601 602 //TODO: introduce policy (See MediaSessionPolicyProvider) Session2Record(MediaCommunicationService service, FullUserRecord fullUser, Session2Token token, Executor controllerExecutor)603 Session2Record(MediaCommunicationService service, FullUserRecord fullUser, 604 Session2Token token, Executor controllerExecutor) { 605 mServiceRef = new WeakReference<>(service); 606 mFullUserRef = new WeakReference<>(fullUser); 607 mSessionToken = token; 608 mController = new MediaController2.Builder(service.getContext(), token) 609 .setControllerCallback(controllerExecutor, new Controller2Callback()) 610 .build(); 611 } 612 getUserId()613 public int getUserId() { 614 return UserHandle.getUserHandleForUid(mSessionToken.getUid()).getIdentifier(); 615 } 616 getFullUser()617 public FullUserRecord getFullUser() { 618 return mFullUserRef.get(); 619 } 620 isClosed()621 public boolean isClosed() { 622 synchronized (mSession2RecordLock) { 623 return mIsClosed; 624 } 625 } 626 close()627 public void close() { 628 synchronized (mSession2RecordLock) { 629 mIsClosed = true; 630 mController.close(); 631 } 632 } 633 getSessionToken()634 public Session2Token getSessionToken() { 635 return mSessionToken; 636 } 637 checkPlaybackActiveState(boolean expected)638 public boolean checkPlaybackActiveState(boolean expected) { 639 synchronized (mSession2RecordLock) { 640 return mIsConnected && mController.isPlaybackActive() == expected; 641 } 642 } 643 644 private class Controller2Callback extends MediaController2.ControllerCallback { 645 @Override onConnected(MediaController2 controller, Session2CommandGroup allowedCommands)646 public void onConnected(MediaController2 controller, 647 Session2CommandGroup allowedCommands) { 648 if (DEBUG) { 649 Log.d(TAG, "connected to " + mSessionToken + ", allowed=" + allowedCommands); 650 } 651 synchronized (mSession2RecordLock) { 652 mIsConnected = true; 653 } 654 } 655 656 @Override onDisconnected(MediaController2 controller)657 public void onDisconnected(MediaController2 controller) { 658 if (DEBUG) { 659 Log.d(TAG, "disconnected from " + mSessionToken); 660 } 661 synchronized (mSession2RecordLock) { 662 mIsConnected = false; 663 } 664 MediaCommunicationService service = mServiceRef.get(); 665 if (service != null) { 666 service.onSessionDied(Session2Record.this); 667 } 668 } 669 670 @Override onPlaybackActiveChanged( @onNull MediaController2 controller, boolean playbackActive)671 public void onPlaybackActiveChanged( 672 @NonNull MediaController2 controller, 673 boolean playbackActive) { 674 if (DEBUG) { 675 Log.d(TAG, "playback active changed, " + mSessionToken + ", active=" 676 + playbackActive); 677 } 678 MediaCommunicationService service = mServiceRef.get(); 679 if (service != null) { 680 service.onSessionPlaybackStateChanged(Session2Record.this, playbackActive); 681 } 682 } 683 } 684 } 685 } 686