1 /* 2 * Copyright (C) 2024 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 17 package com.android.systemui.car.ndo; 18 19 import android.app.INotificationManager; 20 import android.app.Notification; 21 import android.content.Context; 22 import android.media.session.MediaController; 23 import android.media.session.MediaSessionManager; 24 import android.media.session.PlaybackState; 25 import android.os.RemoteException; 26 import android.os.UserHandle; 27 import android.service.notification.StatusBarNotification; 28 import android.util.Log; 29 30 import androidx.annotation.Nullable; 31 import androidx.annotation.VisibleForTesting; 32 import androidx.lifecycle.LiveData; 33 import androidx.lifecycle.MutableLiveData; 34 35 import java.util.ArrayList; 36 import java.util.Collections; 37 import java.util.List; 38 import java.util.concurrent.Executor; 39 40 import javax.inject.Inject; 41 42 /** 43 * Class that handles listening to and returning active media sessions. 44 */ 45 public class MediaSessionHelper extends MediaController.Callback { 46 private static final String TAG = "MediaSessionHelper"; 47 private final MutableLiveData<List<MediaController>> mLiveData = new MutableLiveData<>(); 48 private final MediaSessionManager mMediaSessionManager; 49 private UserHandle mUserHandle; 50 @VisibleForTesting 51 final List<MediaController> mMediaControllersList = new ArrayList<>(); 52 private final Executor mExecutor; 53 private final Context mContext; 54 private final INotificationManager mINotificationManager; 55 56 private final MediaSessionManager.OnActiveSessionsChangedListener mChangedListener = 57 this::onMediaSessionChange; 58 59 @Inject MediaSessionHelper(Context context, INotificationManager iNotificationManager)60 public MediaSessionHelper(Context context, INotificationManager iNotificationManager) { 61 mMediaSessionManager = context.getSystemService(MediaSessionManager.class); 62 mExecutor = context.getMainExecutor(); 63 mContext = context; 64 mINotificationManager = iNotificationManager; 65 } 66 67 /** Performs initialization */ init(UserHandle userHandle)68 public void init(UserHandle userHandle) { 69 mUserHandle = userHandle; 70 // Set initial data 71 onMediaSessionChange(mMediaSessionManager 72 .getActiveSessionsForUser(/* notificationListener= */ null, mUserHandle)); 73 74 mMediaSessionManager.addOnActiveSessionsChangedListener(/* notificationListener= */ null, 75 mUserHandle, mExecutor, mChangedListener); 76 } 77 78 /** Returns MediaControllers of current active media sessions */ getActiveMediaSessions()79 public LiveData<List<MediaController>> getActiveMediaSessions() { 80 return mLiveData; 81 } 82 83 /** Performs cleanup when MediaSessionHelper should no longer be used. */ cleanup()84 public void cleanup() { 85 mMediaSessionManager.removeOnActiveSessionsChangedListener(mChangedListener); 86 unregisterPlaybackChanges(); 87 } 88 89 @Override onPlaybackStateChanged(@ullable PlaybackState state)90 public void onPlaybackStateChanged(@Nullable PlaybackState state) { 91 if (isPausedOrActive(state)) { 92 onMediaSessionChange(mMediaSessionManager 93 .getActiveSessionsForUser(/* notificationListener= */ null, mUserHandle)); 94 } 95 } 96 onMediaSessionChange(List<MediaController> mediaControllers)97 private void onMediaSessionChange(List<MediaController> mediaControllers) { 98 unregisterPlaybackChanges(); 99 if (mediaControllers == null || mediaControllers.isEmpty()) { 100 return; 101 } 102 103 List<MediaController> activeMediaControllers = new ArrayList<>(); 104 List<String> mediaNotificationPackages = getActiveMediaNotificationPackages(); 105 106 for (MediaController mediaController : mediaControllers) { 107 if (isPausedOrActive(mediaController.getPlaybackState()) 108 && mediaNotificationPackages.contains(mediaController.getPackageName())) { 109 activeMediaControllers.add(mediaController); 110 } else { 111 // Since playback state changes don't trigger an active media session change, we 112 // need to listen to the other media sessions in case another one becomes active. 113 registerForPlaybackChanges(mediaController); 114 } 115 } 116 mLiveData.setValue(activeMediaControllers); 117 } 118 119 /** Returns whether the MediaController is paused active */ isPausedOrActive(PlaybackState playbackState)120 private boolean isPausedOrActive(PlaybackState playbackState) { 121 if (playbackState == null) { 122 return false; 123 } 124 return playbackState.isActive() || playbackState.getState() == PlaybackState.STATE_PAUSED; 125 } 126 registerForPlaybackChanges(MediaController controller)127 private void registerForPlaybackChanges(MediaController controller) { 128 if (mMediaControllersList.contains(controller)) { 129 return; 130 } 131 132 controller.registerCallback(this); 133 mMediaControllersList.add(controller); 134 } 135 unregisterPlaybackChanges()136 private void unregisterPlaybackChanges() { 137 for (MediaController mediaController : mMediaControllersList) { 138 if (mediaController != null) { 139 mediaController.unregisterCallback(this); 140 } 141 } 142 mMediaControllersList.clear(); 143 } 144 145 // We only want to detect media sessions with an associated media notification getActiveMediaNotificationPackages()146 private List<String> getActiveMediaNotificationPackages() { 147 try { 148 List<StatusBarNotification> activeNotifications = List.of( 149 mINotificationManager.getActiveNotificationsWithAttribution( 150 mContext.getPackageName(), /* callingAttributionTag= */ null 151 )); 152 153 List<String> packageNames = new ArrayList<>(); 154 for (StatusBarNotification statusBarNotification : activeNotifications) { 155 Notification notification = statusBarNotification.getNotification(); 156 if (notification.extras != null 157 && notification.isMediaNotification()) { 158 packageNames.add(statusBarNotification.getPackageName()); 159 } 160 } 161 162 return packageNames; 163 } catch (RemoteException e) { 164 Log.e(TAG, "Exception trying to get active notifications " + e); 165 return Collections.emptyList(); 166 } 167 } 168 } 169