Index: chrome/android/java_staging/src/org/chromium/chrome/browser/media/MediaNotificationService.java |
diff --git a/chrome/android/java_staging/src/org/chromium/chrome/browser/media/MediaNotificationService.java b/chrome/android/java_staging/src/org/chromium/chrome/browser/media/MediaNotificationService.java |
new file mode 100644 |
index 0000000000000000000000000000000000000000..fadd38351402c80b3629f5fba5e0dec37b4c03cd |
--- /dev/null |
+++ b/chrome/android/java_staging/src/org/chromium/chrome/browser/media/MediaNotificationService.java |
@@ -0,0 +1,318 @@ |
+// Copyright 2015 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+package org.chromium.chrome.browser.media; |
+ |
+import android.app.Notification; |
+import android.app.NotificationManager; |
+import android.app.PendingIntent; |
+import android.app.Service; |
+import android.content.Context; |
+import android.content.Intent; |
+import android.content.SharedPreferences; |
+import android.os.IBinder; |
+import android.preference.PreferenceManager; |
+import android.provider.Browser; |
+import android.support.v4.app.NotificationCompat; |
+import android.util.Log; |
+import android.util.SparseIntArray; |
+ |
+import com.google.android.apps.chrome.R; |
+ |
+import org.chromium.chrome.browser.IntentHandler.TabOpenType; |
+import org.chromium.chrome.browser.Tab; |
+ |
+import java.net.MalformedURLException; |
+import java.net.URL; |
+import java.util.HashSet; |
+import java.util.Iterator; |
+import java.util.Set; |
+ |
+/** |
+ * Service that creates/destroys media related notifications. |
+ * There are two kinds of notifications: |
+ * 1. The WebRTC notification when media capture starts/stops. |
+ * 2. The audio playback notification when a tab is playing audio. |
+ * These notifications are made mutually exclusive: there can be |
+ * only one media notification for a tab. |
+ */ |
+public class MediaNotificationService extends Service { |
+ |
+ private static final String NOTIFICATION_NAMESPACE = "MediaNotificationService"; |
+ |
+ private static final String NOTIFICATION_ID_EXTRA = "NotificationId"; |
+ private static final String NOTIFICATION_MEDIA_TYPE_EXTRA = "NotificationMediaType"; |
+ private static final String NOTIFICATION_MEDIA_URL_EXTRA = "NotificationMediaUrl"; |
+ |
+ private static final String MEDIA_NOTIFICATION_IDS = "WebRTCNotificationIds"; |
+ private static final String LOG_TAG = "MediaNotificationService"; |
+ |
+ private static final int MEDIATYPE_NO_MEDIA = 0; |
+ private static final int MEDIATYPE_AUDIO_AND_VIDEO_CAPTURE = 1; |
+ private static final int MEDIATYPE_VIDEO_CAPTURE_ONLY = 2; |
+ private static final int MEDIATYPE_AUDIO_CAPTURE_ONLY = 3; |
+ private static final int MEDIATYPE_AUDIO_PLAYBACK = 4; |
+ |
+ private NotificationManager mNotificationManager; |
+ private Context mContext; |
+ private SharedPreferences mSharedPreferences; |
+ private final SparseIntArray mNotifications = new SparseIntArray(); |
+ |
+ @Override |
+ public void onCreate() { |
+ mContext = getApplicationContext(); |
+ mNotificationManager = (NotificationManager) mContext.getSystemService( |
+ Context.NOTIFICATION_SERVICE); |
+ mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(mContext); |
+ super.onCreate(); |
+ } |
+ |
+ /** |
+ * @param notificationId Unique id of the notification. |
+ * @param mediaType Media type of the notification. |
+ * @return Whether the notification has already been created for provided notification id and |
+ * mediaType. |
+ */ |
+ private boolean doesNotificationNeedUpdate(int notificationId, int mediaType) { |
+ return mNotifications.get(notificationId) != mediaType; |
+ } |
+ |
+ /** |
+ * @param notificationId Unique id of the notification. |
+ * @return Whether the notification has already been created for the provided notification id. |
+ */ |
+ private boolean doesNotificationExist(int notificationId) { |
+ return mNotifications.indexOfKey(notificationId) >= 0; |
+ } |
+ |
+ @Override |
+ public int onStartCommand(Intent intent, int flags, int startId) { |
+ if (intent == null || intent.getExtras() == null) { |
+ cancelPreviousWebRtcNotifications(); |
+ stopSelf(); |
+ } else { |
+ updateNotification( |
+ intent.getIntExtra(NOTIFICATION_ID_EXTRA, Tab.INVALID_TAB_ID), |
+ intent.getIntExtra(NOTIFICATION_MEDIA_TYPE_EXTRA, MEDIATYPE_NO_MEDIA), |
+ intent.getStringExtra(NOTIFICATION_MEDIA_URL_EXTRA)); |
+ } |
+ return super.onStartCommand(intent, flags, startId); |
+ } |
+ |
+ /** |
+ * Cancel all previously existing notifications. Essential while doing a clean start (may be |
+ * after a browser crash which caused old notifications to exist). |
+ */ |
+ private void cancelPreviousWebRtcNotifications() { |
+ Set<String> notificationIds = |
+ mSharedPreferences.getStringSet(MEDIA_NOTIFICATION_IDS, null); |
+ if (notificationIds == null) return; |
+ Iterator<String> iterator = notificationIds.iterator(); |
+ while (iterator.hasNext()) { |
+ mNotificationManager.cancel(NOTIFICATION_NAMESPACE, Integer.parseInt(iterator.next())); |
+ } |
+ SharedPreferences.Editor sharedPreferenceEditor = mSharedPreferences.edit(); |
+ sharedPreferenceEditor.remove(MediaNotificationService.MEDIA_NOTIFICATION_IDS); |
+ sharedPreferenceEditor.apply(); |
+ } |
+ |
+ /** |
+ * Updates the extisting notification or creates one if none exist for the provided |
+ * notificationId and mediaType. |
+ * @param notificationId Unique id of the notification. |
+ * @param mediaType Media type of the notification. |
+ * @param url Url of the current webrtc call. |
+ */ |
+ private void updateNotification(int notificationId, int mediaType, String url) { |
+ if (doesNotificationExist(notificationId) |
+ && !doesNotificationNeedUpdate(notificationId, mediaType)) { |
+ return; |
+ } |
+ destroyNotification(notificationId); |
+ if (mediaType != MEDIATYPE_NO_MEDIA) { |
+ createNotification(notificationId, mediaType, url); |
+ } |
+ if (mNotifications.size() == 0) stopSelf(); |
+ } |
+ |
+ /** |
+ * Destroys the notification for the id notificationId. |
+ * @param notificationId Unique id of the notification. |
+ */ |
+ private void destroyNotification(int notificationId) { |
+ if (doesNotificationExist(notificationId)) { |
+ mNotificationManager.cancel(NOTIFICATION_NAMESPACE, notificationId); |
+ mNotifications.delete(notificationId); |
+ updateSharedPreferencesEntry(notificationId, true); |
+ } |
+ } |
+ |
+ /** |
+ * Creates a notification for the provided notificationId and mediaType. |
+ * @param notificationId Unique id of the notification. |
+ * @param mediaType Media type of the notification. |
+ * @param url Url of the current webrtc call. |
+ */ |
+ private void createNotification(int notificationId, int mediaType, String url) { |
+ int notificationContentTextId = 0; |
+ int notificationIconId = 0; |
+ if (mediaType == MEDIATYPE_AUDIO_AND_VIDEO_CAPTURE) { |
+ notificationContentTextId = R.string.video_audio_call_notification_text_2; |
+ notificationIconId = R.drawable.webrtc_video; |
+ } else if (mediaType == MEDIATYPE_VIDEO_CAPTURE_ONLY) { |
+ notificationContentTextId = R.string.video_call_notification_text_2; |
+ notificationIconId = R.drawable.webrtc_video; |
+ } else if (mediaType == MEDIATYPE_AUDIO_CAPTURE_ONLY) { |
+ notificationContentTextId = R.string.audio_call_notification_text_2; |
+ notificationIconId = R.drawable.webrtc_audio; |
+ } else if (mediaType == MEDIATYPE_AUDIO_PLAYBACK) { |
+ notificationContentTextId = R.string.audio_playback_notification_text; |
+ notificationIconId = R.drawable.audio_playing; |
+ } |
+ |
+ Intent tabIntent = createMediaTabOpenIntent(notificationId); |
+ PendingIntent contentIntent = PendingIntent.getActivity( |
+ mContext, notificationId, tabIntent, 0); |
+ String contentText = mContext.getResources().getString(notificationContentTextId) + ". " |
+ + mContext.getResources().getString( |
+ R.string.media_notification_link_text, url); |
+ |
+ NotificationCompat.Builder builder = new NotificationCompat.Builder(mContext) |
+ .setAutoCancel(false) |
+ .setOngoing(true) |
+ .setContentIntent(contentIntent) |
+ .setContentTitle(mContext.getString(R.string.app_name)) |
+ .setContentText(contentText) |
+ .setSmallIcon(notificationIconId) |
+ .setLocalOnly(true); |
+ |
+ Notification notification = new NotificationCompat.BigTextStyle(builder) |
+ .bigText(contentText).build(); |
+ mNotificationManager.notify(NOTIFICATION_NAMESPACE, notificationId, notification); |
+ mNotifications.put(notificationId, mediaType); |
+ updateSharedPreferencesEntry(notificationId, false); |
+ } |
+ |
+ /** |
+ * Returns the Intent that opens the tab with the WebRTC call on click of the notification. |
+ */ |
+ private Intent createMediaTabOpenIntent(int tabId) { |
+ Intent intent = new Intent(Intent.ACTION_MAIN); |
+ intent.putExtra(Browser.EXTRA_APPLICATION_ID, mContext.getPackageName()); |
+ intent.putExtra(TabOpenType.BRING_TAB_TO_FRONT.name(), tabId); |
+ intent.setPackage(mContext.getPackageName()); |
+ return intent; |
+ } |
+ |
+ /** |
+ * Update shared preferences entry with ids of the visible notifications. |
+ * @param notificationId Id of the notification. |
+ * @param remove Boolean describing if the notification was added or removed. |
+ */ |
+ private void updateSharedPreferencesEntry(int notificationId, boolean remove) { |
+ Set<String> notificationIds = |
+ new HashSet<String>(mSharedPreferences.getStringSet(MEDIA_NOTIFICATION_IDS, |
+ new HashSet<String>())); |
+ if (remove && !notificationIds.isEmpty() |
+ && notificationIds.contains(String.valueOf(notificationId))) { |
+ notificationIds.remove(String.valueOf(notificationId)); |
+ } else if (!remove) { |
+ notificationIds.add(String.valueOf(notificationId)); |
+ } |
+ SharedPreferences.Editor sharedPreferenceEditor = mSharedPreferences.edit(); |
+ sharedPreferenceEditor.putStringSet(MEDIA_NOTIFICATION_IDS, notificationIds); |
+ sharedPreferenceEditor.apply(); |
+ } |
+ |
+ @Override |
+ public void onDestroy() { |
+ cancelPreviousWebRtcNotifications(); |
+ super.onDestroy(); |
+ } |
+ |
+ @Override |
+ public boolean onUnbind(Intent intent) { |
+ cancelPreviousWebRtcNotifications(); |
+ return super.onUnbind(intent); |
+ } |
+ |
+ @Override |
+ public IBinder onBind(Intent intent) { |
+ return null; |
+ } |
+ |
+ /** |
+ * @param audio If audio is being captured. |
+ * @param video If video is being captured. |
+ * @return A constant identify what media is being captured. |
+ */ |
+ public static int getMediaType(boolean audioCapture, |
+ boolean videoCapture, boolean audioPlayback) { |
+ if (audioCapture && videoCapture) { |
+ return MEDIATYPE_AUDIO_AND_VIDEO_CAPTURE; |
+ } else if (audioCapture) { |
+ return MEDIATYPE_AUDIO_CAPTURE_ONLY; |
+ } else if (videoCapture) { |
+ return MEDIATYPE_VIDEO_CAPTURE_ONLY; |
+ } else if (audioPlayback) { |
+ return MEDIATYPE_AUDIO_PLAYBACK; |
+ } else { |
+ return MEDIATYPE_NO_MEDIA; |
+ } |
+ } |
+ |
+ private static boolean shouldStartService(Context context, int mediaType, int tabId) { |
+ if (mediaType != MEDIATYPE_NO_MEDIA) return true; |
+ SharedPreferences sharedPreferences = |
+ PreferenceManager.getDefaultSharedPreferences(context); |
+ Set<String> notificationIds = |
+ sharedPreferences.getStringSet(MEDIA_NOTIFICATION_IDS, null); |
+ if (notificationIds != null |
+ && !notificationIds.isEmpty() |
+ && notificationIds.contains(String.valueOf(tabId))) { |
+ return true; |
+ } |
+ return false; |
+ } |
+ |
+ /** |
+ * Send an intent to MediaNotificationService to either create, update or destroy the |
+ * notification identified by tabId. |
+ * @param tabId Unique notification id. |
+ * @param audio If audio is being captured. |
+ * @param video If video is being captured. |
+ * @param fullUrl Url of the current webrtc call. |
+ */ |
+ public static void updateMediaNotificationForTab(Context context, int tabId, |
+ boolean audioCapture, boolean videoCapture, boolean audioPlayback, |
+ String fullUrl) { |
+ int mediaType = getMediaType(audioCapture, videoCapture, audioPlayback); |
+ if (!shouldStartService(context, mediaType, tabId)) return; |
+ Intent intent = new Intent(context, MediaNotificationService.class); |
+ intent.putExtra(NOTIFICATION_ID_EXTRA, tabId); |
+ String baseUrl = fullUrl; |
+ try { |
+ URL url = new URL(fullUrl); |
+ baseUrl = url.getProtocol() + "://" + url.getHost(); |
+ } catch (MalformedURLException e) { |
+ Log.w(LOG_TAG, "Error parsing the webrtc url " + fullUrl); |
+ } |
+ intent.putExtra(NOTIFICATION_MEDIA_URL_EXTRA, baseUrl); |
+ intent.putExtra(NOTIFICATION_MEDIA_TYPE_EXTRA, mediaType); |
+ context.startService(intent); |
+ } |
+ |
+ /** |
+ * Clear any previous media notifications. |
+ */ |
+ public static void clearMediaNotifications(Context context) { |
+ SharedPreferences sharedPreferences = |
+ PreferenceManager.getDefaultSharedPreferences(context); |
+ Set<String> notificationIds = |
+ sharedPreferences.getStringSet(MEDIA_NOTIFICATION_IDS, null); |
+ if (notificationIds == null || notificationIds.isEmpty()) return; |
+ |
+ context.startService(new Intent(context, MediaNotificationService.class)); |
+ } |
+} |