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

Unified 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 Min's nits 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 side-by-side diff with in-line comments
Download patch
Index: chrome/android/java/src/org/chromium/chrome/browser/media/ui/NotificationMediaPlaybackControls.java
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/media/ui/NotificationMediaPlaybackControls.java b/chrome/android/java/src/org/chromium/chrome/browser/media/ui/NotificationMediaPlaybackControls.java
new file mode 100644
index 0000000000000000000000000000000000000000..a47822296dc6f694efea674994d57ebaf0fe3827
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/media/ui/NotificationMediaPlaybackControls.java
@@ -0,0 +1,278 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.media.ui;
+
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.os.IBinder;
+import android.provider.Browser;
+import android.support.v4.app.NotificationCompat;
+import android.support.v4.app.NotificationManagerCompat;
+import android.widget.RemoteViews;
+
+import org.chromium.chrome.R;
+import org.chromium.chrome.browser.IntentHandler.TabOpenType;
+import org.chromium.chrome.browser.Tab;
+
+/**
+ * A class for notifications that provide information and optional media controls for a given media.
+ * Internally implements a Service for transforming notification Intents into
+ * {@link MediaPlaybackListener} calls for all registered listeners.
+ */
+public class NotificationMediaPlaybackControls {
+ private static final Object LOCK = new Object();
+ private static final int MSG_ID_UPDATE_NOTIFICATION = 100;
Ted C 2015/07/06 22:18:28 not used
whywhat 2015/07/07 15:44:58 Done.
+ private static NotificationMediaPlaybackControls sInstance;
+
+ /**
+ * Service used to transform intent requests triggered from the notification into
+ * {@code Listener} callbacks. Ideally this class should be private, but public is required to
+ * create as a service.
+ */
+ public static class ListenerService extends Service {
+ private static final String ACTION_PLAY =
+ "NotificationMediaPlaybackControls.ListenerService.PLAY";
+ private static final String ACTION_PAUSE =
+ "NotificationMediaPlaybackControls.ListenerService.PAUSE";
+
+ private PendingIntent getPendingIntent(String action) {
+ Intent intent = new Intent(this, ListenerService.class);
+ intent.setAction(action);
+ return PendingIntent.getService(this, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return null;
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ onServiceStarted(this);
+ }
+
+ @Override
+ public void onDestroy() {
Ted C 2015/07/06 22:18:29 should you call super.onDestroy() here as well?
whywhat 2015/07/07 15:44:58 Done.
+ onServiceDestroyed();
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ if (intent == null
+ || sInstance == null
+ || sInstance.mMediaInfo == null
+ || sInstance.mMediaInfo.listener == null) {
+ stopSelf();
+ return START_NOT_STICKY;
+ }
+
+ String action = intent.getAction();
+ if (ACTION_PLAY.equals(action)) {
+ sInstance.mMediaInfo.listener.onPlay();
+ sInstance.onPlaybackStateChanged(false);
+ } else if (ACTION_PAUSE.equals(action)) {
+ sInstance.mMediaInfo.listener.onPause();
+ sInstance.onPlaybackStateChanged(true);
+ }
+
+ return START_NOT_STICKY;
+ }
+ }
+
+ /**
+ * Shows the notification with media controls with the specified media info. Replaces/updates
+ * the current notification if already showing. Does nothing if |mediaInfo| hasn't changed from
+ * the last one.
+ *
+ * @param applicationContext context to create the notification with
+ * @param mediaInfo information to show in the notification
+ */
+ public static void show(Context applicationContext, MediaInfo mediaInfo) {
+ synchronized (LOCK) {
+ if (sInstance == null) {
+ sInstance = new NotificationMediaPlaybackControls(applicationContext);
+ }
+ }
+ sInstance.showNotification(mediaInfo);
+ }
+
+ /**
+ * Hides the notification for the specified tabId. If tabId equals to
+ * {@link Tab#INVALID_TAB_ID}, hides any notification shown.
+ *
+ * @param tabId the id of the tab that showed the notification or invalid tab id.
+ */
+ public static void hide(int tabId) {
+ if (sInstance == null) return;
+ sInstance.hideNotification(tabId);
+ }
+
+ /**
+ * Registers the started {@link Service} with the singleton and creates the notification.
+ *
+ * @param service the service that was started
+ */
+ private static void onServiceStarted(ListenerService service) {
+ assert sInstance != null;
+ assert sInstance.mService == null;
+ sInstance.mService = service;
+ sInstance.createNotification();
+ }
+
+ /**
+ * Handles the destruction
+ */
+ private static void onServiceDestroyed() {
+ assert sInstance != null;
+ assert sInstance.mService != null;
+ sInstance.destroyNotification();
+ sInstance.mService = null;
+ }
+
+ private final Context mContext;
+
+ // ListenerService running for the notification. Only non-null when showing.
+ private ListenerService mService;
+
+ private final String mPlayDescription;
+
+ private final String mPauseDescription;
+
+ private Notification mNotification;
+
+ private MediaInfo mMediaInfo;
+
+ private NotificationMediaPlaybackControls(Context context) {
+ mContext = context;
+ mPlayDescription = context.getResources().getString(R.string.accessibility_play);
+ mPauseDescription = context.getResources().getString(R.string.accessibility_pause);
+ }
+
+ private void showNotification(MediaInfo mediaInfo) {
+ mContext.startService(new Intent(mContext, ListenerService.class));
+
+ assert mediaInfo != null;
+
+ if (mediaInfo.equals(mMediaInfo)) return;
+
+ mMediaInfo = mediaInfo;
+ updateNotification();
+ }
+
+ private void hideNotification(int tabId) {
+ if (mMediaInfo == null || (tabId != Tab.INVALID_TAB_ID && tabId != mMediaInfo.tabId)) {
Ted C 2015/07/06 22:18:28 If you have a problem with a dangling notification
whywhat 2015/07/07 15:44:58 Done.
+ return;
+ }
+
+ mMediaInfo = null;
+ mContext.stopService(new Intent(mContext, ListenerService.class));
+ }
+
+ private void onPlaybackStateChanged(boolean isPaused) {
+ assert mMediaInfo != null;
+ mMediaInfo = new MediaInfo(
+ mMediaInfo.title,
+ isPaused,
+ mMediaInfo.origin,
+ mMediaInfo.tabId,
+ mMediaInfo.listener);
+ updateNotification();
+ }
+
+ private RemoteViews createContentView() {
+ RemoteViews contentView =
+ new RemoteViews(getContext().getPackageName(), R.layout.playback_notification_bar);
+ return contentView;
+ }
+
+ private void createNotification() {
+ NotificationCompat.Builder notificationBuilder =
Ted C 2015/07/06 22:18:28 I would just inline this in updateNotification. Y
whywhat 2015/07/07 15:44:58 Done.
+ new NotificationCompat.Builder(getContext())
+ .setSmallIcon(R.drawable.audio_playing)
+ .setAutoCancel(false)
+ .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
+ .setOngoing(true)
+ .setContent(createContentView())
+ .setContentIntent(createContentIntent());
+ mNotification = notificationBuilder.build();
+ updateNotification();
+ }
+
+ private void destroyNotification() {
+ NotificationManagerCompat manager = NotificationManagerCompat.from(getContext());
+ manager.cancel(R.id.media_playback_notification);
+
+ mNotification = null;
+ }
+
+ private final Context getContext() {
Ted C 2015/07/06 22:18:28 remove this and just inline mContext everywhere.
whywhat 2015/07/07 15:44:57 Done.
+ return mContext;
+ }
+
+ private String getStatus() {
+ Context context = getContext();
+ return context.getString(R.string.media_notification_link_text,
+ mMediaInfo.origin != null ? mMediaInfo.origin : "");
Ted C 2015/07/06 22:18:28 You should probably have some Unknown URL or somet
whywhat 2015/07/07 15:44:57 Done.
+ }
+
+ private String getTitle() {
+ Context context = getContext();
+ String mediaTitle = mMediaInfo.title;
+ if (mMediaInfo.isPaused) {
+ return context.getString(
+ R.string.media_playback_notification_paused_for_media, mediaTitle);
+ }
+ return context.getString(
+ R.string.media_playback_notification_playing_for_media, mediaTitle);
+ }
+
+ private PendingIntent createContentIntent() {
+ int tabId = mMediaInfo.tabId;
+ Intent intent = new Intent(Intent.ACTION_MAIN);
+ intent.putExtra(Browser.EXTRA_APPLICATION_ID, getContext().getPackageName());
+ intent.putExtra(TabOpenType.BRING_TAB_TO_FRONT.name(), tabId);
+ intent.setPackage(mContext.getPackageName());
+ return PendingIntent.getActivity(getContext(), tabId, intent, 0);
+ }
+
+ private void updateNotification() {
+ if (mService == null) return;
+
+ if (mMediaInfo == null) {
+ // Notification was hidden before we could update it.
+ assert mNotification == null;
+ return;
+ }
+
+ RemoteViews contentView = createContentView();
+
+ contentView.setTextViewText(R.id.title, getTitle());
+ contentView.setTextViewText(R.id.status, getStatus());
+ contentView.setImageViewResource(R.id.icon, R.drawable.audio_playing);
+
+ if (mMediaInfo.isPaused) {
+ contentView.setImageViewResource(R.id.playpause, R.drawable.ic_vidcontrol_play);
+ contentView.setContentDescription(R.id.playpause, mPlayDescription);
+ contentView.setOnClickPendingIntent(R.id.playpause,
+ mService.getPendingIntent(ListenerService.ACTION_PLAY));
+ } else {
+ contentView.setImageViewResource(R.id.playpause, R.drawable.ic_vidcontrol_pause);
+ contentView.setContentDescription(R.id.playpause, mPauseDescription);
+ contentView.setOnClickPendingIntent(R.id.playpause,
+ mService.getPendingIntent(ListenerService.ACTION_PAUSE));
+ }
+
+ mNotification.contentView = contentView;
+
+ NotificationManagerCompat manager = NotificationManagerCompat.from(getContext());
+ manager.notify(R.id.media_playback_notification, mNotification);
Ted C 2015/07/06 22:18:28 I would use a notification namespace here (and it'
whywhat 2015/07/07 15:44:58 So we actually don't need NotificationManager at a
+
+ mService.startForeground(R.id.media_playback_notification, mNotification);
+ }
+}

Powered by Google App Engine
This is Rietveld 408576698