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