OLD | NEW |
---|---|
1 // Copyright 2015 The Chromium Authors. All rights reserved. | 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 package org.chromium.chrome.browser.media.ui; | 5 package org.chromium.chrome.browser.media.ui; |
6 | 6 |
7 import android.app.Notification; | 7 import android.app.Notification; |
8 import android.app.PendingIntent; | 8 import android.app.PendingIntent; |
9 import android.app.Service; | 9 import android.app.Service; |
10 import android.content.BroadcastReceiver; | 10 import android.content.BroadcastReceiver; |
11 import android.content.ComponentName; | 11 import android.content.ComponentName; |
12 import android.content.Context; | 12 import android.content.Context; |
13 import android.content.Intent; | 13 import android.content.Intent; |
14 import android.content.IntentFilter; | 14 import android.content.IntentFilter; |
15 import android.graphics.Bitmap; | 15 import android.graphics.Bitmap; |
16 import android.graphics.drawable.BitmapDrawable; | 16 import android.graphics.drawable.BitmapDrawable; |
17 import android.graphics.drawable.Drawable; | 17 import android.graphics.drawable.Drawable; |
18 import android.media.AudioManager; | 18 import android.media.AudioManager; |
19 import android.os.Build; | 19 import android.os.Build; |
20 import android.os.IBinder; | 20 import android.os.IBinder; |
21 import android.support.v4.app.NotificationCompat; | |
22 import android.support.v4.app.NotificationManagerCompat; | 21 import android.support.v4.app.NotificationManagerCompat; |
23 import android.support.v4.media.MediaMetadataCompat; | 22 import android.support.v4.media.MediaMetadataCompat; |
24 import android.support.v4.media.session.MediaSessionCompat; | 23 import android.support.v4.media.session.MediaSessionCompat; |
25 import android.support.v4.media.session.PlaybackStateCompat; | 24 import android.support.v4.media.session.PlaybackStateCompat; |
25 import android.support.v7.app.NotificationCompat; | |
26 import android.support.v7.media.MediaRouter; | 26 import android.support.v7.media.MediaRouter; |
27 import android.text.TextUtils; | 27 import android.text.TextUtils; |
28 import android.util.SparseArray; | 28 import android.util.SparseArray; |
29 import android.view.KeyEvent; | 29 import android.view.KeyEvent; |
30 import android.view.View; | 30 import android.view.View; |
31 import android.widget.RemoteViews; | 31 import android.widget.RemoteViews; |
32 | 32 |
33 import org.chromium.base.ApiCompatibilityUtils; | 33 import org.chromium.base.ApiCompatibilityUtils; |
34 import org.chromium.base.VisibleForTesting; | 34 import org.chromium.base.VisibleForTesting; |
35 import org.chromium.chrome.R; | 35 import org.chromium.chrome.R; |
36 import org.chromium.chrome.browser.ChromeFeatureList; | |
36 | 37 |
37 import javax.annotation.Nullable; | 38 import javax.annotation.Nullable; |
38 | 39 |
39 /** | 40 /** |
40 * A class for notifications that provide information and optional media control s for a given media. | 41 * A class for notifications that provide information and optional media control s for a given media. |
41 * Internally implements a Service for transforming notification Intents into | 42 * Internally implements a Service for transforming notification Intents into |
42 * {@link MediaNotificationListener} calls for all registered listeners. | 43 * {@link MediaNotificationListener} calls for all registered listeners. |
43 * There's one service started for a distinct notification id. | 44 * There's one service started for a distinct notification id. |
44 */ | 45 */ |
45 public class MediaNotificationManager { | 46 public class MediaNotificationManager { |
47 private static final String TAG = "MediaNotification"; | |
46 | 48 |
47 private static final String TAG = "MediaNotification"; | 49 // The background notification size on Android Wear. See: |
50 // http://developer.android.com/training/wearables/notifications/creating.ht ml | |
51 private static final int WEARABLE_NOTIFICATION_BACKGROUND_WIDTH = 400; | |
52 private static final int WEARABLE_NOTIFICATION_BACKGROUND_HEIGHT = 400; | |
48 | 53 |
49 // We're always used on the UI thread but the LOCK is required by lint when creating the | 54 // We're always used on the UI thread but the LOCK is required by lint when creating the |
50 // singleton. | 55 // singleton. |
51 private static final Object LOCK = new Object(); | 56 private static final Object LOCK = new Object(); |
52 | 57 |
53 // Maps the notification ids to their corresponding notification managers. | 58 // Maps the notification ids to their corresponding notification managers. |
54 private static SparseArray<MediaNotificationManager> sManagers; | 59 private static SparseArray<MediaNotificationManager> sManagers; |
55 | 60 |
56 /** | 61 /** |
57 * Service used to transform intent requests triggered from the notification into | 62 * Service used to transform intent requests triggered from the notification into |
(...skipping 417 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
475 } | 480 } |
476 mContext.stopService(createIntent(mContext)); | 481 mContext.stopService(createIntent(mContext)); |
477 mMediaNotificationInfo = null; | 482 mMediaNotificationInfo = null; |
478 } | 483 } |
479 | 484 |
480 private void hideNotification(int tabId) { | 485 private void hideNotification(int tabId) { |
481 if (mMediaNotificationInfo == null || tabId != mMediaNotificationInfo.ta bId) return; | 486 if (mMediaNotificationInfo == null || tabId != mMediaNotificationInfo.ta bId) return; |
482 clearNotification(); | 487 clearNotification(); |
483 } | 488 } |
484 | 489 |
485 private RemoteViews createContentView() { | |
486 RemoteViews contentView = | |
487 new RemoteViews(mContext.getPackageName(), R.layout.playback_not ification_bar); | |
488 | |
489 // By default, play/pause button is the only one. | |
490 int playPauseButtonId = R.id.button1; | |
491 // On Android pre-L, dismissing the notification when the service is no longer in foreground | |
492 // doesn't work. Instead, a STOP button is shown. | |
493 if (mMediaNotificationInfo.supportsSwipeAway() | |
494 && Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP | |
495 || mMediaNotificationInfo.supportsStop()) { | |
496 contentView.setOnClickPendingIntent(R.id.button1, | |
497 createPendingIntent(ListenerService.ACTION_STOP)); | |
498 contentView.setContentDescription(R.id.button1, mStopDescription); | |
499 | |
500 // If the play/pause needs to be shown, it moves over to the second button from the end. | |
501 playPauseButtonId = R.id.button2; | |
502 } | |
503 | |
504 contentView.setTextViewText(R.id.title, mMediaNotificationInfo.metadata. getTitle()); | |
505 contentView.setTextViewText(R.id.status, mMediaNotificationInfo.origin); | |
506 if (mNotificationIcon != null) { | |
507 contentView.setImageViewBitmap(R.id.icon, mNotificationIcon); | |
508 } else { | |
509 contentView.setImageViewResource(R.id.icon, mMediaNotificationInfo.i con); | |
510 } | |
511 | |
512 if (mMediaNotificationInfo.supportsPlayPause()) { | |
513 if (mMediaNotificationInfo.isPaused) { | |
514 contentView.setImageViewResource(playPauseButtonId, R.drawable.i c_vidcontrol_play); | |
515 contentView.setContentDescription(playPauseButtonId, mPlayDescri ption); | |
516 contentView.setOnClickPendingIntent(playPauseButtonId, | |
517 createPendingIntent(ListenerService.ACTION_PLAY)); | |
518 } else { | |
519 // If we're here, the notification supports play/pause button an d is playing. | |
520 contentView.setImageViewResource(playPauseButtonId, R.drawable.i c_vidcontrol_pause); | |
521 contentView.setContentDescription(playPauseButtonId, mPauseDescr iption); | |
522 contentView.setOnClickPendingIntent(playPauseButtonId, | |
523 createPendingIntent(ListenerService.ACTION_PAUSE)); | |
524 } | |
525 | |
526 contentView.setViewVisibility(playPauseButtonId, View.VISIBLE); | |
527 } else { | |
528 contentView.setViewVisibility(playPauseButtonId, View.GONE); | |
529 } | |
530 | |
531 return contentView; | |
532 } | |
533 | |
534 private MediaMetadataCompat createMetadata() { | 490 private MediaMetadataCompat createMetadata() { |
535 MediaMetadataCompat.Builder metadataBuilder = new MediaMetadataCompat.Bu ilder(); | 491 MediaMetadataCompat.Builder metadataBuilder = new MediaMetadataCompat.Bu ilder(); |
536 | 492 |
537 // Choose the image to use as the icon. | 493 // Choose the image to use as the icon. |
538 Bitmap mediaSessionImage = mMediaNotificationInfo.image == null ? mDefau ltMediaSessionImage | 494 Bitmap mediaSessionImage = mMediaNotificationInfo.largeIcon == null |
539 : mMediaNotificationInfo.image; | 495 ? mDefaultMediaSessionImage |
496 : mMediaNotificationInfo.largeIcon; | |
540 | 497 |
541 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { | 498 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { |
542 metadataBuilder.putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_T ITLE, | 499 metadataBuilder.putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_T ITLE, |
543 mMediaNotificationInfo.metadata.getTitle()); | 500 mMediaNotificationInfo.metadata.getTitle()); |
544 metadataBuilder.putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_S UBTITLE, | 501 metadataBuilder.putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_S UBTITLE, |
545 mMediaNotificationInfo.origin); | 502 mMediaNotificationInfo.origin); |
546 metadataBuilder.putBitmap(MediaMetadataCompat.METADATA_KEY_DISPLAY_I CON, | 503 metadataBuilder.putBitmap(MediaMetadataCompat.METADATA_KEY_DISPLAY_I CON, |
547 mediaSessionImage); | 504 scaleBitmapForWearable(mediaSessionImage)); |
548 // METADATA_KEY_ART is optional and should only be used if we can pr ovide something | 505 // METADATA_KEY_ART is optional and should only be used if we can pr ovide something |
549 // better than the default image. | 506 // better than the default image. |
550 if (mMediaNotificationInfo.image != null) { | 507 if (mMediaNotificationInfo.largeIcon != null) { |
551 metadataBuilder.putBitmap(MediaMetadataCompat.METADATA_KEY_ART, | 508 metadataBuilder.putBitmap( |
552 mMediaNotificationInfo.image); | 509 MediaMetadataCompat.METADATA_KEY_ART, mMediaNotification Info.largeIcon); |
553 } | 510 } |
554 } else { | 511 } else { |
555 metadataBuilder.putString(MediaMetadataCompat.METADATA_KEY_TITLE, | 512 metadataBuilder.putString(MediaMetadataCompat.METADATA_KEY_TITLE, |
556 mMediaNotificationInfo.metadata.getTitle()); | 513 mMediaNotificationInfo.metadata.getTitle()); |
557 metadataBuilder.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, | 514 metadataBuilder.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, |
558 mMediaNotificationInfo.origin); | 515 mMediaNotificationInfo.origin); |
559 metadataBuilder.putBitmap(MediaMetadataCompat.METADATA_KEY_ART, medi aSessionImage); | 516 metadataBuilder.putBitmap(MediaMetadataCompat.METADATA_KEY_ART, medi aSessionImage); |
560 } | 517 } |
561 | 518 |
562 if (!TextUtils.isEmpty(mMediaNotificationInfo.metadata.getArtist())) { | 519 if (!TextUtils.isEmpty(mMediaNotificationInfo.metadata.getArtist())) { |
563 metadataBuilder.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, | 520 metadataBuilder.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, |
564 mMediaNotificationInfo.metadata.getArtist()); | 521 mMediaNotificationInfo.metadata.getArtist()); |
565 } | 522 } |
566 if (!TextUtils.isEmpty(mMediaNotificationInfo.metadata.getAlbum())) { | 523 if (!TextUtils.isEmpty(mMediaNotificationInfo.metadata.getAlbum())) { |
567 metadataBuilder.putString(MediaMetadataCompat.METADATA_KEY_ALBUM, | 524 metadataBuilder.putString(MediaMetadataCompat.METADATA_KEY_ALBUM, |
568 mMediaNotificationInfo.metadata.getAlbum()); | 525 mMediaNotificationInfo.metadata.getAlbum()); |
569 } | 526 } |
570 | 527 |
571 return metadataBuilder.build(); | 528 return metadataBuilder.build(); |
572 } | 529 } |
573 | 530 |
574 private void updateNotification() { | 531 private void updateNotification() { |
575 if (mService == null) return; | 532 if (mService == null) return; |
576 | 533 |
577 if (mMediaNotificationInfo == null) return; | 534 if (mMediaNotificationInfo == null) return; |
578 | 535 |
579 // Android doesn't badge the icons for RemoteViews automatically when | 536 updateMediaSession(); |
580 // running the app under the Work profile. | 537 |
581 if (mNotificationIcon == null) { | 538 mNotificationBuilder = new NotificationCompat.Builder(mContext); |
582 Drawable notificationIconDrawable = ApiCompatibilityUtils.getUserBad gedIcon( | 539 if (ChromeFeatureList.isEnabled(ChromeFeatureList.MEDIA_STYLE_NOTIFICATI ON)) { |
583 mContext, mMediaNotificationInfo.icon); | 540 setMediaStyleLayoutForNotificationBuilder(mNotificationBuilder); |
584 mNotificationIcon = drawableToBitmap(notificationIconDrawable); | 541 } else { |
542 setCustomLayoutForNotificationBuilder(mNotificationBuilder); | |
585 } | 543 } |
586 | 544 mNotificationBuilder.setSmallIcon(mMediaNotificationInfo.icon); |
587 if (mNotificationBuilder == null) { | 545 mNotificationBuilder.setAutoCancel(false); |
588 mNotificationBuilder = new NotificationCompat.Builder(mContext) | 546 mNotificationBuilder.setLocalOnly(true); |
589 .setSmallIcon(mMediaNotificationInfo.icon) | |
590 .setAutoCancel(false) | |
591 .setLocalOnly(true) | |
592 .setDeleteIntent(createPendingIntent(ListenerService.ACTION_STOP )); | |
593 } | |
594 | 547 |
595 if (mMediaNotificationInfo.supportsSwipeAway()) { | 548 if (mMediaNotificationInfo.supportsSwipeAway()) { |
596 mNotificationBuilder.setOngoing(!mMediaNotificationInfo.isPaused); | 549 mNotificationBuilder.setOngoing(!mMediaNotificationInfo.isPaused); |
597 } | 550 } |
598 | 551 |
599 // The intent will currently only be null when using a custom tab. | 552 // The intent will currently only be null when using a custom tab. |
600 // TODO(avayvod) work out what we should do in this case. See https://cr bug.com/585395. | 553 // TODO(avayvod) work out what we should do in this case. See https://cr bug.com/585395. |
601 if (mMediaNotificationInfo.contentIntent != null) { | 554 if (mMediaNotificationInfo.contentIntent != null) { |
602 mNotificationBuilder.setContentIntent(PendingIntent.getActivity(mCon text, | 555 mNotificationBuilder.setContentIntent(PendingIntent.getActivity(mCon text, |
603 mMediaNotificationInfo.tabId, | 556 mMediaNotificationInfo.tabId, |
604 mMediaNotificationInfo.contentIntent, 0)); | 557 mMediaNotificationInfo.contentIntent, 0)); |
605 } | 558 } |
606 | 559 |
607 mNotificationBuilder.setContent(createContentView()); | |
608 mNotificationBuilder.setVisibility( | 560 mNotificationBuilder.setVisibility( |
609 mMediaNotificationInfo.isPrivate ? NotificationCompat.VISIBILITY _PRIVATE | 561 mMediaNotificationInfo.isPrivate ? NotificationCompat.VISIBILITY _PRIVATE |
610 : NotificationCompat.VISIBILITY _PUBLIC); | 562 : NotificationCompat.VISIBILITY _PUBLIC); |
611 | 563 |
612 | |
613 if (mMediaNotificationInfo.supportsPlayPause()) { | |
614 | |
615 if (mMediaSession == null) mMediaSession = createMediaSession(); | |
616 try { | |
617 // Tell the MediaRouter about the session, so that Chrome can co ntrol the volume | |
618 // on the remote cast device (if any). | |
619 // Pre-MR1 versions of JB do not have the complete MediaRouter A PIs, | |
620 // so getting the MediaRouter instance will throw an exception. | |
621 MediaRouter.getInstance(mContext).setMediaSessionCompat(mMediaSe ssion); | |
622 } catch (NoSuchMethodError e) { | |
623 // Do nothing. Chrome can't be casting without a MediaRouter, so there is nothing | |
624 // to do here. | |
625 } | |
626 mMediaSession.setMetadata(createMetadata()); | |
627 | |
628 PlaybackStateCompat.Builder playbackStateBuilder = new PlaybackState Compat.Builder() | |
629 .setActions(PlaybackStateCompat.ACTION_PLAY | PlaybackStateC ompat.ACTION_PAUSE); | |
630 if (mMediaNotificationInfo.isPaused) { | |
631 playbackStateBuilder.setState(PlaybackStateCompat.STATE_PAUSED, | |
632 PlaybackStateCompat.PLAYBACK_POSITION_UNKNOWN, 1.0f); | |
633 } else { | |
634 playbackStateBuilder.setState(PlaybackStateCompat.STATE_PLAYING, | |
635 PlaybackStateCompat.PLAYBACK_POSITION_UNKNOWN, 1.0f); | |
636 } | |
637 mMediaSession.setPlaybackState(playbackStateBuilder.build()); | |
638 } | |
639 | |
640 Notification notification = mNotificationBuilder.build(); | 564 Notification notification = mNotificationBuilder.build(); |
641 | 565 |
642 // We keep the service as a foreground service while the media is playin g. When it is not, | 566 // We keep the service as a foreground service while the media is playin g. When it is not, |
643 // the service isn't stopped but is no longer in foreground, thus at a l ower priority. | 567 // the service isn't stopped but is no longer in foreground, thus at a l ower priority. |
644 // While the service is in foreground, the associated notification can't be swipped away. | 568 // While the service is in foreground, the associated notification can't be swipped away. |
645 // Moving it back to background allows the user to remove the notificati on. | 569 // Moving it back to background allows the user to remove the notificati on. |
646 if (mMediaNotificationInfo.supportsSwipeAway() && mMediaNotificationInfo .isPaused) { | 570 if (mMediaNotificationInfo.supportsSwipeAway() && mMediaNotificationInfo .isPaused) { |
647 mService.stopForeground(false /* removeNotification */); | 571 mService.stopForeground(false /* removeNotification */); |
648 | 572 |
649 NotificationManagerCompat manager = NotificationManagerCompat.from(m Context); | 573 NotificationManagerCompat manager = NotificationManagerCompat.from(m Context); |
650 manager.notify(mMediaNotificationInfo.id, notification); | 574 manager.notify(mMediaNotificationInfo.id, notification); |
651 } else { | 575 } else { |
652 mService.startForeground(mMediaNotificationInfo.id, notification); | 576 mService.startForeground(mMediaNotificationInfo.id, notification); |
653 } | 577 } |
654 } | 578 } |
655 | 579 |
580 private void updateMediaSession() { | |
581 if (!mMediaNotificationInfo.supportsPlayPause()) return; | |
582 | |
583 if (mMediaSession == null) mMediaSession = createMediaSession(); | |
584 | |
585 try { | |
586 // Tell the MediaRouter about the session, so that Chrome can contro l the volume | |
587 // on the remote cast device (if any). | |
588 // Pre-MR1 versions of JB do not have the complete MediaRouter APIs, | |
589 // so getting the MediaRouter instance will throw an exception. | |
590 MediaRouter.getInstance(mContext).setMediaSessionCompat(mMediaSessio n); | |
591 } catch (NoSuchMethodError e) { | |
592 // Do nothing. Chrome can't be casting without a MediaRouter, so the re is nothing | |
593 // to do here. | |
594 } | |
595 | |
596 mMediaSession.setMetadata(createMetadata()); | |
597 | |
598 PlaybackStateCompat.Builder playbackStateBuilder = | |
599 new PlaybackStateCompat.Builder().setActions( | |
600 PlaybackStateCompat.ACTION_PLAY | PlaybackStateCompat.AC TION_PAUSE); | |
601 if (mMediaNotificationInfo.isPaused) { | |
602 playbackStateBuilder.setState(PlaybackStateCompat.STATE_PAUSED, | |
603 PlaybackStateCompat.PLAYBACK_POSITION_UNKNOWN, 1.0f); | |
604 } else { | |
605 // If notification only supports stop, still pretend | |
606 playbackStateBuilder.setState(PlaybackStateCompat.STATE_PLAYING, | |
607 PlaybackStateCompat.PLAYBACK_POSITION_UNKNOWN, 1.0f); | |
608 } | |
609 mMediaSession.setPlaybackState(playbackStateBuilder.build()); | |
610 } | |
611 | |
656 private MediaSessionCompat createMediaSession() { | 612 private MediaSessionCompat createMediaSession() { |
657 MediaSessionCompat mediaSession = new MediaSessionCompat( | 613 MediaSessionCompat mediaSession = new MediaSessionCompat( |
658 mContext, | 614 mContext, |
659 mContext.getString(R.string.app_name), | 615 mContext.getString(R.string.app_name), |
660 new ComponentName(mContext.getPackageName(), | 616 new ComponentName(mContext.getPackageName(), |
661 getButtonReceiverClassName()), | 617 getButtonReceiverClassName()), |
662 null); | 618 null); |
663 mediaSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS | 619 mediaSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS |
664 | MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS); | 620 | MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS); |
665 mediaSession.setCallback(mMediaSessionCallback); | 621 mediaSession.setCallback(mMediaSessionCallback); |
666 | 622 |
667 // TODO(mlamouri): the following code is to work around a bug that hopef ully | 623 // TODO(mlamouri): the following code is to work around a bug that hopef ully |
668 // MediaSessionCompat will handle directly. see b/24051980. | 624 // MediaSessionCompat will handle directly. see b/24051980. |
669 try { | 625 try { |
670 mediaSession.setActive(true); | 626 mediaSession.setActive(true); |
671 } catch (NullPointerException e) { | 627 } catch (NullPointerException e) { |
672 // Some versions of KitKat do not support AudioManager.registerMedia ButtonIntent | 628 // Some versions of KitKat do not support AudioManager.registerMedia ButtonIntent |
673 // with a PendingIntent. They will throw a NullPointerException, in which case | 629 // with a PendingIntent. They will throw a NullPointerException, in which case |
674 // they should be able to activate a MediaSessionCompat with only tr ansport | 630 // they should be able to activate a MediaSessionCompat with only tr ansport |
675 // controls. | 631 // controls. |
676 mediaSession.setActive(false); | 632 mediaSession.setActive(false); |
677 mediaSession.setFlags(MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONT ROLS); | 633 mediaSession.setFlags(MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONT ROLS); |
678 mediaSession.setActive(true); | 634 mediaSession.setActive(true); |
679 } | 635 } |
680 return mediaSession; | 636 return mediaSession; |
681 } | 637 } |
682 | 638 |
639 private void setMediaStyleLayoutForNotificationBuilder(NotificationCompat.Bu ilder builder) { | |
640 // TODO(zqzhang): After we ship the new style, we should see how to pres ent the | |
641 // metadata.artist and metadata.album. See http://crbug.com/599937 | |
642 builder.setContentTitle(mMediaNotificationInfo.metadata.getTitle()); | |
643 builder.setContentText(mMediaNotificationInfo.origin); | |
644 // TODO(zqzhang): Update the default icon when a new one in provided. | |
645 // See http://crbug.com/600396. | |
646 if (mMediaNotificationInfo.largeIcon != null) { | |
647 builder.setLargeIcon(mMediaNotificationInfo.largeIcon); | |
648 } else { | |
649 builder.setLargeIcon(mDefaultMediaSessionImage); | |
650 } | |
651 // TODO(zqzhang): It's weird that setShowWhen() don't work on K. Calling setWhen() to force | |
652 // removing the time. | |
653 builder.setShowWhen(false).setWhen(0); | |
654 | |
655 // Only apply MediaStyle when NotificationInfo supports play/pause. | |
656 if (mMediaNotificationInfo.supportsPlayPause()) { | |
657 NotificationCompat.MediaStyle style = new NotificationCompat.MediaSt yle(); | |
658 style.setMediaSession(mMediaSession.getSessionToken()); | |
659 | |
660 if (mMediaNotificationInfo.isPaused) { | |
661 builder.addAction(R.drawable.ic_vidcontrol_play, mPlayDescriptio n, | |
662 createPendingIntent(ListenerService.ACTION_PLAY)); | |
663 } else { | |
664 // If we're here, the notification supports play/pause button an d is playing. | |
665 builder.addAction(R.drawable.ic_vidcontrol_pause, mPauseDescript ion, | |
666 createPendingIntent(ListenerService.ACTION_PAUSE)); | |
667 } | |
668 style.setShowActionsInCompactView(0); | |
669 style.setCancelButtonIntent(createPendingIntent(ListenerService.ACTI ON_STOP)); | |
670 style.setShowCancelButton(true); | |
671 builder.setStyle(style); | |
672 } | |
673 | |
674 if (mMediaNotificationInfo.supportsStop()) { | |
675 builder.addAction(R.drawable.ic_vidcontrol_stop, mStopDescription, | |
676 createPendingIntent(ListenerService.ACTION_STOP)); | |
677 } | |
678 } | |
679 | |
680 private void setCustomLayoutForNotificationBuilder(NotificationCompat.Builde r builder) { | |
681 builder.setContent(createContentView()); | |
682 } | |
683 | |
684 private RemoteViews createContentView() { | |
685 RemoteViews contentView = | |
686 new RemoteViews(mContext.getPackageName(), R.layout.playback_not ification_bar); | |
687 | |
688 // By default, play/pause button is the only one. | |
689 int playPauseButtonId = R.id.button1; | |
690 // On Android pre-L, dismissing the notification when the service is no longer in foreground | |
691 // doesn't work. Instead, a STOP button is shown. | |
692 if (mMediaNotificationInfo.supportsSwipeAway() | |
693 && Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP | |
694 || mMediaNotificationInfo.supportsStop()) { | |
Bernhard Bauer
2016/04/14 12:58:20
I don't quite see how this code matches the commen
Zhiqiang Zhang (Slow)
2016/04/14 13:09:17
MediaNotificationInfo.supportsSwipeAway() means th
| |
695 contentView.setOnClickPendingIntent( | |
696 R.id.button1, createPendingIntent(ListenerService.ACTION_STO P)); | |
697 contentView.setContentDescription(R.id.button1, mStopDescription); | |
698 | |
699 // If the play/pause needs to be shown, it moves over to the second button from the end. | |
700 playPauseButtonId = R.id.button2; | |
701 } | |
702 | |
703 contentView.setTextViewText(R.id.title, mMediaNotificationInfo.metadata. getTitle()); | |
704 contentView.setTextViewText(R.id.status, mMediaNotificationInfo.origin); | |
705 | |
706 // Android doesn't badge the icons for RemoteViews automatically when | |
707 // running the app under the Work profile. | |
708 if (mNotificationIcon == null) { | |
709 Drawable notificationIconDrawable = | |
710 ApiCompatibilityUtils.getUserBadgedIcon(mContext, mMediaNoti ficationInfo.icon); | |
711 mNotificationIcon = drawableToBitmap(notificationIconDrawable); | |
712 } | |
713 | |
714 if (mNotificationIcon != null) { | |
715 contentView.setImageViewBitmap(R.id.icon, mNotificationIcon); | |
716 } else { | |
717 contentView.setImageViewResource(R.id.icon, mMediaNotificationInfo.i con); | |
718 } | |
719 | |
720 if (mMediaNotificationInfo.supportsPlayPause()) { | |
721 if (mMediaNotificationInfo.isPaused) { | |
722 contentView.setImageViewResource(playPauseButtonId, R.drawable.i c_vidcontrol_play); | |
723 contentView.setContentDescription(playPauseButtonId, mPlayDescri ption); | |
724 contentView.setOnClickPendingIntent( | |
725 playPauseButtonId, createPendingIntent(ListenerService.A CTION_PLAY)); | |
726 } else { | |
727 // If we're here, the notification supports play/pause button an d is playing. | |
728 contentView.setImageViewResource(playPauseButtonId, R.drawable.i c_vidcontrol_pause); | |
729 contentView.setContentDescription(playPauseButtonId, mPauseDescr iption); | |
730 contentView.setOnClickPendingIntent( | |
731 playPauseButtonId, createPendingIntent(ListenerService.A CTION_PAUSE)); | |
732 } | |
733 | |
734 contentView.setViewVisibility(playPauseButtonId, View.VISIBLE); | |
735 } else { | |
736 contentView.setViewVisibility(playPauseButtonId, View.GONE); | |
737 } | |
738 | |
739 return contentView; | |
740 } | |
741 | |
742 /** | |
743 * Scales the Bitmap to make the notification background on Wearable devices look better. | |
744 * The returned Bitmap size will be exactly 400x400. | |
745 * According to http://developer.android.com/training/wearables/notification s/creating.html, | |
746 * the background image of Android Wear notifications should be of size 400x 400 or 640x400. | |
747 * Otherwise, it will be scaled to fit the desired size. However for some re ason (maybe battery | |
748 * concern), the smoothing filter is not applied when scaling the image, so we need to manually | |
749 * scale the image with filter applied before sending to Android Wear. | |
750 */ | |
751 private Bitmap scaleBitmapForWearable(Bitmap original) { | |
752 Bitmap result = Bitmap.createScaledBitmap(original, WEARABLE_NOTIFICATIO N_BACKGROUND_WIDTH, | |
753 WEARABLE_NOTIFICATION_BACKGROUND_HEIGHT, true); | |
754 return result; | |
755 } | |
756 | |
683 private Bitmap drawableToBitmap(Drawable drawable) { | 757 private Bitmap drawableToBitmap(Drawable drawable) { |
684 if (!(drawable instanceof BitmapDrawable)) return null; | 758 if (!(drawable instanceof BitmapDrawable)) return null; |
685 | 759 |
686 BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable; | 760 BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable; |
687 return bitmapDrawable.getBitmap(); | 761 return bitmapDrawable.getBitmap(); |
688 } | 762 } |
689 } | 763 } |
OLD | NEW |