NotificationMediaManager.java revision 09322286c176af651bd1816a4540572d78e01aa1
1/*
2 * Copyright (C) 2017 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 */
16package com.android.systemui.statusbar;
17
18import android.app.Notification;
19import android.content.Context;
20import android.media.MediaMetadata;
21import android.media.session.MediaController;
22import android.media.session.MediaSession;
23import android.media.session.MediaSessionManager;
24import android.media.session.PlaybackState;
25import android.os.UserHandle;
26import android.util.Log;
27
28import com.android.systemui.Dumpable;
29
30import java.io.FileDescriptor;
31import java.io.PrintWriter;
32import java.util.ArrayList;
33import java.util.List;
34
35/**
36 * Handles tasks and state related to media notifications. For example, there is a 'current' media
37 * notification, which this class keeps track of.
38 */
39public class NotificationMediaManager implements Dumpable {
40    private static final String TAG = "NotificationMediaManager";
41    public static final boolean DEBUG_MEDIA = false;
42
43    private final NotificationPresenter mPresenter;
44    private final Context mContext;
45    private final MediaSessionManager mMediaSessionManager;
46    private MediaController mMediaController;
47    private String mMediaNotificationKey;
48    private MediaMetadata mMediaMetadata;
49
50    private final MediaController.Callback mMediaListener = new MediaController.Callback() {
51        @Override
52        public void onPlaybackStateChanged(PlaybackState state) {
53            super.onPlaybackStateChanged(state);
54            if (DEBUG_MEDIA) {
55                Log.v(TAG, "DEBUG_MEDIA: onPlaybackStateChanged: " + state);
56            }
57            if (state != null) {
58                if (!isPlaybackActive(state.getState())) {
59                    clearCurrentMediaNotification();
60                    mPresenter.updateMediaMetaData(true, true);
61                }
62            }
63        }
64
65        @Override
66        public void onMetadataChanged(MediaMetadata metadata) {
67            super.onMetadataChanged(metadata);
68            if (DEBUG_MEDIA) {
69                Log.v(TAG, "DEBUG_MEDIA: onMetadataChanged: " + metadata);
70            }
71            mMediaMetadata = metadata;
72            mPresenter.updateMediaMetaData(true, true);
73        }
74    };
75
76    public NotificationMediaManager(NotificationPresenter presenter, Context context) {
77        mPresenter = presenter;
78        mContext = context;
79        mMediaSessionManager
80                = (MediaSessionManager) mContext.getSystemService(Context.MEDIA_SESSION_SERVICE);
81        // TODO: use MediaSessionManager.SessionListener to hook us up to future updates
82        // in session state
83    }
84
85    public void onNotificationRemoved(String key) {
86        if (key.equals(mMediaNotificationKey)) {
87            clearCurrentMediaNotification();
88            mPresenter.updateMediaMetaData(true, true);
89        }
90    }
91
92    public String getMediaNotificationKey() {
93        return mMediaNotificationKey;
94    }
95
96    public MediaMetadata getMediaMetadata() {
97        return mMediaMetadata;
98    }
99
100    public void findAndUpdateMediaNotifications() {
101        boolean metaDataChanged = false;
102
103        synchronized (mPresenter.getNotificationData()) {
104            ArrayList<NotificationData.Entry> activeNotifications = mPresenter
105                    .getNotificationData().getActiveNotifications();
106            final int N = activeNotifications.size();
107
108            // Promote the media notification with a controller in 'playing' state, if any.
109            NotificationData.Entry mediaNotification = null;
110            MediaController controller = null;
111            for (int i = 0; i < N; i++) {
112                final NotificationData.Entry entry = activeNotifications.get(i);
113
114                if (isMediaNotification(entry)) {
115                    final MediaSession.Token token =
116                            entry.notification.getNotification().extras.getParcelable(
117                                    Notification.EXTRA_MEDIA_SESSION);
118                    if (token != null) {
119                        MediaController aController = new MediaController(mContext, token);
120                        if (PlaybackState.STATE_PLAYING ==
121                                getMediaControllerPlaybackState(aController)) {
122                            if (DEBUG_MEDIA) {
123                                Log.v(TAG, "DEBUG_MEDIA: found mediastyle controller matching "
124                                        + entry.notification.getKey());
125                            }
126                            mediaNotification = entry;
127                            controller = aController;
128                            break;
129                        }
130                    }
131                }
132            }
133            if (mediaNotification == null) {
134                // Still nothing? OK, let's just look for live media sessions and see if they match
135                // one of our notifications. This will catch apps that aren't (yet!) using media
136                // notifications.
137
138                if (mMediaSessionManager != null) {
139                    // TODO: Should this really be for all users?
140                    final List<MediaController> sessions
141                            = mMediaSessionManager.getActiveSessionsForUser(
142                            null,
143                            UserHandle.USER_ALL);
144
145                    for (MediaController aController : sessions) {
146                        if (PlaybackState.STATE_PLAYING ==
147                                getMediaControllerPlaybackState(aController)) {
148                            // now to see if we have one like this
149                            final String pkg = aController.getPackageName();
150
151                            for (int i = 0; i < N; i++) {
152                                final NotificationData.Entry entry = activeNotifications.get(i);
153                                if (entry.notification.getPackageName().equals(pkg)) {
154                                    if (DEBUG_MEDIA) {
155                                        Log.v(TAG, "DEBUG_MEDIA: found controller matching "
156                                                + entry.notification.getKey());
157                                    }
158                                    controller = aController;
159                                    mediaNotification = entry;
160                                    break;
161                                }
162                            }
163                        }
164                    }
165                }
166            }
167
168            if (controller != null && !sameSessions(mMediaController, controller)) {
169                // We have a new media session
170                clearCurrentMediaNotification();
171                mMediaController = controller;
172                mMediaController.registerCallback(mMediaListener);
173                mMediaMetadata = mMediaController.getMetadata();
174                if (DEBUG_MEDIA) {
175                    Log.v(TAG, "DEBUG_MEDIA: insert listener, receive metadata: "
176                            + mMediaMetadata);
177                }
178
179                if (mediaNotification != null) {
180                    mMediaNotificationKey = mediaNotification.notification.getKey();
181                    if (DEBUG_MEDIA) {
182                        Log.v(TAG, "DEBUG_MEDIA: Found new media notification: key="
183                                + mMediaNotificationKey + " controller=" + mMediaController);
184                    }
185                }
186                metaDataChanged = true;
187            }
188        }
189
190        if (metaDataChanged) {
191            mPresenter.updateNotifications();
192        }
193        mPresenter.updateMediaMetaData(metaDataChanged, true);
194    }
195
196    public void clearCurrentMediaNotification() {
197        mMediaNotificationKey = null;
198        mMediaMetadata = null;
199        if (mMediaController != null) {
200            if (DEBUG_MEDIA) {
201                Log.v(TAG, "DEBUG_MEDIA: Disconnecting from old controller: "
202                        + mMediaController.getPackageName());
203            }
204            mMediaController.unregisterCallback(mMediaListener);
205        }
206        mMediaController = null;
207    }
208
209    @Override
210    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
211        pw.print("    mMediaSessionManager=");
212        pw.println(mMediaSessionManager);
213        pw.print("    mMediaNotificationKey=");
214        pw.println(mMediaNotificationKey);
215        pw.print("    mMediaController=");
216        pw.print(mMediaController);
217        if (mMediaController != null) {
218            pw.print(" state=" + mMediaController.getPlaybackState());
219        }
220        pw.println();
221        pw.print("    mMediaMetadata=");
222        pw.print(mMediaMetadata);
223        if (mMediaMetadata != null) {
224            pw.print(" title=" + mMediaMetadata.getText(MediaMetadata.METADATA_KEY_TITLE));
225        }
226        pw.println();
227    }
228
229    private boolean isPlaybackActive(int state) {
230        return state != PlaybackState.STATE_STOPPED && state != PlaybackState.STATE_ERROR
231                && state != PlaybackState.STATE_NONE;
232    }
233
234    private boolean sameSessions(MediaController a, MediaController b) {
235        if (a == b) {
236            return true;
237        }
238        if (a == null) {
239            return false;
240        }
241        return a.controlsSameSession(b);
242    }
243
244    private int getMediaControllerPlaybackState(MediaController controller) {
245        if (controller != null) {
246            final PlaybackState playbackState = controller.getPlaybackState();
247            if (playbackState != null) {
248                return playbackState.getState();
249            }
250        }
251        return PlaybackState.STATE_NONE;
252    }
253
254    private boolean isMediaNotification(NotificationData.Entry entry) {
255        // TODO: confirm that there's a valid media key
256        return entry.getExpandedContentView() != null &&
257                entry.getExpandedContentView()
258                        .findViewById(com.android.internal.R.id.media_actions) != null;
259    }
260}
261