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

Side by Side Diff: chrome/android/java/src/org/chromium/chrome/browser/media/ui/MediaNotificationManager.java

Issue 1847063005: [Media, UI] Change MediaNotification style to MediaStyle (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: addressed Mounir's comments, fixed time, origin and STOP_ACTION Created 4 years, 8 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 unified diff | Download patch
OLDNEW
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
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 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698