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