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; |
| 6 |
| 7 import android.app.Notification; |
| 8 import android.app.NotificationManager; |
| 9 import android.app.PendingIntent; |
| 10 import android.app.Service; |
| 11 import android.content.Context; |
| 12 import android.content.Intent; |
| 13 import android.content.SharedPreferences; |
| 14 import android.os.IBinder; |
| 15 import android.preference.PreferenceManager; |
| 16 import android.provider.Browser; |
| 17 import android.support.v4.app.NotificationCompat; |
| 18 import android.util.Log; |
| 19 import android.util.SparseIntArray; |
| 20 |
| 21 import com.google.android.apps.chrome.R; |
| 22 |
| 23 import org.chromium.chrome.browser.IntentHandler.TabOpenType; |
| 24 import org.chromium.chrome.browser.Tab; |
| 25 |
| 26 import java.net.MalformedURLException; |
| 27 import java.net.URL; |
| 28 import java.util.HashSet; |
| 29 import java.util.Iterator; |
| 30 import java.util.Set; |
| 31 |
| 32 /** |
| 33 * Service that creates/destroys media related notifications. |
| 34 * There are two kinds of notifications: |
| 35 * 1. The WebRTC notification when media capture starts/stops. |
| 36 * 2. The audio playback notification when a tab is playing audio. |
| 37 * These notifications are made mutually exclusive: there can be |
| 38 * only one media notification for a tab. |
| 39 */ |
| 40 public class MediaNotificationService extends Service { |
| 41 |
| 42 private static final String NOTIFICATION_NAMESPACE = "MediaNotificationServi
ce"; |
| 43 |
| 44 private static final String NOTIFICATION_ID_EXTRA = "NotificationId"; |
| 45 private static final String NOTIFICATION_MEDIA_TYPE_EXTRA = "NotificationMed
iaType"; |
| 46 private static final String NOTIFICATION_MEDIA_URL_EXTRA = "NotificationMedi
aUrl"; |
| 47 |
| 48 private static final String MEDIA_NOTIFICATION_IDS = "WebRTCNotificationIds"
; |
| 49 private static final String LOG_TAG = "MediaNotificationService"; |
| 50 |
| 51 private static final int MEDIATYPE_NO_MEDIA = 0; |
| 52 private static final int MEDIATYPE_AUDIO_AND_VIDEO_CAPTURE = 1; |
| 53 private static final int MEDIATYPE_VIDEO_CAPTURE_ONLY = 2; |
| 54 private static final int MEDIATYPE_AUDIO_CAPTURE_ONLY = 3; |
| 55 private static final int MEDIATYPE_AUDIO_PLAYBACK = 4; |
| 56 |
| 57 private NotificationManager mNotificationManager; |
| 58 private Context mContext; |
| 59 private SharedPreferences mSharedPreferences; |
| 60 private final SparseIntArray mNotifications = new SparseIntArray(); |
| 61 |
| 62 @Override |
| 63 public void onCreate() { |
| 64 mContext = getApplicationContext(); |
| 65 mNotificationManager = (NotificationManager) mContext.getSystemService( |
| 66 Context.NOTIFICATION_SERVICE); |
| 67 mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(mCont
ext); |
| 68 super.onCreate(); |
| 69 } |
| 70 |
| 71 /** |
| 72 * @param notificationId Unique id of the notification. |
| 73 * @param mediaType Media type of the notification. |
| 74 * @return Whether the notification has already been created for provided no
tification id and |
| 75 * mediaType. |
| 76 */ |
| 77 private boolean doesNotificationNeedUpdate(int notificationId, int mediaType
) { |
| 78 return mNotifications.get(notificationId) != mediaType; |
| 79 } |
| 80 |
| 81 /** |
| 82 * @param notificationId Unique id of the notification. |
| 83 * @return Whether the notification has already been created for the provide
d notification id. |
| 84 */ |
| 85 private boolean doesNotificationExist(int notificationId) { |
| 86 return mNotifications.indexOfKey(notificationId) >= 0; |
| 87 } |
| 88 |
| 89 @Override |
| 90 public int onStartCommand(Intent intent, int flags, int startId) { |
| 91 if (intent == null || intent.getExtras() == null) { |
| 92 cancelPreviousWebRtcNotifications(); |
| 93 stopSelf(); |
| 94 } else { |
| 95 updateNotification( |
| 96 intent.getIntExtra(NOTIFICATION_ID_EXTRA, Tab.INVALID_TAB_ID
), |
| 97 intent.getIntExtra(NOTIFICATION_MEDIA_TYPE_EXTRA, MEDIATYPE_
NO_MEDIA), |
| 98 intent.getStringExtra(NOTIFICATION_MEDIA_URL_EXTRA)); |
| 99 } |
| 100 return super.onStartCommand(intent, flags, startId); |
| 101 } |
| 102 |
| 103 /** |
| 104 * Cancel all previously existing notifications. Essential while doing a cle
an start (may be |
| 105 * after a browser crash which caused old notifications to exist). |
| 106 */ |
| 107 private void cancelPreviousWebRtcNotifications() { |
| 108 Set<String> notificationIds = |
| 109 mSharedPreferences.getStringSet(MEDIA_NOTIFICATION_IDS, null); |
| 110 if (notificationIds == null) return; |
| 111 Iterator<String> iterator = notificationIds.iterator(); |
| 112 while (iterator.hasNext()) { |
| 113 mNotificationManager.cancel(NOTIFICATION_NAMESPACE, Integer.parseInt
(iterator.next())); |
| 114 } |
| 115 SharedPreferences.Editor sharedPreferenceEditor = mSharedPreferences.edi
t(); |
| 116 sharedPreferenceEditor.remove(MediaNotificationService.MEDIA_NOTIFICATIO
N_IDS); |
| 117 sharedPreferenceEditor.apply(); |
| 118 } |
| 119 |
| 120 /** |
| 121 * Updates the extisting notification or creates one if none exist for the p
rovided |
| 122 * notificationId and mediaType. |
| 123 * @param notificationId Unique id of the notification. |
| 124 * @param mediaType Media type of the notification. |
| 125 * @param url Url of the current webrtc call. |
| 126 */ |
| 127 private void updateNotification(int notificationId, int mediaType, String ur
l) { |
| 128 if (doesNotificationExist(notificationId) |
| 129 && !doesNotificationNeedUpdate(notificationId, mediaType)) { |
| 130 return; |
| 131 } |
| 132 destroyNotification(notificationId); |
| 133 if (mediaType != MEDIATYPE_NO_MEDIA) { |
| 134 createNotification(notificationId, mediaType, url); |
| 135 } |
| 136 if (mNotifications.size() == 0) stopSelf(); |
| 137 } |
| 138 |
| 139 /** |
| 140 * Destroys the notification for the id notificationId. |
| 141 * @param notificationId Unique id of the notification. |
| 142 */ |
| 143 private void destroyNotification(int notificationId) { |
| 144 if (doesNotificationExist(notificationId)) { |
| 145 mNotificationManager.cancel(NOTIFICATION_NAMESPACE, notificationId); |
| 146 mNotifications.delete(notificationId); |
| 147 updateSharedPreferencesEntry(notificationId, true); |
| 148 } |
| 149 } |
| 150 |
| 151 /** |
| 152 * Creates a notification for the provided notificationId and mediaType. |
| 153 * @param notificationId Unique id of the notification. |
| 154 * @param mediaType Media type of the notification. |
| 155 * @param url Url of the current webrtc call. |
| 156 */ |
| 157 private void createNotification(int notificationId, int mediaType, String ur
l) { |
| 158 int notificationContentTextId = 0; |
| 159 int notificationIconId = 0; |
| 160 if (mediaType == MEDIATYPE_AUDIO_AND_VIDEO_CAPTURE) { |
| 161 notificationContentTextId = R.string.video_audio_call_notification_t
ext_2; |
| 162 notificationIconId = R.drawable.webrtc_video; |
| 163 } else if (mediaType == MEDIATYPE_VIDEO_CAPTURE_ONLY) { |
| 164 notificationContentTextId = R.string.video_call_notification_text_2; |
| 165 notificationIconId = R.drawable.webrtc_video; |
| 166 } else if (mediaType == MEDIATYPE_AUDIO_CAPTURE_ONLY) { |
| 167 notificationContentTextId = R.string.audio_call_notification_text_2; |
| 168 notificationIconId = R.drawable.webrtc_audio; |
| 169 } else if (mediaType == MEDIATYPE_AUDIO_PLAYBACK) { |
| 170 notificationContentTextId = R.string.audio_playback_notification_tex
t; |
| 171 notificationIconId = R.drawable.audio_playing; |
| 172 } |
| 173 |
| 174 Intent tabIntent = createMediaTabOpenIntent(notificationId); |
| 175 PendingIntent contentIntent = PendingIntent.getActivity( |
| 176 mContext, notificationId, tabIntent, 0); |
| 177 String contentText = mContext.getResources().getString(notificationConte
ntTextId) + ". " |
| 178 + mContext.getResources().getString( |
| 179 R.string.media_notification_link_text, url); |
| 180 |
| 181 NotificationCompat.Builder builder = new NotificationCompat.Builder(mCon
text) |
| 182 .setAutoCancel(false) |
| 183 .setOngoing(true) |
| 184 .setContentIntent(contentIntent) |
| 185 .setContentTitle(mContext.getString(R.string.app_name)) |
| 186 .setContentText(contentText) |
| 187 .setSmallIcon(notificationIconId) |
| 188 .setLocalOnly(true); |
| 189 |
| 190 Notification notification = new NotificationCompat.BigTextStyle(builder) |
| 191 .bigText(contentText).build(); |
| 192 mNotificationManager.notify(NOTIFICATION_NAMESPACE, notificationId, noti
fication); |
| 193 mNotifications.put(notificationId, mediaType); |
| 194 updateSharedPreferencesEntry(notificationId, false); |
| 195 } |
| 196 |
| 197 /** |
| 198 * Returns the Intent that opens the tab with the WebRTC call on click of th
e notification. |
| 199 */ |
| 200 private Intent createMediaTabOpenIntent(int tabId) { |
| 201 Intent intent = new Intent(Intent.ACTION_MAIN); |
| 202 intent.putExtra(Browser.EXTRA_APPLICATION_ID, mContext.getPackageName())
; |
| 203 intent.putExtra(TabOpenType.BRING_TAB_TO_FRONT.name(), tabId); |
| 204 intent.setPackage(mContext.getPackageName()); |
| 205 return intent; |
| 206 } |
| 207 |
| 208 /** |
| 209 * Update shared preferences entry with ids of the visible notifications. |
| 210 * @param notificationId Id of the notification. |
| 211 * @param remove Boolean describing if the notification was added or removed
. |
| 212 */ |
| 213 private void updateSharedPreferencesEntry(int notificationId, boolean remove
) { |
| 214 Set<String> notificationIds = |
| 215 new HashSet<String>(mSharedPreferences.getStringSet(MEDIA_NOTIFI
CATION_IDS, |
| 216 new HashSet<String>())); |
| 217 if (remove && !notificationIds.isEmpty() |
| 218 && notificationIds.contains(String.valueOf(notificationId))) { |
| 219 notificationIds.remove(String.valueOf(notificationId)); |
| 220 } else if (!remove) { |
| 221 notificationIds.add(String.valueOf(notificationId)); |
| 222 } |
| 223 SharedPreferences.Editor sharedPreferenceEditor = mSharedPreferences.ed
it(); |
| 224 sharedPreferenceEditor.putStringSet(MEDIA_NOTIFICATION_IDS, notification
Ids); |
| 225 sharedPreferenceEditor.apply(); |
| 226 } |
| 227 |
| 228 @Override |
| 229 public void onDestroy() { |
| 230 cancelPreviousWebRtcNotifications(); |
| 231 super.onDestroy(); |
| 232 } |
| 233 |
| 234 @Override |
| 235 public boolean onUnbind(Intent intent) { |
| 236 cancelPreviousWebRtcNotifications(); |
| 237 return super.onUnbind(intent); |
| 238 } |
| 239 |
| 240 @Override |
| 241 public IBinder onBind(Intent intent) { |
| 242 return null; |
| 243 } |
| 244 |
| 245 /** |
| 246 * @param audio If audio is being captured. |
| 247 * @param video If video is being captured. |
| 248 * @return A constant identify what media is being captured. |
| 249 */ |
| 250 public static int getMediaType(boolean audioCapture, |
| 251 boolean videoCapture, boolean audioPlayback) { |
| 252 if (audioCapture && videoCapture) { |
| 253 return MEDIATYPE_AUDIO_AND_VIDEO_CAPTURE; |
| 254 } else if (audioCapture) { |
| 255 return MEDIATYPE_AUDIO_CAPTURE_ONLY; |
| 256 } else if (videoCapture) { |
| 257 return MEDIATYPE_VIDEO_CAPTURE_ONLY; |
| 258 } else if (audioPlayback) { |
| 259 return MEDIATYPE_AUDIO_PLAYBACK; |
| 260 } else { |
| 261 return MEDIATYPE_NO_MEDIA; |
| 262 } |
| 263 } |
| 264 |
| 265 private static boolean shouldStartService(Context context, int mediaType, in
t tabId) { |
| 266 if (mediaType != MEDIATYPE_NO_MEDIA) return true; |
| 267 SharedPreferences sharedPreferences = |
| 268 PreferenceManager.getDefaultSharedPreferences(context); |
| 269 Set<String> notificationIds = |
| 270 sharedPreferences.getStringSet(MEDIA_NOTIFICATION_IDS, null); |
| 271 if (notificationIds != null |
| 272 && !notificationIds.isEmpty() |
| 273 && notificationIds.contains(String.valueOf(tabId))) { |
| 274 return true; |
| 275 } |
| 276 return false; |
| 277 } |
| 278 |
| 279 /** |
| 280 * Send an intent to MediaNotificationService to either create, update or de
stroy the |
| 281 * notification identified by tabId. |
| 282 * @param tabId Unique notification id. |
| 283 * @param audio If audio is being captured. |
| 284 * @param video If video is being captured. |
| 285 * @param fullUrl Url of the current webrtc call. |
| 286 */ |
| 287 public static void updateMediaNotificationForTab(Context context, int tabId, |
| 288 boolean audioCapture, boolean videoCapture, boolean audioPlayback, |
| 289 String fullUrl) { |
| 290 int mediaType = getMediaType(audioCapture, videoCapture, audioPlayback); |
| 291 if (!shouldStartService(context, mediaType, tabId)) return; |
| 292 Intent intent = new Intent(context, MediaNotificationService.class); |
| 293 intent.putExtra(NOTIFICATION_ID_EXTRA, tabId); |
| 294 String baseUrl = fullUrl; |
| 295 try { |
| 296 URL url = new URL(fullUrl); |
| 297 baseUrl = url.getProtocol() + "://" + url.getHost(); |
| 298 } catch (MalformedURLException e) { |
| 299 Log.w(LOG_TAG, "Error parsing the webrtc url " + fullUrl); |
| 300 } |
| 301 intent.putExtra(NOTIFICATION_MEDIA_URL_EXTRA, baseUrl); |
| 302 intent.putExtra(NOTIFICATION_MEDIA_TYPE_EXTRA, mediaType); |
| 303 context.startService(intent); |
| 304 } |
| 305 |
| 306 /** |
| 307 * Clear any previous media notifications. |
| 308 */ |
| 309 public static void clearMediaNotifications(Context context) { |
| 310 SharedPreferences sharedPreferences = |
| 311 PreferenceManager.getDefaultSharedPreferences(context); |
| 312 Set<String> notificationIds = |
| 313 sharedPreferences.getStringSet(MEDIA_NOTIFICATION_IDS, null); |
| 314 if (notificationIds == null || notificationIds.isEmpty()) return; |
| 315 |
| 316 context.startService(new Intent(context, MediaNotificationService.class)
); |
| 317 } |
| 318 } |
OLD | NEW |