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 android.app.ActivityManager; 19 import android.car.media.CarMediaManager; 20 import android.car.media.CarMediaManager.MediaSourceChangedListener; 21 import android.car.media.ICarMedia; 22 import android.car.media.ICarMediaSourceListener; 23 import android.content.BroadcastReceiver; 24 import android.content.Context; 25 import android.content.Intent; 26 import android.content.IntentFilter; 27 import android.content.SharedPreferences; 28 import android.content.pm.PackageManager; 29 import android.content.pm.ResolveInfo; 30 import android.media.session.MediaController; 31 import android.media.session.MediaController.TransportControls; 32 import android.media.session.MediaSession; 33 import android.media.session.MediaSession.Token; 34 import android.media.session.MediaSessionManager; 35 import android.media.session.MediaSessionManager.OnActiveSessionsChangedListener; 36 import android.media.session.PlaybackState; 37 import android.os.Handler; 38 import android.os.HandlerThread; 39 import android.os.RemoteCallbackList; 40 import android.os.RemoteException; 41 import android.service.media.MediaBrowserService; 42 import android.text.TextUtils; 43 import android.util.Log; 44 45 import androidx.annotation.NonNull; 46 import androidx.annotation.Nullable; 47 48 import com.android.car.user.CarUserService; 49 50 import java.io.PrintWriter; 51 import java.util.ArrayDeque; 52 import java.util.ArrayList; 53 import java.util.Arrays; 54 import java.util.Deque; 55 import java.util.HashMap; 56 import java.util.List; 57 import java.util.Map; 58 import java.util.stream.Collectors; 59 60 /** 61 * CarMediaService manages the currently active media source for car apps. This is different from 62 * the MediaSessionManager's active sessions, as there can only be one active source in the car, 63 * through both browse and playback. 64 * 65 * In the car, the active media source does not necessarily have an active MediaSession, e.g. if 66 * it were being browsed only. However, that source is still considered the active source, and 67 * should be the source displayed in any Media related UIs (Media Center, home screen, etc). 68 */ 69 public class CarMediaService extends ICarMedia.Stub implements CarServiceBase { 70 71 private static final String SOURCE_KEY = "media_source"; 72 private static final String PLAYBACK_STATE_KEY = "playback_state"; 73 private static final String SHARED_PREF = "com.android.car.media.car_media_service"; 74 private static final String PACKAGE_NAME_SEPARATOR = ","; 75 76 private Context mContext; 77 private final MediaSessionManager mMediaSessionManager; 78 private MediaSessionUpdater mMediaSessionUpdater; 79 private String mPrimaryMediaPackage; 80 private SharedPreferences mSharedPrefs; 81 // MediaController for the current active user's active media session. This controller can be 82 // null if playback has not been started yet. 83 private MediaController mActiveUserMediaController; 84 private SessionChangedListener mSessionsListener; 85 private boolean mStartPlayback; 86 87 private RemoteCallbackList<ICarMediaSourceListener> mMediaSourceListeners = 88 new RemoteCallbackList(); 89 90 // Handler to receive PlaybackState callbacks from the active media controller. 91 private Handler mHandler; 92 private HandlerThread mHandlerThread; 93 94 /** The package name of the last media source that was removed while being primary. */ 95 private String mRemovedMediaSourcePackage; 96 97 /** 98 * Listens to {@link Intent#ACTION_PACKAGE_REMOVED} and {@link Intent#ACTION_PACKAGE_REPLACED} 99 * so we can reset the media source to null when its application is uninstalled, and restore it 100 * when the application is reinstalled. 101 */ 102 private BroadcastReceiver mPackageRemovedReceiver = new BroadcastReceiver() { 103 @Override 104 public void onReceive(Context context, Intent intent) { 105 if (intent.getData() == null) { 106 return; 107 } 108 String intentPackage = intent.getData().getSchemeSpecificPart(); 109 if (Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())) { 110 if (mPrimaryMediaPackage != null && mPrimaryMediaPackage.equals(intentPackage)) { 111 mRemovedMediaSourcePackage = intentPackage; 112 setPrimaryMediaSource(null); 113 } 114 } else if (Intent.ACTION_PACKAGE_REPLACED.equals(intent.getAction()) 115 || Intent.ACTION_PACKAGE_ADDED.equals(intent.getAction())) { 116 if (mRemovedMediaSourcePackage != null 117 && mRemovedMediaSourcePackage.equals(intentPackage) 118 && isMediaService(intentPackage)) { 119 setPrimaryMediaSource(mRemovedMediaSourcePackage); 120 } 121 } 122 } 123 }; 124 125 private BroadcastReceiver mUserSwitchReceiver = new BroadcastReceiver() { 126 @Override 127 public void onReceive(Context context, Intent intent) { 128 updateMediaSessionCallbackForCurrentUser(); 129 } 130 }; 131 CarMediaService(Context context)132 public CarMediaService(Context context) { 133 mContext = context; 134 mMediaSessionManager = mContext.getSystemService(MediaSessionManager.class); 135 mMediaSessionUpdater = new MediaSessionUpdater(); 136 137 mHandlerThread = new HandlerThread(CarLog.TAG_MEDIA); 138 mHandlerThread.start(); 139 mHandler = new Handler(mHandlerThread.getLooper()); 140 141 IntentFilter filter = new IntentFilter(); 142 filter.addAction(Intent.ACTION_PACKAGE_REMOVED); 143 filter.addAction(Intent.ACTION_PACKAGE_REPLACED); 144 filter.addAction(Intent.ACTION_PACKAGE_ADDED); 145 filter.addDataScheme("package"); 146 mContext.registerReceiver(mPackageRemovedReceiver, filter); 147 148 IntentFilter userSwitchFilter = new IntentFilter(); 149 userSwitchFilter.addAction(Intent.ACTION_USER_SWITCHED); 150 mContext.registerReceiver(mUserSwitchReceiver, userSwitchFilter); 151 152 updateMediaSessionCallbackForCurrentUser(); 153 } 154 155 @Override init()156 public void init() { 157 CarLocalServices.getService(CarUserService.class).runOnUser0Unlock(() -> { 158 mSharedPrefs = mContext.getSharedPreferences(SHARED_PREF, Context.MODE_PRIVATE); 159 mPrimaryMediaPackage = getLastMediaPackage(); 160 mStartPlayback = mSharedPrefs.getInt(PLAYBACK_STATE_KEY, PlaybackState.STATE_NONE) 161 == PlaybackState.STATE_PLAYING; 162 notifyListeners(); 163 }); 164 } 165 166 @Override release()167 public void release() { 168 mMediaSessionUpdater.unregisterCallbacks(); 169 } 170 171 @Override dump(PrintWriter writer)172 public void dump(PrintWriter writer) { 173 writer.println("*CarMediaService*"); 174 writer.println("\tCurrent media package: " + mPrimaryMediaPackage); 175 if (mActiveUserMediaController != null) { 176 writer.println( 177 "\tCurrent media controller: " + mActiveUserMediaController.getPackageName()); 178 } 179 writer.println("\tNumber of active media sessions: " 180 + mMediaSessionManager.getActiveSessionsForUser(null, 181 ActivityManager.getCurrentUser()).size()); 182 } 183 184 /** 185 * @see {@link CarMediaManager#setMediaSource(String)} 186 */ 187 @Override setMediaSource(String packageName)188 public synchronized void setMediaSource(String packageName) { 189 ICarImpl.assertPermission(mContext, android.Manifest.permission.MEDIA_CONTENT_CONTROL); 190 setPrimaryMediaSource(packageName); 191 } 192 193 /** 194 * @see {@link CarMediaManager#getMediaSource()} 195 */ 196 @Override getMediaSource()197 public synchronized String getMediaSource() { 198 ICarImpl.assertPermission(mContext, android.Manifest.permission.MEDIA_CONTENT_CONTROL); 199 return mPrimaryMediaPackage; 200 } 201 202 /** 203 * @see {@link CarMediaManager#registerMediaSourceListener(MediaSourceChangedListener)} 204 */ 205 @Override registerMediaSourceListener(ICarMediaSourceListener callback)206 public synchronized void registerMediaSourceListener(ICarMediaSourceListener callback) { 207 ICarImpl.assertPermission(mContext, android.Manifest.permission.MEDIA_CONTENT_CONTROL); 208 mMediaSourceListeners.register(callback); 209 } 210 211 /** 212 * @see {@link CarMediaManager#unregisterMediaSourceListener(ICarMediaSourceListener)} 213 */ 214 @Override unregisterMediaSourceListener(ICarMediaSourceListener callback)215 public synchronized void unregisterMediaSourceListener(ICarMediaSourceListener callback) { 216 ICarImpl.assertPermission(mContext, android.Manifest.permission.MEDIA_CONTENT_CONTROL); 217 mMediaSourceListeners.unregister(callback); 218 } 219 updateMediaSessionCallbackForCurrentUser()220 private void updateMediaSessionCallbackForCurrentUser() { 221 if (mSessionsListener != null) { 222 mMediaSessionManager.removeOnActiveSessionsChangedListener(mSessionsListener); 223 } 224 mSessionsListener = new SessionChangedListener(ActivityManager.getCurrentUser()); 225 mMediaSessionManager.addOnActiveSessionsChangedListener(mSessionsListener, null, 226 ActivityManager.getCurrentUser(), null); 227 mMediaSessionUpdater.registerCallbacks(mMediaSessionManager.getActiveSessionsForUser( 228 null, ActivityManager.getCurrentUser())); 229 } 230 231 /** 232 * Attempts to play the current source using MediaController.TransportControls.play() 233 */ play()234 private void play() { 235 if (mActiveUserMediaController != null) { 236 TransportControls controls = mActiveUserMediaController.getTransportControls(); 237 if (controls != null) { 238 controls.play(); 239 } 240 } 241 } 242 243 /** 244 * Attempts to stop the current source using MediaController.TransportControls.stop() 245 */ stop()246 private void stop() { 247 if (mActiveUserMediaController != null) { 248 TransportControls controls = mActiveUserMediaController.getTransportControls(); 249 if (controls != null) { 250 controls.stop(); 251 } 252 } 253 } 254 255 private class SessionChangedListener implements OnActiveSessionsChangedListener { 256 private final int mCurrentUser; 257 SessionChangedListener(int currentUser)258 SessionChangedListener(int currentUser) { 259 mCurrentUser = currentUser; 260 } 261 262 @Override onActiveSessionsChanged(List<MediaController> controllers)263 public void onActiveSessionsChanged(List<MediaController> controllers) { 264 if (ActivityManager.getCurrentUser() != mCurrentUser) { 265 Log.e(CarLog.TAG_MEDIA, "Active session callback for old user: " + mCurrentUser); 266 return; 267 } 268 mMediaSessionUpdater.registerCallbacks(controllers); 269 } 270 } 271 272 private class MediaControllerCallback extends MediaController.Callback { 273 274 private final MediaController mMediaController; 275 private int mPreviousPlaybackState; 276 MediaControllerCallback(MediaController mediaController)277 private MediaControllerCallback(MediaController mediaController) { 278 mMediaController = mediaController; 279 PlaybackState state = mediaController.getPlaybackState(); 280 mPreviousPlaybackState = (state == null) ? PlaybackState.STATE_NONE : state.getState(); 281 } 282 register()283 private void register() { 284 mMediaController.registerCallback(this); 285 } 286 unregister()287 private void unregister() { 288 mMediaController.unregisterCallback(this); 289 } 290 291 @Override onPlaybackStateChanged(@ullable PlaybackState state)292 public void onPlaybackStateChanged(@Nullable PlaybackState state) { 293 if (state.getState() == PlaybackState.STATE_PLAYING 294 && state.getState() != mPreviousPlaybackState) { 295 setPrimaryMediaSource(mMediaController.getPackageName()); 296 } 297 mPreviousPlaybackState = state.getState(); 298 } 299 } 300 301 private class MediaSessionUpdater { 302 private Map<Token, MediaControllerCallback> mCallbacks = new HashMap<>(); 303 304 /** 305 * Register a {@link MediaControllerCallback} for each given controller. Note that if a 306 * controller was already watched, we don't register a callback again. This prevents an 307 * undesired revert of the primary media source. Callbacks for previously watched 308 * controllers that are not present in the given list are unregistered. 309 */ registerCallbacks(List<MediaController> newControllers)310 private void registerCallbacks(List<MediaController> newControllers) { 311 312 List<MediaController> additions = new ArrayList<>(newControllers.size()); 313 Map<MediaSession.Token, MediaControllerCallback> updatedCallbacks = 314 new HashMap<>(newControllers.size()); 315 316 for (MediaController controller : newControllers) { 317 MediaSession.Token token = controller.getSessionToken(); 318 MediaControllerCallback callback = mCallbacks.get(token); 319 if (callback == null) { 320 callback = new MediaControllerCallback(controller); 321 callback.register(); 322 additions.add(controller); 323 } 324 updatedCallbacks.put(token, callback); 325 } 326 327 for (MediaSession.Token token : mCallbacks.keySet()) { 328 if (!updatedCallbacks.containsKey(token)) { 329 mCallbacks.get(token).unregister(); 330 } 331 } 332 333 mCallbacks = updatedCallbacks; 334 updatePrimaryMediaSourceWithCurrentlyPlaying(additions); 335 // If there are no playing media sources, and we don't currently have the controller 336 // for the active source, check the new active sessions for a matching controller. 337 if (mActiveUserMediaController == null) { 338 updateActiveMediaController(additions); 339 } 340 } 341 342 /** 343 * Unregister all MediaController callbacks 344 */ unregisterCallbacks()345 private void unregisterCallbacks() { 346 for (Map.Entry<Token, MediaControllerCallback> entry : mCallbacks.entrySet()) { 347 entry.getValue().unregister(); 348 } 349 } 350 } 351 352 /** 353 * Updates the primary media source, then notifies content observers of the change 354 */ setPrimaryMediaSource(@ullable String packageName)355 private synchronized void setPrimaryMediaSource(@Nullable String packageName) { 356 if (mPrimaryMediaPackage != null && mPrimaryMediaPackage.equals((packageName))) { 357 return; 358 } 359 360 stop(); 361 362 mStartPlayback = false; 363 mPrimaryMediaPackage = packageName; 364 updateActiveMediaController(mMediaSessionManager 365 .getActiveSessionsForUser(null, ActivityManager.getCurrentUser())); 366 367 if (mSharedPrefs != null) { 368 if (!TextUtils.isEmpty(mPrimaryMediaPackage)) { 369 saveLastMediaPackage(mPrimaryMediaPackage); 370 mRemovedMediaSourcePackage = null; 371 } 372 } else { 373 // Shouldn't reach this unless there is some other error in CarService 374 Log.e(CarLog.TAG_MEDIA, "Error trying to save last media source, prefs uninitialized"); 375 } 376 notifyListeners(); 377 } 378 notifyListeners()379 private void notifyListeners() { 380 int i = mMediaSourceListeners.beginBroadcast(); 381 while (i-- > 0) { 382 try { 383 ICarMediaSourceListener callback = mMediaSourceListeners.getBroadcastItem(i); 384 callback.onMediaSourceChanged(mPrimaryMediaPackage); 385 } catch (RemoteException e) { 386 Log.e(CarLog.TAG_MEDIA, "calling onMediaSourceChanged failed " + e); 387 } 388 } 389 mMediaSourceListeners.finishBroadcast(); 390 } 391 392 private MediaController.Callback mMediaControllerCallback = new MediaController.Callback() { 393 @Override 394 public void onPlaybackStateChanged(PlaybackState state) { 395 savePlaybackState(state); 396 // Try to start playback if the new state allows the play action 397 maybeRestartPlayback(state); 398 } 399 }; 400 401 /** 402 * Finds the currently playing media source, then updates the active source if different 403 */ updatePrimaryMediaSourceWithCurrentlyPlaying( List<MediaController> controllers)404 private synchronized void updatePrimaryMediaSourceWithCurrentlyPlaying( 405 List<MediaController> controllers) { 406 for (MediaController controller : controllers) { 407 if (controller.getPlaybackState() != null 408 && controller.getPlaybackState().getState() == PlaybackState.STATE_PLAYING) { 409 if (mPrimaryMediaPackage == null || !mPrimaryMediaPackage.equals( 410 controller.getPackageName())) { 411 setPrimaryMediaSource(controller.getPackageName()); 412 } 413 return; 414 } 415 } 416 } 417 isMediaService(String packageName)418 private boolean isMediaService(String packageName) { 419 return getBrowseServiceClassName(packageName) != null; 420 } 421 getBrowseServiceClassName(@onNull String packageName)422 private String getBrowseServiceClassName(@NonNull String packageName) { 423 PackageManager packageManager = mContext.getPackageManager(); 424 Intent mediaIntent = new Intent(); 425 mediaIntent.setPackage(packageName); 426 mediaIntent.setAction(MediaBrowserService.SERVICE_INTERFACE); 427 428 List<ResolveInfo> mediaServices = packageManager.queryIntentServices(mediaIntent, 429 PackageManager.GET_RESOLVED_FILTER); 430 431 if (mediaServices == null || mediaServices.isEmpty()) { 432 return null; 433 } 434 return mediaServices.get(0).serviceInfo.name; 435 } 436 saveLastMediaPackage(@onNull String packageName)437 private void saveLastMediaPackage(@NonNull String packageName) { 438 String serialized = mSharedPrefs.getString(SOURCE_KEY, null); 439 if (serialized == null) { 440 mSharedPrefs.edit().putString(SOURCE_KEY, packageName).apply(); 441 } else { 442 Deque<String> packageNames = getPackageNameList(serialized); 443 packageNames.remove(packageName); 444 packageNames.addFirst(packageName); 445 mSharedPrefs.edit().putString(SOURCE_KEY, serializePackageNameList(packageNames)) 446 .apply(); 447 } 448 } 449 getLastMediaPackage()450 private String getLastMediaPackage() { 451 String serialized = mSharedPrefs.getString(SOURCE_KEY, null); 452 if (!TextUtils.isEmpty(serialized)) { 453 for (String packageName : getPackageNameList(serialized)) { 454 if (isMediaService(packageName)) { 455 return packageName; 456 } 457 } 458 } 459 460 String defaultSourcePackage = mContext.getString(R.string.default_media_application); 461 if (isMediaService(defaultSourcePackage)) { 462 return defaultSourcePackage; 463 } 464 return null; 465 } 466 serializePackageNameList(Deque<String> packageNames)467 private String serializePackageNameList(Deque<String> packageNames) { 468 return packageNames.stream().collect(Collectors.joining(PACKAGE_NAME_SEPARATOR)); 469 } 470 getPackageNameList(String serialized)471 private Deque<String> getPackageNameList(String serialized) { 472 String[] packageNames = serialized.split(PACKAGE_NAME_SEPARATOR); 473 return new ArrayDeque(Arrays.asList(packageNames)); 474 } 475 savePlaybackState(PlaybackState playbackState)476 private void savePlaybackState(PlaybackState playbackState) { 477 int state = playbackState != null ? playbackState.getState() : PlaybackState.STATE_NONE; 478 if (state == PlaybackState.STATE_PLAYING) { 479 // No longer need to request play if audio was resumed already via some other means, 480 // e.g. Assistant starts playback, user uses hardware button, etc. 481 mStartPlayback = false; 482 } 483 if (mSharedPrefs != null) { 484 mSharedPrefs.edit().putInt(PLAYBACK_STATE_KEY, state).apply(); 485 } 486 } 487 maybeRestartPlayback(PlaybackState state)488 private void maybeRestartPlayback(PlaybackState state) { 489 if (mStartPlayback && state != null 490 && (state.getActions() & PlaybackState.ACTION_PLAY) != 0) { 491 play(); 492 mStartPlayback = false; 493 } 494 } 495 496 /** 497 * Updates active media controller from the list that has the same package name as the primary 498 * media package. Clears callback and resets media controller to null if not found. 499 */ updateActiveMediaController(List<MediaController> mediaControllers)500 private void updateActiveMediaController(List<MediaController> mediaControllers) { 501 if (mPrimaryMediaPackage == null) { 502 return; 503 } 504 if (mActiveUserMediaController != null) { 505 mActiveUserMediaController.unregisterCallback(mMediaControllerCallback); 506 mActiveUserMediaController = null; 507 } 508 for (MediaController controller : mediaControllers) { 509 if (mPrimaryMediaPackage.equals(controller.getPackageName())) { 510 mActiveUserMediaController = controller; 511 // Specify Handler to receive callbacks on, to avoid defaulting to the calling 512 // thread; this method can be called from the MediaSessionManager callback. 513 // Using the version of this method without passing a handler causes a 514 // RuntimeException for failing to create a Handler. 515 PlaybackState state = mActiveUserMediaController.getPlaybackState(); 516 savePlaybackState(state); 517 mActiveUserMediaController.registerCallback(mMediaControllerCallback, mHandler); 518 maybeRestartPlayback(state); 519 return; 520 } 521 } 522 } 523 } 524