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