| Index: chrome/android/java/src/org/chromium/chrome/browser/media/remote/NotificationTransportControl.java
|
| diff --git a/chrome/android/java/src/org/chromium/chrome/browser/media/remote/NotificationTransportControl.java b/chrome/android/java/src/org/chromium/chrome/browser/media/remote/NotificationTransportControl.java
|
| deleted file mode 100644
|
| index 055ec4aa3ffa782841fc0748428c0c82dfa7bd4e..0000000000000000000000000000000000000000
|
| --- a/chrome/android/java/src/org/chromium/chrome/browser/media/remote/NotificationTransportControl.java
|
| +++ /dev/null
|
| @@ -1,519 +0,0 @@
|
| -// Copyright 2013 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.remote;
|
| -
|
| -import android.app.Notification;
|
| -import android.app.PendingIntent;
|
| -import android.app.Service;
|
| -import android.content.Context;
|
| -import android.content.Intent;
|
| -import android.content.res.Resources;
|
| -import android.graphics.Bitmap;
|
| -import android.os.Handler;
|
| -import android.os.IBinder;
|
| -import android.support.v4.app.NotificationCompat;
|
| -import android.util.DisplayMetrics;
|
| -import android.view.View;
|
| -import android.widget.RemoteViews;
|
| -
|
| -import org.chromium.base.VisibleForTesting;
|
| -import org.chromium.chrome.R;
|
| -import org.chromium.chrome.browser.media.remote.RemoteVideoInfo.PlayerState;
|
| -
|
| -import java.util.Set;
|
| -
|
| -import javax.annotation.Nullable;
|
| -
|
| -/**
|
| - * A class for notifications that provide information and optional transport controls for a given
|
| - * remote control. Internally implements a Service for transforming notification Intents into
|
| - * {@link TransportControl.Listener} calls for all registered listeners.
|
| - */
|
| -public class NotificationTransportControl
|
| - extends TransportControl implements MediaRouteController.UiListener {
|
| - /**
|
| - * 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 = ListenerService.class.getName() + ".";
|
| -
|
| - // Constants used by intent actions
|
| - public static final int ACTION_ID_PLAY = 0;
|
| - public static final int ACTION_ID_PAUSE = 1;
|
| - public static final int ACTION_ID_SEEK = 2;
|
| - public static final int ACTION_ID_STOP = 3;
|
| - public static final int ACTION_ID_SELECT = 4;
|
| -
|
| - // Intent parameters
|
| - public static final String SEEK_POSITION = "SEEK_POSITION";
|
| -
|
| - // Must be kept in sync with the ACTION_ID_XXX constants above
|
| - private static final String[] ACTION_VERBS = {"PLAY", "PAUSE", "SEEK", "STOP", "SELECT"};
|
| -
|
| - private PendingIntent[] mPendingIntents;
|
| -
|
| - private Notification mNotification;
|
| -
|
| - @VisibleForTesting
|
| - PendingIntent getPendingIntent(int id) {
|
| - return mPendingIntents[id];
|
| - }
|
| -
|
| - @Override
|
| - public IBinder onBind(Intent intent) {
|
| - return null;
|
| - }
|
| -
|
| - @Override
|
| - public void onCreate() {
|
| - // If Android recreates the service the state can be wrong, see
|
| - // https://crbug.com/555266
|
| - if (sInstance == null || sInstance.mService != null) return;
|
| - super.onCreate();
|
| -
|
| - sInstance.mService = this;
|
| -
|
| - // 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);
|
| - }
|
| - }
|
| -
|
| - @Override
|
| - public void onDestroy() {
|
| - if (sInstance == null || sInstance.mService == null) return;
|
| - stop();
|
| - sInstance.mService = null;
|
| - }
|
| -
|
| - @Override
|
| - public void onTaskRemoved(Intent rootIntent) {
|
| - stop();
|
| - }
|
| -
|
| - @Override
|
| - public int onStartCommand(Intent intent, int flags, int startId) {
|
| - if (sInstance == null) {
|
| - stopSelf();
|
| - return START_NOT_STICKY;
|
| - }
|
| -
|
| - if (intent != null) {
|
| - 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();
|
| - }
|
| - } else if (ACTION_VERBS[ACTION_ID_SEEK].equals(action)) {
|
| - int seekPosition = intent.getIntExtra(SEEK_POSITION, 0);
|
| - for (Listener listener : listeners) {
|
| - listener.onSeek(seekPosition);
|
| - }
|
| - } else if (ACTION_VERBS[ACTION_ID_STOP].equals(action)) {
|
| - stop();
|
| - } else if (ACTION_VERBS[ACTION_ID_SELECT].equals(action)) {
|
| - ExpandedControllerActivity.startActivity(this);
|
| - }
|
| - }
|
| - }
|
| -
|
| - updateNotification();
|
| - return START_NOT_STICKY;
|
| - }
|
| -
|
| - private void stop() {
|
| - for (Listener listener : sInstance.getListeners()) listener.onStop();
|
| - stopSelf();
|
| - }
|
| -
|
| - private void createNotification() {
|
| - NotificationCompat.Builder notificationBuilder =
|
| - new NotificationCompat.Builder(this)
|
| - .setDefaults(0)
|
| - .setSmallIcon(R.drawable.ic_notification_media_route)
|
| - .setLocalOnly(true)
|
| - .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
| - .setOnlyAlertOnce(true)
|
| - .setOngoing(true)
|
| - .setContent(createContentView())
|
| - .setContentIntent(getPendingIntent(ListenerService.ACTION_ID_SELECT))
|
| - .setDeleteIntent(getPendingIntent(ListenerService.ACTION_ID_STOP));
|
| - mNotification = notificationBuilder.build();
|
| - }
|
| -
|
| - private void updateNotification() {
|
| - RemoteVideoInfo videoInfo = sInstance.getVideoInfo();
|
| - if (!sInstance.isShowing() || videoInfo == null
|
| - || videoInfo.state == PlayerState.STOPPED
|
| - || videoInfo.state == PlayerState.FINISHED
|
| - || videoInfo.state == PlayerState.INVALIDATED) {
|
| - stopForeground(true);
|
| - mNotification = null;
|
| - } else {
|
| - if (mNotification == null) createNotification();
|
| - RemoteViews contentView = createContentView();
|
| - contentView.setTextViewText(R.id.title, sInstance.getScreenName());
|
| - contentView.setTextViewText(R.id.status, getStatus());
|
| - if (sInstance.mIcon != null) {
|
| - contentView.setImageViewBitmap(R.id.icon, sInstance.mIcon);
|
| - } else {
|
| - contentView.setImageViewResource(
|
| - R.id.icon, R.drawable.ic_notification_media_route);
|
| - }
|
| -
|
| - boolean showPlayPause = false;
|
| - boolean showProgress = false;
|
| - switch (videoInfo.state) {
|
| - case PLAYING:
|
| - showProgress = true;
|
| - showPlayPause = true;
|
| - setProgressBar(videoInfo, contentView);
|
| - contentView.setImageViewResource(
|
| - R.id.playpause, R.drawable.ic_vidcontrol_pause);
|
| - contentView.setContentDescription(
|
| - R.id.playpause, sInstance.mPauseDescription);
|
| - contentView.setOnClickPendingIntent(
|
| - R.id.playpause, getPendingIntent(ListenerService.ACTION_ID_PAUSE));
|
| - scheduleProgressUpdate();
|
| - break;
|
| - case PAUSED:
|
| - showProgress = true;
|
| - showPlayPause = true;
|
| - setProgressBar(videoInfo, contentView);
|
| - contentView.setImageViewResource(
|
| - R.id.playpause, R.drawable.ic_vidcontrol_play);
|
| - contentView.setContentDescription(
|
| - R.id.playpause, sInstance.mPlayDescription);
|
| - contentView.setOnClickPendingIntent(
|
| - R.id.playpause, getPendingIntent(ListenerService.ACTION_ID_PLAY));
|
| - break;
|
| - case LOADING:
|
| - showProgress = true;
|
| - contentView.setProgressBar(R.id.progress, 0, 0, true);
|
| - break;
|
| - case ERROR:
|
| - showProgress = true;
|
| - break;
|
| - default:
|
| - break;
|
| - }
|
| -
|
| - contentView.setViewVisibility(
|
| - R.id.playpause, showPlayPause ? View.VISIBLE : View.INVISIBLE);
|
| - // We use GONE instead of INVISIBLE for this because the notification looks funny
|
| - // with a large gap in the middle if we have no duration. Setting it to GONE forces
|
| - // the layout to squeeze tighter to the middle.
|
| - contentView.setViewVisibility(R.id.progress,
|
| - (showProgress && videoInfo.durationMillis > 0) ? View.VISIBLE : View.GONE);
|
| - contentView.setViewVisibility(
|
| - R.id.stop, showPlayPause ? View.VISIBLE : View.INVISIBLE);
|
| -
|
| - mNotification.contentView = contentView;
|
| -
|
| - startForeground(R.id.remote_notification, mNotification);
|
| - }
|
| - }
|
| -
|
| - private void setProgressBar(RemoteVideoInfo videoInfo, RemoteViews contentView) {
|
| - long durationMillis = videoInfo.durationMillis;
|
| - long currentTimeMillis = videoInfo.currentTimeMillis;
|
| - // Handle ridiculously long videos (25 days+).
|
| - if (durationMillis > Integer.MAX_VALUE) {
|
| - long factor = durationMillis / Integer.MAX_VALUE;
|
| - durationMillis = Integer.MAX_VALUE;
|
| - currentTimeMillis = currentTimeMillis / factor;
|
| - }
|
| - contentView.setProgressBar(R.id.progress, (int) durationMillis, (int) currentTimeMillis,
|
| - false);
|
| - }
|
| -
|
| - private RemoteViews createContentView() {
|
| - RemoteViews contentView =
|
| - new RemoteViews(getPackageName(), R.layout.remote_notification_bar);
|
| - contentView.setOnClickPendingIntent(
|
| - R.id.stop, getPendingIntent(ListenerService.ACTION_ID_STOP));
|
| -
|
| - return contentView;
|
| - }
|
| -
|
| - private String getStatus() {
|
| - if (sInstance.hasError()) return sInstance.getError();
|
| - RemoteVideoInfo videoInfo = sInstance.getVideoInfo();
|
| - // TODO(bclayton): Is there something better to display here?
|
| - if (videoInfo == null) return "";
|
| - String videoTitle = videoInfo.title;
|
| - switch (videoInfo.state) {
|
| - case PLAYING:
|
| - return videoTitle != null
|
| - ? getString(R.string.cast_notification_playing_for_video, videoTitle)
|
| - : getString(R.string.cast_notification_playing);
|
| - case LOADING:
|
| - return videoTitle != null
|
| - ? getString(R.string.cast_notification_loading_for_video, videoTitle)
|
| - : getString(R.string.cast_notification_loading);
|
| - case PAUSED:
|
| - return videoTitle != null
|
| - ? getString(R.string.cast_notification_paused_for_video, videoTitle)
|
| - : getString(R.string.cast_notification_paused);
|
| - case STOPPED:
|
| - return getString(R.string.cast_notification_stopped);
|
| - case FINISHED:
|
| - case INVALIDATED:
|
| - return videoTitle != null
|
| - ? getString(R.string.cast_notification_finished_for_video, videoTitle)
|
| - : getString(R.string.cast_notification_finished);
|
| - case ERROR:
|
| - default:
|
| - return videoInfo.errorMessage;
|
| - }
|
| - }
|
| -
|
| - private void scheduleProgressUpdate() {
|
| - sInstance.mHandler.postDelayed(new Runnable() {
|
| - @Override
|
| - public void run() {
|
| - sInstance.onPositionChanged(sInstance.mMediaRouteController.getPosition());
|
| - }
|
| - }, sInstance.mProgressUpdateInterval);
|
| - }
|
| - }
|
| -
|
| - private static NotificationTransportControl sInstance = null;
|
| - private static final Object LOCK = new Object();
|
| -
|
| - private static final int MINIMUM_PROGRESS_UPDATE_INTERVAL_MS = 1000;
|
| -
|
| - private boolean mShowing = false;
|
| -
|
| - private final String mPlayDescription;
|
| -
|
| - private final String mPauseDescription;
|
| -
|
| - private final Context mContext;
|
| -
|
| - private MediaRouteController mMediaRouteController;
|
| -
|
| - // ListenerService running for the notification.
|
| - private ListenerService mService;
|
| -
|
| - private Bitmap mIcon;
|
| -
|
| - private Handler mHandler;
|
| -
|
| - private int mProgressUpdateInterval = MINIMUM_PROGRESS_UPDATE_INTERVAL_MS;
|
| -
|
| - /**
|
| - * Returns the singleton NotificationTransportControl object.
|
| - *
|
| - * @param context The Context that the notification service needs to be created in.
|
| - * @param mrc The MediaRouteController object to use.
|
| - * @return A {@code NotificationTransportControl} object that uses the given
|
| - * MediaRouteController object.
|
| - */
|
| - public static NotificationTransportControl getOrCreate(Context context,
|
| - @Nullable MediaRouteController mrc) {
|
| - synchronized (LOCK) {
|
| - if (sInstance == null) {
|
| - sInstance = new NotificationTransportControl(context);
|
| - sInstance.setVideoInfo(
|
| - new RemoteVideoInfo(null, 0, RemoteVideoInfo.PlayerState.STOPPED, 0, null));
|
| - }
|
| -
|
| - sInstance.setRouteController(mrc);
|
| - return sInstance;
|
| - }
|
| - }
|
| -
|
| - @VisibleForTesting
|
| - static NotificationTransportControl getIfExists() {
|
| - return sInstance;
|
| - }
|
| -
|
| - /**
|
| - * Scale the specified bitmap to the desired with and height while preserving aspect ratio.
|
| - */
|
| - private Bitmap scaleBitmap(Bitmap bitmap, int maxWidth, int maxHeight) {
|
| - if (bitmap == null) {
|
| - return null;
|
| - }
|
| -
|
| - float scaleX = 1.0f;
|
| - float scaleY = 1.0f;
|
| - if (bitmap.getWidth() > maxWidth) {
|
| - scaleX = maxWidth / (float) bitmap.getWidth();
|
| - }
|
| - if (bitmap.getHeight() > maxHeight) {
|
| - scaleY = maxHeight / (float) bitmap.getHeight();
|
| - }
|
| - float scale = Math.min(scaleX, scaleY);
|
| - int width = (int) (bitmap.getWidth() * scale);
|
| - int height = (int) (bitmap.getHeight() * scale);
|
| - return Bitmap.createScaledBitmap(bitmap, width, height, false);
|
| - }
|
| -
|
| - private NotificationTransportControl(Context context) {
|
| - this.mContext = context;
|
| - mHandler = new Handler(context.getMainLooper());
|
| - mPlayDescription = context.getResources().getString(R.string.accessibility_play);
|
| - mPauseDescription = context.getResources().getString(R.string.accessibility_pause);
|
| - }
|
| -
|
| - @Override
|
| - public void hide() {
|
| - mShowing = false;
|
| - mContext.stopService(new Intent(mContext, ListenerService.class));
|
| - }
|
| -
|
| - /**
|
| - * @return true if the notification is currently visible to the user.
|
| - */
|
| - @VisibleForTesting
|
| - boolean isShowing() {
|
| - return mShowing;
|
| - }
|
| -
|
| - @Override
|
| - public void onDurationUpdated(long durationMillis) {
|
| - // Set the progress update interval based on the screen height/width, since there's no point
|
| - // in updating the progress bar more frequently than what the user can see.
|
| - // getDisplayMetrics() is dependent on the current orientation, so we need to get the max
|
| - // of both height and width so that both portrait and landscape notifications are covered.
|
| - DisplayMetrics metrics = mContext.getResources().getDisplayMetrics();
|
| - float density = metrics.density;
|
| - float dpHeight = metrics.heightPixels / density;
|
| - float dpWidth = metrics.widthPixels / density;
|
| -
|
| - float maxDimen = Math.max(dpHeight, dpWidth);
|
| -
|
| - mProgressUpdateInterval = Math.max(MINIMUM_PROGRESS_UPDATE_INTERVAL_MS,
|
| - Math.round(durationMillis / maxDimen));
|
| -
|
| - RemoteVideoInfo videoInfo = new RemoteVideoInfo(getVideoInfo());
|
| - videoInfo.durationMillis = durationMillis;
|
| - setVideoInfo(videoInfo);
|
| - }
|
| -
|
| - @Override
|
| - public void onError(int error, String errorMessage) {
|
| - // Stop the session for all errors
|
| - hide();
|
| - }
|
| -
|
| - @Override
|
| - public void onPlaybackStateChanged(PlayerState oldState, PlayerState newState) {
|
| - RemoteVideoInfo videoInfo = new RemoteVideoInfo(getVideoInfo());
|
| - videoInfo.state = newState;
|
| - setVideoInfo(videoInfo);
|
| - }
|
| -
|
| - @Override
|
| - public void onPositionChanged(long positionMillis) {
|
| - RemoteVideoInfo videoInfo = new RemoteVideoInfo(getVideoInfo());
|
| - videoInfo.currentTimeMillis = positionMillis;
|
| - setVideoInfo(videoInfo);
|
| - }
|
| -
|
| - @Override
|
| - public void onPrepared(MediaRouteController mediaRouteController) {
|
| - }
|
| -
|
| - @Override
|
| - public void onRouteSelected(String name, MediaRouteController mediaRouteController) {
|
| - setScreenName(name);
|
| - }
|
| -
|
| - @Override
|
| - public void onRouteUnselected(MediaRouteController mediaRouteController) {
|
| - hide();
|
| - }
|
| -
|
| - @Override
|
| - public void onTitleChanged(String title) {
|
| - RemoteVideoInfo videoInfo = new RemoteVideoInfo(getVideoInfo());
|
| - videoInfo.title = title;
|
| - setVideoInfo(videoInfo);
|
| - }
|
| -
|
| - @Override
|
| - public void setRouteController(MediaRouteController controller) {
|
| - if (mMediaRouteController != null) mMediaRouteController.removeUiListener(this);
|
| - mMediaRouteController = controller;
|
| - if (controller != null) controller.addUiListener(this);
|
| - }
|
| -
|
| - @Override
|
| - public void show(PlayerState initialState) {
|
| - mShowing = true;
|
| - RemoteVideoInfo newVideoInfo = new RemoteVideoInfo(mVideoInfo);
|
| - newVideoInfo.state = initialState;
|
| - setVideoInfo(newVideoInfo);
|
| - }
|
| -
|
| - @VisibleForTesting
|
| - Notification getNotification() {
|
| - // TODO(aberent). This is only used by the tests, which should actually simply be using
|
| - // isShowing(); but the tests are still downstream, so this needs to be changed in stages.
|
| - if (getService() == null) return null;
|
| - return getService().mNotification;
|
| - }
|
| -
|
| - @VisibleForTesting
|
| - final ListenerService getService() {
|
| - // TODO(aberent). This is only used by the tests, which should actually simply be using
|
| - // isShowing(); but the tests are still downstream, so this needs to be changed in stages.
|
| - if (!isShowing()) return null;
|
| - return mService;
|
| - }
|
| -
|
| - @Override
|
| - protected void onErrorChanged() {
|
| - mContext.startService(new Intent(mContext, ListenerService.class));
|
| - }
|
| -
|
| - @Override
|
| - protected void onPosterBitmapChanged() {
|
| - Bitmap posterBitmap = getPosterBitmap();
|
| - mIcon = scaleBitmapForIcon(posterBitmap);
|
| - super.onPosterBitmapChanged();
|
| - }
|
| -
|
| - @Override
|
| - protected void onScreenNameChanged() {
|
| - mContext.startService(new Intent(mContext, ListenerService.class));
|
| - }
|
| -
|
| - @Override
|
| - protected void onVideoInfoChanged() {
|
| - mContext.startService(new Intent(mContext, ListenerService.class));
|
| - }
|
| -
|
| - /**
|
| - * Scale the specified bitmap to properly fit as a notification icon. If the argument is null
|
| - * the function returns null.
|
| - */
|
| - private Bitmap scaleBitmapForIcon(Bitmap bitmap) {
|
| - Resources res = mContext.getResources();
|
| - float maxWidth = res.getDimension(R.dimen.remote_notification_logo_max_width);
|
| - float maxHeight = res.getDimension(R.dimen.remote_notification_logo_max_height);
|
| - return scaleBitmap(bitmap, (int) maxWidth, (int) maxHeight);
|
| - }
|
| -
|
| -}
|
|
|