Chromium Code Reviews| 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 10a9ba724baca9db7893b47957417eaeb18384da..73b3e03b1a39f5bb1c8ed210c67bc44473a9cae5 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,11 @@ import javax.annotation.Nullable; |
| * There's one service started for a distinct notification id. |
| */ |
| public class MediaNotificationManager { |
| - |
| private static final String TAG = "MediaNotification"; |
| + private static final int WEARABLE_NOTIFICATION_BACKGROUND_WIDTH = 400; |
| + private static final int WEARABLE_NOTIFICATION_BACKGROUND_HEIGHT = 400; |
|
mlamouri (slow - plz ping)
2016/04/07 19:47:34
Where did you find these numbers?
Zhiqiang Zhang (Slow)
2016/04/11 09:26:28
http://developer.android.com/training/wearables/no
|
| + |
| // 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(); |
| @@ -480,55 +483,6 @@ 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(); |
| @@ -541,8 +495,13 @@ public class MediaNotificationManager { |
| mMediaNotificationInfo.metadata.getTitle()); |
| metadataBuilder.putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_SUBTITLE, |
| mMediaNotificationInfo.origin); |
| - metadataBuilder.putBitmap(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON, |
| - mediaSessionImage); |
| + if (mMediaNotificationInfo.largeIcon == null) { |
| + metadataBuilder.putBitmap(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON, |
| + mediaSessionImage); |
| + } else { |
| + metadataBuilder.putBitmap(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON, |
| + scaleBitmapForWearable(mMediaNotificationInfo.largeIcon)); |
| + } |
| // METADATA_KEY_ART is optional and should only be used if we can provide something |
| // better than the default image. |
|
mlamouri (slow - plz ping)
2016/04/07 19:47:34
Should we try to use METADATA_KEY_ART with the lar
Zhiqiang Zhang (Slow)
2016/04/11 09:26:28
Done. Also I merged MediaNotificationInfo.image an
|
| if (mMediaNotificationInfo.image != null) { |
| @@ -554,7 +513,12 @@ public class MediaNotificationManager { |
| mMediaNotificationInfo.metadata.getTitle()); |
| metadataBuilder.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, |
| mMediaNotificationInfo.origin); |
| - metadataBuilder.putBitmap(MediaMetadataCompat.METADATA_KEY_ART, mediaSessionImage); |
| + if (mMediaNotificationInfo.largeIcon == null) { |
| + metadataBuilder.putBitmap(MediaMetadataCompat.METADATA_KEY_ART, mediaSessionImage); |
| + } else { |
| + metadataBuilder.putBitmap(MediaMetadataCompat.METADATA_KEY_ART, |
| + mMediaNotificationInfo.largeIcon); |
| + } |
| } |
| if (!TextUtils.isEmpty(mMediaNotificationInfo.metadata.getArtist())) { |
| @@ -574,21 +538,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.USE_NEW_MEDIA_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); |
| @@ -602,39 +564,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, |
| @@ -651,6 +584,37 @@ 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, |
| @@ -678,6 +642,124 @@ public class MediaNotificationManager { |
| return mediaSession; |
| } |
| + private void setMediaStyleLayoutForNotificationBuilder(NotificationCompat.Builder builder) { |
| + NotificationCompat.MediaStyle style = new NotificationCompat.MediaStyle(); |
| + if (mMediaSession != null) { |
|
mlamouri (slow - plz ping)
2016/04/07 19:47:34
Is such a thing even possible?
Zhiqiang Zhang (Slow)
2016/04/11 09:26:28
Done. Merged all logic when mNotificationInfo.supp
|
| + style.setMediaSession(mMediaSession.getSessionToken()); |
| + } |
| + |
| + // TODO: with custom notifications, can do: |
| + // - setContentTitle for track title, |
| + // - setContetText() for track artist, |
| + // - setSubText() for track album |
| + // or use subText() for origin and setContentText for artist + album. |
|
mlamouri (slow - plz ping)
2016/04/07 19:47:34
After we ship the new style, we should see how to
Zhiqiang Zhang (Slow)
2016/04/11 09:26:28
Done.
|
| + 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); |
| + |
| + if (mMediaNotificationInfo.supportsPlayPause()) { |
|
mlamouri (slow - plz ping)
2016/04/07 19:47:34
Add a TODO: if it doesn't supports play/pause, we
Zhiqiang Zhang (Slow)
2016/04/11 09:26:28
The logic is already that. Added comments.
|
| + 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); |
| + } |
| + |
| + if (mMediaNotificationInfo.supportsStop()) { |
| + builder.addAction(R.drawable.ic_vidcontrol_stop, mStopDescription, |
| + createPendingIntent(ListenerService.ACTION_STOP)); |
| + } |
| + if (mMediaSession != null) { |
| + builder.setStyle(style); |
| + } |
| + } |
| + |
| + 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; |