• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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