| 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.ComponentName; | 10 import android.content.ComponentName; |
| 11 import android.content.Context; | 11 import android.content.Context; |
| 12 import android.content.Intent; | 12 import android.content.Intent; |
| 13 import android.graphics.Bitmap; | 13 import android.graphics.Bitmap; |
| 14 import android.graphics.drawable.BitmapDrawable; | 14 import android.graphics.drawable.BitmapDrawable; |
| 15 import android.graphics.drawable.Drawable; | 15 import android.graphics.drawable.Drawable; |
| 16 import android.os.Build; | 16 import android.os.Build; |
| 17 import android.os.IBinder; | 17 import android.os.IBinder; |
| 18 import android.support.v4.app.NotificationCompat; | 18 import android.support.v4.app.NotificationCompat; |
| 19 import android.support.v4.app.NotificationManagerCompat; | 19 import android.support.v4.app.NotificationManagerCompat; |
| 20 import android.support.v4.media.MediaMetadataCompat; | 20 import android.support.v4.media.MediaMetadataCompat; |
| 21 import android.support.v4.media.session.MediaSessionCompat; | 21 import android.support.v4.media.session.MediaSessionCompat; |
| 22 import android.support.v4.media.session.PlaybackStateCompat; | 22 import android.support.v4.media.session.PlaybackStateCompat; |
| 23 import android.util.SparseArray; | 23 import android.util.SparseArray; |
| 24 import android.view.KeyEvent; | 24 import android.view.KeyEvent; |
| 25 import android.view.View; | 25 import android.view.View; |
| 26 import android.widget.RemoteViews; | 26 import android.widget.RemoteViews; |
| 27 | 27 |
| 28 import org.chromium.base.ApiCompatibilityUtils; | 28 import org.chromium.base.ApiCompatibilityUtils; |
| 29 import org.chromium.base.Log; |
| 29 import org.chromium.chrome.R; | 30 import org.chromium.chrome.R; |
| 30 import org.chromium.chrome.browser.tab.Tab; | 31 import org.chromium.chrome.browser.tab.Tab; |
| 31 | 32 |
| 32 /** | 33 /** |
| 33 * A class for notifications that provide information and optional media control
s for a given media. | 34 * A class for notifications that provide information and optional media control
s for a given media. |
| 34 * Internally implements a Service for transforming notification Intents into | 35 * Internally implements a Service for transforming notification Intents into |
| 35 * {@link MediaNotificationListener} calls for all registered listeners. | 36 * {@link MediaNotificationListener} calls for all registered listeners. |
| 36 * There's one service started for a distinct notification id. | 37 * There's one service started for a distinct notification id. |
| 37 */ | 38 */ |
| 38 public class MediaNotificationManager { | 39 public class MediaNotificationManager { |
| 39 | 40 |
| 41 private static final String TAG = "cr_MediaNotification"; |
| 42 |
| 40 // We're always used on the UI thread but the LOCK is required by lint when
creating the | 43 // We're always used on the UI thread but the LOCK is required by lint when
creating the |
| 41 // singleton. | 44 // singleton. |
| 42 private static final Object LOCK = new Object(); | 45 private static final Object LOCK = new Object(); |
| 43 | 46 |
| 44 // Maps the notification ids to their corresponding notification managers. | 47 // Maps the notification ids to their corresponding notification managers. |
| 45 private static SparseArray<MediaNotificationManager> sManagers; | 48 private static SparseArray<MediaNotificationManager> sManagers; |
| 46 | 49 |
| 47 /** | 50 /** |
| 48 * Service used to transform intent requests triggered from the notification
into | 51 * Service used to transform intent requests triggered from the notification
into |
| 49 * {@code MediaNotificationListener} callbacks. We have to create a separate
derived class for | 52 * {@code MediaNotificationListener} callbacks. We have to create a separate
derived class for |
| 50 * each type of notification since one class corresponds to one instance of
the service only. | 53 * each type of notification since one class corresponds to one instance of
the service only. |
| 51 */ | 54 */ |
| 52 private abstract static class ListenerService extends Service { | 55 private abstract static class ListenerService extends Service { |
| 53 private static final String ACTION_PLAY = | 56 private static final String ACTION_PLAY = |
| 54 "MediaNotificationManager.ListenerService.PLAY"; | 57 "MediaNotificationManager.ListenerService.PLAY"; |
| 55 private static final String ACTION_PAUSE = | 58 private static final String ACTION_PAUSE = |
| 56 "MediaNotificationManager.ListenerService.PAUSE"; | 59 "MediaNotificationManager.ListenerService.PAUSE"; |
| 57 private static final String ACTION_STOP = | 60 private static final String ACTION_STOP = |
| 58 "MediaNotificationManager.ListenerService.STOP"; | 61 "MediaNotificationManager.ListenerService.STOP"; |
| 59 private static final String EXTRA_NOTIFICATION_ID = | 62 private static final String EXTRA_NOTIFICATION_ID = |
| 60 "MediaNotificationManager.ListenerService.NOTIFICATION_ID"; | 63 MediaButtonReceiver.EXTRA_NOTIFICATION_ID; |
| 61 | 64 |
| 62 // The notification id this service instance corresponds to. | 65 // The notification id this service instance corresponds to. |
| 63 private int mNotificationId = MediaNotificationInfo.INVALID_ID; | 66 private int mNotificationId = MediaNotificationInfo.INVALID_ID; |
| 64 | 67 |
| 65 private PendingIntent getPendingIntent(String action) { | 68 private PendingIntent getPendingIntent(String action) { |
| 66 Intent intent = getIntent(this, mNotificationId).setAction(action); | 69 Intent intent = getIntent(this, mNotificationId).setAction(action); |
| 67 return PendingIntent.getService(this, 0, intent, PendingIntent.FLAG_
CANCEL_CURRENT); | 70 return PendingIntent.getService(this, 0, intent, PendingIntent.FLAG_
CANCEL_CURRENT); |
| 68 } | 71 } |
| 69 | 72 |
| 70 @Override | 73 @Override |
| (...skipping 14 matching lines...) Expand all Loading... |
| 85 @Override | 88 @Override |
| 86 public int onStartCommand(Intent intent, int flags, int startId) { | 89 public int onStartCommand(Intent intent, int flags, int startId) { |
| 87 if (!processIntent(intent)) stopSelf(); | 90 if (!processIntent(intent)) stopSelf(); |
| 88 | 91 |
| 89 return START_NOT_STICKY; | 92 return START_NOT_STICKY; |
| 90 } | 93 } |
| 91 | 94 |
| 92 private boolean processIntent(Intent intent) { | 95 private boolean processIntent(Intent intent) { |
| 93 if (intent == null) return false; | 96 if (intent == null) return false; |
| 94 | 97 |
| 95 mNotificationId = intent.getIntExtra( | 98 int notificationId = intent.getIntExtra( |
| 96 EXTRA_NOTIFICATION_ID, MediaNotificationInfo.INVALID_ID); | 99 EXTRA_NOTIFICATION_ID, MediaNotificationInfo.INVALID_ID); |
| 97 if (mNotificationId == MediaNotificationInfo.INVALID_ID) return fals
e; | 100 |
| 101 // The notification id must always be valid and should match the fir
st notification id |
| 102 // the service got via the intent. |
| 103 if (notificationId == MediaNotificationInfo.INVALID_ID |
| 104 || (mNotificationId != MediaNotificationInfo.INVALID_ID |
| 105 && mNotificationId != notificationId)) { |
| 106 Log.w(TAG, "The service intent's notification id is invalid: ",
notificationId); |
| 107 return false; |
| 108 } |
| 109 |
| 110 // Either the notification id matches or it's the first intent we've
got. |
| 111 mNotificationId = notificationId; |
| 112 |
| 113 assert mNotificationId != MediaNotificationInfo.INVALID_ID; |
| 98 | 114 |
| 99 MediaNotificationManager manager = getManager(mNotificationId); | 115 MediaNotificationManager manager = getManager(mNotificationId); |
| 100 if (manager == null || manager.mMediaNotificationInfo == null) retur
n false; | 116 if (manager == null || manager.mMediaNotificationInfo == null) retur
n false; |
| 101 | 117 |
| 102 manager.onServiceStarted(this); | 118 manager.onServiceStarted(this); |
| 103 | 119 |
| 104 processAction(intent, manager); | 120 processAction(intent, manager); |
| 105 return true; | 121 return true; |
| 106 } | 122 } |
| 107 | 123 |
| (...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 157 private static final int NOTIFICATION_ID = R.id.media_playback_notificat
ion; | 173 private static final int NOTIFICATION_ID = R.id.media_playback_notificat
ion; |
| 158 } | 174 } |
| 159 | 175 |
| 160 /** | 176 /** |
| 161 * This class is used internally but have to be public to be able to launch
the service. | 177 * This class is used internally but have to be public to be able to launch
the service. |
| 162 */ | 178 */ |
| 163 public static final class PresentationListenerService extends ListenerServic
e { | 179 public static final class PresentationListenerService extends ListenerServic
e { |
| 164 private static final int NOTIFICATION_ID = R.id.presentation_notificatio
n; | 180 private static final int NOTIFICATION_ID = R.id.presentation_notificatio
n; |
| 165 } | 181 } |
| 166 | 182 |
| 183 // Two classes to specify the right notification id in the intent. |
| 184 |
| 185 /** |
| 186 * This class is used internally but have to be public to be able to launch
the service. |
| 187 */ |
| 188 public static final class PlaybackMediaButtonReceiver extends MediaButtonRec
eiver { |
| 189 @Override |
| 190 public int getNotificationId() { |
| 191 return PlaybackListenerService.NOTIFICATION_ID; |
| 192 } |
| 193 } |
| 194 |
| 195 /** |
| 196 * This class is used internally but have to be public to be able to launch
the service. |
| 197 */ |
| 198 public static final class PresentationMediaButtonReceiver extends MediaButto
nReceiver { |
| 199 @Override |
| 200 public int getNotificationId() { |
| 201 return PresentationListenerService.NOTIFICATION_ID; |
| 202 } |
| 203 } |
| 204 |
| 167 private static Intent getIntent(Context context, int notificationId) { | 205 private static Intent getIntent(Context context, int notificationId) { |
| 168 Intent intent = null; | 206 Intent intent = null; |
| 169 if (notificationId == PlaybackListenerService.NOTIFICATION_ID) { | 207 if (notificationId == PlaybackListenerService.NOTIFICATION_ID) { |
| 170 intent = new Intent(context, PlaybackListenerService.class); | 208 intent = new Intent(context, PlaybackListenerService.class); |
| 171 } else if (notificationId == PresentationListenerService.NOTIFICATION_ID
) { | 209 } else if (notificationId == PresentationListenerService.NOTIFICATION_ID
) { |
| 172 intent = new Intent(context, PresentationListenerService.class); | 210 intent = new Intent(context, PresentationListenerService.class); |
| 173 } else { | 211 } else { |
| 174 return null; | 212 return null; |
| 175 } | 213 } |
| 176 return intent.putExtra(ListenerService.EXTRA_NOTIFICATION_ID, notificati
onId); | 214 return intent.putExtra(ListenerService.EXTRA_NOTIFICATION_ID, notificati
onId); |
| 177 } | 215 } |
| 178 | 216 |
| 217 private static String getButtonReceiverClassName(int notificationId) { |
| 218 if (notificationId == PlaybackListenerService.NOTIFICATION_ID) { |
| 219 return PlaybackMediaButtonReceiver.class.getName(); |
| 220 } |
| 221 |
| 222 if (notificationId == PresentationListenerService.NOTIFICATION_ID) { |
| 223 return PresentationMediaButtonReceiver.class.getName(); |
| 224 } |
| 225 |
| 226 assert false; |
| 227 return null; |
| 228 } |
| 229 |
| 179 /** | 230 /** |
| 180 * Shows the notification with media controls with the specified media info.
Replaces/updates | 231 * Shows the notification with media controls with the specified media info.
Replaces/updates |
| 181 * the current notification if already showing. Does nothing if |mediaNotifi
cationInfo| hasn't | 232 * the current notification if already showing. Does nothing if |mediaNotifi
cationInfo| hasn't |
| 182 * changed from the last one. | 233 * changed from the last one. |
| 183 * | 234 * |
| 184 * @param applicationContext context to create the notification with | 235 * @param applicationContext context to create the notification with |
| 185 * @param notificationInfoBuilder information to show in the notification | 236 * @param notificationInfoBuilder information to show in the notification |
| 186 */ | 237 */ |
| 187 public static void show(Context applicationContext, | 238 public static void show(Context applicationContext, |
| 188 MediaNotificationInfo.Builder notificationInfoBuilde
r) { | 239 MediaNotificationInfo.Builder notificationInfoBuilde
r) { |
| (...skipping 339 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 528 } else { | 579 } else { |
| 529 mService.startForeground(mMediaNotificationInfo.id, notification); | 580 mService.startForeground(mMediaNotificationInfo.id, notification); |
| 530 } | 581 } |
| 531 } | 582 } |
| 532 | 583 |
| 533 private MediaSessionCompat createMediaSession() { | 584 private MediaSessionCompat createMediaSession() { |
| 534 MediaSessionCompat mediaSession = new MediaSessionCompat( | 585 MediaSessionCompat mediaSession = new MediaSessionCompat( |
| 535 mContext, | 586 mContext, |
| 536 mContext.getString(R.string.app_name), | 587 mContext.getString(R.string.app_name), |
| 537 new ComponentName(mContext.getPackageName(), | 588 new ComponentName(mContext.getPackageName(), |
| 538 MediaButtonReceiver.class.getName()), | 589 getButtonReceiverClassName(mMediaNotificationInfo.id)), |
| 539 null); | 590 null); |
| 540 mediaSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS | 591 mediaSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS |
| 541 | MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS); | 592 | MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS); |
| 542 mediaSession.setCallback(mMediaSessionCallback); | 593 mediaSession.setCallback(mMediaSessionCallback); |
| 543 | 594 |
| 544 // TODO(mlamouri): the following code is to work around a bug that hopef
ully | 595 // TODO(mlamouri): the following code is to work around a bug that hopef
ully |
| 545 // MediaSessionCompat will handle directly. see b/24051980. | 596 // MediaSessionCompat will handle directly. see b/24051980. |
| 546 try { | 597 try { |
| 547 mediaSession.setActive(true); | 598 mediaSession.setActive(true); |
| 548 } catch (NullPointerException e) { | 599 } catch (NullPointerException e) { |
| 549 // Some versions of KitKat do not support AudioManager.registerMedia
ButtonIntent | 600 // Some versions of KitKat do not support AudioManager.registerMedia
ButtonIntent |
| 550 // with a PendingIntent. They will throw a NullPointerException, in
which case | 601 // with a PendingIntent. They will throw a NullPointerException, in
which case |
| 551 // they should be able to activate a MediaSessionCompat with only tr
ansport | 602 // they should be able to activate a MediaSessionCompat with only tr
ansport |
| 552 // controls. | 603 // controls. |
| 553 mediaSession.setActive(false); | 604 mediaSession.setActive(false); |
| 554 mediaSession.setFlags(MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONT
ROLS); | 605 mediaSession.setFlags(MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONT
ROLS); |
| 555 mediaSession.setActive(true); | 606 mediaSession.setActive(true); |
| 556 } | 607 } |
| 557 return mediaSession; | 608 return mediaSession; |
| 558 } | 609 } |
| 559 | 610 |
| 560 private Bitmap drawableToBitmap(Drawable drawable) { | 611 private Bitmap drawableToBitmap(Drawable drawable) { |
| 561 if (!(drawable instanceof BitmapDrawable)) return null; | 612 if (!(drawable instanceof BitmapDrawable)) return null; |
| 562 | 613 |
| 563 BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable; | 614 BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable; |
| 564 return bitmapDrawable.getBitmap(); | 615 return bitmapDrawable.getBitmap(); |
| 565 } | 616 } |
| 566 } | 617 } |
| OLD | NEW |