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