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 |