| Index: chrome/android/java/src/org/chromium/chrome/browser/media/ui/MediaNotificationManager.java
|
| diff --git a/chrome/android/java/src/org/chromium/chrome/browser/media/ui/MediaNotificationManager.java b/chrome/android/java/src/org/chromium/chrome/browser/media/ui/MediaNotificationManager.java
|
| index b36cc9faffc5b28a81de23e6c366eeab18b7ab75..ed0f484016f8fce8c08053063c48baadbf63e3e8 100644
|
| --- a/chrome/android/java/src/org/chromium/chrome/browser/media/ui/MediaNotificationManager.java
|
| +++ b/chrome/android/java/src/org/chromium/chrome/browser/media/ui/MediaNotificationManager.java
|
| @@ -18,11 +18,11 @@ import android.graphics.drawable.Drawable;
|
| import android.media.AudioManager;
|
| import android.os.Build;
|
| import android.os.IBinder;
|
| -import android.support.v4.app.NotificationCompat;
|
| import android.support.v4.app.NotificationManagerCompat;
|
| import android.support.v4.media.MediaMetadataCompat;
|
| import android.support.v4.media.session.MediaSessionCompat;
|
| import android.support.v4.media.session.PlaybackStateCompat;
|
| +import android.support.v7.app.NotificationCompat;
|
| import android.support.v7.media.MediaRouter;
|
| import android.text.TextUtils;
|
| import android.util.SparseArray;
|
| @@ -33,6 +33,7 @@ import android.widget.RemoteViews;
|
| import org.chromium.base.ApiCompatibilityUtils;
|
| import org.chromium.base.VisibleForTesting;
|
| import org.chromium.chrome.R;
|
| +import org.chromium.chrome.browser.ChromeFeatureList;
|
|
|
| import javax.annotation.Nullable;
|
|
|
| @@ -43,9 +44,13 @@ import javax.annotation.Nullable;
|
| * There's one service started for a distinct notification id.
|
| */
|
| public class MediaNotificationManager {
|
| -
|
| private static final String TAG = "MediaNotification";
|
|
|
| + // The background notification size on Android Wear. See:
|
| + // http://developer.android.com/training/wearables/notifications/creating.html
|
| + private static final int WEARABLE_NOTIFICATION_BACKGROUND_WIDTH = 400;
|
| + private static final int WEARABLE_NOTIFICATION_BACKGROUND_HEIGHT = 400;
|
| +
|
| // We're always used on the UI thread but the LOCK is required by lint when creating the
|
| // singleton.
|
| private static final Object LOCK = new Object();
|
| @@ -482,61 +487,13 @@ public class MediaNotificationManager {
|
| clearNotification();
|
| }
|
|
|
| - private RemoteViews createContentView() {
|
| - RemoteViews contentView =
|
| - new RemoteViews(mContext.getPackageName(), R.layout.playback_notification_bar);
|
| -
|
| - // By default, play/pause button is the only one.
|
| - int playPauseButtonId = R.id.button1;
|
| - // On Android pre-L, dismissing the notification when the service is no longer in foreground
|
| - // doesn't work. Instead, a STOP button is shown.
|
| - if (mMediaNotificationInfo.supportsSwipeAway()
|
| - && Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP
|
| - || mMediaNotificationInfo.supportsStop()) {
|
| - contentView.setOnClickPendingIntent(R.id.button1,
|
| - createPendingIntent(ListenerService.ACTION_STOP));
|
| - contentView.setContentDescription(R.id.button1, mStopDescription);
|
| -
|
| - // If the play/pause needs to be shown, it moves over to the second button from the end.
|
| - playPauseButtonId = R.id.button2;
|
| - }
|
| -
|
| - contentView.setTextViewText(R.id.title, mMediaNotificationInfo.metadata.getTitle());
|
| - contentView.setTextViewText(R.id.status, mMediaNotificationInfo.origin);
|
| - if (mNotificationIcon != null) {
|
| - contentView.setImageViewBitmap(R.id.icon, mNotificationIcon);
|
| - } else {
|
| - contentView.setImageViewResource(R.id.icon, mMediaNotificationInfo.icon);
|
| - }
|
| -
|
| - if (mMediaNotificationInfo.supportsPlayPause()) {
|
| - if (mMediaNotificationInfo.isPaused) {
|
| - contentView.setImageViewResource(playPauseButtonId, R.drawable.ic_vidcontrol_play);
|
| - contentView.setContentDescription(playPauseButtonId, mPlayDescription);
|
| - contentView.setOnClickPendingIntent(playPauseButtonId,
|
| - createPendingIntent(ListenerService.ACTION_PLAY));
|
| - } else {
|
| - // If we're here, the notification supports play/pause button and is playing.
|
| - contentView.setImageViewResource(playPauseButtonId, R.drawable.ic_vidcontrol_pause);
|
| - contentView.setContentDescription(playPauseButtonId, mPauseDescription);
|
| - contentView.setOnClickPendingIntent(playPauseButtonId,
|
| - createPendingIntent(ListenerService.ACTION_PAUSE));
|
| - }
|
| -
|
| - contentView.setViewVisibility(playPauseButtonId, View.VISIBLE);
|
| - } else {
|
| - contentView.setViewVisibility(playPauseButtonId, View.GONE);
|
| - }
|
| -
|
| - return contentView;
|
| - }
|
| -
|
| private MediaMetadataCompat createMetadata() {
|
| MediaMetadataCompat.Builder metadataBuilder = new MediaMetadataCompat.Builder();
|
|
|
| // Choose the image to use as the icon.
|
| - Bitmap mediaSessionImage = mMediaNotificationInfo.image == null ? mDefaultMediaSessionImage
|
| - : mMediaNotificationInfo.image;
|
| + Bitmap mediaSessionImage = mMediaNotificationInfo.largeIcon == null
|
| + ? mDefaultMediaSessionImage
|
| + : mMediaNotificationInfo.largeIcon;
|
|
|
| if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
| metadataBuilder.putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE,
|
| @@ -544,12 +501,12 @@ public class MediaNotificationManager {
|
| metadataBuilder.putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_SUBTITLE,
|
| mMediaNotificationInfo.origin);
|
| metadataBuilder.putBitmap(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON,
|
| - mediaSessionImage);
|
| + scaleBitmapForWearable(mediaSessionImage));
|
| // METADATA_KEY_ART is optional and should only be used if we can provide something
|
| // better than the default image.
|
| - if (mMediaNotificationInfo.image != null) {
|
| - metadataBuilder.putBitmap(MediaMetadataCompat.METADATA_KEY_ART,
|
| - mMediaNotificationInfo.image);
|
| + if (mMediaNotificationInfo.largeIcon != null) {
|
| + metadataBuilder.putBitmap(
|
| + MediaMetadataCompat.METADATA_KEY_ART, mMediaNotificationInfo.largeIcon);
|
| }
|
| } else {
|
| metadataBuilder.putString(MediaMetadataCompat.METADATA_KEY_TITLE,
|
| @@ -576,21 +533,19 @@ public class MediaNotificationManager {
|
|
|
| if (mMediaNotificationInfo == null) return;
|
|
|
| - // Android doesn't badge the icons for RemoteViews automatically when
|
| - // running the app under the Work profile.
|
| - if (mNotificationIcon == null) {
|
| - Drawable notificationIconDrawable = ApiCompatibilityUtils.getUserBadgedIcon(
|
| - mContext, mMediaNotificationInfo.icon);
|
| - mNotificationIcon = drawableToBitmap(notificationIconDrawable);
|
| - }
|
| + updateMediaSession();
|
|
|
| - if (mNotificationBuilder == null) {
|
| - mNotificationBuilder = new NotificationCompat.Builder(mContext)
|
| - .setSmallIcon(mMediaNotificationInfo.icon)
|
| - .setAutoCancel(false)
|
| - .setLocalOnly(true)
|
| - .setDeleteIntent(createPendingIntent(ListenerService.ACTION_STOP));
|
| + mNotificationBuilder = new NotificationCompat.Builder(mContext);
|
| + if (ChromeFeatureList.isEnabled(ChromeFeatureList.MEDIA_STYLE_NOTIFICATION)) {
|
| + setMediaStyleLayoutForNotificationBuilder(mNotificationBuilder);
|
| + } else {
|
| + setCustomLayoutForNotificationBuilder(mNotificationBuilder);
|
| }
|
| + // TODO: smallIcon set to Chrome's icon?
|
| + mNotificationBuilder.setSmallIcon(mMediaNotificationInfo.icon);
|
| + mNotificationBuilder.setAutoCancel(false);
|
| + mNotificationBuilder.setLocalOnly(true);
|
| + mNotificationBuilder.setDeleteIntent(createPendingIntent(ListenerService.ACTION_STOP));
|
|
|
| if (mMediaNotificationInfo.supportsSwipeAway()) {
|
| mNotificationBuilder.setOngoing(!mMediaNotificationInfo.isPaused);
|
| @@ -604,39 +559,10 @@ public class MediaNotificationManager {
|
| mMediaNotificationInfo.contentIntent, 0));
|
| }
|
|
|
| - mNotificationBuilder.setContent(createContentView());
|
| mNotificationBuilder.setVisibility(
|
| mMediaNotificationInfo.isPrivate ? NotificationCompat.VISIBILITY_PRIVATE
|
| : NotificationCompat.VISIBILITY_PUBLIC);
|
|
|
| -
|
| - if (mMediaNotificationInfo.supportsPlayPause()) {
|
| -
|
| - if (mMediaSession == null) mMediaSession = createMediaSession();
|
| - try {
|
| - // Tell the MediaRouter about the session, so that Chrome can control the volume
|
| - // on the remote cast device (if any).
|
| - // Pre-MR1 versions of JB do not have the complete MediaRouter APIs,
|
| - // so getting the MediaRouter instance will throw an exception.
|
| - MediaRouter.getInstance(mContext).setMediaSessionCompat(mMediaSession);
|
| - } catch (NoSuchMethodError e) {
|
| - // Do nothing. Chrome can't be casting without a MediaRouter, so there is nothing
|
| - // to do here.
|
| - }
|
| - mMediaSession.setMetadata(createMetadata());
|
| -
|
| - PlaybackStateCompat.Builder playbackStateBuilder = new PlaybackStateCompat.Builder()
|
| - .setActions(PlaybackStateCompat.ACTION_PLAY | PlaybackStateCompat.ACTION_PAUSE);
|
| - if (mMediaNotificationInfo.isPaused) {
|
| - playbackStateBuilder.setState(PlaybackStateCompat.STATE_PAUSED,
|
| - PlaybackStateCompat.PLAYBACK_POSITION_UNKNOWN, 1.0f);
|
| - } else {
|
| - playbackStateBuilder.setState(PlaybackStateCompat.STATE_PLAYING,
|
| - PlaybackStateCompat.PLAYBACK_POSITION_UNKNOWN, 1.0f);
|
| - }
|
| - mMediaSession.setPlaybackState(playbackStateBuilder.build());
|
| - }
|
| -
|
| Notification notification = mNotificationBuilder.build();
|
|
|
| // We keep the service as a foreground service while the media is playing. When it is not,
|
| @@ -653,6 +579,38 @@ public class MediaNotificationManager {
|
| }
|
| }
|
|
|
| + private void updateMediaSession() {
|
| + if (!mMediaNotificationInfo.supportsPlayPause()) return;
|
| +
|
| + if (mMediaSession == null) mMediaSession = createMediaSession();
|
| +
|
| + try {
|
| + // Tell the MediaRouter about the session, so that Chrome can control the volume
|
| + // on the remote cast device (if any).
|
| + // Pre-MR1 versions of JB do not have the complete MediaRouter APIs,
|
| + // so getting the MediaRouter instance will throw an exception.
|
| + MediaRouter.getInstance(mContext).setMediaSessionCompat(mMediaSession);
|
| + } catch (NoSuchMethodError e) {
|
| + // Do nothing. Chrome can't be casting without a MediaRouter, so there is nothing
|
| + // to do here.
|
| + }
|
| +
|
| + mMediaSession.setMetadata(createMetadata());
|
| +
|
| + PlaybackStateCompat.Builder playbackStateBuilder =
|
| + new PlaybackStateCompat.Builder().setActions(
|
| + PlaybackStateCompat.ACTION_PLAY | PlaybackStateCompat.ACTION_PAUSE);
|
| + if (mMediaNotificationInfo.isPaused) {
|
| + playbackStateBuilder.setState(PlaybackStateCompat.STATE_PAUSED,
|
| + PlaybackStateCompat.PLAYBACK_POSITION_UNKNOWN, 1.0f);
|
| + } else {
|
| + // If notification only supports stop, still pretend
|
| + playbackStateBuilder.setState(PlaybackStateCompat.STATE_PLAYING,
|
| + PlaybackStateCompat.PLAYBACK_POSITION_UNKNOWN, 1.0f);
|
| + }
|
| + mMediaSession.setPlaybackState(playbackStateBuilder.build());
|
| + }
|
| +
|
| private MediaSessionCompat createMediaSession() {
|
| MediaSessionCompat mediaSession = new MediaSessionCompat(
|
| mContext,
|
| @@ -680,6 +638,116 @@ public class MediaNotificationManager {
|
| return mediaSession;
|
| }
|
|
|
| + private void setMediaStyleLayoutForNotificationBuilder(NotificationCompat.Builder builder) {
|
| + // TODO(zqzhang): After we ship the new style, we should ses how to present the
|
| + // metadata.artist and metadata.album.
|
| + builder.setContentTitle(mMediaNotificationInfo.metadata.getTitle());
|
| + builder.setContentText(mMediaNotificationInfo.origin);
|
| + // TODO: have our own "Media" icon so we can have large resolution in case of we have no
|
| + // large icon?
|
| + if (mMediaNotificationInfo.largeIcon != null) {
|
| + builder.setLargeIcon(mMediaNotificationInfo.largeIcon);
|
| + } else {
|
| + builder.setLargeIcon(mDefaultMediaSessionImage);
|
| + }
|
| + builder.setShowWhen(false);
|
| +
|
| + // Only apply MediaStyle when NotificationInfo supports play/pause.
|
| + if (mMediaNotificationInfo.supportsPlayPause()) {
|
| + NotificationCompat.MediaStyle style = new NotificationCompat.MediaStyle();
|
| + style.setMediaSession(mMediaSession.getSessionToken());
|
| +
|
| + if (mMediaNotificationInfo.isPaused) {
|
| + builder.addAction(R.drawable.ic_vidcontrol_play, mPlayDescription,
|
| + createPendingIntent(ListenerService.ACTION_PLAY));
|
| + } else {
|
| + // If we're here, the notification supports play/pause button and is playing.
|
| + builder.addAction(R.drawable.ic_vidcontrol_pause, mPauseDescription,
|
| + createPendingIntent(ListenerService.ACTION_PAUSE));
|
| + }
|
| + style.setShowActionsInCompactView(0);
|
| +
|
| + builder.setStyle(style);
|
| + }
|
| +
|
| + if (mMediaNotificationInfo.supportsStop()) {
|
| + builder.addAction(R.drawable.ic_vidcontrol_stop, mStopDescription,
|
| + createPendingIntent(ListenerService.ACTION_STOP));
|
| + }
|
| + }
|
| +
|
| + private void setCustomLayoutForNotificationBuilder(NotificationCompat.Builder builder) {
|
| + builder.setContent(createContentView());
|
| + }
|
| +
|
| + private RemoteViews createContentView() {
|
| + RemoteViews contentView =
|
| + new RemoteViews(mContext.getPackageName(), R.layout.playback_notification_bar);
|
| +
|
| + // By default, play/pause button is the only one.
|
| + int playPauseButtonId = R.id.button1;
|
| + // On Android pre-L, dismissing the notification when the service is no longer in foreground
|
| + // doesn't work. Instead, a STOP button is shown.
|
| + if (mMediaNotificationInfo.supportsSwipeAway()
|
| + && Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP
|
| + || mMediaNotificationInfo.supportsStop()) {
|
| + contentView.setOnClickPendingIntent(
|
| + R.id.button1, createPendingIntent(ListenerService.ACTION_STOP));
|
| + contentView.setContentDescription(R.id.button1, mStopDescription);
|
| +
|
| + // If the play/pause needs to be shown, it moves over to the second button from the end.
|
| + playPauseButtonId = R.id.button2;
|
| + }
|
| +
|
| + contentView.setTextViewText(R.id.title, mMediaNotificationInfo.metadata.getTitle());
|
| + contentView.setTextViewText(R.id.status, mMediaNotificationInfo.origin);
|
| +
|
| + // Android doesn't badge the icons for RemoteViews automatically when
|
| + // running the app under the Work profile.
|
| + if (mNotificationIcon == null) {
|
| + Drawable notificationIconDrawable =
|
| + ApiCompatibilityUtils.getUserBadgedIcon(mContext, mMediaNotificationInfo.icon);
|
| + mNotificationIcon = drawableToBitmap(notificationIconDrawable);
|
| + }
|
| +
|
| + if (mNotificationIcon != null) {
|
| + contentView.setImageViewBitmap(R.id.icon, mNotificationIcon);
|
| + } else {
|
| + contentView.setImageViewResource(R.id.icon, mMediaNotificationInfo.icon);
|
| + }
|
| +
|
| + if (mMediaNotificationInfo.supportsPlayPause()) {
|
| + if (mMediaNotificationInfo.isPaused) {
|
| + contentView.setImageViewResource(playPauseButtonId, R.drawable.ic_vidcontrol_play);
|
| + contentView.setContentDescription(playPauseButtonId, mPlayDescription);
|
| + contentView.setOnClickPendingIntent(
|
| + playPauseButtonId, createPendingIntent(ListenerService.ACTION_PLAY));
|
| + } else {
|
| + // If we're here, the notification supports play/pause button and is playing.
|
| + contentView.setImageViewResource(playPauseButtonId, R.drawable.ic_vidcontrol_pause);
|
| + contentView.setContentDescription(playPauseButtonId, mPauseDescription);
|
| + contentView.setOnClickPendingIntent(
|
| + playPauseButtonId, createPendingIntent(ListenerService.ACTION_PAUSE));
|
| + }
|
| +
|
| + contentView.setViewVisibility(playPauseButtonId, View.VISIBLE);
|
| + } else {
|
| + contentView.setViewVisibility(playPauseButtonId, View.GONE);
|
| + }
|
| +
|
| + return contentView;
|
| + }
|
| +
|
| + /**
|
| + * Scales the Bitmap to have better looking on Wearable devices.
|
| + * The returned Bitmap size will be exactly 400*400.
|
| + */
|
| + private Bitmap scaleBitmapForWearable(Bitmap original) {
|
| + Bitmap result = Bitmap.createScaledBitmap(original, WEARABLE_NOTIFICATION_BACKGROUND_WIDTH,
|
| + WEARABLE_NOTIFICATION_BACKGROUND_HEIGHT, true);
|
| + return result;
|
| + }
|
| +
|
| private Bitmap drawableToBitmap(Drawable drawable) {
|
| if (!(drawable instanceof BitmapDrawable)) return null;
|
|
|
|
|