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 |
| 22 import java.util.Set; |
| 23 |
| 24 /** |
| 25 * A class for notifications that provide information and optional media control
s for a given media. |
| 26 * Internally implements a Service for transforming notification Intents into |
| 27 * {@link MediaPlaybackControls.Listener} calls for all registered listeners. |
| 28 * |
| 29 */ |
| 30 public class NotificationMediaPlaybackControls extends MediaPlaybackControls { |
| 31 /** |
| 32 * Service used to transform intent requests triggered from the notification
into |
| 33 * {@code Listener} callbacks. Ideally this class should be protected, but p
ublic is required to |
| 34 * create as a service. |
| 35 */ |
| 36 public static class ListenerService extends Service { |
| 37 private static final String ACTION_PREFIX = |
| 38 "NotificationMediaPlaybackControls.ListenerService."; |
| 39 |
| 40 // Constants used by intent actions |
| 41 public static final int ACTION_ID_PLAY = 0; |
| 42 public static final int ACTION_ID_PAUSE = 1; |
| 43 |
| 44 // Must be kept in sync with the ACTION_ID_XXX constants above |
| 45 private static final String[] ACTION_VERBS = { "PLAY", "PAUSE" }; |
| 46 |
| 47 private PendingIntent[] mPendingIntents; |
| 48 |
| 49 PendingIntent getPendingIntent(int id) { |
| 50 return mPendingIntents[id]; |
| 51 } |
| 52 |
| 53 @Override |
| 54 public IBinder onBind(Intent intent) { |
| 55 return null; |
| 56 } |
| 57 |
| 58 @Override |
| 59 public void onCreate() { |
| 60 super.onCreate(); |
| 61 |
| 62 // Create all the PendingIntents |
| 63 int actionCount = ACTION_VERBS.length; |
| 64 mPendingIntents = new PendingIntent[actionCount]; |
| 65 for (int i = 0; i < actionCount; ++i) { |
| 66 Intent intent = new Intent(this, ListenerService.class); |
| 67 intent.setAction(ACTION_PREFIX + ACTION_VERBS[i]); |
| 68 mPendingIntents[i] = PendingIntent.getService(this, 0, intent, |
| 69 PendingIntent.FLAG_CANCEL_CURRENT); |
| 70 } |
| 71 onServiceStarted(this); |
| 72 } |
| 73 |
| 74 @Override |
| 75 public void onDestroy() { |
| 76 onServiceDestroyed(); |
| 77 } |
| 78 |
| 79 @Override |
| 80 public int onStartCommand(Intent intent, int flags, int startId) { |
| 81 if (intent == null) { |
| 82 stopSelf(); |
| 83 return START_NOT_STICKY; |
| 84 } |
| 85 |
| 86 String action = intent.getAction(); |
| 87 if (action != null && action.startsWith(ACTION_PREFIX)) { |
| 88 Set<Listener> listeners = sInstance.getListeners(); |
| 89 |
| 90 // Strip the prefix for matching the verb |
| 91 action = action.substring(ACTION_PREFIX.length()); |
| 92 if (ACTION_VERBS[ACTION_ID_PLAY].equals(action)) { |
| 93 for (Listener listener : listeners) listener.onPlay(); |
| 94 } else if (ACTION_VERBS[ACTION_ID_PAUSE].equals(action)) { |
| 95 for (Listener listener : listeners) listener.onPause(); |
| 96 } |
| 97 } |
| 98 |
| 99 return START_STICKY; |
| 100 } |
| 101 } |
| 102 |
| 103 private static NotificationMediaPlaybackControls sInstance = null; |
| 104 private static final Object LOCK = new Object(); |
| 105 private static final int MSG_UPDATE_NOTIFICATION = 100; |
| 106 |
| 107 /** |
| 108 * Returns the singleton NotificationMediaPlaybackControls object. |
| 109 * |
| 110 * @param context The Context that the notification service needs to be crea
ted in. |
| 111 * @return A {@code NotificationMediaPlaybackControls} object. |
| 112 */ |
| 113 public static NotificationMediaPlaybackControls getOrCreate(Context context)
{ |
| 114 synchronized (LOCK) { |
| 115 if (sInstance == null) { |
| 116 sInstance = new NotificationMediaPlaybackControls(context); |
| 117 } |
| 118 |
| 119 return sInstance; |
| 120 } |
| 121 } |
| 122 |
| 123 private static void onServiceDestroyed() { |
| 124 sInstance.destroyNotification(); |
| 125 sInstance.mService = null; |
| 126 } |
| 127 |
| 128 private static void onServiceStarted(ListenerService service) { |
| 129 sInstance.mService = service; |
| 130 sInstance.createNotification(); |
| 131 } |
| 132 |
| 133 private final Context mContext; |
| 134 |
| 135 // ListenerService running for the notification. Only non-null when showing. |
| 136 private ListenerService mService; |
| 137 |
| 138 private final String mPlayDescription; |
| 139 |
| 140 private final String mPauseDescription; |
| 141 |
| 142 private Notification mNotification; |
| 143 |
| 144 private Handler mHandler; |
| 145 |
| 146 private NotificationMediaPlaybackControls(Context context) { |
| 147 this.mContext = context; |
| 148 mHandler = new Handler(context.getMainLooper()) { |
| 149 @Override |
| 150 public void handleMessage(android.os.Message msg) { |
| 151 if (msg.what == MSG_UPDATE_NOTIFICATION) { |
| 152 mHandler.removeMessages(MSG_UPDATE_NOTIFICATION); // Only on
e update is needed. |
| 153 updateNotificationInternal(); |
| 154 } |
| 155 } |
| 156 }; |
| 157 |
| 158 mPlayDescription = context.getResources().getString(R.string.accessibili
ty_play); |
| 159 mPauseDescription = context.getResources().getString(R.string.accessibil
ity_pause); |
| 160 } |
| 161 |
| 162 @Override |
| 163 public void hide(int tabId) { |
| 164 if (getMediaInfo() == null || tabId != getMediaInfo().tabId) return; |
| 165 |
| 166 mContext.stopService(new Intent(mContext, ListenerService.class)); |
| 167 } |
| 168 |
| 169 /** |
| 170 * @return true if the notification is currently visible to the user. |
| 171 */ |
| 172 public boolean isShowing() { |
| 173 return mService != null; |
| 174 } |
| 175 |
| 176 @Override |
| 177 public void onPlaybackStateChanged(boolean isPaused) { |
| 178 MediaInfo mediaInfo = new MediaInfo(getMediaInfo()); |
| 179 mediaInfo.isPaused = isPaused; |
| 180 setMediaInfo(mediaInfo); |
| 181 } |
| 182 |
| 183 @Override |
| 184 public void onTitleChanged(String title) { |
| 185 MediaInfo mediaInfo = new MediaInfo(getMediaInfo()); |
| 186 mediaInfo.title = title; |
| 187 setMediaInfo(mediaInfo); |
| 188 } |
| 189 |
| 190 @Override |
| 191 public void onOriginChanged(String origin) { |
| 192 MediaInfo mediaInfo = new MediaInfo(getMediaInfo()); |
| 193 mediaInfo.origin = origin; |
| 194 setMediaInfo(mediaInfo); |
| 195 } |
| 196 |
| 197 @Override |
| 198 public void onTabIdChanged(int tabId) { |
| 199 MediaInfo mediaInfo = new MediaInfo(getMediaInfo()); |
| 200 mediaInfo.tabId = tabId; |
| 201 setMediaInfo(mediaInfo); |
| 202 } |
| 203 |
| 204 @Override |
| 205 public void show(MediaInfo mediaInfo) { |
| 206 setMediaInfo(mediaInfo); |
| 207 mContext.startService(new Intent(mContext, ListenerService.class)); |
| 208 } |
| 209 |
| 210 private void updateNotification() { |
| 211 // Defer the call to updateNotificationInternal() so it can be cancelled
in |
| 212 // destroyNotification(). This is done to avoid the OS bug b/8798662. |
| 213 mHandler.sendEmptyMessage(MSG_UPDATE_NOTIFICATION); |
| 214 } |
| 215 |
| 216 Notification getNotification() { |
| 217 return mNotification; |
| 218 } |
| 219 |
| 220 final ListenerService getService() { |
| 221 return mService; |
| 222 } |
| 223 |
| 224 @Override |
| 225 protected void onMediaInfoChanged() { |
| 226 if (isShowing()) updateNotification(); |
| 227 } |
| 228 |
| 229 private RemoteViews createContentView() { |
| 230 RemoteViews contentView = |
| 231 new RemoteViews(getContext().getPackageName(), R.layout.playback
_notification_bar); |
| 232 return contentView; |
| 233 } |
| 234 |
| 235 private void createNotification() { |
| 236 NotificationCompat.Builder notificationBuilder = |
| 237 new NotificationCompat.Builder(getContext()) |
| 238 .setSmallIcon(R.drawable.audio_playing) |
| 239 .setAutoCancel(false) |
| 240 .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) |
| 241 .setOngoing(true) |
| 242 .setContent(createContentView()) |
| 243 .setContentIntent(createContentIntent()); |
| 244 mNotification = notificationBuilder.build(); |
| 245 updateNotification(); |
| 246 } |
| 247 |
| 248 private void destroyNotification() { |
| 249 // Cancel any pending updates - we're about to tear down the notificatio
n. |
| 250 mHandler.removeMessages(MSG_UPDATE_NOTIFICATION); |
| 251 |
| 252 NotificationManagerCompat manager = NotificationManagerCompat.from(getCo
ntext()); |
| 253 |
| 254 manager.cancel(R.id.media_playback_notification); |
| 255 mNotification = null; |
| 256 } |
| 257 |
| 258 private final Context getContext() { |
| 259 return mContext; |
| 260 } |
| 261 |
| 262 private String getStatus() { |
| 263 Context context = getContext(); |
| 264 MediaInfo mediaInfo = getMediaInfo(); |
| 265 return context.getString(R.string.media_notification_link_text, |
| 266 mediaInfo.origin != null ? mediaInfo.origin : ""); |
| 267 } |
| 268 |
| 269 private String getTitle() { |
| 270 Context context = getContext(); |
| 271 MediaInfo mediaInfo = getMediaInfo(); |
| 272 String mediaTitle = mediaInfo.title; |
| 273 if (mediaInfo.isPaused) { |
| 274 return context.getString( |
| 275 R.string.media_playback_notification_paused_for_media, media
Title); |
| 276 } else { |
| 277 return context.getString( |
| 278 R.string.media_playback_notification_playing_for_media, medi
aTitle); |
| 279 } |
| 280 } |
| 281 |
| 282 private PendingIntent createContentIntent() { |
| 283 int tabId = getMediaInfo().tabId; |
| 284 Intent intent = new Intent(Intent.ACTION_MAIN); |
| 285 intent.putExtra(Browser.EXTRA_APPLICATION_ID, getContext().getPackageNam
e()); |
| 286 intent.putExtra(TabOpenType.BRING_TAB_TO_FRONT.name(), tabId); |
| 287 intent.setPackage(mContext.getPackageName()); |
| 288 return PendingIntent.getActivity(getContext(), tabId, intent, 0); |
| 289 } |
| 290 |
| 291 private void updateNotificationInternal() { |
| 292 RemoteViews contentView = createContentView(); |
| 293 |
| 294 contentView.setTextViewText(R.id.title, getTitle()); |
| 295 contentView.setTextViewText(R.id.status, getStatus()); |
| 296 contentView.setImageViewResource(R.id.icon, R.drawable.audio_playing); |
| 297 |
| 298 MediaInfo mediaInfo = getMediaInfo(); |
| 299 if (mediaInfo == null) return; |
| 300 |
| 301 if (mediaInfo.isPaused) { |
| 302 contentView.setImageViewResource(R.id.playpause, R.drawable.ic_vidco
ntrol_play); |
| 303 contentView.setContentDescription(R.id.playpause, mPlayDescription); |
| 304 contentView.setOnClickPendingIntent(R.id.playpause, |
| 305 getService().getPendingIntent(ListenerService.ACTION_ID_PLAY
)); |
| 306 } else { |
| 307 contentView.setImageViewResource(R.id.playpause, R.drawable.ic_vidco
ntrol_pause); |
| 308 contentView.setContentDescription(R.id.playpause, mPauseDescription)
; |
| 309 contentView.setOnClickPendingIntent(R.id.playpause, |
| 310 getService().getPendingIntent(ListenerService.ACTION_ID_PAUS
E)); |
| 311 } |
| 312 |
| 313 mNotification.contentView = contentView; |
| 314 |
| 315 NotificationManagerCompat manager = NotificationManagerCompat.from(getCo
ntext()); |
| 316 manager.notify(R.id.media_playback_notification, mNotification); |
| 317 |
| 318 getService().startForeground(R.id.media_playback_notification, mNotifica
tion); |
| 319 } |
| 320 } |
OLD | NEW |