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.CarOccupantZoneManager.INVALID_USER_ID;
19 import static android.car.CarOccupantZoneManager.OccupantZoneInfo.INVALID_ZONE_ID;
20 import static android.car.builtin.os.UserManagerHelper.getMaxRunningUsers;
21 import static android.car.media.CarMediaIntents.EXTRA_MEDIA_COMPONENT;
22 import static android.car.media.CarMediaManager.MEDIA_SOURCE_MODE_BROWSE;
23 import static android.car.media.CarMediaManager.MEDIA_SOURCE_MODE_PLAYBACK;
24 import static android.car.user.CarUserManager.USER_LIFECYCLE_EVENT_TYPE_INVISIBLE;
25 import static android.car.user.CarUserManager.USER_LIFECYCLE_EVENT_TYPE_SWITCHING;
26 import static android.car.user.CarUserManager.USER_LIFECYCLE_EVENT_TYPE_UNLOCKED;
27 import static android.car.user.CarUserManager.USER_LIFECYCLE_EVENT_TYPE_VISIBLE;
28 
29 import static com.android.car.CarServiceUtils.assertPermission;
30 import static com.android.car.CarServiceUtils.getCommonHandlerThread;
31 import static com.android.car.CarServiceUtils.getHandlerThread;
32 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO;
33 
34 import android.annotation.NonNull;
35 import android.annotation.Nullable;
36 import android.annotation.TestApi;
37 import android.annotation.UserIdInt;
38 import android.app.ActivityManager;
39 import android.app.usage.UsageStatsManager;
40 import android.car.Car;
41 import android.car.builtin.util.Slogf;
42 import android.car.builtin.util.TimeUtils;
43 import android.car.builtin.util.UsageStatsManagerHelper;
44 import android.car.hardware.power.CarPowerPolicy;
45 import android.car.hardware.power.CarPowerPolicyFilter;
46 import android.car.hardware.power.ICarPowerPolicyListener;
47 import android.car.hardware.power.PowerComponent;
48 import android.car.media.CarMediaManager;
49 import android.car.media.CarMediaManager.MediaSourceMode;
50 import android.car.media.ICarMedia;
51 import android.car.media.ICarMediaSourceListener;
52 import android.car.user.CarUserManager.UserLifecycleListener;
53 import android.car.user.UserLifecycleEventFilter;
54 import android.content.BroadcastReceiver;
55 import android.content.ComponentName;
56 import android.content.Context;
57 import android.content.Intent;
58 import android.content.IntentFilter;
59 import android.content.SharedPreferences;
60 import android.content.pm.PackageManager;
61 import android.content.pm.ResolveInfo;
62 import android.media.session.MediaController;
63 import android.media.session.MediaController.TransportControls;
64 import android.media.session.MediaSession;
65 import android.media.session.MediaSession.Token;
66 import android.media.session.MediaSessionManager;
67 import android.media.session.MediaSessionManager.OnActiveSessionsChangedListener;
68 import android.media.session.PlaybackState;
69 import android.os.Bundle;
70 import android.os.Handler;
71 import android.os.HandlerThread;
72 import android.os.PersistableBundle;
73 import android.os.RemoteCallbackList;
74 import android.os.RemoteException;
75 import android.os.UserHandle;
76 import android.os.UserManager;
77 import android.service.media.MediaBrowserService;
78 import android.text.TextUtils;
79 import android.util.Log;
80 import android.util.SparseArray;
81 import android.util.proto.ProtoOutputStream;
82 import android.view.KeyEvent;
83 
84 import com.android.car.CarInputService.KeyEventListener;
85 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
86 import com.android.car.internal.os.HandlerExecutor;
87 import com.android.car.internal.util.DebugUtils;
88 import com.android.car.internal.util.IndentingPrintWriter;
89 import com.android.car.power.CarPowerManagementService;
90 import com.android.car.user.CarUserService;
91 import com.android.car.user.UserHandleHelper;
92 import com.android.internal.annotations.GuardedBy;
93 import com.android.internal.annotations.VisibleForTesting;
94 
95 import java.util.ArrayDeque;
96 import java.util.ArrayList;
97 import java.util.Arrays;
98 import java.util.Collections;
99 import java.util.Deque;
100 import java.util.HashMap;
101 import java.util.List;
102 import java.util.Map;
103 
104 /**
105  * CarMediaService manages the currently active media source for car apps. This is different from
106  * the MediaSessionManager's active sessions, as there can only be one active source in the car,
107  * through both browse and playback.
108  *
109  * In the car, the active media source does not necessarily have an active MediaSession, e.g. if
110  * it were being browsed only. However, that source is still considered the active source, and
111  * should be the source displayed in any Media related UIs (Media Center, home screen, etc).
112  */
113 public final class CarMediaService extends ICarMedia.Stub implements CarServiceBase {
114 
115     private static final String TAG = CarLog.TAG_MEDIA;
116     private static final boolean DEBUG = Slogf.isLoggable(TAG, Log.DEBUG);
117 
118     private static final String SOURCE_KEY = "media_source_component";
119     private static final String SOURCE_KEY_SEPARATOR = "_";
120     private static final String PLAYBACK_STATE_KEY = "playback_state";
121     private static final String SHARED_PREF = "com.android.car.media.car_media_service";
122     private static final String COMPONENT_NAME_SEPARATOR = ",";
123     private static final String MEDIA_CONNECTION_ACTION = "com.android.car.media.MEDIA_CONNECTION";
124     private static final String EXTRA_AUTOPLAY = "com.android.car.media.autoplay";
125     private static final String LAST_UPDATE_KEY = "last_update";
126 
127     private static final int MEDIA_SOURCE_MODES = 2;
128 
129     // XML configuration options for autoplay on media source change.
130     private static final int AUTOPLAY_CONFIG_NEVER = 0;
131     private static final int AUTOPLAY_CONFIG_ALWAYS = 1;
132     // This mode uses the current source's last stored playback state to resume playback
133     private static final int AUTOPLAY_CONFIG_RETAIN_PER_SOURCE = 2;
134     // This mode uses the previous source's playback state to resume playback
135     private static final int AUTOPLAY_CONFIG_RETAIN_PREVIOUS = 3;
136 
137     private final Context mContext;
138     private final CarOccupantZoneService mOccupantZoneService;
139     private final CarUserService mUserService;
140     private final CarPowerManagementService mPowerManagementService;
141     private final UserManager mUserManager;
142     private final MediaSessionManager mMediaSessionManager;
143     private final UsageStatsManager mUsageStatsManager;
144 
145     /**
146      * An array to store all per-user media data.
147      *
148      * <p>In most cases there will be one entry for the current user.
149      * On a {@link UserManager#isUsersOnSecondaryDisplaysSupported() MUMD} (multi-user
150      * multi-display) device, there will be multiple entries, one per each visible user.
151      */
152     // TODO(b/262734537) Specify the initial capacity.
153     @GuardedBy("mLock")
154     private final SparseArray<UserMediaPlayContext> mUserMediaPlayContexts;
155 
156     // NOTE: must use getSharedPrefsForWriting() to write to it
157     private SharedPreferences mSharedPrefs;
158     private int mPlayOnMediaSourceChangedConfig;
159     private int mPlayOnBootConfig;
160     private boolean mDefaultIndependentPlaybackConfig;
161 
162     private final Handler mCommonThreadHandler = new Handler(
163             getCommonHandlerThread().getLooper());
164 
165     private final HandlerThread mHandlerThread  = getHandlerThread(
166             getClass().getSimpleName());
167     // Handler to receive PlaybackState callbacks from the active media controller.
168     private final Handler mHandler = new Handler(mHandlerThread.getLooper());
169     private final Object mLock = new Object();
170 
171     private final IntentFilter mPackageUpdateFilter;
172 
173     /**
174      * Listens to {@link Intent#ACTION_PACKAGE_REMOVED}, so we can fall back to a previously used
175      * media source when the active source is uninstalled.
176      */
177     // TODO(b/262734537) Refactor this receiver using PackageMonitor.
178     private final BroadcastReceiver mPackageUpdateReceiver = new BroadcastReceiver() {
179         @Override
180         public void onReceive(Context context, Intent intent) {
181             if (intent.getData() == null) {
182                 return;
183             }
184             String intentPackage = intent.getData().getSchemeSpecificPart();
185             if (DEBUG) {
186                 Slogf.d(TAG, "Received a package update for package: %s, action: %s",
187                         intentPackage, intent.getAction());
188             }
189             if (Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())) {
190                 synchronized (mLock) {
191                     int userArraySize = mUserMediaPlayContexts.size();
192                     for (int i = 0; i < userArraySize; i++) {
193                         int userId = mUserMediaPlayContexts.keyAt(i);
194                         UserMediaPlayContext userMediaContext = mUserMediaPlayContexts.valueAt(i);
195                         ComponentName[] primaryComponents =
196                                 userMediaContext.mPrimaryMediaComponents;
197                         for (int j = 0; j < MEDIA_SOURCE_MODES; j++) {
198                             if (primaryComponents[j] != null
199                                     && primaryComponents[j].getPackageName().equals(
200                                     intentPackage)) {
201                                 if (intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
202                                     // If package is being replaced, it may not be removed from
203                                     // PackageManager queries when we check for available
204                                     // MediaBrowseServices, so we iterate to find the next available
205                                     // source.
206                                     for (ComponentName component
207                                             : getLastMediaSourcesInternal(j, userId)) {
208                                         if (!primaryComponents[j].getPackageName()
209                                                 .equals(component.getPackageName())) {
210                                             userMediaContext.mRemovedMediaSourceComponents[j] =
211                                                     primaryComponents[j];
212                                             if (DEBUG) {
213                                                 Slogf.d(TAG, "temporarily replacing updated media "
214                                                                 + "source %s for user %d with "
215                                                                 + "backup source: %s",
216                                                         primaryComponents[j], userId, component);
217                                             }
218                                             setPrimaryMediaSource(component, j, userId);
219                                             return;
220                                         }
221                                     }
222                                     Slogf.e(TAG, "No available backup media source for user %d",
223                                             userId);
224                                 } else {
225                                     if (DEBUG) {
226                                         Slogf.d(TAG, "replacing removed media source"
227                                                 + " %s with backup source: %s for user %d",
228                                                 primaryComponents[j],
229                                                 getLastMediaSource(j, userId), userId);
230                                     }
231                                     userMediaContext.mRemovedMediaSourceComponents[j] = null;
232                                     setPrimaryMediaSource(getLastMediaSource(j, userId), j, userId);
233                                 }
234                             }
235                         }
236                     }
237                 }
238             } else if (Intent.ACTION_PACKAGE_REPLACED.equals(intent.getAction())
239                     || Intent.ACTION_PACKAGE_ADDED.equals(intent.getAction())) {
240                 synchronized (mLock) {
241                     int userArraySize = mUserMediaPlayContexts.size();
242                     for (int i = 0; i < userArraySize; i++) {
243                         int userId = mUserMediaPlayContexts.keyAt(i);
244                         ComponentName[] removedComponents =
245                                 getRemovedMediaSourceComponentsForUser(userId);
246                         for (int j = 0; j < MEDIA_SOURCE_MODES; j++) {
247                             if (removedComponents[j] != null && removedComponents[j]
248                                     .getPackageName().equals(intentPackage)) {
249                                 if (DEBUG) {
250                                     Slogf.d(TAG, "restoring removed source: %s for user %d",
251                                             removedComponents[j], userId);
252                                 }
253                                 setPrimaryMediaSource(removedComponents[j], j, userId);
254                             }
255                         }
256                     }
257                 }
258             }
259         }
260     };
261 
262     private final UserLifecycleListener mUserLifecycleListener = event -> {
263         if (DEBUG) {
264             Slogf.d(TAG, "CarMediaService.onEvent(%s)", event);
265         }
266 
267         // Note that we receive different event types based on the platform version, beacause of
268         // the way we build the filter when registering the listener.
269         //
270         // Before U:
271         //   Receives USER_SWITCHING and USER UNLOCKED
272         // U and after:
273         //   Receives USER_VISIBLE, USER_INVISIBLE, and USER_UNLOCKED
274         //
275         // See the constructor of this class to see how the UserLifecycleEventFilter is built
276         // differently based on the platform version.
277         switch (event.getEventType()) {
278             case USER_LIFECYCLE_EVENT_TYPE_SWITCHING:
279                 onUserSwitch(event.getPreviousUserId(), event.getUserId());
280                 break;
281             case USER_LIFECYCLE_EVENT_TYPE_VISIBLE:
282                 onUserVisible(event.getUserId());
283                 break;
284             case USER_LIFECYCLE_EVENT_TYPE_UNLOCKED:
285                 onUserUnlocked(event.getUserId());
286                 break;
287             case USER_LIFECYCLE_EVENT_TYPE_INVISIBLE:
288                 onUserInvisible(event.getUserId());
289                 break;
290             default:
291                 break;
292         }
293     };
294 
295     private final ICarPowerPolicyListener mPowerPolicyListener =
296             new ICarPowerPolicyListener.Stub() {
297                 @Override
298                 public void onPolicyChanged(CarPowerPolicy appliedPolicy,
299                         CarPowerPolicy accumulatedPolicy) {
300                     boolean shouldBePlaying;
301                     MediaController mediaController;
302                     boolean isOff = !accumulatedPolicy.isComponentEnabled(PowerComponent.MEDIA);
303                     Slogf.i(TAG, "onPolicyChanged(); MEDIA is " + (isOff ? "OFF" : "ON"));
304                     synchronized (mLock) {
305                         int userArraySize = mUserMediaPlayContexts.size();
306                         // Apply power policy to all users.
307                         for (int i = 0; i < userArraySize; i++) {
308                             int userId = mUserMediaPlayContexts.keyAt(i);
309                             UserMediaPlayContext userMediaContext =
310                                     mUserMediaPlayContexts.valueAt(i);
311                             boolean isUserPlaying = (userMediaContext.mCurrentPlaybackState
312                                     == PlaybackState.STATE_PLAYING);
313                             userMediaContext.mIsDisabledByPowerPolicy = isOff;
314                             if (isOff) {
315                                 if (!userMediaContext.mWasPreviouslyDisabledByPowerPolicy) {
316                                     // We're disabling media component.
317                                     // Remember if we are playing at this transition.
318                                     userMediaContext.mWasPlayingBeforeDisabled = isUserPlaying;
319                                     userMediaContext.mWasPreviouslyDisabledByPowerPolicy = true;
320                                 }
321                                 shouldBePlaying = false;
322                             } else {
323                                 userMediaContext.mWasPreviouslyDisabledByPowerPolicy = false;
324                                 shouldBePlaying = userMediaContext.mWasPlayingBeforeDisabled;
325                             }
326                             if (shouldBePlaying == isUserPlaying) {
327                                 return;
328                             }
329                             ComponentName source = userMediaContext.mPrimaryMediaComponents[
330                                     MEDIA_SOURCE_MODE_PLAYBACK];
331                             try {
332                                 if (DEBUG) {
333                                     Slogf.d(TAG, "Starting media connector service from power "
334                                             + "policy listener. source:%s, playing:%b, userId:%d",
335                                             source, shouldBePlaying, userId);
336                                 }
337                                 startMediaConnectorServiceLocked(
338                                         source, shouldBePlaying, userId);
339                             } catch (Exception e) {
340                                 Slogf.e(TAG, e, "onPolicyChanged(): Failed to "
341                                         + "startMediaConnectorService. Source:%s user:%d",
342                                         source, userId);
343                             }
344                             // Should still pause the media when shouldBePlaying is false.
345                             if (!shouldBePlaying) {
346                                 mediaController = userMediaContext.mActiveMediaController;
347                                 if (mediaController == null) {
348                                     if (DEBUG) {
349                                         Slogf.d(TAG,
350                                                 "No active media controller for user %d. "
351                                                 + "Power policy change does not affect this user's "
352                                                 + "media.", userId);
353                                     }
354                                     return;
355                                 }
356                                 mediaController.getTransportControls().pause();
357                             }
358                         }
359                     }
360                 }
361     };
362 
363     private final UserHandleHelper mUserHandleHelper;
364     private static final PersistableBundle USER_INTERACTION_EXTRAS;
365 
366     static {
367         USER_INTERACTION_EXTRAS = new PersistableBundle();
USER_INTERACTION_EXTRAS.putString(UsageStatsManager.EXTRA_EVENT_CATEGORY, CarMediaService.class.getCanonicalName())368         USER_INTERACTION_EXTRAS.putString(UsageStatsManager.EXTRA_EVENT_CATEGORY,
369                 CarMediaService.class.getCanonicalName());
USER_INTERACTION_EXTRAS.putString(UsageStatsManager.EXTRA_EVENT_ACTION, "setPrimaryMediaSource")370         USER_INTERACTION_EXTRAS.putString(UsageStatsManager.EXTRA_EVENT_ACTION,
371                 "setPrimaryMediaSource");
372     }
373 
CarMediaService(Context context, CarOccupantZoneService occupantZoneService, CarUserService userService, CarPowerManagementService powerManagementService)374     public CarMediaService(Context context, CarOccupantZoneService occupantZoneService,
375             CarUserService userService, CarPowerManagementService powerManagementService) {
376         this(context, occupantZoneService, userService, powerManagementService,
377                 new UserHandleHelper(context, context.getSystemService(UserManager.class)));
378     }
379 
380     @VisibleForTesting
CarMediaService(Context context, CarOccupantZoneService occupantZoneService, CarUserService userService, CarPowerManagementService powerManagementService, @NonNull UserHandleHelper userHandleHelper)381     public CarMediaService(Context context, CarOccupantZoneService occupantZoneService,
382             CarUserService userService, CarPowerManagementService powerManagementService,
383             @NonNull UserHandleHelper userHandleHelper) {
384         mContext = context;
385         mUserManager = mContext.getSystemService(UserManager.class);
386         mMediaSessionManager = mContext.getSystemService(MediaSessionManager.class);
387         mUsageStatsManager = mContext.getSystemService(UsageStatsManager.class);
388         mDefaultIndependentPlaybackConfig = mContext.getResources().getBoolean(
389                 R.bool.config_mediaSourceIndependentPlayback);
390         mUserMediaPlayContexts =
391                 new SparseArray<UserMediaPlayContext>(getMaxRunningUsers(context));
392 
393         mPackageUpdateFilter = new IntentFilter();
394         mPackageUpdateFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
395         mPackageUpdateFilter.addAction(Intent.ACTION_PACKAGE_REPLACED);
396         mPackageUpdateFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
397         mPackageUpdateFilter.addDataScheme("package");
398 
399         mOccupantZoneService = occupantZoneService;
400         mUserService = userService;
401         mPowerManagementService = powerManagementService;
402 
403         // Before U, only listen to USER_SWITCHING and USER_UNLOCKED.
404         // U and after, only listen to USER_VISIBLE, USER_INVISIBLE, and USER_UNLOCKED.
405         UserLifecycleEventFilter.Builder userLifecycleEventFilterBuilder =
406                 new UserLifecycleEventFilter.Builder()
407                         .addEventType(USER_LIFECYCLE_EVENT_TYPE_UNLOCKED);
408         userLifecycleEventFilterBuilder.addEventType(USER_LIFECYCLE_EVENT_TYPE_INVISIBLE)
409                 .addEventType(USER_LIFECYCLE_EVENT_TYPE_VISIBLE);
410         mUserService.addUserLifecycleListener(userLifecycleEventFilterBuilder.build(),
411                 mUserLifecycleListener);
412 
413         mPlayOnMediaSourceChangedConfig =
414                 mContext.getResources().getInteger(R.integer.config_mediaSourceChangedAutoplay);
415         mPlayOnBootConfig = mContext.getResources().getInteger(R.integer.config_mediaBootAutoplay);
416         mUserHandleHelper = userHandleHelper;
417     }
418 
419     @Override
420     // This method is called from ICarImpl after CarMediaService is created.
init()421     public void init() {
422         int currentUserId = ActivityManager.getCurrentUser();
423         if (DEBUG) {
424             Slogf.d(TAG, "init(): currentUser=%d", currentUserId);
425         }
426         // Initialize media service for the current user.
427         maybeInitUser(currentUserId);
428         setKeyEventListener();
429         setPowerPolicyListener();
430     }
431 
maybeInitUser(@serIdInt int userId)432     private void maybeInitUser(@UserIdInt int userId) {
433         if (userId == UserHandle.SYSTEM.getIdentifier() && UserManager.isHeadlessSystemUserMode()) {
434             if (DEBUG) {
435                 Slogf.d(TAG, "maybeInitUser(%d): No need to initialize for the"
436                         + " headless system user", userId);
437             }
438             return;
439         }
440         if (mUserManager.isUserUnlocked(UserHandle.of(userId))) {
441             initUser(userId);
442         } else {
443             synchronized (mLock) {
444                 getOrCreateUserMediaPlayContextLocked(userId).mPendingInit = true;
445             }
446         }
447     }
448 
449     /** Initializes car media service data for the specified user. */
initUser(@serIdInt int userId)450     private void initUser(@UserIdInt int userId) {
451         if (DEBUG) {
452             Slogf.d(TAG, "initUser(): userId=%d, mSharedPrefs=%s", userId, mSharedPrefs);
453         }
454         UserHandle userHandle = UserHandle.of(userId);
455 
456         maybeInitSharedPrefs(userId);
457 
458         ComponentName playbackSource;
459         synchronized (mLock) {
460             UserMediaPlayContext userMediaContext = getOrCreateUserMediaPlayContextLocked(userId);
461             if (userMediaContext.mContext != null) {
462                 userMediaContext.mContext.unregisterReceiver(mPackageUpdateReceiver);
463             }
464             userMediaContext.mContext = mContext.createContextAsUser(userHandle, /* flags= */ 0);
465             userMediaContext.mContext.registerReceiver(mPackageUpdateReceiver, mPackageUpdateFilter,
466                     Context.RECEIVER_NOT_EXPORTED);
467 
468             boolean isEphemeral = isUserEphemeral(userHandle);
469             if (isEphemeral) {
470                 ComponentName defaultMediaSource = getDefaultMediaSource(userId);
471                 userMediaContext.mPrimaryMediaComponents[MEDIA_SOURCE_MODE_PLAYBACK] =
472                         defaultMediaSource;
473                 userMediaContext.mPrimaryMediaComponents[MEDIA_SOURCE_MODE_BROWSE] =
474                         defaultMediaSource;
475                 playbackSource = defaultMediaSource;
476             } else {
477                 playbackSource = getLastMediaSource(MEDIA_SOURCE_MODE_PLAYBACK, userId);
478                 userMediaContext.mPrimaryMediaComponents[MEDIA_SOURCE_MODE_PLAYBACK] =
479                         playbackSource;
480                 userMediaContext.mPrimaryMediaComponents[MEDIA_SOURCE_MODE_BROWSE] =
481                         getLastMediaSource(MEDIA_SOURCE_MODE_BROWSE, userId);
482             }
483             userMediaContext.mActiveMediaController = null;
484 
485             updateMediaSessionCallbackForUserLocked(userHandle);
486         }
487         notifyListeners(MEDIA_SOURCE_MODE_PLAYBACK, userId);
488         notifyListeners(MEDIA_SOURCE_MODE_BROWSE, userId);
489 
490         boolean shouldPlay = shouldStartPlayback(mPlayOnBootConfig, userId);
491         if (isMediaPowerEnabled()) {
492             try {
493                 startMediaConnectorService(playbackSource, shouldPlay, userId);
494             } catch (Exception e) {
495                 Slogf.e(TAG, e, "Failed to startMediaConnectorService. Source:%s user:%d",
496                         playbackSource, userId);
497             }
498         } else {
499             synchronized (mLock) {
500                 UserMediaPlayContext userMediaContext =
501                         getOrCreateUserMediaPlayContextLocked(userId);
502                 // When media is disabled by power policy, mark the necessary fields such that later
503                 // the media play can be resumed when power policy is enabled.
504                 userMediaContext.mWasPlayingBeforeDisabled = shouldPlay;
505                 userMediaContext.mWasPreviouslyDisabledByPowerPolicy = true;
506             }
507         }
508     }
509 
510     @GuardedBy("mLock")
getPrimaryMediaComponentsForUserLocked(@serIdInt int userId)511     private ComponentName[] getPrimaryMediaComponentsForUserLocked(@UserIdInt int userId) {
512         UserMediaPlayContext userMediaContext = getOrCreateUserMediaPlayContextLocked(userId);
513         return userMediaContext.mPrimaryMediaComponents;
514     }
515 
getRemovedMediaSourceComponentsForUser(@serIdInt int userId)516     private ComponentName[] getRemovedMediaSourceComponentsForUser(@UserIdInt int userId) {
517         synchronized (mLock) {
518             UserMediaPlayContext userMediaContext = getOrCreateUserMediaPlayContextLocked(userId);
519             return userMediaContext.mRemovedMediaSourceComponents;
520         }
521     }
522 
maybeInitSharedPrefs(@serIdInt int userId)523     private void maybeInitSharedPrefs(@UserIdInt int userId) {
524         // SharedPreferences are shared among different users thus only need initialized once. And
525         // they should be initialized after user 0 is unlocked because SharedPreferences in
526         // credential encrypted storage are not available until after user 0 is unlocked.
527         // initUser() is called when the current foreground user is unlocked, and by that time user
528         // 0 has been unlocked already, so initializing SharedPreferences in initUser() is fine.
529         if (mSharedPrefs != null) {
530             Slogf.i(TAG, "Shared preferences already set (on directory %s)"
531                     + " when initializing user %d", mContext.getDataDir(), userId);
532             return;
533         }
534         Slogf.i(TAG, "Getting shared preferences when initializing user %d", userId);
535         mSharedPrefs = mContext.getSharedPreferences(SHARED_PREF, Context.MODE_PRIVATE);
536 
537         // Try to access the properties to make sure they were properly open
538         if (DEBUG) {
539             Slogf.d(TAG, "Number of prefs: %d", mSharedPrefs.getAll().size());
540         }
541     }
542 
543     /** Checks if {@link PowerComponent.MEDIA} is currently enabled. */
isMediaPowerEnabled()544     private boolean isMediaPowerEnabled() {
545         CarPowerPolicy currentPolicy = mPowerManagementService.getCurrentPowerPolicy();
546         return currentPolicy.isComponentEnabled(PowerComponent.MEDIA);
547     }
548 
549     /**
550      * Starts a service on the current user that binds to the media browser of the current media
551      * source. We start a new service because this one runs on user 0, and MediaBrowser doesn't
552      * provide an API to connect on a specific user. Additionally, this service will attempt to
553      * resume playback using the MediaSession obtained via the media browser connection, which
554      * is more reliable than using active MediaSessions from MediaSessionManager.
555      */
startMediaConnectorService(@ullable ComponentName playbackMediaSource, boolean startPlayback, @UserIdInt int userId)556     private void startMediaConnectorService(@Nullable ComponentName playbackMediaSource,
557             boolean startPlayback, @UserIdInt int userId) {
558         synchronized (mLock) {
559             startMediaConnectorServiceLocked(playbackMediaSource, startPlayback, userId);
560         }
561     }
562 
563     @GuardedBy("mLock")
startMediaConnectorServiceLocked(@ullable ComponentName playbackMediaSource, boolean startPlayback, @UserIdInt int userId)564     private void startMediaConnectorServiceLocked(@Nullable ComponentName playbackMediaSource,
565             boolean startPlayback, @UserIdInt int userId) {
566         UserMediaPlayContext userMediaPlayContext = getOrCreateUserMediaPlayContextLocked(userId);
567         Context userContext = userMediaPlayContext.mContext;
568         if (userContext == null) {
569             Slogf.wtf(TAG,
570                     "Cannot start MediaConnection service. User %d has not been initialized",
571                     userId);
572             return;
573         }
574         Intent serviceStart = new Intent(MEDIA_CONNECTION_ACTION);
575         serviceStart.setPackage(
576                 mContext.getResources().getString(R.string.serviceMediaConnection));
577         serviceStart.putExtra(EXTRA_AUTOPLAY, startPlayback);
578         if (playbackMediaSource != null) {
579             serviceStart.putExtra(EXTRA_MEDIA_COMPONENT, playbackMediaSource.flattenToString());
580         }
581 
582         ComponentName result = userContext.startForegroundService(serviceStart);
583         userMediaPlayContext.mStartedMediaConnectorService = result;
584         Slogf.i(TAG, "startMediaConnectorService user: %d, source: %s, result: %s", userId,
585                 playbackMediaSource, result);
586     }
587 
sharedPrefsInitialized()588     private boolean sharedPrefsInitialized() {
589         if (mSharedPrefs != null) return true;
590 
591         // It shouldn't reach this but let's be cautious.
592         Slogf.e(TAG, "SharedPreferences are not initialized!");
593         String className = getClass().getName();
594         for (StackTraceElement ste : Thread.currentThread().getStackTrace()) {
595             // Let's print the useful logs only.
596             String log = ste.toString();
597             if (log.contains(className)) {
598                 Slogf.e(TAG, log);
599             }
600         }
601         return false;
602     }
603 
isUserEphemeral(UserHandle userHandle)604     private boolean isUserEphemeral(UserHandle userHandle) {
605         return mUserHandleHelper.isEphemeralUser(userHandle);
606     }
607 
setKeyEventListener()608     private void setKeyEventListener() {
609         int maxKeyCode = KeyEvent.getMaxKeyCode();
610         ArrayList<Integer> mediaKeyCodes = new ArrayList<>(15);
611         for (int key = 1; key <= maxKeyCode; key++) {
612             if (KeyEvent.isMediaSessionKey(key)) {
613                 mediaKeyCodes.add(key);
614             }
615         }
616 
617         CarLocalServices.getService(CarInputService.class)
618                 .registerKeyEventListener(new MediaKeyEventListener(), mediaKeyCodes);
619     }
620 
621     // Sets a listener to be notified when the current power policy changes.
622     // Basically, the listener pauses the audio when a media component is disabled and resumes
623     // the audio when a media component is enabled.
624     // This is called only from init().
setPowerPolicyListener()625     private void setPowerPolicyListener() {
626         CarPowerPolicyFilter filter = new CarPowerPolicyFilter.Builder()
627                 .setComponents(PowerComponent.MEDIA).build();
628         mPowerManagementService.addPowerPolicyListener(filter, mPowerPolicyListener);
629     }
630 
631     @Override
release()632     public void release() {
633         synchronized (mLock) {
634             int userArraySize = mUserMediaPlayContexts.size();
635             for (int i = 0; i < userArraySize; i++) {
636                 clearUserDataLocked(mUserMediaPlayContexts.keyAt(i));
637             }
638         }
639         mUserService.removeUserLifecycleListener(mUserLifecycleListener);
640         mPowerManagementService.removePowerPolicyListener(mPowerPolicyListener);
641     }
642 
643     /** Clears the user data for {@code userId}. */
644     @GuardedBy("mLock")
clearUserDataLocked(@serIdInt int userId)645     private void clearUserDataLocked(@UserIdInt int userId) {
646         if (DEBUG) {
647             Slogf.d(TAG, "clearUserDataLocked() for user %d", userId);
648         }
649         UserMediaPlayContext userMediaContext = mUserMediaPlayContexts.get(userId);
650         if (userMediaContext == null) {
651             return;
652         }
653 
654         if (userMediaContext.mContext != null) {
655             userMediaContext.mContext.unregisterReceiver(mPackageUpdateReceiver);
656             userMediaContext.mContext = null;
657         }
658         userMediaContext.mMediaSessionUpdater.unregisterCallbacks();
659         mMediaSessionManager.removeOnActiveSessionsChangedListener(
660                 userMediaContext.mSessionsListener);
661     }
662 
663     @Override
664     @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
dump(IndentingPrintWriter writer)665     public void dump(IndentingPrintWriter writer) {
666         writer.println("*CarMediaService*");
667         writer.increaseIndent();
668         writer.printf("DEBUG=%b\n", DEBUG);
669         writer.printf("mPlayOnBootConfig=%d\n", mPlayOnBootConfig);
670         writer.printf("mPlayOnMediaSourceChangedConfig=%d\n", mPlayOnMediaSourceChangedConfig);
671         writer.printf("mDefaultIndependentPlaybackConfig=%b\n", mDefaultIndependentPlaybackConfig);
672         writer.println();
673 
674         boolean hasSharedPrefs = mSharedPrefs != null;
675         synchronized (mLock) {
676             int userArraySize = mUserMediaPlayContexts.size();
677             for (int i = 0; i < userArraySize; i++) {
678                 int userId = mUserMediaPlayContexts.keyAt(i);
679                 writer.printf("For user %d:\n", userId);
680                 writer.increaseIndent();
681                 UserMediaPlayContext userMediaContext = mUserMediaPlayContexts.valueAt(i);
682                 writer.printf("Pending init: %b\n", userMediaContext.mPendingInit);
683                 writer.printf("MediaConnectorService: %s\n",
684                         userMediaContext.mStartedMediaConnectorService != null
685                                 ? userMediaContext.mStartedMediaConnectorService
686                                         .flattenToShortString()  : "");
687                 dumpCurrentMediaComponentLocked(writer, "playback", MEDIA_SOURCE_MODE_PLAYBACK,
688                         userId);
689                 dumpCurrentMediaComponentLocked(writer, "browse", MEDIA_SOURCE_MODE_BROWSE,
690                         userId);
691                 MediaController mediaController = userMediaContext.mActiveMediaController;
692                 if (mediaController != null) {
693                     writer.printf("Current media controller: %s\n",
694                             mediaController.getPackageName());
695                     writer.printf("Current browse service extra: %s\n",
696                             getClassName(mediaController));
697                 } else {
698                     writer.println("no active user media controller");
699                 }
700                 writer.printf("Number of active media sessions (for user %d): %d\n", userId,
701                         mMediaSessionManager.getActiveSessionsForUser(
702                                 /* notificationListener= */ null, UserHandle.of(userId)).size());
703 
704                 writer.printf("Disabled by power policy: %b\n",
705                         userMediaContext.mIsDisabledByPowerPolicy);
706                 if (userMediaContext.mIsDisabledByPowerPolicy) {
707                     writer.printf("Before being disabled by power policy, audio was %s\n",
708                             userMediaContext.mWasPlayingBeforeDisabled ? "active" : "inactive");
709                 }
710                 if (hasSharedPrefs) {
711                     dumpLastUpdateTime(writer, userId);
712                     dumpLastMediaSources(writer, "Playback", MEDIA_SOURCE_MODE_PLAYBACK, userId);
713                     dumpLastMediaSources(writer, "Browse", MEDIA_SOURCE_MODE_BROWSE, userId);
714                     dumpPlaybackState(writer, userId);
715                 }
716                 writer.decreaseIndent();
717             }
718         }
719 
720         if (hasSharedPrefs) {
721             dumpSharedPrefs(writer);
722         } else {
723             writer.println("No shared preferences");
724         }
725 
726         writer.decreaseIndent();
727     }
728 
729     @Override
730     @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
dumpProto(ProtoOutputStream proto)731     public void dumpProto(ProtoOutputStream proto) {}
732 
733     @GuardedBy("mLock")
734     @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
dumpCurrentMediaComponentLocked(IndentingPrintWriter writer, String name, @CarMediaManager.MediaSourceMode int mode, @UserIdInt int userId)735     private void dumpCurrentMediaComponentLocked(IndentingPrintWriter writer, String name,
736             @CarMediaManager.MediaSourceMode int mode, @UserIdInt int userId) {
737         ComponentName componentName = getPrimaryMediaComponentsForUserLocked(userId)[mode];
738         writer.printf("For user %d, current %s media component: %s\n", userId,  name,
739                 (componentName == null ? "-" : componentName.flattenToString()));
740     }
741 
742     @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
dumpLastUpdateTime(IndentingPrintWriter writer, @UserIdInt int userId)743     private void dumpLastUpdateTime(IndentingPrintWriter writer, @UserIdInt int userId) {
744         long lastUpdate = mSharedPrefs.getLong(getLastUpdateKey(userId), -1);
745         writer.printf("For user %d, shared preference last updated on %d / ", userId, lastUpdate);
746         TimeUtils.dumpTime(writer, lastUpdate);
747         writer.println();
748     }
749 
750     @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
dumpLastMediaSources(IndentingPrintWriter writer, String name, @CarMediaManager.MediaSourceMode int mode, @UserIdInt int userId)751     private void dumpLastMediaSources(IndentingPrintWriter writer, String name,
752             @CarMediaManager.MediaSourceMode int mode, @UserIdInt int userId) {
753         writer.printf("%s media source history:\n", name);
754         writer.increaseIndent();
755         List<ComponentName> lastMediaSources = getLastMediaSourcesInternal(mode, userId);
756         for (int i = 0; i < lastMediaSources.size(); i++) {
757             ComponentName componentName = lastMediaSources.get(i);
758             if (componentName == null) {
759                 Slogf.e(TAG, "dump(): for user %d, empty last media source of %s"
760                                 + " at index %d: %s",
761                         userId, mediaModeToString(mode), i, lastMediaSources);
762                 continue;
763             }
764             writer.println(componentName.flattenToString());
765         }
766         writer.decreaseIndent();
767     }
768 
769     @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
dumpPlaybackState(IndentingPrintWriter writer, @UserIdInt int userId)770     private void dumpPlaybackState(IndentingPrintWriter writer, @UserIdInt int userId) {
771         String key = getPlaybackStateKey(userId);
772         int playbackState = mSharedPrefs.getInt(key, PlaybackState.STATE_NONE);
773         writer.printf("media playback state: %d\n", playbackState);
774     }
775 
776     @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
dumpSharedPrefs(IndentingPrintWriter writer)777     private void dumpSharedPrefs(IndentingPrintWriter writer) {
778         Map<String, ?> allPrefs = mSharedPrefs.getAll();
779         writer.printf("%d shared preferences (saved on directory %s)",
780                 allPrefs.size(), mContext.getDataDir());
781         if (!Slogf.isLoggable(TAG, Log.VERBOSE) || allPrefs.isEmpty()) {
782             writer.println();
783             return;
784         }
785         writer.println(':');
786     }
787 
788     /**
789      * @see CarMediaManager#setMediaSource(ComponentName, int)
790      */
791     @Override
setMediaSource(@onNull ComponentName componentName, @MediaSourceMode int mode, @UserIdInt int userId)792     public void setMediaSource(@NonNull ComponentName componentName,
793             @MediaSourceMode int mode, @UserIdInt int userId) {
794         enforceValidCallingUser(userId);
795         assertPermission(mContext,
796                 android.Manifest.permission.MEDIA_CONTENT_CONTROL);
797         if (DEBUG) {
798             Slogf.d(TAG, "Changing media source to: %s for user %d",
799                     componentName.getPackageName(), userId);
800         }
801         setPrimaryMediaSource(componentName, mode, userId);
802     }
803 
804     /**
805      * @see CarMediaManager#getMediaSource
806      */
807     @Override
getMediaSource(@arMediaManager.MediaSourceMode int mode, @UserIdInt int userId)808     public ComponentName getMediaSource(@CarMediaManager.MediaSourceMode int mode,
809             @UserIdInt int userId) {
810         enforceValidCallingUser(userId);
811         assertPermission(mContext, android.Manifest.permission.MEDIA_CONTENT_CONTROL);
812         ComponentName componentName;
813         synchronized (mLock) {
814             componentName = getPrimaryMediaComponentsForUserLocked(userId)[mode];
815         }
816         if (componentName == null) {
817             componentName = getDefaultMediaSource(userId);
818             Slogf.e(TAG, "User %d has not been initialized. Returning the default media "
819                             + "source %s.", userId, componentName);
820         }
821         if (DEBUG) {
822             Slogf.d(TAG, "Getting media source mode %d for user %d: %s ",
823                     mode, userId, componentName);
824         }
825         return componentName;
826     }
827 
828     /**
829      * @see
830      * CarMediaManager#removeMediaSourceListener(CarMediaManager.MediaSourceChangedListener, int)
831      */
832     @Override
registerMediaSourceListener(ICarMediaSourceListener callback, @MediaSourceMode int mode, @UserIdInt int userId)833     public void registerMediaSourceListener(ICarMediaSourceListener callback,
834             @MediaSourceMode int mode, @UserIdInt int userId) {
835         enforceValidCallingUser(userId);
836         assertPermission(mContext,
837                 android.Manifest.permission.MEDIA_CONTENT_CONTROL);
838         UserMediaPlayContext userMediaContext;
839         synchronized (mLock) {
840             userMediaContext = getOrCreateUserMediaPlayContextLocked(userId);
841         }
842         userMediaContext.mMediaSourceListeners[mode].register(callback);
843     }
844 
845     /**
846      * @see CarMediaManager#removeMediaSourceListener
847      */
848     @Override
unregisterMediaSourceListener(ICarMediaSourceListener callback, @MediaSourceMode int mode, @UserIdInt int userId)849     public void unregisterMediaSourceListener(ICarMediaSourceListener callback,
850             @MediaSourceMode int mode, @UserIdInt int userId) {
851         enforceValidCallingUser(userId);
852         assertPermission(mContext,
853                 android.Manifest.permission.MEDIA_CONTENT_CONTROL);
854         UserMediaPlayContext userMediaContext;
855         synchronized (mLock) {
856             userMediaContext = getOrCreateUserMediaPlayContextLocked(userId);
857         }
858         userMediaContext.mMediaSourceListeners[mode].unregister(callback);
859     }
860 
861     @Override
getLastMediaSources(@arMediaManager.MediaSourceMode int mode, @UserIdInt int userId)862     public List<ComponentName> getLastMediaSources(@CarMediaManager.MediaSourceMode int mode,
863             @UserIdInt int userId) {
864         enforceValidCallingUser(userId);
865         assertPermission(mContext,
866                 android.Manifest.permission.MEDIA_CONTENT_CONTROL);
867         ArrayList<ComponentName> results = getLastMediaSourcesInternal(mode, userId);
868         // Always add the default media source at the end, if it is not already in the list.
869         // This is to match the results of getLastMediaSources() with what getMediaSource() returns.
870         // For example, when a user is first initialized and no media source has been stored yet,
871         // getLastMediaSources() will return the default media source, instead of an empty list.
872         ComponentName defaultMediaSource = getDefaultMediaSource(userId);
873         if (defaultMediaSource != null && results.indexOf(defaultMediaSource) < 0) {
874             results.add(defaultMediaSource);
875         }
876         return results;
877     }
878 
879     /**
880      * Returns the last media sources for the specified {@code mode} and {@code userId}.
881      *
882      * <p>Anywhere in this file, do not use the public {@link #getLastMediaSources(int, int)}
883      * method. Use this method instead.
884      */
getLastMediaSourcesInternal( @arMediaManager.MediaSourceMode int mode, @UserIdInt int userId)885     private ArrayList<ComponentName> getLastMediaSourcesInternal(
886             @CarMediaManager.MediaSourceMode int mode, @UserIdInt int userId) {
887         String key = getMediaSourceKey(mode, userId);
888         String serialized =
889                 mSharedPrefs == null ? null : mSharedPrefs.getString(key, "");
890         List<String> componentNames = getComponentNameList(serialized);
891         // Set the initial capacity to componentNames.size() + 1, to avoid re-allocation in case
892         // the default media source needs to be added by getLastMediaSources().
893         ArrayList<ComponentName> results = new ArrayList<>(componentNames.size() + 1);
894         for (int i = 0; i < componentNames.size(); i++) {
895             results.add(ComponentName.unflattenFromString(componentNames.get(i)));
896         }
897         return results;
898     }
899 
900     /** See {@link CarMediaManager#isIndependentPlaybackConfig}. */
901     @Override
902     @TestApi
isIndependentPlaybackConfig(@serIdInt int userId)903     public boolean isIndependentPlaybackConfig(@UserIdInt int userId) {
904         enforceValidCallingUser(userId);
905         assertPermission(mContext,
906                 android.Manifest.permission.MEDIA_CONTENT_CONTROL);
907         return isIndependentPlaybackConfigInternal(userId);
908     }
909 
910     /**
911      * Returns independent playback config for the specified {@code userId}.
912      *
913      * <p>Anywhere in this file, do not use the public {@link isIndependentPlaybackConfig(int)}
914      * method.
915      */
isIndependentPlaybackConfigInternal(@serIdInt int userId)916     private boolean isIndependentPlaybackConfigInternal(@UserIdInt int userId) {
917         synchronized (mLock) {
918             return getOrCreateUserMediaPlayContextLocked(userId).mIndependentPlaybackConfig;
919         }
920     }
921 
922     /** See {@link CarMediaManager#setIndependentPlaybackConfig}. */
923     @Override
924     @TestApi
setIndependentPlaybackConfig(boolean independent, @UserIdInt int userId)925     public void setIndependentPlaybackConfig(boolean independent, @UserIdInt int userId) {
926         enforceValidCallingUser(userId);
927         assertPermission(mContext,
928                 android.Manifest.permission.MEDIA_CONTENT_CONTROL);
929         synchronized (mLock) {
930             getOrCreateUserMediaPlayContextLocked(userId).mIndependentPlaybackConfig = independent;
931         }
932     }
933 
enforceValidCallingUser(@serIdInt int userId)934     private void enforceValidCallingUser(@UserIdInt int userId) throws SecurityException {
935         UserHandle callingUser = getCallingUserHandle();
936 
937         // The calling user is valid if it is the system user or the specified userId matches
938         // the calling user.
939         if (!callingUser.isSystem() && callingUser.getIdentifier() != userId) {
940             throw new SecurityException("The calling user " + callingUser.getIdentifier()
941                     + " cannot access media data of user " + userId);
942         }
943     }
944 
945     /** Clears data for {@code fromUserId}, and initializes data for {@code toUserId}. */
onUserSwitch(@serIdInt int fromUserId, @UserIdInt int toUserId)946     private void onUserSwitch(@UserIdInt int fromUserId, @UserIdInt int toUserId) {
947         if (DEBUG) {
948             Slogf.d(TAG, "onUserSwitch() fromUserId=%d, toUserId=%d", fromUserId, toUserId);
949         }
950         // Clean up the data of the fromUser.
951         if (fromUserId != UserHandle.SYSTEM.getIdentifier()) {
952             onUserInvisible(fromUserId);
953         }
954         // Initialize the data of the toUser.
955         onUserVisible(toUserId);
956     }
957 
onUserVisible(@serIdInt int userId)958     private void onUserVisible(@UserIdInt int userId) {
959         if (DEBUG) {
960             Slogf.d(TAG, "onUserVisible() for user=%d", userId);
961         }
962         maybeInitUser(userId);
963     }
964 
965     /** Clears the user data when the user becomes invisible. */
onUserInvisible(@serIdInt int userId)966     private void onUserInvisible(@UserIdInt int userId) {
967         if (DEBUG) {
968             Slogf.d(TAG, "onUserInvisible(): userId=%d. Clearing data for the user.", userId);
969         }
970         synchronized (mLock) {
971             clearUserDataLocked(userId);
972             mUserMediaPlayContexts.delete(userId);
973         }
974     }
975 
976     // TODO(b/153115826): this method was used to be called from the ICar binder thread, but it's
977     // now called by UserCarService. Currently UserCarService is calling every listener in one
978     // non-main thread, but it's not clear how the final behavior will be. So, for now it's ok
979     // to post it to mMainHandler, but once b/145689885 is fixed, we might not need it.
onUserUnlocked(@serIdInt int userId)980     private void onUserUnlocked(@UserIdInt int userId) {
981         if (DEBUG) {
982             Slogf.d(TAG, "onUserUnlocked(): userId=%d", userId);
983         }
984         mCommonThreadHandler.post(() -> {
985             // No need to handle system user, but still need to handle background users.
986             if (userId == UserHandle.SYSTEM.getIdentifier()) {
987                 return;
988             }
989             UserMediaPlayContext userMediaPlayContext;
990             boolean isPendingInit;
991             synchronized (mLock) {
992                 userMediaPlayContext = getOrCreateUserMediaPlayContextLocked(userId);
993                 isPendingInit = userMediaPlayContext.mPendingInit;
994             }
995             if (DEBUG) {
996                 Slogf.d(TAG, "onUserUnlocked(): userId=%d pendingInit=%b", userId,
997                         userMediaPlayContext.mPendingInit);
998             }
999             if (isPendingInit) {
1000                 initUser(userId);
1001                 synchronized (mLock) {
1002                     userMediaPlayContext.mPendingInit = false;
1003                 }
1004                 if (DEBUG) {
1005                     Slogf.d(TAG, "User %d is now unlocked", userId);
1006                 }
1007             }
1008         });
1009     }
1010 
1011     @GuardedBy("mLock")
updateMediaSessionCallbackForUserLocked(UserHandle userHandle)1012     private void updateMediaSessionCallbackForUserLocked(UserHandle userHandle) {
1013         int userId = userHandle.getIdentifier();
1014         UserMediaPlayContext userMediaContext = getOrCreateUserMediaPlayContextLocked(userId);
1015         SessionChangedListener sessionsListener = userMediaContext.mSessionsListener;
1016         if (sessionsListener != null) {
1017             mMediaSessionManager.removeOnActiveSessionsChangedListener(sessionsListener);
1018         }
1019         sessionsListener = new SessionChangedListener(userId);
1020         userMediaContext.mSessionsListener = sessionsListener;
1021         mMediaSessionManager.addOnActiveSessionsChangedListener(null, userHandle,
1022                 new HandlerExecutor(mHandler), sessionsListener);
1023 
1024         MediaSessionUpdater sessionUpdater = new MediaSessionUpdater(userId);
1025         userMediaContext.mMediaSessionUpdater = sessionUpdater;
1026         sessionUpdater.registerCallbacks(mMediaSessionManager.getActiveSessionsForUser(null,
1027                 userHandle));
1028     }
1029 
1030     /**
1031      * Attempts to stop the current source using MediaController.TransportControls.stop()
1032      * This method also unregisters callbacks to the active media controller before calling stop(),
1033      * to preserve the PlaybackState before stopping.
1034      */
stopAndUnregisterCallback(@serIdInt int userId)1035     private void stopAndUnregisterCallback(@UserIdInt int userId) {
1036         UserMediaPlayContext userMediaContext;
1037         MediaController mediaController;
1038         synchronized (mLock) {
1039             userMediaContext = getOrCreateUserMediaPlayContextLocked(userId);
1040             mediaController = userMediaContext.mActiveMediaController;
1041         }
1042         if (mediaController == null) {
1043             if (DEBUG) {
1044                 Slogf.d(TAG, "stopAndUnregisterCallback() for user %d."
1045                         + " Do nothing as there is no active media controller.", userId);
1046             }
1047             return;
1048         }
1049 
1050         mediaController.unregisterCallback(userMediaContext.mMediaControllerCallback);
1051         if (DEBUG) {
1052             Slogf.d(TAG, "stopping %s", mediaController.getPackageName());
1053         }
1054         TransportControls controls = mediaController.getTransportControls();
1055         if (controls != null) {
1056             // In order to prevent some apps from taking back the audio focus after being stopped,
1057             // first call pause, if the app supports pause. This does not affect the saved source
1058             // or the playback state, because the callback has already been unregistered.
1059             PlaybackState playbackState = mediaController.getPlaybackState();
1060             if (playbackState != null
1061                     && (playbackState.getActions() & PlaybackState.ACTION_PAUSE) != 0) {
1062                 if (DEBUG) {
1063                     Slogf.d(TAG, "Call pause before stop");
1064                 }
1065                 controls.pause();
1066             }
1067             controls.stop();
1068         } else {
1069             Slogf.e(TAG, "Can't stop playback, transport controls unavailable %s",
1070                     mediaController.getPackageName());
1071         }
1072     }
1073 
1074     @GuardedBy("mLock")
getOrCreateUserMediaPlayContextLocked(@serIdInt int userId)1075     private UserMediaPlayContext getOrCreateUserMediaPlayContextLocked(@UserIdInt int userId) {
1076         UserMediaPlayContext userMediaContext = mUserMediaPlayContexts.get(userId);
1077         if (userMediaContext == null) {
1078             userMediaContext = new UserMediaPlayContext(userId, mDefaultIndependentPlaybackConfig);
1079             mUserMediaPlayContexts.set(userId, userMediaContext);
1080             if (DEBUG) {
1081                 Slogf.d(TAG, "Create a UserMediaPlayContext for user %d", userId);
1082             }
1083         }
1084 
1085         return userMediaContext;
1086     }
1087 
1088     /** A container to store per-user media play context data. */
1089     private final class UserMediaPlayContext {
1090 
1091         @Nullable
1092         private Context mContext;
1093         // MediaController for the user's active media session. This controller can be null
1094         // if playback has not been started yet.
1095         private MediaController mActiveMediaController;
1096         private int mCurrentPlaybackState;
1097         private boolean mIsDisabledByPowerPolicy;
1098         private boolean mWasPreviouslyDisabledByPowerPolicy;
1099         private boolean mWasPlayingBeforeDisabled;
1100         private boolean mIndependentPlaybackConfig;
1101         private final ComponentName[] mPrimaryMediaComponents;
1102         // The component name of the last media source that was removed while being primary.
1103         private final ComponentName[] mRemovedMediaSourceComponents;
1104         private boolean mPendingInit;
1105         // This field is used for test/debugging.
1106         private ComponentName mStartedMediaConnectorService;
1107 
1108         private final RemoteCallbackList<ICarMediaSourceListener>[] mMediaSourceListeners;
1109         private MediaSessionUpdater mMediaSessionUpdater;
1110         private SessionChangedListener mSessionsListener;
1111         private final MediaController.Callback mMediaControllerCallback;
1112 
UserMediaPlayContext(@serIdInt int userId, boolean independentPlaybackConfig)1113         UserMediaPlayContext(@UserIdInt int userId, boolean independentPlaybackConfig) {
1114             mIndependentPlaybackConfig = independentPlaybackConfig;
1115             mPrimaryMediaComponents = new ComponentName[MEDIA_SOURCE_MODES];
1116             mRemovedMediaSourceComponents = new ComponentName[MEDIA_SOURCE_MODES];
1117             mMediaSourceListeners = new RemoteCallbackList[] {new RemoteCallbackList(),
1118                     new RemoteCallbackList()};
1119             mMediaSessionUpdater = new MediaSessionUpdater(userId);
1120             mSessionsListener = new SessionChangedListener(userId);
1121             mMediaControllerCallback = new ActiveMediaControllerCallback(userId);
1122         }
1123     }
1124 
1125     private final class ActiveMediaControllerCallback extends MediaController.Callback {
1126 
1127         private final @UserIdInt int mUserId;
1128 
ActiveMediaControllerCallback(@serIdInt int userId)1129         ActiveMediaControllerCallback(@UserIdInt int userId) {
1130             mUserId = userId;
1131         }
1132 
1133         @Override
onPlaybackStateChanged(PlaybackState state)1134         public void onPlaybackStateChanged(PlaybackState state) {
1135             savePlaybackState(state, mUserId);
1136         }
1137     }
1138 
1139     private class SessionChangedListener implements OnActiveSessionsChangedListener {
1140         private final @UserIdInt int mUserId;
1141 
SessionChangedListener(int userId)1142         SessionChangedListener(int userId) {
1143             mUserId = userId;
1144         }
1145 
1146         @Override
onActiveSessionsChanged(List<MediaController> controllers)1147         public void onActiveSessionsChanged(List<MediaController> controllers) {
1148             if (DEBUG) {
1149                 Slogf.d(TAG, "onActiveSessionsChanged() for user %d, controllers: %s",
1150                         mUserId, controllers);
1151             }
1152             // Filter controllers based on their user ids.
1153             ArrayList<MediaController> userControllers = new ArrayList<>(controllers.size());
1154             for (int i = 0; i < controllers.size(); i++) {
1155                 MediaController controller = controllers.get(i);
1156                 int userId = UserHandle.getUserHandleForUid(
1157                         controller.getSessionToken().getUid()).getIdentifier();
1158                 if (userId == mUserId) {
1159                     userControllers.add(controller);
1160                 } else {
1161                     Slogf.w(TAG, "onActiveSessionsChanged() received a change for "
1162                             + "a different user: listener user %d, controller %s for user %d",
1163                             mUserId, controller, userId);
1164                 }
1165             }
1166             UserMediaPlayContext userMediaContext;
1167             synchronized (mLock) {
1168                 userMediaContext = getOrCreateUserMediaPlayContextLocked(mUserId);
1169             }
1170             userMediaContext.mMediaSessionUpdater.registerCallbacks(userControllers);
1171         }
1172     }
1173 
1174     private class MediaControllerCallback extends MediaController.Callback {
1175 
1176         private final @UserIdInt int mUserId;
1177         private final MediaController mMediaController;
1178         private PlaybackState mPreviousPlaybackState;
1179 
MediaControllerCallback(MediaController mediaController, @UserIdInt int userId)1180         private MediaControllerCallback(MediaController mediaController, @UserIdInt int userId) {
1181             mUserId = userId;
1182             mMediaController = mediaController;
1183             PlaybackState state = mediaController.getPlaybackState();
1184             mPreviousPlaybackState = state;
1185         }
1186 
register()1187         private void register() {
1188             mMediaController.registerCallback(this);
1189         }
1190 
unregister()1191         private void unregister() {
1192             mMediaController.unregisterCallback(this);
1193         }
1194 
1195         @Override
onPlaybackStateChanged(@ullable PlaybackState state)1196         public void onPlaybackStateChanged(@Nullable PlaybackState state) {
1197             if (DEBUG) {
1198                 Slogf.d(TAG, "onPlaybackStateChanged() for user %d; previous state: %s,"
1199                         + " new state: %s", mUserId, mPreviousPlaybackState, state.getState());
1200             }
1201             if (state != null && state.isActive()
1202                     && (mPreviousPlaybackState == null || !mPreviousPlaybackState.isActive())) {
1203                 ComponentName mediaSource = getMediaSource(mMediaController.getPackageName(),
1204                         getClassName(mMediaController), mUserId);
1205                 if (mediaSource != null && Slogf.isLoggable(TAG, Log.INFO)) {
1206                     synchronized (mLock) {
1207                         if (!mediaSource.equals(getPrimaryMediaComponentsForUserLocked(mUserId)
1208                                 [MEDIA_SOURCE_MODE_PLAYBACK])) {
1209                             Slogf.i(TAG, "Changing media source for user %d due to playback state "
1210                                     + "change: %s", mUserId, mediaSource.flattenToString());
1211                         }
1212                     }
1213                 }
1214                 setPrimaryMediaSource(mediaSource, MEDIA_SOURCE_MODE_PLAYBACK, mUserId);
1215             }
1216             mPreviousPlaybackState = state;
1217         }
1218     }
1219 
1220     private class MediaSessionUpdater {
1221 
1222         private final @UserIdInt int mUserId;
1223         private Map<Token, MediaControllerCallback> mCallbacks = new HashMap<>();
1224 
MediaSessionUpdater(@serIdInt int userId)1225         MediaSessionUpdater(@UserIdInt int userId) {
1226             mUserId = userId;
1227         }
1228 
1229         /**
1230          * Register a {@link MediaControllerCallback} for each given controller. Note that if a
1231          * controller was already watched, we don't register a callback again. This prevents an
1232          * undesired revert of the primary media source. Callbacks for previously watched
1233          * controllers that are not present in the given list are unregistered.
1234          */
registerCallbacks(List<MediaController> newControllers)1235         private void registerCallbacks(List<MediaController> newControllers) {
1236 
1237             List<MediaController> additions = new ArrayList<>(newControllers.size());
1238             Map<MediaSession.Token, MediaControllerCallback> updatedCallbacks =
1239                     new HashMap<>(newControllers.size());
1240             for (MediaController controller : newControllers) {
1241                 MediaSession.Token token = controller.getSessionToken();
1242                 MediaControllerCallback callback = mCallbacks.get(token);
1243                 if (callback == null) {
1244                     callback = new MediaControllerCallback(controller, mUserId);
1245                     callback.register();
1246                     additions.add(controller);
1247                 }
1248                 updatedCallbacks.put(token, callback);
1249             }
1250 
1251             for (MediaSession.Token token : mCallbacks.keySet()) {
1252                 if (!updatedCallbacks.containsKey(token)) {
1253                     mCallbacks.get(token).unregister();
1254                 }
1255             }
1256 
1257             mCallbacks = updatedCallbacks;
1258             updatePrimaryMediaSourceWithCurrentlyPlaying(additions, mUserId);
1259             // If there are no playing media sources, and we don't currently have the controller
1260             // for the active source, check the active sessions for a matching controller. If this
1261             // is called after a user switch, its possible for a matching controller to already be
1262             // active before the user is unlocked, so we check all of the current controllers
1263             synchronized (mLock) {
1264                 UserMediaPlayContext userMediaContext =
1265                         getOrCreateUserMediaPlayContextLocked(mUserId);
1266                 if (userMediaContext.mActiveMediaController == null) {
1267                     updateActiveMediaControllerLocked(newControllers, mUserId);
1268                 }
1269             }
1270         }
1271 
1272         /**
1273          * Unregister all MediaController callbacks
1274          */
unregisterCallbacks()1275         private void unregisterCallbacks() {
1276             for (Map.Entry<Token, MediaControllerCallback> entry : mCallbacks.entrySet()) {
1277                 entry.getValue().unregister();
1278             }
1279         }
1280     }
1281 
1282     /**
1283      * Updates the primary media source, then notifies content observers of the change
1284      * Will update both the playback and browse sources if independent playback is not supported
1285      */
setPrimaryMediaSource(ComponentName componentName, @CarMediaManager.MediaSourceMode int mode, @UserIdInt int userId)1286     private void setPrimaryMediaSource(ComponentName componentName,
1287             @CarMediaManager.MediaSourceMode int mode, @UserIdInt int userId) {
1288         if (DEBUG) {
1289             Slogf.d(TAG, "setPrimaryMediaSource(component=%s, mode=%d, userId=%d)",
1290                     componentName, mode, userId);
1291         }
1292         ComponentName mediaComponent;
1293         synchronized (mLock) {
1294             mediaComponent = getPrimaryMediaComponentsForUserLocked(userId)[mode];
1295         }
1296         if (mediaComponent != null && mediaComponent.equals((componentName))) {
1297             return;
1298         }
1299 
1300         if (!isIndependentPlaybackConfigInternal(userId)) {
1301             setPlaybackMediaSource(componentName, userId);
1302             setBrowseMediaSource(componentName, userId);
1303         } else if (mode == MEDIA_SOURCE_MODE_PLAYBACK) {
1304             setPlaybackMediaSource(componentName, userId);
1305         } else if (mode == MEDIA_SOURCE_MODE_BROWSE) {
1306             setBrowseMediaSource(componentName, userId);
1307         }
1308         // Android logs app usage into UsageStatsManager. ACTIVITY_RESUMED and ACTIVITY_STOPPED
1309         // events do not capture media app usage on AAOS because apps are hosted by a proxy such as
1310         // Media Center. Reporting a USER_INTERACTION event in setPrimaryMediaSource allows
1311         // attribution of non-foreground media app interactions to the app's package name
1312         if (componentName != null) {
1313             UsageStatsManagerHelper.reportUserInteraction(mUsageStatsManager,
1314                     componentName.getPackageName(), userId, USER_INTERACTION_EXTRAS);
1315         }
1316     }
1317 
setPlaybackMediaSource(ComponentName playbackMediaSource, @UserIdInt int userId)1318     private void setPlaybackMediaSource(ComponentName playbackMediaSource, @UserIdInt int userId) {
1319         stopAndUnregisterCallback(userId);
1320         UserMediaPlayContext userMediaContext;
1321 
1322         synchronized (mLock) {
1323             userMediaContext = getOrCreateUserMediaPlayContextLocked(userId);
1324             userMediaContext.mActiveMediaController = null;
1325             userMediaContext.mPrimaryMediaComponents[MEDIA_SOURCE_MODE_PLAYBACK] =
1326                     playbackMediaSource;
1327         }
1328 
1329         UserHandle userHandle = UserHandle.of(userId);
1330         if (playbackMediaSource != null
1331                 && !TextUtils.isEmpty(playbackMediaSource.flattenToString())) {
1332             if (!isUserEphemeral(userHandle)) {
1333                 saveLastMediaSource(playbackMediaSource, MEDIA_SOURCE_MODE_PLAYBACK, userId);
1334             }
1335             if (playbackMediaSource
1336                     .equals(getRemovedMediaSourceComponentsForUser(userId)
1337                             [MEDIA_SOURCE_MODE_PLAYBACK])) {
1338                 getRemovedMediaSourceComponentsForUser(userId)[MEDIA_SOURCE_MODE_PLAYBACK] = null;
1339             }
1340             notifyListeners(MEDIA_SOURCE_MODE_PLAYBACK, userId);
1341             startMediaConnectorService(playbackMediaSource,
1342                     shouldStartPlayback(mPlayOnMediaSourceChangedConfig, userId), userId);
1343         } else {
1344             Slogf.i(TAG, "Media source is null for user %d, skip starting media "
1345                     + "connector service", userId);
1346             // We will still notify the listeners that playback changed
1347             notifyListeners(MEDIA_SOURCE_MODE_PLAYBACK, userId);
1348         }
1349 
1350         // Reset current playback state for the new source, in the case that the app is in an error
1351         // state (e.g. not signed in). This state will be updated from the app callback registered
1352         // below, to make sure mCurrentPlaybackState reflects the current source only.
1353         synchronized (mLock) {
1354             userMediaContext.mCurrentPlaybackState = PlaybackState.STATE_NONE;
1355             updateActiveMediaControllerLocked(mMediaSessionManager
1356                     .getActiveSessionsForUser(null, userHandle), userId);
1357         }
1358     }
1359 
setBrowseMediaSource(ComponentName browseMediaSource, @UserIdInt int userId)1360     private void setBrowseMediaSource(ComponentName browseMediaSource, @UserIdInt int userId) {
1361         synchronized (mLock) {
1362             getPrimaryMediaComponentsForUserLocked(userId)[MEDIA_SOURCE_MODE_BROWSE] =
1363                     browseMediaSource;
1364         }
1365 
1366         if (browseMediaSource != null && !TextUtils.isEmpty(browseMediaSource.flattenToString())) {
1367             if (!isUserEphemeral(UserHandle.of(userId))) {
1368                 saveLastMediaSource(browseMediaSource, MEDIA_SOURCE_MODE_BROWSE, userId);
1369             }
1370             if (browseMediaSource
1371                     .equals(getRemovedMediaSourceComponentsForUser(
1372                             userId)[MEDIA_SOURCE_MODE_BROWSE])) {
1373                 getRemovedMediaSourceComponentsForUser(userId)[MEDIA_SOURCE_MODE_BROWSE] = null;
1374             }
1375         }
1376 
1377         notifyListeners(MEDIA_SOURCE_MODE_BROWSE, userId);
1378     }
1379 
notifyListeners(@arMediaManager.MediaSourceMode int mode, @UserIdInt int userId)1380     private void notifyListeners(@CarMediaManager.MediaSourceMode int mode, @UserIdInt int userId) {
1381         synchronized (mLock) {
1382             UserMediaPlayContext userMediaContext = getOrCreateUserMediaPlayContextLocked(userId);
1383             RemoteCallbackList<ICarMediaSourceListener> callbackList =
1384                     userMediaContext.mMediaSourceListeners[mode];
1385             ComponentName primaryMediaComponent = userMediaContext.mPrimaryMediaComponents[mode];
1386             int i = callbackList.beginBroadcast();
1387             if (DEBUG) {
1388                 Slogf.d(TAG, "Notify %d media source listeners for mode %d, user %d",
1389                         i, mode, userId);
1390             }
1391             while (i-- > 0) {
1392                 try {
1393                     ICarMediaSourceListener callback = callbackList.getBroadcastItem(i);
1394                     callback.onMediaSourceChanged(primaryMediaComponent);
1395                 } catch (RemoteException e) {
1396                     Slogf.e(TAG, e, "calling onMediaSourceChanged failed for user %d", userId);
1397                 }
1398             }
1399             callbackList.finishBroadcast();
1400         }
1401     }
1402 
1403     /**
1404      * Finds the currently playing media source, then updates the active source if the component
1405      * name is different.
1406      */
updatePrimaryMediaSourceWithCurrentlyPlaying( List<MediaController> controllers, @UserIdInt int userId)1407     private void updatePrimaryMediaSourceWithCurrentlyPlaying(
1408             List<MediaController> controllers, @UserIdInt int userId) {
1409         for (MediaController controller : controllers) {
1410             PlaybackState state = controller.getPlaybackState();
1411             if (state != null && state.isActive()) {
1412                 String newPackageName = controller.getPackageName();
1413                 String newClassName = getClassName(controller);
1414                 if (!matchPrimaryMediaSource(newPackageName, newClassName,
1415                         MEDIA_SOURCE_MODE_PLAYBACK, userId)) {
1416                     ComponentName mediaSource =
1417                             getMediaSource(newPackageName, newClassName, userId);
1418                     if (Slogf.isLoggable(TAG, Log.INFO)) {
1419                         if (mediaSource != null) {
1420                             Slogf.i(TAG,
1421                                     "MediaController changed, updating media source for user %d "
1422                                     + "to: %s", userId, mediaSource.flattenToString());
1423                         } else {
1424                             // Some apps, like Chrome, have a MediaSession but no
1425                             // MediaBrowseService. Media Center doesn't consider such apps as
1426                             // valid media sources.
1427                             Slogf.i(TAG,
1428                                     "MediaController changed, but no media browse service for user"
1429                                             + " %d found in package: %s", userId, newPackageName);
1430                         }
1431                     }
1432                     setPrimaryMediaSource(mediaSource, MEDIA_SOURCE_MODE_PLAYBACK, userId);
1433                 }
1434                 return;
1435             }
1436         }
1437     }
1438 
matchPrimaryMediaSource(String newPackageName, String newClassName, @CarMediaManager.MediaSourceMode int mode, @UserIdInt int userId)1439     private boolean matchPrimaryMediaSource(String newPackageName, String newClassName,
1440             @CarMediaManager.MediaSourceMode int mode, @UserIdInt int userId) {
1441         synchronized (mLock) {
1442             ComponentName mediaComponent = getPrimaryMediaComponentsForUserLocked(userId)[mode];
1443             if (mediaComponent != null
1444                     && mediaComponent.getPackageName().equals(newPackageName)) {
1445                 // If the class name of currently active source is not specified, only checks
1446                 // package name; otherwise checks both package name and class name.
1447                 if (TextUtils.isEmpty(newClassName)) {
1448                     return true;
1449                 } else {
1450                     return newClassName.equals(mediaComponent.getClassName());
1451                 }
1452             }
1453         }
1454         return false;
1455     }
1456 
1457     /**
1458      * Returns {@code true} if the provided component has a valid {@link MediaBrowserService}.
1459      */
1460     @VisibleForTesting
isMediaService(ComponentName componentName, @UserIdInt int userId)1461     public boolean isMediaService(ComponentName componentName, @UserIdInt int userId) {
1462         return getMediaService(componentName, userId) != null;
1463     }
1464 
1465     /*
1466      * Gets the media service that matches the componentName for the specified user.
1467      */
getMediaService(ComponentName componentName, @UserIdInt int userId)1468     private ComponentName getMediaService(ComponentName componentName, @UserIdInt int userId) {
1469         String packageName = componentName.getPackageName();
1470         String className = componentName.getClassName();
1471 
1472         PackageManager packageManager = mContext.getPackageManager();
1473         Intent mediaIntent = new Intent();
1474         mediaIntent.setPackage(packageName);
1475         mediaIntent.setAction(MediaBrowserService.SERVICE_INTERFACE);
1476         List<ResolveInfo> mediaServices = packageManager.queryIntentServicesAsUser(mediaIntent,
1477                 PackageManager.GET_RESOLVED_FILTER, UserHandle.of(userId));
1478 
1479         for (ResolveInfo service : mediaServices) {
1480             String serviceName = service.serviceInfo.name;
1481             if (!TextUtils.isEmpty(serviceName)
1482                     // If className is not specified, returns the first service in the package;
1483                     // otherwise returns the matched service.
1484                     // TODO(b/136274456): find a proper way to handle the case where there are
1485                     //  multiple services and the className is not specified.
1486                     && (TextUtils.isEmpty(className) || serviceName.equals(className))) {
1487                 return new ComponentName(packageName, serviceName);
1488             }
1489         }
1490 
1491         if (DEBUG) {
1492             Slogf.d(TAG, "No MediaBrowseService for user %d with ComponentName: %s",
1493                     userId, componentName.flattenToString());
1494         }
1495         return null;
1496     }
1497 
1498     /*
1499      * Gets the component name of the media service.
1500      */
1501     @Nullable
getMediaSource(String packageName, String className, @UserIdInt int userId)1502     private ComponentName getMediaSource(String packageName, String className,
1503             @UserIdInt int userId) {
1504         return getMediaService(new ComponentName(packageName, className), userId);
1505     }
1506 
saveLastMediaSource(ComponentName component, int mode, @UserIdInt int userId)1507     private void saveLastMediaSource(ComponentName component, int mode, @UserIdInt int userId) {
1508         if (!sharedPrefsInitialized()) {
1509             return;
1510         }
1511         String componentName = component.flattenToString();
1512         String key = getMediaSourceKey(mode, userId);
1513         String serialized = mSharedPrefs.getString(key, null);
1514         String modeName = null;
1515         if (DEBUG) {
1516             modeName = mediaModeToString(mode);
1517         }
1518 
1519         if (serialized == null) {
1520             if (DEBUG) {
1521                 Slogf.d(TAG, "saveLastMediaSource(%s, %s, %d): no value for key %s",
1522                         componentName, modeName, userId, key);
1523             }
1524             getSharedPrefsForWriting(userId).putString(key, componentName).apply();
1525         } else {
1526             Deque<String> componentNames = new ArrayDeque<>(getComponentNameList(serialized));
1527             componentNames.remove(componentName);
1528             componentNames.addFirst(componentName);
1529             String newSerialized = serializeComponentNameList(componentNames);
1530             if (DEBUG) {
1531                 Slogf.d(TAG, "saveLastMediaSource(%s, %s, %d): updating %s from %s to %s",
1532                         componentName, modeName, userId, key, serialized, newSerialized);
1533             }
1534             getSharedPrefsForWriting(userId).putString(key, newSerialized).apply();
1535         }
1536     }
1537 
getLastMediaSource(int mode, @UserIdInt int userId)1538     private @NonNull ComponentName getLastMediaSource(int mode, @UserIdInt int userId) {
1539         if (sharedPrefsInitialized()) {
1540             String key = getMediaSourceKey(mode, userId);
1541             String serialized = mSharedPrefs.getString(key, "");
1542             if (!TextUtils.isEmpty(serialized)) {
1543                 for (String name : getComponentNameList(serialized)) {
1544                     ComponentName componentName = ComponentName.unflattenFromString(name);
1545                     if (isMediaService(componentName, userId)) {
1546                         return componentName;
1547                     }
1548                 }
1549             }
1550         }
1551         return getDefaultMediaSource(userId);
1552     }
1553 
getDefaultMediaSource(@serIdInt int userId)1554     private ComponentName getDefaultMediaSource(@UserIdInt int userId) {
1555         String defaultMediaSource = mContext.getString(R.string.config_defaultMediaSource);
1556         ComponentName defaultComponent = ComponentName.unflattenFromString(defaultMediaSource);
1557         if (isMediaService(defaultComponent, userId)) {
1558             return defaultComponent;
1559         }
1560         Slogf.e(TAG, "No media service for user %d in the default component: %s",
1561                 userId, defaultComponent);
1562         return null;
1563     }
1564 
serializeComponentNameList(Deque<String> componentNames)1565     private String serializeComponentNameList(Deque<String> componentNames) {
1566         return String.join(COMPONENT_NAME_SEPARATOR, componentNames);
1567     }
1568 
getComponentNameList(String serialized)1569     private List<String> getComponentNameList(String serialized) {
1570         // Note that for an empty string, String#split returns an array of size 1 with an empty
1571         // string as its entry. Instead, we just return an empty list.
1572         if (TextUtils.isEmpty(serialized)) {
1573             return Collections.emptyList();
1574         }
1575         String[] componentNames = serialized.split(COMPONENT_NAME_SEPARATOR);
1576         return (Arrays.asList(componentNames));
1577     }
1578 
savePlaybackState(PlaybackState playbackState, @UserIdInt int userId)1579     private void savePlaybackState(PlaybackState playbackState, @UserIdInt int userId) {
1580         if (!sharedPrefsInitialized()) {
1581             return;
1582         }
1583         if (isUserEphemeral(UserHandle.of(userId))) {
1584             return;
1585         }
1586         int state = playbackState != null ? playbackState.getState() : PlaybackState.STATE_NONE;
1587         synchronized (mLock) {
1588             getOrCreateUserMediaPlayContextLocked(userId).mCurrentPlaybackState = state;
1589         }
1590 
1591         String key = getPlaybackStateKey(userId);
1592         if (DEBUG) {
1593             Slogf.d(TAG, "savePlaybackState() for user %d: %s = %d)", userId, key, state);
1594         }
1595         getSharedPrefsForWriting(userId).putInt(key, state).apply();
1596     }
1597 
1598     /**
1599      * Builds a string key for saving the playback state for a specific media source (and user)
1600      */
getPlaybackStateKey(@serIdInt int userId)1601     private String getPlaybackStateKey(@UserIdInt int userId) {
1602         ComponentName mediaComponent;
1603         synchronized (mLock) {
1604             mediaComponent =
1605                     getPrimaryMediaComponentsForUserLocked(userId)[MEDIA_SOURCE_MODE_PLAYBACK];
1606         }
1607         StringBuilder builder = new StringBuilder().append(PLAYBACK_STATE_KEY).append(userId);
1608         if (mediaComponent != null) {
1609             builder.append(mediaComponent.flattenToString());
1610         }
1611         return builder.toString();
1612     }
1613 
getMediaSourceKey(int mode, @UserIdInt int userId)1614     private String getMediaSourceKey(int mode, @UserIdInt int userId) {
1615         return SOURCE_KEY + mode + SOURCE_KEY_SEPARATOR + userId;
1616     }
1617 
getLastUpdateKey(@serIdInt int userId)1618     private String getLastUpdateKey(@UserIdInt int userId) {
1619         return LAST_UPDATE_KEY + userId;
1620     }
1621 
1622     /**
1623      * Updates active media controller from the list that has the same component name as the primary
1624      * media component. Clears callback and resets media controller to null if not found.
1625      */
1626     @GuardedBy("mLock")
updateActiveMediaControllerLocked(List<MediaController> mediaControllers, @UserIdInt int userId)1627     private void updateActiveMediaControllerLocked(List<MediaController> mediaControllers,
1628             @UserIdInt int userId) {
1629         UserMediaPlayContext userMediaPlayContext = getOrCreateUserMediaPlayContextLocked(userId);
1630         if (userMediaPlayContext.mPrimaryMediaComponents[MEDIA_SOURCE_MODE_PLAYBACK] == null) {
1631             return;
1632         }
1633         if (userMediaPlayContext.mActiveMediaController != null) {
1634             userMediaPlayContext.mActiveMediaController.unregisterCallback(
1635                     userMediaPlayContext.mMediaControllerCallback);
1636             userMediaPlayContext.mActiveMediaController = null;
1637         }
1638         for (MediaController controller : mediaControllers) {
1639             if (matchPrimaryMediaSource(controller.getPackageName(), getClassName(controller),
1640                     MEDIA_SOURCE_MODE_PLAYBACK, userId)) {
1641                 userMediaPlayContext.mActiveMediaController = controller;
1642                 PlaybackState state = controller.getPlaybackState();
1643                 savePlaybackState(state, userId);
1644                 // Specify Handler to receive callbacks on, to avoid defaulting to the calling
1645                 // thread; this method can be called from the MediaSessionManager callback.
1646                 // Using the version of this method without passing a handler causes a
1647                 // RuntimeException for failing to create a Handler.
1648                 controller.registerCallback(userMediaPlayContext.mMediaControllerCallback,
1649                         mHandler);
1650                 return;
1651             }
1652         }
1653     }
1654 
1655     /**
1656      * Returns whether we should autoplay the current media source
1657      */
shouldStartPlayback(int config, @UserIdInt int userId)1658     private boolean shouldStartPlayback(int config, @UserIdInt int userId) {
1659         switch (config) {
1660             case AUTOPLAY_CONFIG_NEVER:
1661                 return false;
1662             case AUTOPLAY_CONFIG_ALWAYS:
1663                 return true;
1664             case AUTOPLAY_CONFIG_RETAIN_PER_SOURCE:
1665                 if (!sharedPrefsInitialized()) {
1666                     return false;
1667                 }
1668                 int savedState =
1669                         mSharedPrefs.getInt(getPlaybackStateKey(userId), PlaybackState.STATE_NONE);
1670                 if (DEBUG) {
1671                     Slogf.d(TAG, "Getting saved playback state %d for user %d. Last saved on %d",
1672                             savedState, userId,
1673                             mSharedPrefs.getLong(getLastUpdateKey(userId), -1));
1674                 }
1675                 return savedState == PlaybackState.STATE_PLAYING;
1676             case AUTOPLAY_CONFIG_RETAIN_PREVIOUS:
1677                 int currentPlaybackState;
1678                 synchronized (mLock) {
1679                     currentPlaybackState =
1680                             getOrCreateUserMediaPlayContextLocked(userId).mCurrentPlaybackState;
1681                 }
1682                 return currentPlaybackState == PlaybackState.STATE_PLAYING;
1683             default:
1684                 Slogf.e(TAG, "Unsupported playback configuration: " + config);
1685                 return false;
1686         }
1687     }
1688 
1689     /**
1690      * Gets the editor used to update shared preferences.
1691      */
getSharedPrefsForWriting(@serIdInt int userId)1692     private SharedPreferences.Editor getSharedPrefsForWriting(@UserIdInt int userId) {
1693         long now = System.currentTimeMillis();
1694         String lastUpdateKey = getLastUpdateKey(userId);
1695         Slogf.i(TAG, "Updating %s to %d", lastUpdateKey, now);
1696         return mSharedPrefs.edit().putLong(lastUpdateKey, now);
1697     }
1698 
1699     @NonNull
getClassName(MediaController controller)1700     private static String getClassName(MediaController controller) {
1701         Bundle sessionExtras = controller.getExtras();
1702         String value =
1703                 sessionExtras == null ? "" : sessionExtras.getString(
1704                         Car.CAR_EXTRA_BROWSE_SERVICE_FOR_SESSION);
1705         return value != null ? value : "";
1706     }
1707 
mediaModeToString(@arMediaManager.MediaSourceMode int mode)1708     private static String mediaModeToString(@CarMediaManager.MediaSourceMode int mode) {
1709         return DebugUtils.constantToString(CarMediaManager.class, "MEDIA_SOURCE_", mode);
1710     }
1711 
1712     private final class MediaKeyEventListener implements KeyEventListener {
1713 
1714         /**
1715          * Handles a media key event from {@link CarInputService}.
1716          *
1717          * <p>When there are multiple active media sessions, stop after first successful delivery.
1718          */
1719         @Override
onKeyEvent(KeyEvent event, int displayType, int seat)1720         public void onKeyEvent(KeyEvent event, int displayType, int seat) {
1721             if (DEBUG) {
1722                 Slogf.d(TAG, "onKeyEvent(%s, %d, %d)", event, displayType, seat);
1723             }
1724             int occupantZoneId = mOccupantZoneService.getOccupantZoneIdForSeat(seat);
1725             if (occupantZoneId == INVALID_ZONE_ID) {
1726                 Slogf.w(TAG, "Failed to find a valid occupant zone for seat %d."
1727                         + " Ignoring key event %s", seat, event);
1728                 return;
1729             }
1730             int userId = mOccupantZoneService.getUserForOccupant(occupantZoneId);
1731             if (userId == INVALID_USER_ID) {
1732                 Slogf.w(TAG, "Failed to find a valid user for occupant zone %d."
1733                         + " Ignoring key event %s", occupantZoneId, event);
1734                 return;
1735             }
1736             List<MediaController> mediaControllers = mMediaSessionManager.getActiveSessionsForUser(
1737                     /* notificationListeners= */ null, UserHandle.of(userId));
1738             // Send the key event until it is successfully sent to any of the active sessions.
1739             boolean sent = false;
1740             for (int i = 0; !sent && i < mediaControllers.size(); i++) {
1741                 sent = mediaControllers.get(i).dispatchMediaButtonEvent(event);
1742             }
1743 
1744             if (DEBUG) {
1745                 if (sent) {
1746                     Slogf.d(TAG, "Successfully sent the key event %s to user %d", event, userId);
1747                 } else {
1748                     Slogf.d(TAG, "No active media session can receive the key event %s for user %d",
1749                             event, userId);
1750                 }
1751             }
1752         }
1753     }
1754 }
1755