blob: abc261e6bbf66343d96e005f130d222d2d2beb09 [file] [log] [blame]
/*
* 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;
protected NotificationPresenter mPresenter;
protected NotificationEntryManager mEntryManager;
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,
NotificationEntryManager entryManager) {
mPresenter = presenter;
mEntryManager = entryManager;
}
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 (mEntryManager.getNotificationData()) {
ArrayList<NotificationData.Entry> activeNotifications = mEntryManager
.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 (mediaNotification != null) {
mMediaNotificationKey = mediaNotification.notification.getKey();
if (DEBUG_MEDIA) {
Log.v(TAG, "DEBUG_MEDIA: Found new media notification: key="
+ mMediaNotificationKey + " controller=" + mMediaController);
}
}
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);
}
metaDataChanged = true;
}
}
if (metaDataChanged) {
mEntryManager.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;
}
}