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

Unified Diff: chrome/android/java/src/org/chromium/chrome/browser/media/remote/NotificationTransportControl.java

Issue 1652163002: Refactor Clank cast code to use MediaNotificationManager (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@MediaUi
Patch Set: Respond to final batch of nits Created 4 years, 10 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/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);
- }
-
-}

Powered by Google App Engine
This is Rietveld 408576698