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