Skip to content

Commit

Permalink
WebLayer: update MediaStream notification content and channel
Browse files Browse the repository at this point in the history
1. add WebLayerChannelDefinitions
   - Contains just the "Media" notification channel
   - Contains just the general WebLayer notification group,
     which requires a new string and a new WebLayer-specific
     strings target

2. Re-use Chrome's media capture notification code, for correct
   appearance and localization.
   - setSmallIcon(int) is not sufficient for WebLayer because the
     icon's drawable is not in the client application's package.
     For WebLayer, this must be replaced with setSmallIcon(Icon).
     A placeholder icon from android.R is set for versions <= L.

Bug: 1025622,1075594
Change-Id: I80ffbea5bdb5e402f2073737c8c5acc42dabf8b9
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2165461
Reviewed-by: Lei Zhang <thestig@chromium.org>
Reviewed-by: Mounir Lamouri <mlamouri@chromium.org>
Reviewed-by: Richard Coles <torne@chromium.org>
Reviewed-by: Clark DuVall <cduvall@chromium.org>
Commit-Queue: Evan Stade <estade@chromium.org>
Cr-Commit-Position: refs/heads/master@{#764711}
  • Loading branch information
Evan Stade authored and Commit Bot committed May 1, 2020
1 parent fd014a4 commit 2c9aa41
Show file tree
Hide file tree
Showing 90 changed files with 692 additions and 63 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -185,8 +185,8 @@ private void createNotification(
? buildStopCapturePendingIntent(notificationId)
: null;
ChromeNotification notification = MediaCaptureNotificationUtil.createNotification(builder,
notificationId, mediaType, url, appContext.getString(R.string.app_name),
isIncognito, contentIntent, stopIntent);
mediaType, url, appContext.getString(R.string.app_name), isIncognito, contentIntent,
stopIntent, null /*resPackageName*/);

mNotificationManager.notify(notification);
mNotifications.put(notificationId, mediaType);
Expand Down
3 changes: 0 additions & 3 deletions chrome/browser/ui/android/strings/android_chrome_strings.grd
Original file line number Diff line number Diff line change
Expand Up @@ -223,9 +223,6 @@ CHAR-LIMIT guidelines:
<message name="IDS_NOTIFICATION_CATEGORY_INCOGNITO" desc="Label for notification that indicates incognito mode is active, within a list of notification categories. [CHAR-LIMIT=32]">
Incognito
</message>
<message name="IDS_NOTIFICATION_CATEGORY_MEDIA" desc="Label for notifications shown when media is playing or recording, within a list of notification categories. [CHAR-LIMIT=32]">
Media
</message>
<message name="IDS_NOTIFICATION_CATEGORY_DOWNLOADS" desc="Label for notifications shown when something is downloading, within a list of notification categories. [CHAR-LIMIT=32]">
Downloads
</message>
Expand Down
3 changes: 3 additions & 0 deletions components/browser_ui/strings/android/browser_ui_strings.grd
Original file line number Diff line number Diff line change
Expand Up @@ -476,6 +476,9 @@
<message name="IDS_SCREEN_CAPTURE_NOTIFICATION_TITLE" desc="Text to be shown as a notification when screen capture is in progress.">
Sharing your screen
</message>
<message name="IDS_NOTIFICATION_CATEGORY_MEDIA" desc="Label for notifications shown when media is playing or recording, within a list of notification categories. [CHAR-LIMIT=32]">
Media
</message>

</messages>
</release>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import android.app.PendingIntent;
import android.content.Context;
import android.graphics.drawable.Icon;
import android.os.Build;

import androidx.annotation.IntDef;
Expand Down Expand Up @@ -33,25 +34,34 @@ public class MediaCaptureNotificationUtil {

/**
* Creates a notification for the provided parameters.
* @param notificationId Unique id of the notification.
* @param mediaType Media type of the notification.
* @param url Url of the current webrtc call.
* @param appName the display name for the app, e.g. "Chromium".
* @param isIncognito whether the notification is for an off-the-record context.
* @param contentIntent the intent to be sent when the notification is clicked.
* @param stopIntent if non-null, a stop button that triggers this intent will be added.
* @param resPackageName if non-null, the name of the package that contains the drawable.
*/
public static ChromeNotification createNotification(ChromeNotificationBuilder builder,
int notificationId, @MediaType int mediaType, String url, String appName,
boolean isIncognito, @Nullable PendingIntentProvider contentIntent,
@Nullable PendingIntent stopIntent) {
builder.setAutoCancel(false)
.setOngoing(true)
.setSmallIcon(getNotificationIconId(mediaType))
.setLocalOnly(true)
.setContentIntent(contentIntent);

@MediaType int mediaType, String url, @Nullable String appName, boolean isIncognito,
@Nullable PendingIntentProvider contentIntent, @Nullable PendingIntent stopIntent,
@Nullable String resPackageName) {
Context appContext = ContextUtils.getApplicationContext();
builder.setAutoCancel(false).setOngoing(true).setLocalOnly(true).setContentIntent(
contentIntent);

if (resPackageName != null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
builder.setSmallIcon(
Icon.createWithResource(resPackageName, getNotificationIconId(mediaType)));
} else {
// Some fallback is required, or the notification won't appear.
builder.setSmallIcon(android.R.drawable.radiobutton_on_background);
}
} else {
builder.setSmallIcon(getNotificationIconId(mediaType));
}

if (stopIntent != null) {
builder.setPriorityBeforeO(NotificationCompat.PRIORITY_HIGH);
builder.setVibrate(new long[0]);
Expand All @@ -64,7 +74,7 @@ public static ChromeNotification createNotification(ChromeNotificationBuilder bu
String titleText = getNotificationTitleText(mediaType);
// App name is automatically added to the title from Android N, but needs to be added
// explicitly for prior versions.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N || appName == null) {
builder.setContentTitle(titleText);
} else {
builder.setContentTitle(appContext.getString(
Expand Down
1 change: 1 addition & 0 deletions tools/gritsettings/translation_expectations.pyl
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
"ui/android/java/strings/android_ui_strings.grd",
"ui/chromeos/ui_chromeos_strings.grd",
"ui/strings/ui_strings.grd",
"weblayer/browser/java/weblayer_strings.grd",
],
},
# The policy_templates are translated only into a subset of the languages.
Expand Down
13 changes: 13 additions & 0 deletions weblayer/browser/java/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import("//build/config/android/config.gni")
import("//build/config/android/rules.gni")
import("//build/config/locales.gni")
import("//weblayer/variables.gni")

android_resources("weblayer_resources") {
Expand All @@ -13,6 +14,7 @@ android_resources("weblayer_resources") {
]
custom_package = "org.chromium.weblayer_private"
deps = [
":weblayer_strings_grd",
"//components/browser_ui/strings/android:browser_ui_strings_grd",
"//components/browser_ui/styles/android:java_resources",
"//components/page_info/android:java_resources",
Expand All @@ -34,6 +36,14 @@ java_cpp_template("resource_id_javagen") {
]
}

java_strings_grd("weblayer_strings_grd") {
grd_file = "weblayer_strings.grd"
outputs = [ "values/weblayer_strings.xml" ] +
process_file_template(
android_bundle_locales_as_resources,
[ "values-{{source_name_part}}/weblayer_strings.xml" ])
}

java_cpp_enum("generated_enums") {
sources = [
"//weblayer/browser/controls_visibility_reason.h",
Expand Down Expand Up @@ -81,6 +91,7 @@ android_library("java") {
"org/chromium/weblayer_private/WebLayerExceptionFilter.java",
"org/chromium/weblayer_private/WebLayerFactoryImpl.java",
"org/chromium/weblayer_private/WebLayerImpl.java",
"org/chromium/weblayer_private/WebLayerNotificationChannels.java",
"org/chromium/weblayer_private/WebLayerTabModalPresenter.java",
"org/chromium/weblayer_private/WebViewCompatibilityHelperImpl.java",
"org/chromium/weblayer_private/metrics/MetricsServiceClient.java",
Expand All @@ -97,6 +108,7 @@ android_library("java") {
"//base:jni_java",
"//components/autofill/android:provider_java",
"//components/browser_ui/modaldialog/android:java",
"//components/browser_ui/notifications/android:java",
"//components/browser_ui/styles/android:java",
"//components/browser_ui/styles/android:java_resources",
"//components/browser_ui/util/android:java",
Expand Down Expand Up @@ -124,6 +136,7 @@ android_library("java") {
"//components/url_formatter/android:url_formatter_java",
"//components/variations/android:variations_java",
"//components/version_info/android:version_constants_java",
"//components/webrtc/android:java",
"//content/public/android:content_java",
"//net/android:net_java",
"//services/network/public/mojom:mojom_java",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,28 @@

package org.chromium.weblayer_private;

import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Build;
import android.os.RemoteException;
import android.util.AndroidRuntimeException;
import android.webkit.ValueCallback;

import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;

import org.chromium.base.BuildInfo;
import org.chromium.base.ContextUtils;
import org.chromium.base.annotations.CalledByNative;
import org.chromium.base.annotations.JNINamespace;
import org.chromium.base.annotations.NativeMethods;
import org.chromium.components.browser_ui.notifications.ChromeNotification;
import org.chromium.components.browser_ui.notifications.NotificationBuilder;
import org.chromium.components.browser_ui.notifications.NotificationManagerProxy;
import org.chromium.components.browser_ui.notifications.NotificationManagerProxyImpl;
import org.chromium.components.browser_ui.notifications.NotificationMetadata;
import org.chromium.components.browser_ui.notifications.PendingIntentProvider;
import org.chromium.components.browser_ui.notifications.channels.ChannelsInitializer;
import org.chromium.components.webrtc.MediaCaptureNotificationUtil;
import org.chromium.components.webrtc.MediaCaptureNotificationUtil.MediaType;
import org.chromium.content_public.browser.WebContents;
import org.chromium.weblayer_private.interfaces.IMediaCaptureCallbackClient;
import org.chromium.weblayer_private.interfaces.ObjectWrapper;
Expand All @@ -35,9 +40,7 @@
*/
@JNINamespace("weblayer")
public class MediaStreamManager {
private static boolean sCreatedChannel = false;
private static final String WEBRTC_PREFIX = "org.chromium.weblayer.webrtc";
private static final String CHANNEL_ID = WEBRTC_PREFIX + ".channel";
private static final String EXTRA_TAB_ID = WEBRTC_PREFIX + ".TAB_ID";
private static final String ACTIVATE_TAB_INTENT = WEBRTC_PREFIX + ".ACTIVATE_TAB";
private static final String AV_STREAM_TAG = WEBRTC_PREFIX + ".avstream";
Expand All @@ -53,6 +56,8 @@ public class MediaStreamManager {

private IMediaCaptureCallbackClient mClient;

private TabImpl mTab;

// The notification ID matches the tab ID, which uniquely identifies the notification when
// paired with the tag.
private int mNotificationId;
Expand Down Expand Up @@ -94,7 +99,7 @@ public static void onWebLayerInit() {
prefs.getStringSet(PREF_ACTIVE_AV_STREAM_NOTIFICATION_IDS, null);
if (staleNotificationIds == null) return;

NotificationManagerCompat manager = getNotificationManager();
NotificationManagerProxy manager = getNotificationManager();
if (manager == null) return;

for (String id : staleNotificationIds) {
Expand All @@ -104,6 +109,7 @@ public static void onWebLayerInit() {
}

public MediaStreamManager(TabImpl tab) {
mTab = tab;
mNotificationId = tab.getId();
mNative = MediaStreamManagerJni.get().create(this, tab.getWebContents());
}
Expand All @@ -124,7 +130,7 @@ public void stopStreaming() {
}

private void cancelNotification() {
NotificationManagerCompat notificationManager = getNotificationManager();
NotificationManagerProxy notificationManager = getNotificationManager();
if (notificationManager != null) {
notificationManager.cancel(AV_STREAM_TAG, mNotificationId);
}
Expand Down Expand Up @@ -203,50 +209,36 @@ private void update(boolean audio, boolean video) {
return;
}

NotificationManagerCompat notificationManager = getNotificationManager();
if (notificationManager == null) return;
createNotificationChannel();

Context appContext = ContextUtils.getApplicationContext();
Intent intent = WebLayerImpl.createIntent();
intent.putExtra(EXTRA_TAB_ID, mNotificationId);
intent.setAction(ACTIVATE_TAB_INTENT);
PendingIntent pendingIntent = PendingIntent.getBroadcast(
ContextUtils.getApplicationContext(), mNotificationId, intent, 0);
// TODO(estade): use localized text and correct icon.
NotificationCompat.Builder builder =
new NotificationCompat.Builder(ContextUtils.getApplicationContext(), CHANNEL_ID)
.setOngoing(true)
.setLocalOnly(true)
.setAutoCancel(false)
.setContentIntent(pendingIntent)
.setSmallIcon(android.R.drawable.ic_menu_camera)
.setContentTitle(audio && video
? "all the streamz"
: audio ? "audio streamz" : "video streamz");
notificationManager.notify(AV_STREAM_TAG, mNotificationId, builder.build());
PendingIntentProvider contentIntent =
PendingIntentProvider.getBroadcast(appContext, mNotificationId, intent, 0);

int mediaType = audio && video ? MediaType.AUDIO_AND_VIDEO
: audio ? MediaType.AUDIO_ONLY : MediaType.VIDEO_ONLY;

NotificationManagerProxy notificationManagerProxy = getNotificationManager();
ChannelsInitializer channelsInitializer = new ChannelsInitializer(notificationManagerProxy,
WebLayerNotificationChannels.getInstance(), appContext.getResources());

// TODO(crbug/1076098): don't hard-code incognito to false.
ChromeNotification notification = MediaCaptureNotificationUtil.createNotification(
new NotificationBuilder(appContext, WebLayerNotificationChannels.ChannelId.MEDIA,
channelsInitializer,
new NotificationMetadata(0, AV_STREAM_TAG, mNotificationId)),
mediaType, mTab.getWebContents().getVisibleUrl().getSpec(),
WebLayerImpl.getClientApplicationName(), false /*isIncognito*/, contentIntent,
null /*stopIntent*/, BuildInfo.getInstance().packageName);
notificationManagerProxy.notify(notification);

updateActiveNotifications(true);
notifyClient(audio, video);
}

private void createNotificationChannel() {
// Create the NotificationChannel, but only on API 26+ because
// the NotificationChannel class is new and not in the support library
if (!sCreatedChannel && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// TODO(estade): use localized channel name.
ContextUtils.getApplicationContext()
.getSystemService(NotificationManager.class)
.createNotificationChannel(new NotificationChannel(
CHANNEL_ID, "WebRTC", NotificationManager.IMPORTANCE_LOW));
}

sCreatedChannel = true;
}

private static NotificationManagerCompat getNotificationManager() {
if (ContextUtils.getApplicationContext() == null) {
return null;
}
return NotificationManagerCompat.from(ContextUtils.getApplicationContext());
private static NotificationManagerProxy getNotificationManager() {
return new NotificationManagerProxyImpl(ContextUtils.getApplicationContext());
}

@NativeMethods
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,14 @@ public static Intent createIntent() {
}
}

public static String getClientApplicationName() {
Context context = ContextUtils.getApplicationContext();
return new StringBuilder()
.append(context.getPackageManager().getApplicationLabel(
context.getApplicationInfo()))
.toString();
}

/**
* Performs the minimal initialization needed for a context. This is used for example in
* CrashReporterControllerImpl, so it can be used before full WebLayer initialization.
Expand Down
Loading

0 comments on commit 2c9aa41

Please sign in to comment.