• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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