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

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 browser tests 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.NotificationManager;
9 import android.app.PendingIntent;
10 import android.app.Service;
11 import android.content.Context;
12 import android.content.Intent;
13 import android.os.Handler;
14 import android.os.IBinder;
15 import android.provider.Browser;
16 import android.support.v4.app.NotificationCompat;
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 *
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(int oldState, int newState) {
178 MediaInfo mediaInfo = new MediaInfo(getMediaInfo());
179 mediaInfo.state = newState;
180 setMediaInfo(mediaInfo);
181
182 if (newState == oldState) return;
183
184 show(mediaInfo);
185 }
186
187 @Override
188 public void onTitleChanged(String title) {
189 MediaInfo mediaInfo = new MediaInfo(getMediaInfo());
190 mediaInfo.title = title;
191 setMediaInfo(mediaInfo);
192 }
193
194 @Override
195 public void onBroadDomainChanged(String broadDomain) {
196 MediaInfo mediaInfo = new MediaInfo(getMediaInfo());
197 mediaInfo.broadDomain = broadDomain;
198 setMediaInfo(mediaInfo);
199 }
200
201 @Override
202 public void onTabIdChanged(int tabId) {
203 MediaInfo mediaInfo = new MediaInfo(getMediaInfo());
204 mediaInfo.tabId = tabId;
205 setMediaInfo(mediaInfo);
206 }
207
208 @Override
209 public void show(MediaInfo mediaInfo) {
210 setMediaInfo(mediaInfo);
211 mContext.startService(new Intent(mContext, ListenerService.class));
212 }
213
214 private void updateNotification() {
215 // Defer the call to updateNotificationInternal() so it can be cancelled in
216 // destroyNotification(). This is done to avoid the OS bug b/8798662.
217 mHandler.sendEmptyMessage(MSG_UPDATE_NOTIFICATION);
218 }
219
220 Notification getNotification() {
221 return mNotification;
222 }
223
224 final ListenerService getService() {
225 return mService;
226 }
227
228 @Override
229 protected void onMediaInfoChanged() {
230 if (isShowing()) updateNotification();
231 }
232
233 private RemoteViews createContentView() {
234 RemoteViews contentView =
235 new RemoteViews(getContext().getPackageName(), R.layout.playback _notification_bar);
236 return contentView;
237 }
238
239 private void createNotification() {
240 NotificationCompat.Builder notificationBuilder =
241 new NotificationCompat.Builder(getContext())
242 .setSmallIcon(R.drawable.audio_playing)
243 .setAutoCancel(false)
244 .setLocalOnly(true)
245 .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
246 .setOngoing(true)
247 .setContent(createContentView())
248 .setContentIntent(createContentIntent());
249 mNotification = notificationBuilder.build();
250 updateNotification();
251 }
252
253 private void destroyNotification() {
254 // Cancel any pending updates - we're about to tear down the notificatio n.
255 mHandler.removeMessages(MSG_UPDATE_NOTIFICATION);
256
257 NotificationManager manager =
258 (NotificationManager) getContext().getSystemService(Context.NOTI FICATION_SERVICE);
259 manager.cancel(R.id.media_playback_notification);
260 mNotification = null;
261 }
262
263 private final Context getContext() {
264 return mContext;
265 }
266
267 private String getStatus() {
268 Context context = getContext();
269 MediaInfo mediaInfo = getMediaInfo();
270 return context.getString(R.string.media_notification_link_text,
271 mediaInfo.broadDomain != null ? mediaInfo.broadDomain : "");
272 }
273
274 private String getTitle() {
275 Context context = getContext();
276 MediaInfo mediaInfo = getMediaInfo();
277 String mediaTitle = mediaInfo.title;
278 switch (mediaInfo.state) {
279 case MediaInfo.PLAYING:
280 return context.getString(
281 R.string.media_playback_notification_playing_for_media, mediaTitle);
282 case MediaInfo.PAUSED:
283 return context.getString(
284 R.string.media_playback_notification_paused_for_media, m ediaTitle);
285 default:
286 return mediaTitle;
287 }
288 }
289
290 private PendingIntent createContentIntent() {
291 int tabId = getMediaInfo().tabId;
292 Intent intent = new Intent(Intent.ACTION_MAIN);
293 intent.putExtra(Browser.EXTRA_APPLICATION_ID, getContext().getPackageNam e());
294 intent.putExtra(TabOpenType.BRING_TAB_TO_FRONT.name(), tabId);
295 intent.setPackage(mContext.getPackageName());
296 return PendingIntent.getActivity(getContext(), tabId, intent, 0);
297 }
298
299 private void updateNotificationInternal() {
300 RemoteViews contentView = createContentView();
301
302 contentView.setTextViewText(R.id.title, getTitle());
303 contentView.setTextViewText(R.id.status, getStatus());
304 contentView.setImageViewResource(R.id.icon, R.drawable.audio_playing);
305
306 MediaInfo mediaInfo = getMediaInfo();
307 if (mediaInfo == null) return;
308
309 switch (mediaInfo.state) {
310 case MediaInfo.PLAYING:
311 contentView.setImageViewResource(R.id.playpause, R.drawable.ic_v idcontrol_pause);
312 contentView.setContentDescription(R.id.playpause, mPauseDescript ion);
313 contentView.setOnClickPendingIntent(R.id.playpause,
314 getService().getPendingIntent(ListenerService.ACTION_ID_ PAUSE));
315 break;
316 case MediaInfo.PAUSED:
317 contentView.setImageViewResource(R.id.playpause, R.drawable.ic_v idcontrol_play);
318 contentView.setContentDescription(R.id.playpause, mPlayDescripti on);
319 contentView.setOnClickPendingIntent(R.id.playpause,
320 getService().getPendingIntent(ListenerService.ACTION_ID_ PLAY));
321 break;
322 default:
323 break;
324 }
325
326 mNotification.contentView = contentView;
327
328 NotificationManager manager = (NotificationManager) getContext().getSyst emService(
329 Context.NOTIFICATION_SERVICE);
330 manager.notify(R.id.media_playback_notification, mNotification);
331
332 getService().startForeground(R.id.media_playback_notification, mNotifica tion);
333 }
334 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698