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