Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(212)

Side by Side Diff: chrome/android/java/src/org/chromium/chrome/browser/media/ui/NotificationMediaPlaybackControls.java

Issue 1159113006: [Android] A prototype of the interactive media notification. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Added some null checks and hiding the notification when the tab is destroyed. Created 5 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 package org.chromium.chrome.browser.media.ui;
6
7 import android.app.Notification;
8 import android.app.PendingIntent;
9 import android.app.Service;
10 import android.content.Context;
11 import android.content.Intent;
12 import android.os.Handler;
13 import android.os.IBinder;
14 import android.provider.Browser;
15 import android.support.v4.app.NotificationCompat;
16 import android.support.v4.app.NotificationManagerCompat;
17 import android.widget.RemoteViews;
18
19 import org.chromium.chrome.R;
20 import org.chromium.chrome.browser.IntentHandler.TabOpenType;
21 import org.chromium.chrome.browser.Tab;
22
23 /**
24 * A class for notifications that provide information and optional media control s for a given media.
25 * Internally implements a Service for transforming notification Intents into
26 * {@link MediaPlaybackListener} calls for all registered listeners.
27 */
28 public class NotificationMediaPlaybackControls {
29 private static final Object LOCK = new Object();
30 private static final int MSG_ID_UPDATE_NOTIFICATION = 100;
31 private static NotificationMediaPlaybackControls sInstance;
32
33 /**
34 * Service used to transform intent requests triggered from the notification into
35 * {@code Listener} callbacks. Ideally this class should be private, but pub lic is required to
36 * create as a service.
37 */
38 public static class ListenerService extends Service {
39 private static final String ACTION_PLAY =
40 "NotificationMediaPlaybackControls.ListenerService.PLAY";
41 private static final String ACTION_PAUSE =
42 "NotificationMediaPlaybackControls.ListenerService.PAUSE";
43
44 private PendingIntent getPendingIntent(String action) {
45 Intent intent = new Intent(this, ListenerService.class);
46 intent.setAction(action);
47 return PendingIntent.getService(this, 0, intent, PendingIntent.FLAG_ CANCEL_CURRENT);
48 }
49
50 @Override
51 public IBinder onBind(Intent intent) {
52 return null;
53 }
54
55 @Override
56 public void onCreate() {
57 super.onCreate();
58 onServiceStarted(this);
59 }
60
61 @Override
62 public void onDestroy() {
63 onServiceDestroyed();
64 }
65
66 @Override
67 public int onStartCommand(Intent intent, int flags, int startId) {
68 if (intent == null
69 || sInstance == null
70 || sInstance.mMediaInfo == null
71 || sInstance.mMediaInfo.listener == null) {
72 stopSelf();
73 return START_NOT_STICKY;
74 }
75
76 String action = intent.getAction();
77 if (ACTION_PLAY.equals(action)) {
78 sInstance.mMediaInfo.listener.onPlay();
79 sInstance.onPlaybackStateChanged(false);
80 } else if (ACTION_PAUSE.equals(action)) {
81 sInstance.mMediaInfo.listener.onPause();
82 sInstance.onPlaybackStateChanged(true);
83 }
84
85 return START_NOT_STICKY;
86 }
87 }
88
89 /**
90 * Shows the notification with media controls with the specified media info. Replaces/updates
91 * the current notification if already showing. Does nothing if |mediaInfo| hasn't changed from
92 * the last one.
93 *
94 * @param applicationContext context to create the notification with
95 * @param mediaInfo information to show in the notification
96 */
97 public static void show(Context applicationContext, MediaInfo mediaInfo) {
98 synchronized (LOCK) {
99 if (sInstance == null) {
100 sInstance = new NotificationMediaPlaybackControls(applicationCon text);
101 }
102 }
103 sInstance.showNotification(mediaInfo);
104 }
105
106 /**
107 * Hides the notification for the specified tabId. If tabId equals to
108 * {@link Tab#INVALID_TAB_ID}, hides any notification shown.
109 *
110 * @param tabId the id of the tab that showed the notification or invalid ta b id.
111 */
112 public static void hide(int tabId) {
113 if (sInstance == null) return;
114 sInstance.hideNotification(tabId);
115 }
116
117 /**
118 * Registers the started {@link Service} with the singleton and creates the notification.
119 *
120 * @param service the service that was started
121 */
122 private static void onServiceStarted(ListenerService service) {
123 assert sInstance != null;
124 assert sInstance.mService == null;
125 sInstance.mService = service;
126 sInstance.createNotification();
127 }
128
129 /**
130 * Handles the destruction
131 */
132 private static void onServiceDestroyed() {
133 assert sInstance != null;
134 assert sInstance.mService != null;
135 sInstance.destroyNotification();
136 sInstance.mService = null;
137 }
138
139 private final Context mContext;
140
141 // ListenerService running for the notification. Only non-null when showing.
142 private ListenerService mService;
143
144 private final String mPlayDescription;
145
146 private final String mPauseDescription;
147
148 private Notification mNotification;
149
150 private Handler mHandler;
151 private MediaInfo mMediaInfo;
152
153 private NotificationMediaPlaybackControls(Context context) {
154 mContext = context;
155 mHandler = new Handler(context.getMainLooper()) {
156 @Override
157 public void handleMessage(android.os.Message msg) {
158 if (msg.what == MSG_ID_UPDATE_NOTIFICATION) {
159 // Only one update is needed.
160 mHandler.removeMessages(MSG_ID_UPDATE_NOTIFICATION);
161 updateNotificationInternal();
162 }
163 }
164 };
165
166 mPlayDescription = context.getResources().getString(R.string.accessibili ty_play);
167 mPauseDescription = context.getResources().getString(R.string.accessibil ity_pause);
168 }
169
170 private void showNotification(MediaInfo mediaInfo) {
171 mContext.startService(new Intent(mContext, ListenerService.class));
172
173 assert mediaInfo != null;
174
175 if (mediaInfo.equals(mMediaInfo)) return;
176
177 mMediaInfo = mediaInfo;
178 updateNotification();
179 }
180
181 private void hideNotification(int tabId) {
182 if (mMediaInfo == null || (tabId != Tab.INVALID_TAB_ID && tabId != mMedi aInfo.tabId)) {
183 return;
184 }
185
186 mMediaInfo = null;
187 mContext.stopService(new Intent(mContext, ListenerService.class));
188 }
189
190 private void onPlaybackStateChanged(boolean isPaused) {
191 assert mMediaInfo != null;
192 mMediaInfo = new MediaInfo(
193 mMediaInfo.title,
194 isPaused,
195 mMediaInfo.origin,
196 mMediaInfo.tabId,
197 mMediaInfo.listener);
198 updateNotification();
199 }
200
201 private void updateNotification() {
202 if (mService == null) return;
203
204 // Defer the call to updateNotificationInternal() so it can be cancelled
205 // in
206 // destroyNotification(). This is done to avoid the OS bug b/8798662.
qinmin 2015/07/04 19:30:46 nit: this line can fit into the above line
whywhat 2015/07/06 17:38:24 Removed this logic since it doesn't really needed
207 mHandler.sendEmptyMessage(MSG_ID_UPDATE_NOTIFICATION);
208 }
209
210 private RemoteViews createContentView() {
211 RemoteViews contentView =
212 new RemoteViews(getContext().getPackageName(), R.layout.playback _notification_bar);
213 return contentView;
214 }
215
216 private void createNotification() {
217 NotificationCompat.Builder notificationBuilder =
218 new NotificationCompat.Builder(getContext())
219 .setSmallIcon(R.drawable.audio_playing)
220 .setAutoCancel(false)
221 .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
222 .setOngoing(true)
223 .setContent(createContentView())
224 .setContentIntent(createContentIntent());
225 mNotification = notificationBuilder.build();
226 updateNotification();
227 }
228
229 private void destroyNotification() {
230 // Cancel any pending updates - we're about to tear down the
231 // notification.
232 mHandler.removeMessages(MSG_ID_UPDATE_NOTIFICATION);
233
234 NotificationManagerCompat manager = NotificationManagerCompat.from(getCo ntext());
235 manager.cancel(R.id.media_playback_notification);
236
237 mNotification = null;
238 }
239
240 private final Context getContext() {
241 return mContext;
242 }
243
244 private String getStatus() {
245 Context context = getContext();
246 return context.getString(R.string.media_notification_link_text,
247 mMediaInfo.origin != null ? mMediaInfo.origin : "");
248 }
249
250 private String getTitle() {
251 Context context = getContext();
252 String mediaTitle = mMediaInfo.title;
253 if (mMediaInfo.isPaused) {
254 return context.getString(
255 R.string.media_playback_notification_paused_for_media, media Title);
256 }
257 return context.getString(
258 R.string.media_playback_notification_playing_for_media, mediaTit le);
259 }
260
261 private PendingIntent createContentIntent() {
262 int tabId = mMediaInfo.tabId;
263 Intent intent = new Intent(Intent.ACTION_MAIN);
264 intent.putExtra(Browser.EXTRA_APPLICATION_ID, getContext().getPackageNam e());
265 intent.putExtra(TabOpenType.BRING_TAB_TO_FRONT.name(), tabId);
266 intent.setPackage(mContext.getPackageName());
267 return PendingIntent.getActivity(getContext(), tabId, intent, 0);
268 }
269
270 private void updateNotificationInternal() {
271 if (mMediaInfo == null) {
272 // Notification was hidden before we could update it.
273 assert mNotification == null;
274 return;
275 }
276
277 RemoteViews contentView = createContentView();
278
279 contentView.setTextViewText(R.id.title, getTitle());
280 contentView.setTextViewText(R.id.status, getStatus());
281 contentView.setImageViewResource(R.id.icon, R.drawable.audio_playing);
282
283 if (mMediaInfo.isPaused) {
284 contentView.setImageViewResource(R.id.playpause, R.drawable.ic_vidco ntrol_play);
285 contentView.setContentDescription(R.id.playpause, mPlayDescription);
286 contentView.setOnClickPendingIntent(R.id.playpause,
287 mService.getPendingIntent(ListenerService.ACTION_PLAY));
288 } else {
289 contentView.setImageViewResource(R.id.playpause, R.drawable.ic_vidco ntrol_pause);
290 contentView.setContentDescription(R.id.playpause, mPauseDescription) ;
291 contentView.setOnClickPendingIntent(R.id.playpause,
292 mService.getPendingIntent(ListenerService.ACTION_PAUSE));
293 }
294
295 mNotification.contentView = contentView;
296
297 NotificationManagerCompat manager = NotificationManagerCompat.from(getCo ntext());
298 manager.notify(R.id.media_playback_notification, mNotification);
299
300 mService.startForeground(R.id.media_playback_notification, mNotification );
301 }
302 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698