Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 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 | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 package org.chromium.chrome.browser.media.ui; | |
| 6 | |
| 7 import android.app.Notification; | |
| 8 import android.app.PendingIntent; | |
| 9 import android.app.Service; | |
| 10 import android.content.Context; | |
| 11 import android.content.Intent; | |
| 12 import android.os.IBinder; | |
| 13 import android.provider.Browser; | |
| 14 import android.support.v4.app.NotificationCompat; | |
| 15 import android.support.v4.app.NotificationManagerCompat; | |
| 16 import android.widget.RemoteViews; | |
| 17 | |
| 18 import org.chromium.chrome.R; | |
| 19 import org.chromium.chrome.browser.IntentHandler.TabOpenType; | |
| 20 import org.chromium.chrome.browser.Tab; | |
| 21 | |
| 22 /** | |
| 23 * A class for notifications that provide information and optional media control s for a given media. | |
| 24 * Internally implements a Service for transforming notification Intents into | |
| 25 * {@link MediaPlaybackListener} calls for all registered listeners. | |
| 26 */ | |
| 27 public class NotificationMediaPlaybackControls { | |
| 28 private static final Object LOCK = new Object(); | |
| 29 private static final int MSG_ID_UPDATE_NOTIFICATION = 100; | |
|
Ted C
2015/07/06 22:18:28
not used
whywhat
2015/07/07 15:44:58
Done.
| |
| 30 private static NotificationMediaPlaybackControls sInstance; | |
| 31 | |
| 32 /** | |
| 33 * Service used to transform intent requests triggered from the notification into | |
| 34 * {@code Listener} callbacks. Ideally this class should be private, but pub lic is required to | |
| 35 * create as a service. | |
| 36 */ | |
| 37 public static class ListenerService extends Service { | |
| 38 private static final String ACTION_PLAY = | |
| 39 "NotificationMediaPlaybackControls.ListenerService.PLAY"; | |
| 40 private static final String ACTION_PAUSE = | |
| 41 "NotificationMediaPlaybackControls.ListenerService.PAUSE"; | |
| 42 | |
| 43 private PendingIntent getPendingIntent(String action) { | |
| 44 Intent intent = new Intent(this, ListenerService.class); | |
| 45 intent.setAction(action); | |
| 46 return PendingIntent.getService(this, 0, intent, PendingIntent.FLAG_ CANCEL_CURRENT); | |
| 47 } | |
| 48 | |
| 49 @Override | |
| 50 public IBinder onBind(Intent intent) { | |
| 51 return null; | |
| 52 } | |
| 53 | |
| 54 @Override | |
| 55 public void onCreate() { | |
| 56 super.onCreate(); | |
| 57 onServiceStarted(this); | |
| 58 } | |
| 59 | |
| 60 @Override | |
| 61 public void onDestroy() { | |
|
Ted C
2015/07/06 22:18:29
should you call super.onDestroy() here as well?
whywhat
2015/07/07 15:44:58
Done.
| |
| 62 onServiceDestroyed(); | |
| 63 } | |
| 64 | |
| 65 @Override | |
| 66 public int onStartCommand(Intent intent, int flags, int startId) { | |
| 67 if (intent == null | |
| 68 || sInstance == null | |
| 69 || sInstance.mMediaInfo == null | |
| 70 || sInstance.mMediaInfo.listener == null) { | |
| 71 stopSelf(); | |
| 72 return START_NOT_STICKY; | |
| 73 } | |
| 74 | |
| 75 String action = intent.getAction(); | |
| 76 if (ACTION_PLAY.equals(action)) { | |
| 77 sInstance.mMediaInfo.listener.onPlay(); | |
| 78 sInstance.onPlaybackStateChanged(false); | |
| 79 } else if (ACTION_PAUSE.equals(action)) { | |
| 80 sInstance.mMediaInfo.listener.onPause(); | |
| 81 sInstance.onPlaybackStateChanged(true); | |
| 82 } | |
| 83 | |
| 84 return START_NOT_STICKY; | |
| 85 } | |
| 86 } | |
| 87 | |
| 88 /** | |
| 89 * Shows the notification with media controls with the specified media info. Replaces/updates | |
| 90 * the current notification if already showing. Does nothing if |mediaInfo| hasn't changed from | |
| 91 * the last one. | |
| 92 * | |
| 93 * @param applicationContext context to create the notification with | |
| 94 * @param mediaInfo information to show in the notification | |
| 95 */ | |
| 96 public static void show(Context applicationContext, MediaInfo mediaInfo) { | |
| 97 synchronized (LOCK) { | |
| 98 if (sInstance == null) { | |
| 99 sInstance = new NotificationMediaPlaybackControls(applicationCon text); | |
| 100 } | |
| 101 } | |
| 102 sInstance.showNotification(mediaInfo); | |
| 103 } | |
| 104 | |
| 105 /** | |
| 106 * Hides the notification for the specified tabId. If tabId equals to | |
| 107 * {@link Tab#INVALID_TAB_ID}, hides any notification shown. | |
| 108 * | |
| 109 * @param tabId the id of the tab that showed the notification or invalid ta b id. | |
| 110 */ | |
| 111 public static void hide(int tabId) { | |
| 112 if (sInstance == null) return; | |
| 113 sInstance.hideNotification(tabId); | |
| 114 } | |
| 115 | |
| 116 /** | |
| 117 * Registers the started {@link Service} with the singleton and creates the notification. | |
| 118 * | |
| 119 * @param service the service that was started | |
| 120 */ | |
| 121 private static void onServiceStarted(ListenerService service) { | |
| 122 assert sInstance != null; | |
| 123 assert sInstance.mService == null; | |
| 124 sInstance.mService = service; | |
| 125 sInstance.createNotification(); | |
| 126 } | |
| 127 | |
| 128 /** | |
| 129 * Handles the destruction | |
| 130 */ | |
| 131 private static void onServiceDestroyed() { | |
| 132 assert sInstance != null; | |
| 133 assert sInstance.mService != null; | |
| 134 sInstance.destroyNotification(); | |
| 135 sInstance.mService = null; | |
| 136 } | |
| 137 | |
| 138 private final Context mContext; | |
| 139 | |
| 140 // ListenerService running for the notification. Only non-null when showing. | |
| 141 private ListenerService mService; | |
| 142 | |
| 143 private final String mPlayDescription; | |
| 144 | |
| 145 private final String mPauseDescription; | |
| 146 | |
| 147 private Notification mNotification; | |
| 148 | |
| 149 private MediaInfo mMediaInfo; | |
| 150 | |
| 151 private NotificationMediaPlaybackControls(Context context) { | |
| 152 mContext = context; | |
| 153 mPlayDescription = context.getResources().getString(R.string.accessibili ty_play); | |
| 154 mPauseDescription = context.getResources().getString(R.string.accessibil ity_pause); | |
| 155 } | |
| 156 | |
| 157 private void showNotification(MediaInfo mediaInfo) { | |
| 158 mContext.startService(new Intent(mContext, ListenerService.class)); | |
| 159 | |
| 160 assert mediaInfo != null; | |
| 161 | |
| 162 if (mediaInfo.equals(mMediaInfo)) return; | |
| 163 | |
| 164 mMediaInfo = mediaInfo; | |
| 165 updateNotification(); | |
| 166 } | |
| 167 | |
| 168 private void hideNotification(int tabId) { | |
| 169 if (mMediaInfo == null || (tabId != Tab.INVALID_TAB_ID && tabId != mMedi aInfo.tabId)) { | |
|
Ted C
2015/07/06 22:18:28
If you have a problem with a dangling notification
whywhat
2015/07/07 15:44:58
Done.
| |
| 170 return; | |
| 171 } | |
| 172 | |
| 173 mMediaInfo = null; | |
| 174 mContext.stopService(new Intent(mContext, ListenerService.class)); | |
| 175 } | |
| 176 | |
| 177 private void onPlaybackStateChanged(boolean isPaused) { | |
| 178 assert mMediaInfo != null; | |
| 179 mMediaInfo = new MediaInfo( | |
| 180 mMediaInfo.title, | |
| 181 isPaused, | |
| 182 mMediaInfo.origin, | |
| 183 mMediaInfo.tabId, | |
| 184 mMediaInfo.listener); | |
| 185 updateNotification(); | |
| 186 } | |
| 187 | |
| 188 private RemoteViews createContentView() { | |
| 189 RemoteViews contentView = | |
| 190 new RemoteViews(getContext().getPackageName(), R.layout.playback _notification_bar); | |
| 191 return contentView; | |
| 192 } | |
| 193 | |
| 194 private void createNotification() { | |
| 195 NotificationCompat.Builder notificationBuilder = | |
|
Ted C
2015/07/06 22:18:28
I would just inline this in updateNotification. Y
whywhat
2015/07/07 15:44:58
Done.
| |
| 196 new NotificationCompat.Builder(getContext()) | |
| 197 .setSmallIcon(R.drawable.audio_playing) | |
| 198 .setAutoCancel(false) | |
| 199 .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) | |
| 200 .setOngoing(true) | |
| 201 .setContent(createContentView()) | |
| 202 .setContentIntent(createContentIntent()); | |
| 203 mNotification = notificationBuilder.build(); | |
| 204 updateNotification(); | |
| 205 } | |
| 206 | |
| 207 private void destroyNotification() { | |
| 208 NotificationManagerCompat manager = NotificationManagerCompat.from(getCo ntext()); | |
| 209 manager.cancel(R.id.media_playback_notification); | |
| 210 | |
| 211 mNotification = null; | |
| 212 } | |
| 213 | |
| 214 private final Context getContext() { | |
|
Ted C
2015/07/06 22:18:28
remove this and just inline mContext everywhere.
whywhat
2015/07/07 15:44:57
Done.
| |
| 215 return mContext; | |
| 216 } | |
| 217 | |
| 218 private String getStatus() { | |
| 219 Context context = getContext(); | |
| 220 return context.getString(R.string.media_notification_link_text, | |
| 221 mMediaInfo.origin != null ? mMediaInfo.origin : ""); | |
|
Ted C
2015/07/06 22:18:28
You should probably have some Unknown URL or somet
whywhat
2015/07/07 15:44:57
Done.
| |
| 222 } | |
| 223 | |
| 224 private String getTitle() { | |
| 225 Context context = getContext(); | |
| 226 String mediaTitle = mMediaInfo.title; | |
| 227 if (mMediaInfo.isPaused) { | |
| 228 return context.getString( | |
| 229 R.string.media_playback_notification_paused_for_media, media Title); | |
| 230 } | |
| 231 return context.getString( | |
| 232 R.string.media_playback_notification_playing_for_media, mediaTit le); | |
| 233 } | |
| 234 | |
| 235 private PendingIntent createContentIntent() { | |
| 236 int tabId = mMediaInfo.tabId; | |
| 237 Intent intent = new Intent(Intent.ACTION_MAIN); | |
| 238 intent.putExtra(Browser.EXTRA_APPLICATION_ID, getContext().getPackageNam e()); | |
| 239 intent.putExtra(TabOpenType.BRING_TAB_TO_FRONT.name(), tabId); | |
| 240 intent.setPackage(mContext.getPackageName()); | |
| 241 return PendingIntent.getActivity(getContext(), tabId, intent, 0); | |
| 242 } | |
| 243 | |
| 244 private void updateNotification() { | |
| 245 if (mService == null) return; | |
| 246 | |
| 247 if (mMediaInfo == null) { | |
| 248 // Notification was hidden before we could update it. | |
| 249 assert mNotification == null; | |
| 250 return; | |
| 251 } | |
| 252 | |
| 253 RemoteViews contentView = createContentView(); | |
| 254 | |
| 255 contentView.setTextViewText(R.id.title, getTitle()); | |
| 256 contentView.setTextViewText(R.id.status, getStatus()); | |
| 257 contentView.setImageViewResource(R.id.icon, R.drawable.audio_playing); | |
| 258 | |
| 259 if (mMediaInfo.isPaused) { | |
| 260 contentView.setImageViewResource(R.id.playpause, R.drawable.ic_vidco ntrol_play); | |
| 261 contentView.setContentDescription(R.id.playpause, mPlayDescription); | |
| 262 contentView.setOnClickPendingIntent(R.id.playpause, | |
| 263 mService.getPendingIntent(ListenerService.ACTION_PLAY)); | |
| 264 } else { | |
| 265 contentView.setImageViewResource(R.id.playpause, R.drawable.ic_vidco ntrol_pause); | |
| 266 contentView.setContentDescription(R.id.playpause, mPauseDescription) ; | |
| 267 contentView.setOnClickPendingIntent(R.id.playpause, | |
| 268 mService.getPendingIntent(ListenerService.ACTION_PAUSE)); | |
| 269 } | |
| 270 | |
| 271 mNotification.contentView = contentView; | |
| 272 | |
| 273 NotificationManagerCompat manager = NotificationManagerCompat.from(getCo ntext()); | |
| 274 manager.notify(R.id.media_playback_notification, mNotification); | |
|
Ted C
2015/07/06 22:18:28
I would use a notification namespace here (and it'
whywhat
2015/07/07 15:44:58
So we actually don't need NotificationManager at a
| |
| 275 | |
| 276 mService.startForeground(R.id.media_playback_notification, mNotification ); | |
| 277 } | |
| 278 } | |
| OLD | NEW |