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; |