Home | History | Annotate | Download | only in tv
      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 
     17 package com.android.systemui.pip.tv;
     18 
     19 import android.app.Notification;
     20 import android.app.NotificationManager;
     21 import android.app.PendingIntent;
     22 import android.content.BroadcastReceiver;
     23 import android.content.Context;
     24 import android.content.Intent;
     25 import android.content.IntentFilter;
     26 import android.content.pm.ParceledListSlice;
     27 import android.content.res.Resources;
     28 import android.graphics.Bitmap;
     29 import android.graphics.drawable.Icon;
     30 import android.media.MediaMetadata;
     31 import android.media.session.MediaController;
     32 import android.media.session.PlaybackState;
     33 import android.text.TextUtils;
     34 import android.util.Log;
     35 import android.view.View;
     36 
     37 import com.android.systemui.util.NotificationChannels;
     38 import com.android.systemui.R;
     39 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
     40 
     41 /**
     42  * A notification that informs users that PIP is running and also provides PIP controls.
     43  * <p>Once it's created, it will manage the PIP notification UI by itself except for handling
     44  * configuration changes.
     45  */
     46 public class PipNotification {
     47     private static final String TAG = "PipNotification";
     48     private static final String NOTIFICATION_TAG = PipNotification.class.getName();
     49     private static final boolean DEBUG = PipManager.DEBUG;
     50 
     51     private static final String ACTION_MENU = "PipNotification.menu";
     52     private static final String ACTION_CLOSE = "PipNotification.close";
     53 
     54     private final PipManager mPipManager = PipManager.getInstance();
     55 
     56     private final NotificationManager mNotificationManager;
     57     private final Notification.Builder mNotificationBuilder;
     58 
     59     private MediaController mMediaController;
     60     private String mDefaultTitle;
     61     private int mDefaultIconResId;
     62 
     63     private boolean mNotified;
     64     private String mTitle;
     65     private Bitmap mArt;
     66 
     67     private PipManager.Listener mPipListener = new PipManager.Listener() {
     68         @Override
     69         public void onPipEntered() {
     70             updateMediaControllerMetadata();
     71             notifyPipNotification();
     72         }
     73 
     74         @Override
     75         public void onPipActivityClosed() {
     76             dismissPipNotification();
     77         }
     78 
     79         @Override
     80         public void onShowPipMenu() {
     81             // no-op.
     82         }
     83 
     84         @Override
     85         public void onPipMenuActionsChanged(ParceledListSlice actions) {
     86             // no-op.
     87         }
     88 
     89         @Override
     90         public void onMoveToFullscreen() {
     91             dismissPipNotification();
     92         }
     93 
     94         @Override
     95         public void onPipResizeAboutToStart() {
     96             // no-op.
     97         }
     98     };
     99 
    100     private MediaController.Callback mMediaControllerCallback = new MediaController.Callback() {
    101         @Override
    102         public void onPlaybackStateChanged(PlaybackState state) {
    103             if (updateMediaControllerMetadata() && mNotified) {
    104                 // update notification
    105                 notifyPipNotification();
    106             }
    107         }
    108     };
    109 
    110     private final PipManager.MediaListener mPipMediaListener = new PipManager.MediaListener() {
    111         @Override
    112         public void onMediaControllerChanged() {
    113             MediaController newController = mPipManager.getMediaController();
    114             if (mMediaController == newController) {
    115                 return;
    116             }
    117             if (mMediaController != null) {
    118                 mMediaController.unregisterCallback(mMediaControllerCallback);
    119             }
    120             mMediaController = newController;
    121             if (mMediaController != null) {
    122                 mMediaController.registerCallback(mMediaControllerCallback);
    123             }
    124             if (updateMediaControllerMetadata() && mNotified) {
    125                 // update notification
    126                 notifyPipNotification();
    127             }
    128         }
    129     };
    130 
    131     private final BroadcastReceiver mEventReceiver = new BroadcastReceiver() {
    132         @Override
    133         public void onReceive(Context context, Intent intent) {
    134             if (DEBUG) {
    135                 Log.d(TAG, "Received " + intent.getAction() + " from the notification UI");
    136             }
    137             switch (intent.getAction()) {
    138                 case ACTION_MENU:
    139                     mPipManager.showPictureInPictureMenu();
    140                     break;
    141                 case ACTION_CLOSE:
    142                     mPipManager.closePip();
    143                     break;
    144             }
    145         }
    146     };
    147 
    148     public PipNotification(Context context) {
    149         mNotificationManager = (NotificationManager) context.getSystemService(
    150                 Context.NOTIFICATION_SERVICE);
    151 
    152         mNotificationBuilder = new Notification.Builder(context, NotificationChannels.TVPIP)
    153                 .setLocalOnly(true)
    154                 .setOngoing(false)
    155                 .setCategory(Notification.CATEGORY_SYSTEM)
    156                 .extend(new Notification.TvExtender()
    157                         .setContentIntent(createPendingIntent(context, ACTION_MENU))
    158                         .setDeleteIntent(createPendingIntent(context, ACTION_CLOSE)));
    159 
    160         mPipManager.addListener(mPipListener);
    161         mPipManager.addMediaListener(mPipMediaListener);
    162 
    163         IntentFilter intentFilter = new IntentFilter();
    164         intentFilter.addAction(ACTION_MENU);
    165         intentFilter.addAction(ACTION_CLOSE);
    166         context.registerReceiver(mEventReceiver, intentFilter);
    167 
    168         onConfigurationChanged(context);
    169     }
    170 
    171     /**
    172      * Called by {@link PipManager} when the configuration is changed.
    173      */
    174     void onConfigurationChanged(Context context) {
    175         Resources res = context.getResources();
    176         mDefaultTitle = res.getString(R.string.pip_notification_unknown_title);
    177         mDefaultIconResId = R.drawable.pip_icon;
    178         if (mNotified) {
    179             // update notification
    180             notifyPipNotification();
    181         }
    182     }
    183 
    184     private void notifyPipNotification() {
    185         mNotified = true;
    186         mNotificationBuilder
    187                 .setShowWhen(true)
    188                 .setWhen(System.currentTimeMillis())
    189                 .setSmallIcon(mDefaultIconResId)
    190                 .setContentTitle(!TextUtils.isEmpty(mTitle) ? mTitle : mDefaultTitle);
    191         if (mArt != null) {
    192             mNotificationBuilder.setStyle(new Notification.BigPictureStyle()
    193                     .bigPicture(mArt));
    194         } else {
    195             mNotificationBuilder.setStyle(null);
    196         }
    197         mNotificationManager.notify(NOTIFICATION_TAG, SystemMessage.NOTE_TV_PIP,
    198                 mNotificationBuilder.build());
    199     }
    200 
    201     private void dismissPipNotification() {
    202         mNotified = false;
    203         mNotificationManager.cancel(NOTIFICATION_TAG, SystemMessage.NOTE_TV_PIP);
    204     }
    205 
    206     private boolean updateMediaControllerMetadata() {
    207         String title = null;
    208         Bitmap art = null;
    209         if (mPipManager.getMediaController() != null) {
    210             MediaMetadata metadata = mPipManager.getMediaController().getMetadata();
    211             if (metadata != null) {
    212                 title = metadata.getString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE);
    213                 if (TextUtils.isEmpty(title)) {
    214                     title = metadata.getString(MediaMetadata.METADATA_KEY_TITLE);
    215                 }
    216                 art = metadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART);
    217                 if (art == null) {
    218                     art = metadata.getBitmap(MediaMetadata.METADATA_KEY_ART);
    219                 }
    220             }
    221         }
    222         if (!TextUtils.equals(title, mTitle) || art != mArt) {
    223             mTitle = title;
    224             mArt = art;
    225             return true;
    226         }
    227         return false;
    228     }
    229 
    230     private static PendingIntent createPendingIntent(Context context, String action) {
    231         return PendingIntent.getBroadcast(context, 0,
    232                 new Intent(action), PendingIntent.FLAG_CANCEL_CURRENT);
    233     }
    234 }
    235