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