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 private static final int WEARABLE_NOTIFICATION_BACKGROUND_WIDTH = 400; |
50 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
| |
48 | 51 |
49 // We're always used on the UI thread but the LOCK is required by lint when creating the | 52 // We're always used on the UI thread but the LOCK is required by lint when creating the |
50 // singleton. | 53 // singleton. |
51 private static final Object LOCK = new Object(); | 54 private static final Object LOCK = new Object(); |
52 | 55 |
53 // Maps the notification ids to their corresponding notification managers. | 56 // Maps the notification ids to their corresponding notification managers. |
54 private static SparseArray<MediaNotificationManager> sManagers; | 57 private static SparseArray<MediaNotificationManager> sManagers; |
55 | 58 |
56 /** | 59 /** |
57 * Service used to transform intent requests triggered from the notification into | 60 * Service used to transform intent requests triggered from the notification into |
(...skipping 415 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
473 } | 476 } |
474 mContext.stopService(createIntent(mContext)); | 477 mContext.stopService(createIntent(mContext)); |
475 mMediaNotificationInfo = null; | 478 mMediaNotificationInfo = null; |
476 } | 479 } |
477 | 480 |
478 private void hideNotification(int tabId) { | 481 private void hideNotification(int tabId) { |
479 if (mMediaNotificationInfo == null || tabId != mMediaNotificationInfo.ta bId) return; | 482 if (mMediaNotificationInfo == null || tabId != mMediaNotificationInfo.ta bId) return; |
480 clearNotification(); | 483 clearNotification(); |
481 } | 484 } |
482 | 485 |
483 private RemoteViews createContentView() { | |
484 RemoteViews contentView = | |
485 new RemoteViews(mContext.getPackageName(), R.layout.playback_not ification_bar); | |
486 | |
487 // By default, play/pause button is the only one. | |
488 int playPauseButtonId = R.id.button1; | |
489 // On Android pre-L, dismissing the notification when the service is no longer in foreground | |
490 // doesn't work. Instead, a STOP button is shown. | |
491 if (mMediaNotificationInfo.supportsSwipeAway() | |
492 && Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP | |
493 || mMediaNotificationInfo.supportsStop()) { | |
494 contentView.setOnClickPendingIntent(R.id.button1, | |
495 createPendingIntent(ListenerService.ACTION_STOP)); | |
496 contentView.setContentDescription(R.id.button1, mStopDescription); | |
497 | |
498 // If the play/pause needs to be shown, it moves over to the second button from the end. | |
499 playPauseButtonId = R.id.button2; | |
500 } | |
501 | |
502 contentView.setTextViewText(R.id.title, mMediaNotificationInfo.metadata. getTitle()); | |
503 contentView.setTextViewText(R.id.status, mMediaNotificationInfo.origin); | |
504 if (mNotificationIcon != null) { | |
505 contentView.setImageViewBitmap(R.id.icon, mNotificationIcon); | |
506 } else { | |
507 contentView.setImageViewResource(R.id.icon, mMediaNotificationInfo.i con); | |
508 } | |
509 | |
510 if (mMediaNotificationInfo.supportsPlayPause()) { | |
511 if (mMediaNotificationInfo.isPaused) { | |
512 contentView.setImageViewResource(playPauseButtonId, R.drawable.i c_vidcontrol_play); | |
513 contentView.setContentDescription(playPauseButtonId, mPlayDescri ption); | |
514 contentView.setOnClickPendingIntent(playPauseButtonId, | |
515 createPendingIntent(ListenerService.ACTION_PLAY)); | |
516 } else { | |
517 // If we're here, the notification supports play/pause button an d is playing. | |
518 contentView.setImageViewResource(playPauseButtonId, R.drawable.i c_vidcontrol_pause); | |
519 contentView.setContentDescription(playPauseButtonId, mPauseDescr iption); | |
520 contentView.setOnClickPendingIntent(playPauseButtonId, | |
521 createPendingIntent(ListenerService.ACTION_PAUSE)); | |
522 } | |
523 | |
524 contentView.setViewVisibility(playPauseButtonId, View.VISIBLE); | |
525 } else { | |
526 contentView.setViewVisibility(playPauseButtonId, View.GONE); | |
527 } | |
528 | |
529 return contentView; | |
530 } | |
531 | |
532 private MediaMetadataCompat createMetadata() { | 486 private MediaMetadataCompat createMetadata() { |
533 MediaMetadataCompat.Builder metadataBuilder = new MediaMetadataCompat.Bu ilder(); | 487 MediaMetadataCompat.Builder metadataBuilder = new MediaMetadataCompat.Bu ilder(); |
534 | 488 |
535 // Choose the image to use as the icon. | 489 // Choose the image to use as the icon. |
536 Bitmap mediaSessionImage = mMediaNotificationInfo.image == null ? mDefau ltMediaSessionImage | 490 Bitmap mediaSessionImage = mMediaNotificationInfo.image == null ? mDefau ltMediaSessionImage |
537 : mMediaNotificationInfo.image; | 491 : mMediaNotificationInfo.image; |
538 | 492 |
539 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { | 493 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { |
540 metadataBuilder.putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_T ITLE, | 494 metadataBuilder.putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_T ITLE, |
541 mMediaNotificationInfo.metadata.getTitle()); | 495 mMediaNotificationInfo.metadata.getTitle()); |
542 metadataBuilder.putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_S UBTITLE, | 496 metadataBuilder.putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_S UBTITLE, |
543 mMediaNotificationInfo.origin); | 497 mMediaNotificationInfo.origin); |
544 metadataBuilder.putBitmap(MediaMetadataCompat.METADATA_KEY_DISPLAY_I CON, | 498 if (mMediaNotificationInfo.largeIcon == null) { |
545 mediaSessionImage); | 499 metadataBuilder.putBitmap(MediaMetadataCompat.METADATA_KEY_DISPL AY_ICON, |
500 mediaSessionImage); | |
501 } else { | |
502 metadataBuilder.putBitmap(MediaMetadataCompat.METADATA_KEY_DISPL AY_ICON, | |
503 scaleBitmapForWearable(mMediaNotificationInfo.largeIcon) ); | |
504 } | |
546 // 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 |
547 // better than the default image. | 506 // 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
| |
548 if (mMediaNotificationInfo.image != null) { | 507 if (mMediaNotificationInfo.image != null) { |
549 metadataBuilder.putBitmap(MediaMetadataCompat.METADATA_KEY_ART, | 508 metadataBuilder.putBitmap(MediaMetadataCompat.METADATA_KEY_ART, |
550 mMediaNotificationInfo.image); | 509 mMediaNotificationInfo.image); |
551 } | 510 } |
552 } else { | 511 } else { |
553 metadataBuilder.putString(MediaMetadataCompat.METADATA_KEY_TITLE, | 512 metadataBuilder.putString(MediaMetadataCompat.METADATA_KEY_TITLE, |
554 mMediaNotificationInfo.metadata.getTitle()); | 513 mMediaNotificationInfo.metadata.getTitle()); |
555 metadataBuilder.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, | 514 metadataBuilder.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, |
556 mMediaNotificationInfo.origin); | 515 mMediaNotificationInfo.origin); |
557 metadataBuilder.putBitmap(MediaMetadataCompat.METADATA_KEY_ART, medi aSessionImage); | 516 if (mMediaNotificationInfo.largeIcon == null) { |
517 metadataBuilder.putBitmap(MediaMetadataCompat.METADATA_KEY_ART, mediaSessionImage); | |
518 } else { | |
519 metadataBuilder.putBitmap(MediaMetadataCompat.METADATA_KEY_ART, | |
520 mMediaNotificationInfo.largeIcon); | |
521 } | |
558 } | 522 } |
559 | 523 |
560 if (!TextUtils.isEmpty(mMediaNotificationInfo.metadata.getArtist())) { | 524 if (!TextUtils.isEmpty(mMediaNotificationInfo.metadata.getArtist())) { |
561 metadataBuilder.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, | 525 metadataBuilder.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, |
562 mMediaNotificationInfo.metadata.getArtist()); | 526 mMediaNotificationInfo.metadata.getArtist()); |
563 } | 527 } |
564 if (!TextUtils.isEmpty(mMediaNotificationInfo.metadata.getAlbum())) { | 528 if (!TextUtils.isEmpty(mMediaNotificationInfo.metadata.getAlbum())) { |
565 metadataBuilder.putString(MediaMetadataCompat.METADATA_KEY_ALBUM, | 529 metadataBuilder.putString(MediaMetadataCompat.METADATA_KEY_ALBUM, |
566 mMediaNotificationInfo.metadata.getAlbum()); | 530 mMediaNotificationInfo.metadata.getAlbum()); |
567 } | 531 } |
568 | 532 |
569 return metadataBuilder.build(); | 533 return metadataBuilder.build(); |
570 } | 534 } |
571 | 535 |
572 private void updateNotification() { | 536 private void updateNotification() { |
573 if (mService == null) return; | 537 if (mService == null) return; |
574 | 538 |
575 if (mMediaNotificationInfo == null) return; | 539 if (mMediaNotificationInfo == null) return; |
576 | 540 |
577 // Android doesn't badge the icons for RemoteViews automatically when | 541 updateMediaSession(); |
578 // running the app under the Work profile. | 542 |
579 if (mNotificationIcon == null) { | 543 mNotificationBuilder = new NotificationCompat.Builder(mContext); |
580 Drawable notificationIconDrawable = ApiCompatibilityUtils.getUserBad gedIcon( | 544 if (ChromeFeatureList.isEnabled(ChromeFeatureList.USE_NEW_MEDIA_NOTIFICA TION)) { |
581 mContext, mMediaNotificationInfo.icon); | 545 setMediaStyleLayoutForNotificationBuilder(mNotificationBuilder); |
582 mNotificationIcon = drawableToBitmap(notificationIconDrawable); | 546 } else { |
547 setCustomLayoutForNotificationBuilder(mNotificationBuilder); | |
583 } | 548 } |
584 | 549 // TODO: smallIcon set to Chrome's icon? |
585 if (mNotificationBuilder == null) { | 550 mNotificationBuilder.setSmallIcon(mMediaNotificationInfo.icon); |
586 mNotificationBuilder = new NotificationCompat.Builder(mContext) | 551 mNotificationBuilder.setAutoCancel(false); |
587 .setSmallIcon(mMediaNotificationInfo.icon) | 552 mNotificationBuilder.setLocalOnly(true); |
588 .setAutoCancel(false) | 553 mNotificationBuilder.setDeleteIntent(createPendingIntent(ListenerService .ACTION_STOP)); |
589 .setLocalOnly(true) | |
590 .setDeleteIntent(createPendingIntent(ListenerService.ACTION_STOP )); | |
591 } | |
592 | 554 |
593 if (mMediaNotificationInfo.supportsSwipeAway()) { | 555 if (mMediaNotificationInfo.supportsSwipeAway()) { |
594 mNotificationBuilder.setOngoing(!mMediaNotificationInfo.isPaused); | 556 mNotificationBuilder.setOngoing(!mMediaNotificationInfo.isPaused); |
595 } | 557 } |
596 | 558 |
597 // The intent will currently only be null when using a custom tab. | 559 // The intent will currently only be null when using a custom tab. |
598 // TODO(avayvod) work out what we should do in this case. See https://cr bug.com/585395. | 560 // TODO(avayvod) work out what we should do in this case. See https://cr bug.com/585395. |
599 if (mMediaNotificationInfo.contentIntent != null) { | 561 if (mMediaNotificationInfo.contentIntent != null) { |
600 mNotificationBuilder.setContentIntent(PendingIntent.getActivity(mCon text, | 562 mNotificationBuilder.setContentIntent(PendingIntent.getActivity(mCon text, |
601 mMediaNotificationInfo.tabId, | 563 mMediaNotificationInfo.tabId, |
602 mMediaNotificationInfo.contentIntent, 0)); | 564 mMediaNotificationInfo.contentIntent, 0)); |
603 } | 565 } |
604 | 566 |
605 mNotificationBuilder.setContent(createContentView()); | |
606 mNotificationBuilder.setVisibility( | 567 mNotificationBuilder.setVisibility( |
607 mMediaNotificationInfo.isPrivate ? NotificationCompat.VISIBILITY _PRIVATE | 568 mMediaNotificationInfo.isPrivate ? NotificationCompat.VISIBILITY _PRIVATE |
608 : NotificationCompat.VISIBILITY _PUBLIC); | 569 : NotificationCompat.VISIBILITY _PUBLIC); |
609 | 570 |
610 | |
611 if (mMediaNotificationInfo.supportsPlayPause()) { | |
612 | |
613 if (mMediaSession == null) mMediaSession = createMediaSession(); | |
614 try { | |
615 // Tell the MediaRouter about the session, so that Chrome can co ntrol the volume | |
616 // on the remote cast device (if any). | |
617 // Pre-MR1 versions of JB do not have the complete MediaRouter A PIs, | |
618 // so getting the MediaRouter instance will throw an exception. | |
619 MediaRouter.getInstance(mContext).setMediaSessionCompat(mMediaSe ssion); | |
620 } catch (NoSuchMethodError e) { | |
621 // Do nothing. Chrome can't be casting without a MediaRouter, so there is nothing | |
622 // to do here. | |
623 } | |
624 mMediaSession.setMetadata(createMetadata()); | |
625 | |
626 PlaybackStateCompat.Builder playbackStateBuilder = new PlaybackState Compat.Builder() | |
627 .setActions(PlaybackStateCompat.ACTION_PLAY | PlaybackStateC ompat.ACTION_PAUSE); | |
628 if (mMediaNotificationInfo.isPaused) { | |
629 playbackStateBuilder.setState(PlaybackStateCompat.STATE_PAUSED, | |
630 PlaybackStateCompat.PLAYBACK_POSITION_UNKNOWN, 1.0f); | |
631 } else { | |
632 playbackStateBuilder.setState(PlaybackStateCompat.STATE_PLAYING, | |
633 PlaybackStateCompat.PLAYBACK_POSITION_UNKNOWN, 1.0f); | |
634 } | |
635 mMediaSession.setPlaybackState(playbackStateBuilder.build()); | |
636 } | |
637 | |
638 Notification notification = mNotificationBuilder.build(); | 571 Notification notification = mNotificationBuilder.build(); |
639 | 572 |
640 // We keep the service as a foreground service while the media is playin g. When it is not, | 573 // We keep the service as a foreground service while the media is playin g. When it is not, |
641 // the service isn't stopped but is no longer in foreground, thus at a l ower priority. | 574 // the service isn't stopped but is no longer in foreground, thus at a l ower priority. |
642 // While the service is in foreground, the associated notification can't be swipped away. | 575 // While the service is in foreground, the associated notification can't be swipped away. |
643 // Moving it back to background allows the user to remove the notificati on. | 576 // Moving it back to background allows the user to remove the notificati on. |
644 if (mMediaNotificationInfo.supportsSwipeAway() && mMediaNotificationInfo .isPaused) { | 577 if (mMediaNotificationInfo.supportsSwipeAway() && mMediaNotificationInfo .isPaused) { |
645 mService.stopForeground(false /* removeNotification */); | 578 mService.stopForeground(false /* removeNotification */); |
646 | 579 |
647 NotificationManagerCompat manager = NotificationManagerCompat.from(m Context); | 580 NotificationManagerCompat manager = NotificationManagerCompat.from(m Context); |
648 manager.notify(mMediaNotificationInfo.id, notification); | 581 manager.notify(mMediaNotificationInfo.id, notification); |
649 } else { | 582 } else { |
650 mService.startForeground(mMediaNotificationInfo.id, notification); | 583 mService.startForeground(mMediaNotificationInfo.id, notification); |
651 } | 584 } |
652 } | 585 } |
653 | 586 |
587 private void updateMediaSession() { | |
588 if (!mMediaNotificationInfo.supportsPlayPause()) return; | |
589 | |
590 if (mMediaSession == null) mMediaSession = createMediaSession(); | |
591 | |
592 try { | |
593 // Tell the MediaRouter about the session, so that Chrome can contro l the volume | |
594 // on the remote cast device (if any). | |
595 // Pre-MR1 versions of JB do not have the complete MediaRouter APIs, | |
596 // so getting the MediaRouter instance will throw an exception. | |
597 MediaRouter.getInstance(mContext).setMediaSessionCompat(mMediaSessio n); | |
598 } catch (NoSuchMethodError e) { | |
599 // Do nothing. Chrome can't be casting without a MediaRouter, so the re is nothing | |
600 // to do here. | |
601 } | |
602 | |
603 mMediaSession.setMetadata(createMetadata()); | |
604 | |
605 PlaybackStateCompat.Builder playbackStateBuilder = new PlaybackStateComp at.Builder() | |
606 .setActions(PlaybackStateCompat.ACTION_PLAY | PlaybackStateCompa t.ACTION_PAUSE); | |
607 if (mMediaNotificationInfo.isPaused) { | |
608 playbackStateBuilder.setState(PlaybackStateCompat.STATE_PAUSED, | |
609 PlaybackStateCompat.PLAYBACK_POSITION_UNKNOWN, 1.0f); | |
610 } else { | |
611 // If notification only supports stop, still pretend | |
612 playbackStateBuilder.setState(PlaybackStateCompat.STATE_PLAYING, | |
613 PlaybackStateCompat.PLAYBACK_POSITION_UNKNOWN, 1.0f); | |
614 } | |
615 mMediaSession.setPlaybackState(playbackStateBuilder.build()); | |
616 } | |
617 | |
654 private MediaSessionCompat createMediaSession() { | 618 private MediaSessionCompat createMediaSession() { |
655 MediaSessionCompat mediaSession = new MediaSessionCompat( | 619 MediaSessionCompat mediaSession = new MediaSessionCompat( |
656 mContext, | 620 mContext, |
657 mContext.getString(R.string.app_name), | 621 mContext.getString(R.string.app_name), |
658 new ComponentName(mContext.getPackageName(), | 622 new ComponentName(mContext.getPackageName(), |
659 getButtonReceiverClassName()), | 623 getButtonReceiverClassName()), |
660 null); | 624 null); |
661 mediaSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS | 625 mediaSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS |
662 | MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS); | 626 | MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS); |
663 mediaSession.setCallback(mMediaSessionCallback); | 627 mediaSession.setCallback(mMediaSessionCallback); |
664 | 628 |
665 // TODO(mlamouri): the following code is to work around a bug that hopef ully | 629 // TODO(mlamouri): the following code is to work around a bug that hopef ully |
666 // MediaSessionCompat will handle directly. see b/24051980. | 630 // MediaSessionCompat will handle directly. see b/24051980. |
667 try { | 631 try { |
668 mediaSession.setActive(true); | 632 mediaSession.setActive(true); |
669 } catch (NullPointerException e) { | 633 } catch (NullPointerException e) { |
670 // Some versions of KitKat do not support AudioManager.registerMedia ButtonIntent | 634 // Some versions of KitKat do not support AudioManager.registerMedia ButtonIntent |
671 // with a PendingIntent. They will throw a NullPointerException, in which case | 635 // with a PendingIntent. They will throw a NullPointerException, in which case |
672 // they should be able to activate a MediaSessionCompat with only tr ansport | 636 // they should be able to activate a MediaSessionCompat with only tr ansport |
673 // controls. | 637 // controls. |
674 mediaSession.setActive(false); | 638 mediaSession.setActive(false); |
675 mediaSession.setFlags(MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONT ROLS); | 639 mediaSession.setFlags(MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONT ROLS); |
676 mediaSession.setActive(true); | 640 mediaSession.setActive(true); |
677 } | 641 } |
678 return mediaSession; | 642 return mediaSession; |
679 } | 643 } |
680 | 644 |
645 private void setMediaStyleLayoutForNotificationBuilder(NotificationCompat.Bu ilder builder) { | |
646 NotificationCompat.MediaStyle style = new NotificationCompat.MediaStyle( ); | |
647 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
| |
648 style.setMediaSession(mMediaSession.getSessionToken()); | |
649 } | |
650 | |
651 // TODO: with custom notifications, can do: | |
652 // - setContentTitle for track title, | |
653 // - setContetText() for track artist, | |
654 // - setSubText() for track album | |
655 // 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.
| |
656 builder.setContentTitle(mMediaNotificationInfo.metadata.getTitle()); | |
657 builder.setContentText(mMediaNotificationInfo.origin); | |
658 // TODO: have our own "Media" icon so we can have large resolution in ca se of we have no | |
659 // large icon? | |
660 if (mMediaNotificationInfo.largeIcon != null) { | |
661 builder.setLargeIcon(mMediaNotificationInfo.largeIcon); | |
662 } else { | |
663 builder.setLargeIcon(mDefaultMediaSessionImage); | |
664 } | |
665 builder.setShowWhen(false); | |
666 | |
667 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.
| |
668 if (mMediaNotificationInfo.isPaused) { | |
669 builder.addAction(R.drawable.ic_vidcontrol_play, mPlayDescriptio n, | |
670 createPendingIntent(ListenerService.ACTION_PLAY)); | |
671 } else { | |
672 // If we're here, the notification supports play/pause button an d is playing. | |
673 builder.addAction(R.drawable.ic_vidcontrol_pause, mPauseDescript ion, | |
674 createPendingIntent(ListenerService.ACTION_PAUSE)); | |
675 } | |
676 style.setShowActionsInCompactView(0); | |
677 } | |
678 | |
679 if (mMediaNotificationInfo.supportsStop()) { | |
680 builder.addAction(R.drawable.ic_vidcontrol_stop, mStopDescription, | |
681 createPendingIntent(ListenerService.ACTION_STOP)); | |
682 } | |
683 if (mMediaSession != null) { | |
684 builder.setStyle(style); | |
685 } | |
686 } | |
687 | |
688 private void setCustomLayoutForNotificationBuilder(NotificationCompat.Builde r builder) { | |
689 builder.setContent(createContentView()); | |
690 } | |
691 | |
692 private RemoteViews createContentView() { | |
693 RemoteViews contentView = | |
694 new RemoteViews(mContext.getPackageName(), R.layout.playback_no tification_bar); | |
695 | |
696 // By default, play/pause button is the only one. | |
697 int playPauseButtonId = R.id.button1; | |
698 // On Android pre-L, dismissing the notification when the service is no longer in foreground | |
699 // doesn't work. Instead, a STOP button is shown. | |
700 if (mMediaNotificationInfo.supportsSwipeAway() | |
701 && Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP | |
702 || mMediaNotificationInfo.supportsStop()) { | |
703 contentView.setOnClickPendingIntent(R.id.button1, | |
704 createPendingIntent(ListenerService.ACTION_STOP)); | |
705 contentView.setContentDescription(R.id.button1, mStopDescription); | |
706 | |
707 // If the play/pause needs to be shown, it moves over to the second button from the end. | |
708 playPauseButtonId = R.id.button2; | |
709 } | |
710 | |
711 contentView.setTextViewText(R.id.title, mMediaNotificationInfo.metadata. getTitle()); | |
712 contentView.setTextViewText(R.id.status, mMediaNotificationInfo.origin); | |
713 | |
714 // Android doesn't badge the icons for RemoteViews automatically when | |
715 // running the app under the Work profile. | |
716 if (mNotificationIcon == null) { | |
717 Drawable notificationIconDrawable = ApiCompatibilityUtils.getUserBad gedIcon( | |
718 mContext, mMediaNotificationInfo.icon); | |
719 mNotificationIcon = drawableToBitmap(notificationIconDrawable); | |
720 } | |
721 | |
722 if (mNotificationIcon != null) { | |
723 contentView.setImageViewBitmap(R.id.icon, mNotificationIcon); | |
724 } else { | |
725 contentView.setImageViewResource(R.id.icon, mMediaNotificationInfo.i con); | |
726 } | |
727 | |
728 if (mMediaNotificationInfo.supportsPlayPause()) { | |
729 if (mMediaNotificationInfo.isPaused) { | |
730 contentView.setImageViewResource(playPauseButtonId, R.drawable.i c_vidcontrol_play); | |
731 contentView.setContentDescription(playPauseButtonId, mPlayDescri ption); | |
732 contentView.setOnClickPendingIntent(playPauseButtonId, | |
733 createPendingIntent(ListenerService.ACTION_PLAY)); | |
734 } else { | |
735 // If we're here, the notification supports play/pause button an d is playing. | |
736 contentView.setImageViewResource(playPauseButtonId, R.drawable.i c_vidcontrol_pause); | |
737 contentView.setContentDescription(playPauseButtonId, mPauseDescr iption); | |
738 contentView.setOnClickPendingIntent(playPauseButtonId, | |
739 createPendingIntent(ListenerService.ACTION_PAUSE)); | |
740 } | |
741 | |
742 contentView.setViewVisibility(playPauseButtonId, View.VISIBLE); | |
743 } else { | |
744 contentView.setViewVisibility(playPauseButtonId, View.GONE); | |
745 } | |
746 | |
747 return contentView; | |
748 } | |
749 | |
750 /** | |
751 * Scales the Bitmap to have better looking on Wearable devices. | |
752 * The returned Bitmap size will be exactly 400*400. | |
753 */ | |
754 private Bitmap scaleBitmapForWearable(Bitmap original) { | |
755 Bitmap result = Bitmap.createScaledBitmap( | |
756 original, | |
757 WEARABLE_NOTIFICATION_BACKGROUND_WIDTH, | |
758 WEARABLE_NOTIFICATION_BACKGROUND_HEIGHT, | |
759 true); | |
760 return result; | |
761 } | |
762 | |
681 private Bitmap drawableToBitmap(Drawable drawable) { | 763 private Bitmap drawableToBitmap(Drawable drawable) { |
682 if (!(drawable instanceof BitmapDrawable)) return null; | 764 if (!(drawable instanceof BitmapDrawable)) return null; |
683 | 765 |
684 BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable; | 766 BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable; |
685 return bitmapDrawable.getBitmap(); | 767 return bitmapDrawable.getBitmap(); |
686 } | 768 } |
687 } | 769 } |
OLD | NEW |