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

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: Fixed a few missed nits Created 5 years, 6 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
22 import java.util.Set;
23
24 /**
25 * A class for notifications that provide information and optional media control s for a given media.
26 * Internally implements a Service for transforming notification Intents into
27 * {@link MediaPlaybackControls.Listener} calls for all registered listeners.
28 *
mlamouri (slow - plz ping) 2015/06/23 14:58:57 nit: remove that line?
whywhat 2015/06/23 19:39:11 Done.
29 */
30 public class NotificationMediaPlaybackControls extends MediaPlaybackControls {
31 /**
32 * Service used to transform intent requests triggered from the notification into
33 * {@code Listener} callbacks. Ideally this class should be protected, but p ublic is required to
34 * create as a service.
35 */
36 public static class ListenerService extends Service {
37 private static final String ACTION_PREFIX =
38 "NotificationMediaPlaybackControls.ListenerService.";
39
40 // Constants used by intent actions
41 public static final int ACTION_ID_PLAY = 0;
42 public static final int ACTION_ID_PAUSE = 1;
43
44 // Must be kept in sync with the ACTION_ID_XXX constants above
45 private static final String[] ACTION_VERBS = { "PLAY", "PAUSE" };
46
47 private PendingIntent[] mPendingIntents;
48
49 PendingIntent getPendingIntent(int id) {
50 return mPendingIntents[id];
51 }
52
53 @Override
54 public IBinder onBind(Intent intent) {
55 return null;
56 }
57
58 @Override
59 public void onCreate() {
60 super.onCreate();
61
62 // Create all the PendingIntents
63 int actionCount = ACTION_VERBS.length;
64 mPendingIntents = new PendingIntent[actionCount];
65 for (int i = 0; i < actionCount; ++i) {
66 Intent intent = new Intent(this, ListenerService.class);
67 intent.setAction(ACTION_PREFIX + ACTION_VERBS[i]);
68 mPendingIntents[i] = PendingIntent.getService(this, 0, intent,
69 PendingIntent.FLAG_CANCEL_CURRENT);
70 }
71 onServiceStarted(this);
72 }
73
74 @Override
75 public void onDestroy() {
76 onServiceDestroyed();
77 }
78
79 @Override
80 public int onStartCommand(Intent intent, int flags, int startId) {
81 if (intent == null) {
82 stopSelf();
83 return START_NOT_STICKY;
84 }
85
86 String action = intent.getAction();
87 if (action != null && action.startsWith(ACTION_PREFIX)) {
88 Set<Listener> listeners = sInstance.getListeners();
89
90 // Strip the prefix for matching the verb
91 action = action.substring(ACTION_PREFIX.length());
92 if (ACTION_VERBS[ACTION_ID_PLAY].equals(action)) {
93 for (Listener listener : listeners) listener.onPlay();
94 } else if (ACTION_VERBS[ACTION_ID_PAUSE].equals(action)) {
95 for (Listener listener : listeners) listener.onPause();
96 }
97 }
98
99 return START_STICKY;
100 }
101 }
102
103 private static NotificationMediaPlaybackControls sInstance = null;
104 private static final Object LOCK = new Object();
105 private static final int MSG_UPDATE_NOTIFICATION = 100;
106
107 /**
108 * Returns the singleton NotificationMediaPlaybackControls object.
109 *
110 * @param context The Context that the notification service needs to be crea ted in.
111 * @return A {@code NotificationMediaPlaybackControls} object.
112 */
113 public static NotificationMediaPlaybackControls getOrCreate(Context context) {
114 synchronized (LOCK) {
115 if (sInstance == null) {
116 sInstance = new NotificationMediaPlaybackControls(context);
117 }
118
119 return sInstance;
120 }
121 }
122
123 private static void onServiceDestroyed() {
124 sInstance.destroyNotification();
125 sInstance.mService = null;
126 }
127
128 private static void onServiceStarted(ListenerService service) {
129 sInstance.mService = service;
130 sInstance.createNotification();
131 }
132
133 private final Context mContext;
134
135 // ListenerService running for the notification. Only non-null when showing.
136 private ListenerService mService;
137
138 private final String mPlayDescription;
139
140 private final String mPauseDescription;
141
142 private Notification mNotification;
143
144 private Handler mHandler;
145
146 private NotificationMediaPlaybackControls(Context context) {
147 this.mContext = context;
148 mHandler = new Handler(context.getMainLooper()) {
149 @Override
150 public void handleMessage(android.os.Message msg) {
151 if (msg.what == MSG_UPDATE_NOTIFICATION) {
152 mHandler.removeMessages(MSG_UPDATE_NOTIFICATION); // Only on e update is needed.
153 updateNotificationInternal();
154 }
155 }
156 };
157
158 mPlayDescription = context.getResources().getString(R.string.accessibili ty_play);
159 mPauseDescription = context.getResources().getString(R.string.accessibil ity_pause);
160 }
161
162 @Override
163 public void hide(int tabId) {
164 if (getMediaInfo() == null || tabId != getMediaInfo().tabId) return;
165
166 mContext.stopService(new Intent(mContext, ListenerService.class));
167 }
168
169 /**
170 * @return true if the notification is currently visible to the user.
171 */
172 public boolean isShowing() {
173 return mService != null;
174 }
175
176 @Override
177 public void onPlaybackStateChanged(boolean isPaused) {
178 MediaInfo mediaInfo = new MediaInfo(getMediaInfo());
179 mediaInfo.isPaused = isPaused;
180 setMediaInfo(mediaInfo);
181 }
182
183 @Override
184 public void onTitleChanged(String title) {
185 MediaInfo mediaInfo = new MediaInfo(getMediaInfo());
186 mediaInfo.title = title;
187 setMediaInfo(mediaInfo);
188 }
189
190 @Override
191 public void onOriginChanged(String origin) {
192 MediaInfo mediaInfo = new MediaInfo(getMediaInfo());
193 mediaInfo.origin = origin;
194 setMediaInfo(mediaInfo);
195 }
196
197 @Override
198 public void onTabIdChanged(int tabId) {
199 MediaInfo mediaInfo = new MediaInfo(getMediaInfo());
200 mediaInfo.tabId = tabId;
201 setMediaInfo(mediaInfo);
202 }
203
204 @Override
205 public void show(MediaInfo mediaInfo) {
206 setMediaInfo(mediaInfo);
207 mContext.startService(new Intent(mContext, ListenerService.class));
208 }
209
210 private void updateNotification() {
211 // Defer the call to updateNotificationInternal() so it can be cancelled in
212 // destroyNotification(). This is done to avoid the OS bug b/8798662.
213 mHandler.sendEmptyMessage(MSG_UPDATE_NOTIFICATION);
214 }
215
216 Notification getNotification() {
217 return mNotification;
218 }
219
220 final ListenerService getService() {
221 return mService;
222 }
223
224 @Override
225 protected void onMediaInfoChanged() {
226 if (isShowing()) updateNotification();
227 }
228
229 private RemoteViews createContentView() {
230 RemoteViews contentView =
231 new RemoteViews(getContext().getPackageName(), R.layout.playback _notification_bar);
232 return contentView;
233 }
234
235 private void createNotification() {
236 NotificationCompat.Builder notificationBuilder =
237 new NotificationCompat.Builder(getContext())
238 .setSmallIcon(R.drawable.audio_playing)
239 .setAutoCancel(false)
240 .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
241 .setOngoing(true)
242 .setContent(createContentView())
mlamouri (slow - plz ping) 2015/06/23 14:58:57 Why not using setContentTitle() and setContentText
whywhat 2015/06/23 19:39:11 It's how we achieve the desired consistent layout
243 .setContentIntent(createContentIntent());
244 mNotification = notificationBuilder.build();
245 updateNotification();
246 }
247
248 private void destroyNotification() {
249 // Cancel any pending updates - we're about to tear down the notificatio n.
250 mHandler.removeMessages(MSG_UPDATE_NOTIFICATION);
251
252 NotificationManagerCompat manager = NotificationManagerCompat.from(getCo ntext());
253
254 manager.cancel(R.id.media_playback_notification);
255 mNotification = null;
256 }
257
258 private final Context getContext() {
259 return mContext;
260 }
261
262 private String getStatus() {
263 Context context = getContext();
264 MediaInfo mediaInfo = getMediaInfo();
265 return context.getString(R.string.media_notification_link_text,
266 mediaInfo.origin != null ? mediaInfo.origin : "");
267 }
268
269 private String getTitle() {
270 Context context = getContext();
271 MediaInfo mediaInfo = getMediaInfo();
272 String mediaTitle = mediaInfo.title;
273 if (mediaInfo.isPaused) {
274 return context.getString(
275 R.string.media_playback_notification_paused_for_media, media Title);
276 } else {
277 return context.getString(
278 R.string.media_playback_notification_playing_for_media, medi aTitle);
279 }
280 }
281
282 private PendingIntent createContentIntent() {
283 int tabId = getMediaInfo().tabId;
284 Intent intent = new Intent(Intent.ACTION_MAIN);
285 intent.putExtra(Browser.EXTRA_APPLICATION_ID, getContext().getPackageNam e());
286 intent.putExtra(TabOpenType.BRING_TAB_TO_FRONT.name(), tabId);
287 intent.setPackage(mContext.getPackageName());
288 return PendingIntent.getActivity(getContext(), tabId, intent, 0);
289 }
290
291 private void updateNotificationInternal() {
292 RemoteViews contentView = createContentView();
293
294 contentView.setTextViewText(R.id.title, getTitle());
295 contentView.setTextViewText(R.id.status, getStatus());
296 contentView.setImageViewResource(R.id.icon, R.drawable.audio_playing);
297
298 MediaInfo mediaInfo = getMediaInfo();
299 if (mediaInfo == null) return;
300
301 if (mediaInfo.isPaused) {
302 contentView.setImageViewResource(R.id.playpause, R.drawable.ic_vidco ntrol_play);
303 contentView.setContentDescription(R.id.playpause, mPlayDescription);
304 contentView.setOnClickPendingIntent(R.id.playpause,
305 getService().getPendingIntent(ListenerService.ACTION_ID_PLAY ));
mlamouri (slow - plz ping) 2015/06/23 14:58:56 We might want to consider using addAction() in ord
whywhat 2015/06/23 19:39:11 Ack.
306 } else {
307 contentView.setImageViewResource(R.id.playpause, R.drawable.ic_vidco ntrol_pause);
308 contentView.setContentDescription(R.id.playpause, mPauseDescription) ;
309 contentView.setOnClickPendingIntent(R.id.playpause,
310 getService().getPendingIntent(ListenerService.ACTION_ID_PAUS E));
311 }
312
313 mNotification.contentView = contentView;
314
315 NotificationManagerCompat manager = NotificationManagerCompat.from(getCo ntext());
316 manager.notify(R.id.media_playback_notification, mNotification);
317
318 getService().startForeground(R.id.media_playback_notification, mNotifica tion);
319 }
320 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698