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