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