| 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..9783f487dda106c3a4db509f9dad84d72a0fac53
|
| --- /dev/null
|
| +++ b/chrome/android/java/src/org/chromium/chrome/browser/media/ui/NotificationMediaPlaybackControls.java
|
| @@ -0,0 +1,334 @@
|
| +// 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.NotificationManager;
|
| +import android.app.PendingIntent;
|
| +import android.app.Service;
|
| +import android.content.Context;
|
| +import android.content.Intent;
|
| +import android.os.Handler;
|
| +import android.os.IBinder;
|
| +import android.provider.Browser;
|
| +import android.support.v4.app.NotificationCompat;
|
| +import android.widget.RemoteViews;
|
| +
|
| +import org.chromium.chrome.R;
|
| +import org.chromium.chrome.browser.IntentHandler.TabOpenType;
|
| +
|
| +import java.util.Set;
|
| +
|
| +/**
|
| + * 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 MediaPlaybackControls.Listener} calls for all registered listeners.
|
| + *
|
| + */
|
| +public class NotificationMediaPlaybackControls extends MediaPlaybackControls {
|
| + /**
|
| + * Service used to transform intent requests triggered from the notification into
|
| + * {@code Listener} callbacks. Ideally this class should be protected, but public is required to
|
| + * create as a service.
|
| + */
|
| + public static class ListenerService extends Service {
|
| + private static final String ACTION_PREFIX =
|
| + "NotificationMediaPlaybackControls.ListenerService.";
|
| +
|
| + // Constants used by intent actions
|
| + public static final int ACTION_ID_PLAY = 0;
|
| + public static final int ACTION_ID_PAUSE = 1;
|
| +
|
| + // Must be kept in sync with the ACTION_ID_XXX constants above
|
| + private static final String[] ACTION_VERBS = { "PLAY", "PAUSE" };
|
| +
|
| + private PendingIntent[] mPendingIntents;
|
| +
|
| + PendingIntent getPendingIntent(int id) {
|
| + return mPendingIntents[id];
|
| + }
|
| +
|
| + @Override
|
| + public IBinder onBind(Intent intent) {
|
| + return null;
|
| + }
|
| +
|
| + @Override
|
| + public void onCreate() {
|
| + super.onCreate();
|
| +
|
| + // Create all the PendingIntents
|
| + int actionCount = ACTION_VERBS.length;
|
| + mPendingIntents = new PendingIntent[actionCount];
|
| + for (int i = 0; i < actionCount; ++i) {
|
| + Intent intent = new Intent(this, ListenerService.class);
|
| + intent.setAction(ACTION_PREFIX + ACTION_VERBS[i]);
|
| + mPendingIntents[i] = PendingIntent.getService(this, 0, intent,
|
| + PendingIntent.FLAG_CANCEL_CURRENT);
|
| + }
|
| + onServiceStarted(this);
|
| + }
|
| +
|
| + @Override
|
| + public void onDestroy() {
|
| + onServiceDestroyed();
|
| + }
|
| +
|
| + @Override
|
| + public int onStartCommand(Intent intent, int flags, int startId) {
|
| + if (intent == null) {
|
| + stopSelf();
|
| + return START_NOT_STICKY;
|
| + }
|
| +
|
| + String action = intent.getAction();
|
| + if (action != null && action.startsWith(ACTION_PREFIX)) {
|
| + Set<Listener> listeners = sInstance.getListeners();
|
| +
|
| + // Strip the prefix for matching the verb
|
| + action = action.substring(ACTION_PREFIX.length());
|
| + if (ACTION_VERBS[ACTION_ID_PLAY].equals(action)) {
|
| + for (Listener listener : listeners) listener.onPlay();
|
| + } else if (ACTION_VERBS[ACTION_ID_PAUSE].equals(action)) {
|
| + for (Listener listener : listeners) listener.onPause();
|
| + }
|
| + }
|
| +
|
| + return START_STICKY;
|
| + }
|
| + }
|
| +
|
| + private static NotificationMediaPlaybackControls sInstance = null;
|
| + private static final Object LOCK = new Object();
|
| + private static final int MSG_UPDATE_NOTIFICATION = 100;
|
| +
|
| + /**
|
| + * Returns the singleton NotificationMediaPlaybackControls object.
|
| + *
|
| + * @param context The Context that the notification service needs to be created in.
|
| + * @return A {@code NotificationMediaPlaybackControls} object.
|
| + */
|
| + public static NotificationMediaPlaybackControls getOrCreate(Context context) {
|
| + synchronized (LOCK) {
|
| + if (sInstance == null) {
|
| + sInstance = new NotificationMediaPlaybackControls(context);
|
| + }
|
| +
|
| + return sInstance;
|
| + }
|
| + }
|
| +
|
| + private static void onServiceDestroyed() {
|
| + sInstance.destroyNotification();
|
| + sInstance.mService = null;
|
| + }
|
| +
|
| + private static void onServiceStarted(ListenerService service) {
|
| + sInstance.mService = service;
|
| + sInstance.createNotification();
|
| + }
|
| +
|
| + 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 Handler mHandler;
|
| +
|
| + private NotificationMediaPlaybackControls(Context context) {
|
| + this.mContext = context;
|
| + mHandler = new Handler(context.getMainLooper()) {
|
| + @Override
|
| + public void handleMessage(android.os.Message msg) {
|
| + if (msg.what == MSG_UPDATE_NOTIFICATION) {
|
| + mHandler.removeMessages(MSG_UPDATE_NOTIFICATION); // Only one update is needed.
|
| + updateNotificationInternal();
|
| + }
|
| + }
|
| + };
|
| +
|
| + mPlayDescription = context.getResources().getString(R.string.accessibility_play);
|
| + mPauseDescription = context.getResources().getString(R.string.accessibility_pause);
|
| + }
|
| +
|
| + @Override
|
| + public void hide(int tabId) {
|
| + if (getMediaInfo() == null || tabId != getMediaInfo().tabId) return;
|
| +
|
| + mContext.stopService(new Intent(mContext, ListenerService.class));
|
| + }
|
| +
|
| + /**
|
| + * @return true if the notification is currently visible to the user.
|
| + */
|
| + public boolean isShowing() {
|
| + return mService != null;
|
| + }
|
| +
|
| + @Override
|
| + public void onPlaybackStateChanged(int oldState, int newState) {
|
| + MediaInfo mediaInfo = new MediaInfo(getMediaInfo());
|
| + mediaInfo.state = newState;
|
| + setMediaInfo(mediaInfo);
|
| +
|
| + if (newState == oldState) return;
|
| +
|
| + show(mediaInfo);
|
| + }
|
| +
|
| + @Override
|
| + public void onTitleChanged(String title) {
|
| + MediaInfo mediaInfo = new MediaInfo(getMediaInfo());
|
| + mediaInfo.title = title;
|
| + setMediaInfo(mediaInfo);
|
| + }
|
| +
|
| + @Override
|
| + public void onBroadDomainChanged(String broadDomain) {
|
| + MediaInfo mediaInfo = new MediaInfo(getMediaInfo());
|
| + mediaInfo.broadDomain = broadDomain;
|
| + setMediaInfo(mediaInfo);
|
| + }
|
| +
|
| + @Override
|
| + public void onTabIdChanged(int tabId) {
|
| + MediaInfo mediaInfo = new MediaInfo(getMediaInfo());
|
| + mediaInfo.tabId = tabId;
|
| + setMediaInfo(mediaInfo);
|
| + }
|
| +
|
| + @Override
|
| + public void show(MediaInfo mediaInfo) {
|
| + setMediaInfo(mediaInfo);
|
| + mContext.startService(new Intent(mContext, ListenerService.class));
|
| + }
|
| +
|
| + private void updateNotification() {
|
| + // Defer the call to updateNotificationInternal() so it can be cancelled in
|
| + // destroyNotification(). This is done to avoid the OS bug b/8798662.
|
| + mHandler.sendEmptyMessage(MSG_UPDATE_NOTIFICATION);
|
| + }
|
| +
|
| + Notification getNotification() {
|
| + return mNotification;
|
| + }
|
| +
|
| + final ListenerService getService() {
|
| + return mService;
|
| + }
|
| +
|
| + @Override
|
| + protected void onMediaInfoChanged() {
|
| + if (isShowing()) updateNotification();
|
| + }
|
| +
|
| + private RemoteViews createContentView() {
|
| + RemoteViews contentView =
|
| + new RemoteViews(getContext().getPackageName(), R.layout.playback_notification_bar);
|
| + return contentView;
|
| + }
|
| +
|
| + private void createNotification() {
|
| + NotificationCompat.Builder notificationBuilder =
|
| + new NotificationCompat.Builder(getContext())
|
| + .setSmallIcon(R.drawable.audio_playing)
|
| + .setAutoCancel(false)
|
| + .setLocalOnly(true)
|
| + .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
| + .setOngoing(true)
|
| + .setContent(createContentView())
|
| + .setContentIntent(createContentIntent());
|
| + mNotification = notificationBuilder.build();
|
| + updateNotification();
|
| + }
|
| +
|
| + private void destroyNotification() {
|
| + // Cancel any pending updates - we're about to tear down the notification.
|
| + mHandler.removeMessages(MSG_UPDATE_NOTIFICATION);
|
| +
|
| + NotificationManager manager =
|
| + (NotificationManager) getContext().getSystemService(Context.NOTIFICATION_SERVICE);
|
| + manager.cancel(R.id.media_playback_notification);
|
| + mNotification = null;
|
| + }
|
| +
|
| + private final Context getContext() {
|
| + return mContext;
|
| + }
|
| +
|
| + private String getStatus() {
|
| + Context context = getContext();
|
| + MediaInfo mediaInfo = getMediaInfo();
|
| + return context.getString(R.string.media_notification_link_text,
|
| + mediaInfo.broadDomain != null ? mediaInfo.broadDomain : "");
|
| + }
|
| +
|
| + private String getTitle() {
|
| + Context context = getContext();
|
| + MediaInfo mediaInfo = getMediaInfo();
|
| + String mediaTitle = mediaInfo.title;
|
| + switch (mediaInfo.state) {
|
| + case MediaInfo.PLAYING:
|
| + return context.getString(
|
| + R.string.media_playback_notification_playing_for_media, mediaTitle);
|
| + case MediaInfo.PAUSED:
|
| + return context.getString(
|
| + R.string.media_playback_notification_paused_for_media, mediaTitle);
|
| + default:
|
| + return mediaTitle;
|
| + }
|
| + }
|
| +
|
| + private PendingIntent createContentIntent() {
|
| + int tabId = getMediaInfo().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 updateNotificationInternal() {
|
| + RemoteViews contentView = createContentView();
|
| +
|
| + contentView.setTextViewText(R.id.title, getTitle());
|
| + contentView.setTextViewText(R.id.status, getStatus());
|
| + contentView.setImageViewResource(R.id.icon, R.drawable.audio_playing);
|
| +
|
| + MediaInfo mediaInfo = getMediaInfo();
|
| + if (mediaInfo == null) return;
|
| +
|
| + switch (mediaInfo.state) {
|
| + case MediaInfo.PLAYING:
|
| + contentView.setImageViewResource(R.id.playpause, R.drawable.ic_vidcontrol_pause);
|
| + contentView.setContentDescription(R.id.playpause, mPauseDescription);
|
| + contentView.setOnClickPendingIntent(R.id.playpause,
|
| + getService().getPendingIntent(ListenerService.ACTION_ID_PAUSE));
|
| + break;
|
| + case MediaInfo.PAUSED:
|
| + contentView.setImageViewResource(R.id.playpause, R.drawable.ic_vidcontrol_play);
|
| + contentView.setContentDescription(R.id.playpause, mPlayDescription);
|
| + contentView.setOnClickPendingIntent(R.id.playpause,
|
| + getService().getPendingIntent(ListenerService.ACTION_ID_PLAY));
|
| + break;
|
| + default:
|
| + break;
|
| + }
|
| +
|
| + mNotification.contentView = contentView;
|
| +
|
| + NotificationManager manager = (NotificationManager) getContext().getSystemService(
|
| + Context.NOTIFICATION_SERVICE);
|
| + manager.notify(R.id.media_playback_notification, mNotification);
|
| +
|
| + getService().startForeground(R.id.media_playback_notification, mNotification);
|
| + }
|
| +}
|
|
|