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