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 |