| /* |
| * Copyright (C) 2017 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License |
| */ |
| package com.android.systemui.statusbar; |
| |
| import android.app.Notification; |
| import android.content.Context; |
| import android.media.MediaMetadata; |
| import android.media.session.MediaController; |
| import android.media.session.MediaSession; |
| import android.media.session.MediaSessionManager; |
| import android.media.session.PlaybackState; |
| import android.os.UserHandle; |
| import android.util.Log; |
| |
| import com.android.systemui.Dumpable; |
| |
| import java.io.FileDescriptor; |
| import java.io.PrintWriter; |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| /** |
| * Handles tasks and state related to media notifications. For example, there is a 'current' media |
| * notification, which this class keeps track of. |
| */ |
| public class NotificationMediaManager implements Dumpable { |
| private static final String TAG = "NotificationMediaManager"; |
| public static final boolean DEBUG_MEDIA = false; |
| |
| private final Context mContext; |
| private final MediaSessionManager mMediaSessionManager; |
| |
| private NotificationPresenter mPresenter; |
| private MediaController mMediaController; |
| private String mMediaNotificationKey; |
| private MediaMetadata mMediaMetadata; |
| |
| private final MediaController.Callback mMediaListener = new MediaController.Callback() { |
| @Override |
| public void onPlaybackStateChanged(PlaybackState state) { |
| super.onPlaybackStateChanged(state); |
| if (DEBUG_MEDIA) { |
| Log.v(TAG, "DEBUG_MEDIA: onPlaybackStateChanged: " + state); |
| } |
| if (state != null) { |
| if (!isPlaybackActive(state.getState())) { |
| clearCurrentMediaNotification(); |
| mPresenter.updateMediaMetaData(true, true); |
| } |
| } |
| } |
| |
| @Override |
| public void onMetadataChanged(MediaMetadata metadata) { |
| super.onMetadataChanged(metadata); |
| if (DEBUG_MEDIA) { |
| Log.v(TAG, "DEBUG_MEDIA: onMetadataChanged: " + metadata); |
| } |
| mMediaMetadata = metadata; |
| mPresenter.updateMediaMetaData(true, true); |
| } |
| }; |
| |
| public NotificationMediaManager(Context context) { |
| mContext = context; |
| mMediaSessionManager |
| = (MediaSessionManager) mContext.getSystemService(Context.MEDIA_SESSION_SERVICE); |
| // TODO: use MediaSessionManager.SessionListener to hook us up to future updates |
| // in session state |
| } |
| |
| public void setUpWithPresenter(NotificationPresenter presenter) { |
| mPresenter = presenter; |
| } |
| |
| public void onNotificationRemoved(String key) { |
| if (key.equals(mMediaNotificationKey)) { |
| clearCurrentMediaNotification(); |
| mPresenter.updateMediaMetaData(true, true); |
| } |
| } |
| |
| public String getMediaNotificationKey() { |
| return mMediaNotificationKey; |
| } |
| |
| public MediaMetadata getMediaMetadata() { |
| return mMediaMetadata; |
| } |
| |
| public void findAndUpdateMediaNotifications() { |
| boolean metaDataChanged = false; |
| |
| synchronized (mPresenter.getEntryManager().getNotificationData()) { |
| ArrayList<NotificationData.Entry> activeNotifications = mPresenter.getEntryManager() |
| .getNotificationData().getActiveNotifications(); |
| final int N = activeNotifications.size(); |
| |
| // Promote the media notification with a controller in 'playing' state, if any. |
| NotificationData.Entry mediaNotification = null; |
| MediaController controller = null; |
| for (int i = 0; i < N; i++) { |
| final NotificationData.Entry entry = activeNotifications.get(i); |
| |
| if (isMediaNotification(entry)) { |
| final MediaSession.Token token = |
| entry.notification.getNotification().extras.getParcelable( |
| Notification.EXTRA_MEDIA_SESSION); |
| if (token != null) { |
| MediaController aController = new MediaController(mContext, token); |
| if (PlaybackState.STATE_PLAYING == |
| getMediaControllerPlaybackState(aController)) { |
| if (DEBUG_MEDIA) { |
| Log.v(TAG, "DEBUG_MEDIA: found mediastyle controller matching " |
| + entry.notification.getKey()); |
| } |
| mediaNotification = entry; |
| controller = aController; |
| break; |
| } |
| } |
| } |
| } |
| if (mediaNotification == null) { |
| // Still nothing? OK, let's just look for live media sessions and see if they match |
| // one of our notifications. This will catch apps that aren't (yet!) using media |
| // notifications. |
| |
| if (mMediaSessionManager != null) { |
| // TODO: Should this really be for all users? |
| final List<MediaController> sessions |
| = mMediaSessionManager.getActiveSessionsForUser( |
| null, |
| UserHandle.USER_ALL); |
| |
| for (MediaController aController : sessions) { |
| if (PlaybackState.STATE_PLAYING == |
| getMediaControllerPlaybackState(aController)) { |
| // now to see if we have one like this |
| final String pkg = aController.getPackageName(); |
| |
| for (int i = 0; i < N; i++) { |
| final NotificationData.Entry entry = activeNotifications.get(i); |
| if (entry.notification.getPackageName().equals(pkg)) { |
| if (DEBUG_MEDIA) { |
| Log.v(TAG, "DEBUG_MEDIA: found controller matching " |
| + entry.notification.getKey()); |
| } |
| controller = aController; |
| mediaNotification = entry; |
| break; |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| if (controller != null && !sameSessions(mMediaController, controller)) { |
| // We have a new media session |
| clearCurrentMediaNotification(); |
| mMediaController = controller; |
| mMediaController.registerCallback(mMediaListener); |
| mMediaMetadata = mMediaController.getMetadata(); |
| if (DEBUG_MEDIA) { |
| Log.v(TAG, "DEBUG_MEDIA: insert listener, receive metadata: " |
| + mMediaMetadata); |
| } |
| |
| if (mediaNotification != null) { |
| mMediaNotificationKey = mediaNotification.notification.getKey(); |
| if (DEBUG_MEDIA) { |
| Log.v(TAG, "DEBUG_MEDIA: Found new media notification: key=" |
| + mMediaNotificationKey + " controller=" + mMediaController); |
| } |
| } |
| metaDataChanged = true; |
| } |
| } |
| |
| if (metaDataChanged) { |
| mPresenter.getEntryManager().updateNotifications(); |
| } |
| mPresenter.updateMediaMetaData(metaDataChanged, true); |
| } |
| |
| public void clearCurrentMediaNotification() { |
| mMediaNotificationKey = null; |
| mMediaMetadata = null; |
| if (mMediaController != null) { |
| if (DEBUG_MEDIA) { |
| Log.v(TAG, "DEBUG_MEDIA: Disconnecting from old controller: " |
| + mMediaController.getPackageName()); |
| } |
| mMediaController.unregisterCallback(mMediaListener); |
| } |
| mMediaController = null; |
| } |
| |
| @Override |
| public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { |
| pw.print(" mMediaSessionManager="); |
| pw.println(mMediaSessionManager); |
| pw.print(" mMediaNotificationKey="); |
| pw.println(mMediaNotificationKey); |
| pw.print(" mMediaController="); |
| pw.print(mMediaController); |
| if (mMediaController != null) { |
| pw.print(" state=" + mMediaController.getPlaybackState()); |
| } |
| pw.println(); |
| pw.print(" mMediaMetadata="); |
| pw.print(mMediaMetadata); |
| if (mMediaMetadata != null) { |
| pw.print(" title=" + mMediaMetadata.getText(MediaMetadata.METADATA_KEY_TITLE)); |
| } |
| pw.println(); |
| } |
| |
| private boolean isPlaybackActive(int state) { |
| return state != PlaybackState.STATE_STOPPED && state != PlaybackState.STATE_ERROR |
| && state != PlaybackState.STATE_NONE; |
| } |
| |
| private boolean sameSessions(MediaController a, MediaController b) { |
| if (a == b) { |
| return true; |
| } |
| if (a == null) { |
| return false; |
| } |
| return a.controlsSameSession(b); |
| } |
| |
| private int getMediaControllerPlaybackState(MediaController controller) { |
| if (controller != null) { |
| final PlaybackState playbackState = controller.getPlaybackState(); |
| if (playbackState != null) { |
| return playbackState.getState(); |
| } |
| } |
| return PlaybackState.STATE_NONE; |
| } |
| |
| private boolean isMediaNotification(NotificationData.Entry entry) { |
| // TODO: confirm that there's a valid media key |
| return entry.getExpandedContentView() != null && |
| entry.getExpandedContentView() |
| .findViewById(com.android.internal.R.id.media_actions) != null; |
| } |
| } |