1 /* 2 * Copyright (C) 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 package com.android.car; 17 18 import static android.car.media.CarMediaManager.MEDIA_SOURCE_MODE_BROWSE; 19 import static android.car.media.CarMediaManager.MEDIA_SOURCE_MODE_PLAYBACK; 20 21 import android.annotation.NonNull; 22 import android.annotation.Nullable; 23 import android.annotation.TestApi; 24 import android.app.ActivityManager; 25 import android.car.Car; 26 import android.car.hardware.power.CarPowerPolicy; 27 import android.car.hardware.power.CarPowerPolicyFilter; 28 import android.car.hardware.power.ICarPowerPolicyListener; 29 import android.car.hardware.power.PowerComponent; 30 import android.car.media.CarMediaManager; 31 import android.car.media.CarMediaManager.MediaSourceChangedListener; 32 import android.car.media.CarMediaManager.MediaSourceMode; 33 import android.car.media.ICarMedia; 34 import android.car.media.ICarMediaSourceListener; 35 import android.car.user.CarUserManager; 36 import android.car.user.CarUserManager.UserLifecycleListener; 37 import android.content.BroadcastReceiver; 38 import android.content.ComponentName; 39 import android.content.Context; 40 import android.content.Intent; 41 import android.content.IntentFilter; 42 import android.content.SharedPreferences; 43 import android.content.pm.PackageManager; 44 import android.content.pm.ResolveInfo; 45 import android.media.session.MediaController; 46 import android.media.session.MediaController.TransportControls; 47 import android.media.session.MediaSession; 48 import android.media.session.MediaSession.Token; 49 import android.media.session.MediaSessionManager; 50 import android.media.session.MediaSessionManager.OnActiveSessionsChangedListener; 51 import android.media.session.PlaybackState; 52 import android.os.Bundle; 53 import android.os.Handler; 54 import android.os.HandlerExecutor; 55 import android.os.HandlerThread; 56 import android.os.Looper; 57 import android.os.RemoteCallbackList; 58 import android.os.RemoteException; 59 import android.os.UserHandle; 60 import android.os.UserManager; 61 import android.service.media.MediaBrowserService; 62 import android.text.TextUtils; 63 import android.util.IndentingPrintWriter; 64 import android.util.Log; 65 import android.util.Slog; 66 67 import com.android.car.power.CarPowerManagementService; 68 import com.android.car.user.CarUserService; 69 import com.android.internal.annotations.GuardedBy; 70 import com.android.internal.annotations.VisibleForTesting; 71 72 import java.util.ArrayDeque; 73 import java.util.ArrayList; 74 import java.util.Arrays; 75 import java.util.Deque; 76 import java.util.HashMap; 77 import java.util.List; 78 import java.util.Map; 79 import java.util.stream.Collectors; 80 81 /** 82 * CarMediaService manages the currently active media source for car apps. This is different from 83 * the MediaSessionManager's active sessions, as there can only be one active source in the car, 84 * through both browse and playback. 85 * 86 * In the car, the active media source does not necessarily have an active MediaSession, e.g. if 87 * it were being browsed only. However, that source is still considered the active source, and 88 * should be the source displayed in any Media related UIs (Media Center, home screen, etc). 89 */ 90 public class CarMediaService extends ICarMedia.Stub implements CarServiceBase { 91 92 private static final String SOURCE_KEY = "media_source_component"; 93 private static final String SOURCE_KEY_SEPARATOR = "_"; 94 private static final String PLAYBACK_STATE_KEY = "playback_state"; 95 private static final String SHARED_PREF = "com.android.car.media.car_media_service"; 96 private static final String COMPONENT_NAME_SEPARATOR = ","; 97 private static final String MEDIA_CONNECTION_ACTION = "com.android.car.media.MEDIA_CONNECTION"; 98 private static final String EXTRA_AUTOPLAY = "com.android.car.media.autoplay"; 99 100 private static final int MEDIA_SOURCE_MODES = 2; 101 102 // XML configuration options for autoplay on media source change. 103 private static final int AUTOPLAY_CONFIG_NEVER = 0; 104 private static final int AUTOPLAY_CONFIG_ALWAYS = 1; 105 // This mode uses the current source's last stored playback state to resume playback 106 private static final int AUTOPLAY_CONFIG_RETAIN_PER_SOURCE = 2; 107 // This mode uses the previous source's playback state to resume playback 108 private static final int AUTOPLAY_CONFIG_RETAIN_PREVIOUS = 3; 109 110 private final Context mContext; 111 private final CarUserService mUserService; 112 private final UserManager mUserManager; 113 private final MediaSessionManager mMediaSessionManager; 114 private final MediaSessionUpdater mMediaSessionUpdater = new MediaSessionUpdater(); 115 @GuardedBy("mLock") 116 private ComponentName[] mPrimaryMediaComponents = new ComponentName[MEDIA_SOURCE_MODES]; 117 // MediaController for the current active user's active media session. This controller can be 118 // null if playback has not been started yet. 119 @GuardedBy("mLock") 120 private MediaController mActiveUserMediaController; 121 @GuardedBy("mLock") 122 private int mCurrentPlaybackState; 123 @GuardedBy("mLock") 124 private boolean mIsDisabledByPowerPolicy; 125 @GuardedBy("mLock") 126 private boolean mWasPreviouslyDisabledByPowerPolicy; 127 @GuardedBy("mLock") 128 private boolean mWasPlayingBeforeDisabled; 129 private SharedPreferences mSharedPrefs; 130 private SessionChangedListener mSessionsListener; 131 private int mPlayOnMediaSourceChangedConfig; 132 private int mPlayOnBootConfig; 133 private boolean mIndependentPlaybackConfig; 134 135 private boolean mPendingInit; 136 137 @GuardedBy("mLock") 138 private final RemoteCallbackList<ICarMediaSourceListener>[] mMediaSourceListeners = 139 new RemoteCallbackList[MEDIA_SOURCE_MODES]; 140 141 private final Handler mMainHandler = new Handler(Looper.getMainLooper()); 142 143 144 private final HandlerThread mHandlerThread = CarServiceUtils.getHandlerThread( 145 getClass().getSimpleName()); 146 // Handler to receive PlaybackState callbacks from the active media controller. 147 private final Handler mHandler = new Handler(mHandlerThread.getLooper()); 148 private final Object mLock = new Object(); 149 150 /** The component name of the last media source that was removed while being primary. */ 151 private ComponentName[] mRemovedMediaSourceComponents = new ComponentName[MEDIA_SOURCE_MODES]; 152 153 private final IntentFilter mPackageUpdateFilter; 154 private boolean mIsPackageUpdateReceiverRegistered; 155 156 /** 157 * Listens to {@link Intent#ACTION_PACKAGE_REMOVED}, so we can fall back to a previously used 158 * media source when the active source is uninstalled. 159 */ 160 private final BroadcastReceiver mPackageUpdateReceiver = new BroadcastReceiver() { 161 @Override 162 public void onReceive(Context context, Intent intent) { 163 if (intent.getData() == null) { 164 return; 165 } 166 String intentPackage = intent.getData().getSchemeSpecificPart(); 167 if (Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())) { 168 synchronized (mLock) { 169 for (int i = 0; i < MEDIA_SOURCE_MODES; i++) { 170 if (mPrimaryMediaComponents[i] != null 171 && mPrimaryMediaComponents[i].getPackageName().equals( 172 intentPackage)) { 173 if (intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) { 174 // If package is being replaced, it may not be removed from 175 // PackageManager queries when we check for available 176 // MediaBrowseServices, so we iterate to find the next available 177 // source. 178 for (ComponentName component : getLastMediaSources(i)) { 179 if (!mPrimaryMediaComponents[i].getPackageName() 180 .equals(component.getPackageName())) { 181 mRemovedMediaSourceComponents[i] = 182 mPrimaryMediaComponents[i]; 183 if (Log.isLoggable(CarLog.TAG_MEDIA, Log.DEBUG)) { 184 Slog.d(CarLog.TAG_MEDIA, 185 "temporarily replacing updated media source " 186 + mPrimaryMediaComponents[i] 187 + "with backup source: " 188 + component); 189 } 190 setPrimaryMediaSource(component, i); 191 return; 192 } 193 } 194 Slog.e(CarLog.TAG_MEDIA, "No available backup media source"); 195 } else { 196 if (Log.isLoggable(CarLog.TAG_MEDIA, Log.DEBUG)) { 197 Slog.d(CarLog.TAG_MEDIA, "replacing removed media source " 198 + mPrimaryMediaComponents[i] + "with backup source: " 199 + getLastMediaSource(i)); 200 } 201 mRemovedMediaSourceComponents[i] = null; 202 setPrimaryMediaSource(getLastMediaSource(i), i); 203 } 204 } 205 } 206 } 207 } else if (Intent.ACTION_PACKAGE_REPLACED.equals(intent.getAction()) 208 || Intent.ACTION_PACKAGE_ADDED.equals(intent.getAction())) { 209 for (int i = 0; i < MEDIA_SOURCE_MODES; i++) { 210 if (mRemovedMediaSourceComponents[i] != null && mRemovedMediaSourceComponents[i] 211 .getPackageName().equals(intentPackage)) { 212 if (Log.isLoggable(CarLog.TAG_MEDIA, Log.DEBUG)) { 213 Slog.d(CarLog.TAG_MEDIA, "restoring removed source: " 214 + mRemovedMediaSourceComponents[i]); 215 } 216 setPrimaryMediaSource(mRemovedMediaSourceComponents[i], i); 217 } 218 } 219 } 220 } 221 }; 222 223 private final UserLifecycleListener mUserLifecycleListener = event -> { 224 if (Log.isLoggable(CarLog.TAG_MEDIA, Log.DEBUG)) { 225 Slog.d(CarLog.TAG_MEDIA, "CarMediaService.onEvent(" + event + ")"); 226 } 227 228 switch (event.getEventType()) { 229 case CarUserManager.USER_LIFECYCLE_EVENT_TYPE_SWITCHING: 230 maybeInitUser(event.getUserId()); 231 break; 232 case CarUserManager.USER_LIFECYCLE_EVENT_TYPE_UNLOCKED: 233 onUserUnlock(event.getUserId()); 234 break; 235 } 236 }; 237 238 private final ICarPowerPolicyListener mPowerPolicyListener = 239 new ICarPowerPolicyListener.Stub() { 240 @Override 241 public void onPolicyChanged(CarPowerPolicy appliedPolicy, 242 CarPowerPolicy accumulatedPolicy) { 243 boolean shouldBePlaying; 244 MediaController mediaController; 245 boolean isOff = !accumulatedPolicy.isComponentEnabled(PowerComponent.MEDIA); 246 synchronized (mLock) { 247 boolean weArePlaying = mCurrentPlaybackState == PlaybackState.STATE_PLAYING; 248 mIsDisabledByPowerPolicy = isOff; 249 if (isOff) { 250 if (!mWasPreviouslyDisabledByPowerPolicy) { 251 // We're disabling media component. 252 // Remember if we are playing at this transition. 253 mWasPlayingBeforeDisabled = weArePlaying; 254 mWasPreviouslyDisabledByPowerPolicy = true; 255 } 256 shouldBePlaying = false; 257 } else { 258 mWasPreviouslyDisabledByPowerPolicy = false; 259 shouldBePlaying = mWasPlayingBeforeDisabled; 260 } 261 if (shouldBePlaying == weArePlaying) { 262 return; 263 } 264 // Make a change 265 mediaController = mActiveUserMediaController; 266 if (mediaController == null) { 267 return; 268 } 269 } 270 PlaybackState oldState = mediaController.getPlaybackState(); 271 savePlaybackState( 272 // The new state is the same as the old state, except for play/pause 273 new PlaybackState.Builder(oldState) 274 .setState(shouldBePlaying ? PlaybackState.STATE_PLAYING 275 : PlaybackState.STATE_PAUSED, 276 oldState.getPosition(), 277 oldState.getPlaybackSpeed()) 278 .build()); 279 if (shouldBePlaying) { 280 mediaController.getTransportControls().play(); 281 } else { 282 mediaController.getTransportControls().pause(); 283 } 284 } 285 }; 286 CarMediaService(Context context, CarUserService userService)287 public CarMediaService(Context context, CarUserService userService) { 288 mContext = context; 289 mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE); 290 mMediaSessionManager = mContext.getSystemService(MediaSessionManager.class); 291 mMediaSourceListeners[MEDIA_SOURCE_MODE_PLAYBACK] = new RemoteCallbackList(); 292 mMediaSourceListeners[MEDIA_SOURCE_MODE_BROWSE] = new RemoteCallbackList(); 293 mIndependentPlaybackConfig = mContext.getResources().getBoolean( 294 R.bool.config_mediaSourceIndependentPlayback); 295 296 mPackageUpdateFilter = new IntentFilter(); 297 mPackageUpdateFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); 298 mPackageUpdateFilter.addAction(Intent.ACTION_PACKAGE_REPLACED); 299 mPackageUpdateFilter.addAction(Intent.ACTION_PACKAGE_ADDED); 300 mPackageUpdateFilter.addDataScheme("package"); 301 mUserService = userService; 302 mUserService.addUserLifecycleListener(mUserLifecycleListener); 303 304 mPlayOnMediaSourceChangedConfig = 305 mContext.getResources().getInteger(R.integer.config_mediaSourceChangedAutoplay); 306 mPlayOnBootConfig = mContext.getResources().getInteger(R.integer.config_mediaBootAutoplay); 307 } 308 309 @Override 310 // This method is called from ICarImpl after CarMediaService is created. init()311 public void init() { 312 int currentUser = ActivityManager.getCurrentUser(); 313 maybeInitUser(currentUser); 314 setPowerPolicyListener(); 315 } 316 maybeInitUser(int userId)317 private void maybeInitUser(int userId) { 318 if (userId == UserHandle.USER_SYSTEM) { 319 return; 320 } 321 if (mUserManager.isUserUnlocked(userId)) { 322 initUser(userId); 323 } else { 324 mPendingInit = true; 325 } 326 } 327 initUser(int userId)328 private void initUser(int userId) { 329 // SharedPreferences are shared among different users thus only need initialized once. And 330 // they should be initialized after user 0 is unlocked because SharedPreferences in 331 // credential encrypted storage are not available until after user 0 is unlocked. 332 // initUser() is called when the current foreground user is unlocked, and by that time user 333 // 0 has been unlocked already, so initializing SharedPreferences in initUser() is fine. 334 synchronized (mLock) { 335 if (mSharedPrefs == null) { 336 mSharedPrefs = mContext.getSharedPreferences(SHARED_PREF, Context.MODE_PRIVATE); 337 } 338 339 if (mIsPackageUpdateReceiverRegistered) { 340 mContext.unregisterReceiver(mPackageUpdateReceiver); 341 } 342 UserHandle currentUser = new UserHandle(userId); 343 mContext.registerReceiverAsUser(mPackageUpdateReceiver, currentUser, 344 mPackageUpdateFilter, null, null); 345 mIsPackageUpdateReceiverRegistered = true; 346 347 mPrimaryMediaComponents[MEDIA_SOURCE_MODE_PLAYBACK] = isCurrentUserEphemeral() 348 ? getDefaultMediaSource() : getLastMediaSource(MEDIA_SOURCE_MODE_PLAYBACK); 349 mPrimaryMediaComponents[MEDIA_SOURCE_MODE_BROWSE] = isCurrentUserEphemeral() 350 ? getDefaultMediaSource() : getLastMediaSource(MEDIA_SOURCE_MODE_BROWSE); 351 mActiveUserMediaController = null; 352 353 updateMediaSessionCallbackForCurrentUser(); 354 notifyListeners(MEDIA_SOURCE_MODE_PLAYBACK); 355 notifyListeners(MEDIA_SOURCE_MODE_BROWSE); 356 357 startMediaConnectorService(shouldStartPlayback(mPlayOnBootConfig), currentUser); 358 } 359 } 360 361 /** 362 * Starts a service on the current user that binds to the media browser of the current media 363 * source. We start a new service because this one runs on user 0, and MediaBrowser doesn't 364 * provide an API to connect on a specific user. Additionally, this service will attempt to 365 * resume playback using the MediaSession obtained via the media browser connection, which 366 * is more reliable than using active MediaSessions from MediaSessionManager. 367 */ startMediaConnectorService(boolean startPlayback, UserHandle currentUser)368 private void startMediaConnectorService(boolean startPlayback, UserHandle currentUser) { 369 Intent serviceStart = new Intent(MEDIA_CONNECTION_ACTION); 370 serviceStart.setPackage(mContext.getResources().getString(R.string.serviceMediaConnection)); 371 serviceStart.putExtra(EXTRA_AUTOPLAY, startPlayback); 372 mContext.startForegroundServiceAsUser(serviceStart, currentUser); 373 } 374 sharedPrefsInitialized()375 private boolean sharedPrefsInitialized() { 376 if (mSharedPrefs == null) { 377 // It shouldn't reach this but let's be cautious. 378 Slog.e(CarLog.TAG_MEDIA, "SharedPreferences are not initialized!"); 379 String className = getClass().getName(); 380 for (StackTraceElement ste : Thread.currentThread().getStackTrace()) { 381 // Let's print the useful logs only. 382 String log = ste.toString(); 383 if (log.contains(className)) { 384 Slog.e(CarLog.TAG_MEDIA, log); 385 } 386 } 387 return false; 388 } 389 return true; 390 } 391 isCurrentUserEphemeral()392 private boolean isCurrentUserEphemeral() { 393 return mUserManager.getUserInfo(ActivityManager.getCurrentUser()).isEphemeral(); 394 } 395 396 // Sets a listener to be notified when the current power policy changes. 397 // Basically, the listener pauses the audio when a media component is disabled and resumes 398 // the audio when a media component is enabled. 399 // This is called only from init(). setPowerPolicyListener()400 private void setPowerPolicyListener() { 401 CarPowerPolicyFilter filter = new CarPowerPolicyFilter.Builder() 402 .setComponents(PowerComponent.MEDIA).build(); 403 CarLocalServices.getService(CarPowerManagementService.class) 404 .addPowerPolicyListener(filter, mPowerPolicyListener); 405 } 406 407 @Override release()408 public void release() { 409 mMediaSessionUpdater.unregisterCallbacks(); 410 mUserService.removeUserLifecycleListener(mUserLifecycleListener); 411 CarLocalServices.getService(CarPowerManagementService.class) 412 .removePowerPolicyListener(mPowerPolicyListener); 413 } 414 415 @Override dump(IndentingPrintWriter writer)416 public void dump(IndentingPrintWriter writer) { 417 synchronized (mLock) { 418 writer.println("*CarMediaService*"); 419 writer.increaseIndent(); 420 writer.printf("Current playback media component: %s\n", 421 mPrimaryMediaComponents[MEDIA_SOURCE_MODE_PLAYBACK] == null ? "-" 422 : mPrimaryMediaComponents[MEDIA_SOURCE_MODE_PLAYBACK].flattenToString()); 423 writer.printf("Current browse media component: %s\n", 424 mPrimaryMediaComponents[MEDIA_SOURCE_MODE_BROWSE] == null ? "-" 425 : mPrimaryMediaComponents[MEDIA_SOURCE_MODE_BROWSE].flattenToString()); 426 if (mActiveUserMediaController != null) { 427 writer.printf("Current media controller: %s\n", 428 mActiveUserMediaController.getPackageName()); 429 writer.printf("Current browse service extra: %s\n", 430 getClassName(mActiveUserMediaController)); 431 } 432 writer.printf("Number of active media sessions: %s\n", mMediaSessionManager 433 .getActiveSessionsForUser(null, 434 new UserHandle(ActivityManager.getCurrentUser())).size()); 435 436 writer.println("Playback media source history:"); 437 writer.increaseIndent(); 438 for (ComponentName name : getLastMediaSources(MEDIA_SOURCE_MODE_PLAYBACK)) { 439 writer.println(name.flattenToString()); 440 } 441 writer.decreaseIndent(); 442 writer.println("Browse media source history:"); 443 writer.increaseIndent(); 444 for (ComponentName name : getLastMediaSources(MEDIA_SOURCE_MODE_BROWSE)) { 445 writer.println(name.flattenToString()); 446 } 447 writer.decreaseIndent(); 448 writer.printf("Disabled by power policy: %s\n", mIsDisabledByPowerPolicy); 449 if (mIsDisabledByPowerPolicy) { 450 writer.printf("Before being disabled by power policy, audio was %s\n", 451 mWasPlayingBeforeDisabled ? "active" : "inactive"); 452 } 453 } 454 } 455 456 /** 457 * @see {@link CarMediaManager#setMediaSource(ComponentName)} 458 */ 459 @Override setMediaSource(@onNull ComponentName componentName, @MediaSourceMode int mode)460 public void setMediaSource(@NonNull ComponentName componentName, 461 @MediaSourceMode int mode) { 462 ICarImpl.assertPermission(mContext, android.Manifest.permission.MEDIA_CONTENT_CONTROL); 463 if (Log.isLoggable(CarLog.TAG_MEDIA, Log.DEBUG)) { 464 Slog.d(CarLog.TAG_MEDIA, "Changing media source to: " + componentName.getPackageName()); 465 } 466 setPrimaryMediaSource(componentName, mode); 467 } 468 469 /** 470 * @see {@link CarMediaManager#getMediaSource()} 471 */ 472 @Override getMediaSource(@arMediaManager.MediaSourceMode int mode)473 public ComponentName getMediaSource(@CarMediaManager.MediaSourceMode int mode) { 474 ICarImpl.assertPermission(mContext, android.Manifest.permission.MEDIA_CONTENT_CONTROL); 475 synchronized (mLock) { 476 return mPrimaryMediaComponents[mode]; 477 } 478 } 479 480 /** 481 * @see {@link CarMediaManager#registerMediaSourceListener(MediaSourceChangedListener)} 482 */ 483 @Override registerMediaSourceListener(ICarMediaSourceListener callback, @MediaSourceMode int mode)484 public void registerMediaSourceListener(ICarMediaSourceListener callback, 485 @MediaSourceMode int mode) { 486 ICarImpl.assertPermission(mContext, android.Manifest.permission.MEDIA_CONTENT_CONTROL); 487 synchronized (mLock) { 488 mMediaSourceListeners[mode].register(callback); 489 } 490 } 491 492 /** 493 * @see {@link CarMediaManager#unregisterMediaSourceListener(ICarMediaSourceListener)} 494 */ 495 @Override unregisterMediaSourceListener(ICarMediaSourceListener callback, @MediaSourceMode int mode)496 public void unregisterMediaSourceListener(ICarMediaSourceListener callback, 497 @MediaSourceMode int mode) { 498 ICarImpl.assertPermission(mContext, android.Manifest.permission.MEDIA_CONTENT_CONTROL); 499 synchronized (mLock) { 500 mMediaSourceListeners[mode].unregister(callback); 501 } 502 } 503 504 @Override getLastMediaSources(@arMediaManager.MediaSourceMode int mode)505 public List<ComponentName> getLastMediaSources(@CarMediaManager.MediaSourceMode int mode) { 506 ICarImpl.assertPermission(mContext, android.Manifest.permission.MEDIA_CONTENT_CONTROL); 507 String key = getMediaSourceKey(mode); 508 String serialized = mSharedPrefs.getString(key, ""); 509 return getComponentNameList(serialized).stream() 510 .map(name -> ComponentName.unflattenFromString(name)).collect(Collectors.toList()); 511 } 512 513 /** See {@link CarMediaManager#isIndependentPlaybackConfig}. */ 514 @Override 515 @TestApi isIndependentPlaybackConfig()516 public boolean isIndependentPlaybackConfig() { 517 ICarImpl.assertPermission(mContext, android.Manifest.permission.MEDIA_CONTENT_CONTROL); 518 synchronized (mLock) { 519 return mIndependentPlaybackConfig; 520 } 521 } 522 523 /** See {@link CarMediaManager#setIndependentPlaybackConfig}. */ 524 @Override 525 @TestApi setIndependentPlaybackConfig(boolean independent)526 public void setIndependentPlaybackConfig(boolean independent) { 527 ICarImpl.assertPermission(mContext, android.Manifest.permission.MEDIA_CONTENT_CONTROL); 528 synchronized (mLock) { 529 mIndependentPlaybackConfig = independent; 530 } 531 } 532 533 // TODO(b/153115826): this method was used to be called from the ICar binder thread, but it's 534 // now called by UserCarService. Currently UserCarServie is calling every listener in one 535 // non-main thread, but it's not clear how the final behavior will be. So, for now it's ok 536 // to post it to mMainHandler, but once b/145689885 is fixed, we might not need it. onUserUnlock(int userId)537 private void onUserUnlock(int userId) { 538 mMainHandler.post(() -> { 539 // No need to handle system user, non current foreground user. 540 if (userId == UserHandle.USER_SYSTEM 541 || userId != ActivityManager.getCurrentUser()) { 542 return; 543 } 544 if (mPendingInit) { 545 initUser(userId); 546 mPendingInit = false; 547 if (Log.isLoggable(CarLog.TAG_MEDIA, Log.DEBUG)) { 548 Slog.d(CarLog.TAG_MEDIA, 549 "User " + userId + " is now unlocked"); 550 } 551 } 552 }); 553 } 554 updateMediaSessionCallbackForCurrentUser()555 private void updateMediaSessionCallbackForCurrentUser() { 556 if (mSessionsListener != null) { 557 mMediaSessionManager.removeOnActiveSessionsChangedListener(mSessionsListener); 558 } 559 mSessionsListener = new SessionChangedListener(ActivityManager.getCurrentUser()); 560 UserHandle currentUserHandle = new UserHandle(ActivityManager.getCurrentUser()); 561 mMediaSessionManager.addOnActiveSessionsChangedListener(null, currentUserHandle, 562 new HandlerExecutor(mHandler), mSessionsListener); 563 mMediaSessionUpdater.registerCallbacks(mMediaSessionManager.getActiveSessionsForUser(null, 564 currentUserHandle)); 565 } 566 567 /** 568 * Attempts to stop the current source using MediaController.TransportControls.stop() 569 * This method also unregisters callbacks to the active media controller before calling stop(), 570 * to preserve the PlaybackState before stopping. 571 */ stopAndUnregisterCallback()572 private void stopAndUnregisterCallback() { 573 synchronized (mLock) { 574 if (mActiveUserMediaController != null) { 575 mActiveUserMediaController.unregisterCallback(mMediaControllerCallback); 576 if (Log.isLoggable(CarLog.TAG_MEDIA, Log.DEBUG)) { 577 Slog.d(CarLog.TAG_MEDIA, 578 "stopping " + mActiveUserMediaController.getPackageName()); 579 } 580 TransportControls controls = mActiveUserMediaController.getTransportControls(); 581 if (controls != null) { 582 controls.stop(); 583 } else { 584 Slog.e(CarLog.TAG_MEDIA, "Can't stop playback, transport controls unavailable " 585 + mActiveUserMediaController.getPackageName()); 586 } 587 } 588 } 589 } 590 591 private class SessionChangedListener implements OnActiveSessionsChangedListener { 592 private final int mCurrentUser; 593 SessionChangedListener(int currentUser)594 SessionChangedListener(int currentUser) { 595 mCurrentUser = currentUser; 596 } 597 598 @Override onActiveSessionsChanged(List<MediaController> controllers)599 public void onActiveSessionsChanged(List<MediaController> controllers) { 600 if (ActivityManager.getCurrentUser() != mCurrentUser) { 601 Slog.e(CarLog.TAG_MEDIA, "Active session callback for old user: " + mCurrentUser); 602 return; 603 } 604 mMediaSessionUpdater.registerCallbacks(controllers); 605 } 606 } 607 608 private class MediaControllerCallback extends MediaController.Callback { 609 610 private final MediaController mMediaController; 611 private int mPreviousPlaybackState; 612 MediaControllerCallback(MediaController mediaController)613 private MediaControllerCallback(MediaController mediaController) { 614 mMediaController = mediaController; 615 PlaybackState state = mediaController.getPlaybackState(); 616 mPreviousPlaybackState = (state == null) ? PlaybackState.STATE_NONE : state.getState(); 617 } 618 register()619 private void register() { 620 mMediaController.registerCallback(this); 621 } 622 unregister()623 private void unregister() { 624 mMediaController.unregisterCallback(this); 625 } 626 627 @Override onPlaybackStateChanged(@ullable PlaybackState state)628 public void onPlaybackStateChanged(@Nullable PlaybackState state) { 629 if (state.getState() == PlaybackState.STATE_PLAYING 630 && state.getState() != mPreviousPlaybackState) { 631 ComponentName mediaSource = getMediaSource(mMediaController.getPackageName(), 632 getClassName(mMediaController)); 633 if (mediaSource != null 634 && !mediaSource.equals(mPrimaryMediaComponents[MEDIA_SOURCE_MODE_PLAYBACK]) 635 && Log.isLoggable(CarLog.TAG_MEDIA, Log.INFO)) { 636 Slog.i(CarLog.TAG_MEDIA, "Changing media source due to playback state change: " 637 + mediaSource.flattenToString()); 638 } 639 setPrimaryMediaSource(mediaSource, MEDIA_SOURCE_MODE_PLAYBACK); 640 } 641 mPreviousPlaybackState = state.getState(); 642 } 643 } 644 645 private class MediaSessionUpdater { 646 private Map<Token, MediaControllerCallback> mCallbacks = new HashMap<>(); 647 648 /** 649 * Register a {@link MediaControllerCallback} for each given controller. Note that if a 650 * controller was already watched, we don't register a callback again. This prevents an 651 * undesired revert of the primary media source. Callbacks for previously watched 652 * controllers that are not present in the given list are unregistered. 653 */ registerCallbacks(List<MediaController> newControllers)654 private void registerCallbacks(List<MediaController> newControllers) { 655 656 List<MediaController> additions = new ArrayList<>(newControllers.size()); 657 Map<MediaSession.Token, MediaControllerCallback> updatedCallbacks = 658 new HashMap<>(newControllers.size()); 659 660 for (MediaController controller : newControllers) { 661 MediaSession.Token token = controller.getSessionToken(); 662 MediaControllerCallback callback = mCallbacks.get(token); 663 if (callback == null) { 664 callback = new MediaControllerCallback(controller); 665 callback.register(); 666 additions.add(controller); 667 } 668 updatedCallbacks.put(token, callback); 669 } 670 671 for (MediaSession.Token token : mCallbacks.keySet()) { 672 if (!updatedCallbacks.containsKey(token)) { 673 mCallbacks.get(token).unregister(); 674 } 675 } 676 677 mCallbacks = updatedCallbacks; 678 updatePrimaryMediaSourceWithCurrentlyPlaying(additions); 679 // If there are no playing media sources, and we don't currently have the controller 680 // for the active source, check the active sessions for a matching controller. If this 681 // is called after a user switch, its possible for a matching controller to already be 682 // active before the user is unlocked, so we check all of the current controllers 683 synchronized (mLock) { 684 if (mActiveUserMediaController == null) { 685 updateActiveMediaControllerLocked(newControllers); 686 } 687 } 688 } 689 690 /** 691 * Unregister all MediaController callbacks 692 */ unregisterCallbacks()693 private void unregisterCallbacks() { 694 for (Map.Entry<Token, MediaControllerCallback> entry : mCallbacks.entrySet()) { 695 entry.getValue().unregister(); 696 } 697 } 698 } 699 700 /** 701 * Updates the primary media source, then notifies content observers of the change 702 * Will update both the playback and browse sources if independent playback is not supported 703 */ setPrimaryMediaSource(@onNull ComponentName componentName, @CarMediaManager.MediaSourceMode int mode)704 private void setPrimaryMediaSource(@NonNull ComponentName componentName, 705 @CarMediaManager.MediaSourceMode int mode) { 706 synchronized (mLock) { 707 if (mPrimaryMediaComponents[mode] != null 708 && mPrimaryMediaComponents[mode].equals((componentName))) { 709 return; 710 } 711 } 712 713 if (!mIndependentPlaybackConfig) { 714 setPlaybackMediaSource(componentName); 715 setBrowseMediaSource(componentName); 716 } else if (mode == MEDIA_SOURCE_MODE_PLAYBACK) { 717 setPlaybackMediaSource(componentName); 718 } else if (mode == MEDIA_SOURCE_MODE_BROWSE) { 719 setBrowseMediaSource(componentName); 720 } 721 } 722 setPlaybackMediaSource(ComponentName playbackMediaSource)723 private void setPlaybackMediaSource(ComponentName playbackMediaSource) { 724 stopAndUnregisterCallback(); 725 726 synchronized (mLock) { 727 mActiveUserMediaController = null; 728 mPrimaryMediaComponents[MEDIA_SOURCE_MODE_PLAYBACK] = playbackMediaSource; 729 } 730 731 if (playbackMediaSource != null 732 && !TextUtils.isEmpty(playbackMediaSource.flattenToString())) { 733 if (!isCurrentUserEphemeral()) { 734 saveLastMediaSource(playbackMediaSource, MEDIA_SOURCE_MODE_PLAYBACK); 735 } 736 if (playbackMediaSource 737 .equals(mRemovedMediaSourceComponents[MEDIA_SOURCE_MODE_PLAYBACK])) { 738 mRemovedMediaSourceComponents[MEDIA_SOURCE_MODE_PLAYBACK] = null; 739 } 740 } 741 742 notifyListeners(MEDIA_SOURCE_MODE_PLAYBACK); 743 744 startMediaConnectorService(shouldStartPlayback(mPlayOnMediaSourceChangedConfig), 745 new UserHandle(ActivityManager.getCurrentUser())); 746 // Reset current playback state for the new source, in the case that the app is in an error 747 // state (e.g. not signed in). This state will be updated from the app callback registered 748 // below, to make sure mCurrentPlaybackState reflects the current source only. 749 synchronized (mLock) { 750 mCurrentPlaybackState = PlaybackState.STATE_NONE; 751 updateActiveMediaControllerLocked(mMediaSessionManager 752 .getActiveSessionsForUser(null, 753 new UserHandle(ActivityManager.getCurrentUser()))); 754 } 755 } 756 setBrowseMediaSource(ComponentName browseMediaSource)757 private void setBrowseMediaSource(ComponentName browseMediaSource) { 758 synchronized (mLock) { 759 mPrimaryMediaComponents[MEDIA_SOURCE_MODE_BROWSE] = browseMediaSource; 760 } 761 762 if (browseMediaSource != null && !TextUtils.isEmpty(browseMediaSource.flattenToString())) { 763 if (!isCurrentUserEphemeral()) { 764 saveLastMediaSource(browseMediaSource, MEDIA_SOURCE_MODE_BROWSE); 765 } 766 if (browseMediaSource 767 .equals(mRemovedMediaSourceComponents[MEDIA_SOURCE_MODE_BROWSE])) { 768 mRemovedMediaSourceComponents[MEDIA_SOURCE_MODE_BROWSE] = null; 769 } 770 } 771 772 notifyListeners(MEDIA_SOURCE_MODE_BROWSE); 773 } 774 notifyListeners(@arMediaManager.MediaSourceMode int mode)775 private void notifyListeners(@CarMediaManager.MediaSourceMode int mode) { 776 synchronized (mLock) { 777 int i = mMediaSourceListeners[mode].beginBroadcast(); 778 while (i-- > 0) { 779 try { 780 ICarMediaSourceListener callback = 781 mMediaSourceListeners[mode].getBroadcastItem(i); 782 callback.onMediaSourceChanged(mPrimaryMediaComponents[mode]); 783 } catch (RemoteException e) { 784 Slog.e(CarLog.TAG_MEDIA, "calling onMediaSourceChanged failed " + e); 785 } 786 } 787 mMediaSourceListeners[mode].finishBroadcast(); 788 } 789 } 790 791 private MediaController.Callback mMediaControllerCallback = new MediaController.Callback() { 792 @Override 793 public void onPlaybackStateChanged(PlaybackState state) { 794 savePlaybackState(state); 795 } 796 }; 797 798 /** 799 * Finds the currently playing media source, then updates the active source if the component 800 * name is different. 801 */ updatePrimaryMediaSourceWithCurrentlyPlaying( List<MediaController> controllers)802 private void updatePrimaryMediaSourceWithCurrentlyPlaying( 803 List<MediaController> controllers) { 804 for (MediaController controller : controllers) { 805 if (controller.getPlaybackState() != null 806 && controller.getPlaybackState().getState() == PlaybackState.STATE_PLAYING) { 807 String newPackageName = controller.getPackageName(); 808 String newClassName = getClassName(controller); 809 if (!matchPrimaryMediaSource(newPackageName, newClassName, 810 MEDIA_SOURCE_MODE_PLAYBACK)) { 811 ComponentName mediaSource = getMediaSource(newPackageName, newClassName); 812 if (Log.isLoggable(CarLog.TAG_MEDIA, Log.INFO)) { 813 if (mediaSource != null) { 814 Slog.i(CarLog.TAG_MEDIA, 815 "MediaController changed, updating media source to: " 816 + mediaSource.flattenToString()); 817 } else { 818 // Some apps, like Chrome, have a MediaSession but no 819 // MediaBrowseService. Media Center doesn't consider such apps as 820 // valid media sources. 821 Slog.i(CarLog.TAG_MEDIA, 822 "MediaController changed, but no media browse service found " 823 + "in package: " + newPackageName); 824 } 825 } 826 setPrimaryMediaSource(mediaSource, MEDIA_SOURCE_MODE_PLAYBACK); 827 } 828 return; 829 } 830 } 831 } 832 matchPrimaryMediaSource(@onNull String newPackageName, @NonNull String newClassName, @CarMediaManager.MediaSourceMode int mode)833 private boolean matchPrimaryMediaSource(@NonNull String newPackageName, 834 @NonNull String newClassName, @CarMediaManager.MediaSourceMode int mode) { 835 synchronized (mLock) { 836 if (mPrimaryMediaComponents[mode] != null 837 && mPrimaryMediaComponents[mode].getPackageName().equals(newPackageName)) { 838 // If the class name of currently active source is not specified, only checks 839 // package name; otherwise checks both package name and class name. 840 if (TextUtils.isEmpty(newClassName)) { 841 return true; 842 } else { 843 return newClassName.equals(mPrimaryMediaComponents[mode].getClassName()); 844 } 845 } 846 } 847 return false; 848 } 849 850 /** 851 * Returns {@code true} if the provided component has a valid {@link MediaBrowseService}. 852 */ 853 @VisibleForTesting isMediaService(@onNull ComponentName componentName)854 public boolean isMediaService(@NonNull ComponentName componentName) { 855 return getMediaService(componentName) != null; 856 } 857 858 /* 859 * Gets the media service that matches the componentName for the current foreground user. 860 */ getMediaService(@onNull ComponentName componentName)861 private ComponentName getMediaService(@NonNull ComponentName componentName) { 862 String packageName = componentName.getPackageName(); 863 String className = componentName.getClassName(); 864 865 PackageManager packageManager = mContext.getPackageManager(); 866 Intent mediaIntent = new Intent(); 867 mediaIntent.setPackage(packageName); 868 mediaIntent.setAction(MediaBrowserService.SERVICE_INTERFACE); 869 List<ResolveInfo> mediaServices = packageManager.queryIntentServicesAsUser(mediaIntent, 870 PackageManager.GET_RESOLVED_FILTER, ActivityManager.getCurrentUser()); 871 872 for (ResolveInfo service : mediaServices) { 873 String serviceName = service.serviceInfo.name; 874 if (!TextUtils.isEmpty(serviceName) 875 // If className is not specified, returns the first service in the package; 876 // otherwise returns the matched service. 877 // TODO(b/136274456): find a proper way to handle the case where there are 878 // multiple services and the className is not specified. 879 880 && (TextUtils.isEmpty(className) || serviceName.equals(className))) { 881 return new ComponentName(packageName, serviceName); 882 } 883 } 884 885 if (Log.isLoggable(CarLog.TAG_MEDIA, Log.DEBUG)) { 886 Slog.d(CarLog.TAG_MEDIA, "No MediaBrowseService with ComponentName: " 887 + componentName.flattenToString()); 888 } 889 return null; 890 } 891 892 /* 893 * Gets the component name of the media service. 894 */ 895 @Nullable getMediaSource(@onNull String packageName, @NonNull String className)896 private ComponentName getMediaSource(@NonNull String packageName, @NonNull String className) { 897 return getMediaService(new ComponentName(packageName, className)); 898 } 899 saveLastMediaSource(@onNull ComponentName component, int mode)900 private void saveLastMediaSource(@NonNull ComponentName component, int mode) { 901 if (!sharedPrefsInitialized()) { 902 return; 903 } 904 String componentName = component.flattenToString(); 905 String key = getMediaSourceKey(mode); 906 String serialized = mSharedPrefs.getString(key, null); 907 if (serialized == null) { 908 mSharedPrefs.edit().putString(key, componentName).apply(); 909 } else { 910 Deque<String> componentNames = new ArrayDeque<>(getComponentNameList(serialized)); 911 componentNames.remove(componentName); 912 componentNames.addFirst(componentName); 913 mSharedPrefs.edit().putString(key, serializeComponentNameList(componentNames)).apply(); 914 } 915 } 916 getLastMediaSource(int mode)917 private @NonNull ComponentName getLastMediaSource(int mode) { 918 if (sharedPrefsInitialized()) { 919 String key = getMediaSourceKey(mode); 920 String serialized = mSharedPrefs.getString(key, ""); 921 if (!TextUtils.isEmpty(serialized)) { 922 for (String name : getComponentNameList(serialized)) { 923 ComponentName componentName = ComponentName.unflattenFromString(name); 924 if (isMediaService(componentName)) { 925 return componentName; 926 } 927 } 928 } 929 } 930 return getDefaultMediaSource(); 931 } 932 getDefaultMediaSource()933 private ComponentName getDefaultMediaSource() { 934 String defaultMediaSource = mContext.getString(R.string.config_defaultMediaSource); 935 ComponentName defaultComponent = ComponentName.unflattenFromString(defaultMediaSource); 936 if (isMediaService(defaultComponent)) { 937 return defaultComponent; 938 } 939 return null; 940 } 941 serializeComponentNameList(Deque<String> componentNames)942 private String serializeComponentNameList(Deque<String> componentNames) { 943 return componentNames.stream().collect(Collectors.joining(COMPONENT_NAME_SEPARATOR)); 944 } 945 getComponentNameList(@onNull String serialized)946 private List<String> getComponentNameList(@NonNull String serialized) { 947 String[] componentNames = serialized.split(COMPONENT_NAME_SEPARATOR); 948 return (Arrays.asList(componentNames)); 949 } 950 savePlaybackState(PlaybackState playbackState)951 private void savePlaybackState(PlaybackState playbackState) { 952 if (!sharedPrefsInitialized()) { 953 return; 954 } 955 if (isCurrentUserEphemeral()) { 956 return; 957 } 958 int state = playbackState != null ? playbackState.getState() : PlaybackState.STATE_NONE; 959 synchronized (mLock) { 960 mCurrentPlaybackState = state; 961 } 962 String key = getPlaybackStateKey(); 963 mSharedPrefs.edit().putInt(key, state).apply(); 964 } 965 966 /** 967 * Builds a string key for saving the playback state for a specific media source (and user) 968 */ getPlaybackStateKey()969 private String getPlaybackStateKey() { 970 synchronized (mLock) { 971 return PLAYBACK_STATE_KEY + ActivityManager.getCurrentUser() 972 + (mPrimaryMediaComponents[MEDIA_SOURCE_MODE_PLAYBACK] == null ? "" 973 : mPrimaryMediaComponents[MEDIA_SOURCE_MODE_PLAYBACK].flattenToString()); 974 } 975 } 976 getMediaSourceKey(int mode)977 private String getMediaSourceKey(int mode) { 978 return SOURCE_KEY + mode + SOURCE_KEY_SEPARATOR + ActivityManager.getCurrentUser(); 979 } 980 981 /** 982 * Updates active media controller from the list that has the same component name as the primary 983 * media component. Clears callback and resets media controller to null if not found. 984 */ updateActiveMediaControllerLocked(List<MediaController> mediaControllers)985 private void updateActiveMediaControllerLocked(List<MediaController> mediaControllers) { 986 if (mPrimaryMediaComponents[MEDIA_SOURCE_MODE_PLAYBACK] == null) { 987 return; 988 } 989 if (mActiveUserMediaController != null) { 990 mActiveUserMediaController.unregisterCallback(mMediaControllerCallback); 991 mActiveUserMediaController = null; 992 } 993 for (MediaController controller : mediaControllers) { 994 if (matchPrimaryMediaSource(controller.getPackageName(), getClassName(controller), 995 MEDIA_SOURCE_MODE_PLAYBACK)) { 996 mActiveUserMediaController = controller; 997 PlaybackState state = mActiveUserMediaController.getPlaybackState(); 998 savePlaybackState(state); 999 // Specify Handler to receive callbacks on, to avoid defaulting to the calling 1000 // thread; this method can be called from the MediaSessionManager callback. 1001 // Using the version of this method without passing a handler causes a 1002 // RuntimeException for failing to create a Handler. 1003 mActiveUserMediaController.registerCallback(mMediaControllerCallback, mHandler); 1004 return; 1005 } 1006 } 1007 } 1008 1009 /** 1010 * Returns whether we should autoplay the current media source 1011 */ shouldStartPlayback(int config)1012 private boolean shouldStartPlayback(int config) { 1013 switch (config) { 1014 case AUTOPLAY_CONFIG_NEVER: 1015 return false; 1016 case AUTOPLAY_CONFIG_ALWAYS: 1017 return true; 1018 case AUTOPLAY_CONFIG_RETAIN_PER_SOURCE: 1019 if (!sharedPrefsInitialized()) { 1020 return false; 1021 } 1022 return mSharedPrefs.getInt(getPlaybackStateKey(), PlaybackState.STATE_NONE) 1023 == PlaybackState.STATE_PLAYING; 1024 case AUTOPLAY_CONFIG_RETAIN_PREVIOUS: 1025 synchronized (mLock) { 1026 return mCurrentPlaybackState == PlaybackState.STATE_PLAYING; 1027 } 1028 default: 1029 Slog.e(CarLog.TAG_MEDIA, "Unsupported playback configuration: " + config); 1030 return false; 1031 } 1032 } 1033 1034 @NonNull getClassName(@onNull MediaController controller)1035 private static String getClassName(@NonNull MediaController controller) { 1036 Bundle sessionExtras = controller.getExtras(); 1037 String value = 1038 sessionExtras == null ? "" : sessionExtras.getString( 1039 Car.CAR_EXTRA_BROWSE_SERVICE_FOR_SESSION); 1040 return value != null ? value : ""; 1041 } 1042 } 1043