Commit 007b626e authored by Evan Stade's avatar Evan Stade Committed by Commit Bot

WebLayer: Remove media stream notifications from prior app instances.

When the app crashes, it can leave behind media streaming notifications.
Track these in a pref and remove on next startup.

We could use getActiveNotifications instead of storing a pref, but it's
only available in API 23.

Testing is still a todo and may be somewhat difficult as full
integration testing requires crashing the app. For now this is manually
tested by removing the cancel() calls that execute on stream/tab
destruction and restarting the app.

Bug: 1025622
Change-Id: Icdd475707ac1af0789968264c202e00fd8964d33
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2142394
Commit-Queue: Evan Stade <estade@chromium.org>
Reviewed-by: default avatarJohn Abd-El-Malek <jam@chromium.org>
Cr-Commit-Position: refs/heads/master@{#757944}
parent 4568e176
......@@ -8,6 +8,7 @@ import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Build;
import android.os.RemoteException;
import android.util.AndroidRuntimeException;
......@@ -21,11 +22,12 @@ import org.chromium.base.annotations.JNINamespace;
import org.chromium.base.annotations.NativeMethods;
import org.chromium.content_public.browser.WebContents;
import java.util.HashSet;
import java.util.Set;
/**
* A per-tab object that manages notifications for ongoing media streams (microphone/camera). This
* object is created by {@link TabImpl} and creates and destroys its native equivalent.
*
* TODO(estade): remove notifications that have persisted across restarts (due to the app crashing).
*/
@JNINamespace("weblayer")
public class MediaStreamManager {
......@@ -36,6 +38,15 @@ public class MediaStreamManager {
private static final String ACTIVATE_TAB_INTENT = WEBRTC_PREFIX + ".ACTIVATE_TAB";
private static final String AV_STREAM_TAG = WEBRTC_PREFIX + ".avstream";
/**
* A key used in the app's shared preferences to track a set of active streaming notifications.
* This is used to clear notifications that may have persisted across restarts due to a crash.
* TODO(estade): remove this approach and simply iterate across all notifications via
* {@link NotificationManager#getActiveNotifications} once the minimum API level is 23.
*/
private static final String PREF_ACTIVE_AV_STREAM_NOTIFICATION_IDS =
WEBRTC_PREFIX + ".avstream_notifications";
// The notification ID matches the tab ID, which uniquely identifies the notification when
// paired with the tag.
private int mNotificationId;
......@@ -67,17 +78,62 @@ public class MediaStreamManager {
}
}
/**
* To be called when WebLayer is started. Clears notifications that may have persisted from
* before a crash.
*/
public static void onWebLayerInit() {
SharedPreferences prefs = ContextUtils.getAppSharedPreferences();
Set<String> staleNotificationIds =
prefs.getStringSet(PREF_ACTIVE_AV_STREAM_NOTIFICATION_IDS, null);
if (staleNotificationIds == null) return;
NotificationManagerCompat manager = getNotificationManager();
if (manager == null) return;
for (String id : staleNotificationIds) {
manager.cancel(AV_STREAM_TAG, Integer.parseInt(id));
}
prefs.edit().remove(PREF_ACTIVE_AV_STREAM_NOTIFICATION_IDS).apply();
}
public MediaStreamManager(TabImpl tab) {
mNotificationId = tab.getId();
mNative = MediaStreamManagerJni.get().create(this, tab.getWebContents());
}
public void destroy() {
cancelNotification();
MediaStreamManagerJni.get().destroy(mNative);
}
private void cancelNotification() {
NotificationManagerCompat notificationManager = getNotificationManager();
if (notificationManager == null) return;
if (notificationManager != null) {
notificationManager.cancel(AV_STREAM_TAG, mNotificationId);
}
updateActiveNotifications(false);
}
MediaStreamManagerJni.get().destroy(mNative);
/**
* Updates the list of active notifications stored in the SharedPrefences.
*
* @param active if true, then {@link mNotificationId} will be added to the list of active
* notifications, otherwise it will be removed.
*/
private void updateActiveNotifications(boolean active) {
SharedPreferences prefs = ContextUtils.getAppSharedPreferences();
Set<String> activeIds = new HashSet<String>(
prefs.getStringSet(PREF_ACTIVE_AV_STREAM_NOTIFICATION_IDS, new HashSet<String>()));
if (active) {
activeIds.add(Integer.toString(mNotificationId));
} else {
activeIds.remove(Integer.toString(mNotificationId));
}
prefs.edit()
.putStringSet(PREF_ACTIVE_AV_STREAM_NOTIFICATION_IDS,
activeIds.isEmpty() ? null : activeIds)
.apply();
}
/**
......@@ -94,15 +150,15 @@ public class MediaStreamManager {
// The notification intent is not handled in the client prior to M84.
if (WebLayerFactoryImpl.getClientMajorVersion() < 84) return;
NotificationManagerCompat notificationManager = getNotificationManager();
if (notificationManager == null) return;
createNotificationChannel();
if (!audio && !video) {
notificationManager.cancel(AV_STREAM_TAG, mNotificationId);
cancelNotification();
return;
}
NotificationManagerCompat notificationManager = getNotificationManager();
if (notificationManager == null) return;
createNotificationChannel();
Intent intent = WebLayerImpl.createIntent();
intent.putExtra(EXTRA_TAB_ID, mNotificationId);
intent.setAction(ACTIVATE_TAB_INTENT);
......@@ -120,6 +176,7 @@ public class MediaStreamManager {
? "all the streamz"
: audio ? "audio streamz" : "video streamz");
notificationManager.notify(AV_STREAM_TAG, mNotificationId, builder.build());
updateActiveNotifications(true);
}
private void createNotificationChannel() {
......@@ -136,7 +193,7 @@ public class MediaStreamManager {
sCreatedChannel = true;
}
private NotificationManagerCompat getNotificationManager() {
private static NotificationManagerCompat getNotificationManager() {
if (ContextUtils.getApplicationContext() == null) {
return null;
}
......
......@@ -259,6 +259,8 @@ public final class WebLayerImpl extends IWebLayer.Stub {
LibraryLoader.getInstance().ensureInitialized();
}
GmsBridge.getInstance().setSafeBrowsingHandler();
MediaStreamManager.onWebLayerInit();
}
@Override
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment