Commit 8eb5ed93 authored by Peter E Conn's avatar Peter E Conn Committed by Commit Bot

🔁 Create a client for Trusted Web Activity Notification Delegation.

This is the chromium side change that goes with:
  https://chromium-review.googlesource.com/c/custom-tabs-client/+/951306

Bug: 800424
Change-Id: Id0937ca2bd310312927ed5326701150c34105835
Reviewed-on: https://chromium-review.googlesource.com/978126Reviewed-by: default avatarYaron Friedman <yfriedman@chromium.org>
Reviewed-by: default avatarAnita Woodruff <awdf@chromium.org>
Reviewed-by: default avatarBenoit L <lizeb@chromium.org>
Reviewed-by: default avatarBernhard Bauer <bauerb@chromium.org>
Commit-Queue: Peter Conn <peconn@chromium.org>
Cr-Commit-Position: refs/heads/master@{#555332}
parent a3e13eba
...@@ -518,7 +518,7 @@ deps = { ...@@ -518,7 +518,7 @@ deps = {
}, },
'src/third_party/custom_tabs_client/src': { 'src/third_party/custom_tabs_client/src': {
'url': Var('chromium_git') + '/custom-tabs-client.git' + '@' + '5f4df5ce54f4956212eef396e67be376fc4c043c', 'url': Var('chromium_git') + '/custom-tabs-client.git' + '@' + 'fea7d0fe778ef486d4dbbdfa4b1c5f5bec9df186',
'condition': 'checkout_android', 'condition': 'checkout_android',
}, },
......
...@@ -7,6 +7,9 @@ package org.chromium.base; ...@@ -7,6 +7,9 @@ package org.chromium.base;
import android.content.Context; import android.content.Context;
import android.content.pm.PackageInfo; import android.content.pm.PackageInfo;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
/** /**
* This class provides package checking related methods. * This class provides package checking related methods.
...@@ -31,6 +34,22 @@ public class PackageUtils { ...@@ -31,6 +34,22 @@ public class PackageUtils {
return versionCode; return versionCode;
} }
/**
* Decodes into a Bitmap an Image resource stored in another package.
* @param otherPackage The package containing the resource.
* @param resourceId The id of the resource.
* @return A Bitmap containing the resource or null if the package could not be found.
*/
public static Bitmap decodeImageResource(String otherPackage, int resourceId) {
PackageManager packageManager = ContextUtils.getApplicationContext().getPackageManager();
try {
Resources resources = packageManager.getResourcesForApplication(otherPackage);
return BitmapFactory.decodeResource(resources, resourceId);
} catch (PackageManager.NameNotFoundException e) {
return null;
}
}
private PackageUtils() { private PackageUtils() {
// Hide constructor // Hide constructor
} }
......
...@@ -102,6 +102,7 @@ public class OriginVerifier { ...@@ -102,6 +102,7 @@ public class OriginVerifier {
*/ */
public static void addVerifiedOriginForPackage( public static void addVerifiedOriginForPackage(
String packageName, Origin origin, @Relation int relation) { String packageName, Origin origin, @Relation int relation) {
Log.d(TAG, "Adding: %s for %s", packageName, origin);
ThreadUtils.assertOnUiThread(); ThreadUtils.assertOnUiThread();
if (sPackageToCachedOrigins == null) sPackageToCachedOrigins = new HashMap<>(); if (sPackageToCachedOrigins == null) sPackageToCachedOrigins = new HashMap<>();
Set<Origin> cachedOrigins = Set<Origin> cachedOrigins =
...@@ -111,6 +112,9 @@ public class OriginVerifier { ...@@ -111,6 +112,9 @@ public class OriginVerifier {
sPackageToCachedOrigins.put(new Pair<>(packageName, relation), cachedOrigins); sPackageToCachedOrigins.put(new Pair<>(packageName, relation), cachedOrigins);
} }
cachedOrigins.add(origin); cachedOrigins.add(origin);
TrustedWebActivityClient.registerClient(ContextUtils.getApplicationContext(),
origin, packageName);
} }
/** /**
......
// Copyright 2018 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.browserservices;
import android.content.Context;
import android.content.res.Resources;
import android.net.Uri;
import android.support.customtabs.trusted.TrustedWebActivityServiceConnectionManager;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.notifications.NotificationBuilderBase;
import org.chromium.chrome.browser.notifications.NotificationUmaTracker;
/**
* Uses a Trusted Web Activity client to display notifications.
*/
public class TrustedWebActivityClient {
private final TrustedWebActivityServiceConnectionManager mConnection;
private final Context mContext;
/**
* Creates a TrustedWebActivityService.
*/
public TrustedWebActivityClient(Context context) {
mConnection = new TrustedWebActivityServiceConnectionManager(context);
mContext = context.getApplicationContext();
}
/**
* Whether a Trusted Web Activity client is available to display notifications of the given
* scope.
* @param scope The scope of the Service Worker that triggered the notification.
* @return Whether a Trusted Web Activity client was found to show the notification.
*/
public boolean twaExistsForScope(Uri scope) {
return mConnection.serviceExistsForScope(scope, new Origin(scope).toString());
}
/**
* Displays a notification through a Trusted Web Activity client.
* @param scope The scope of the Service Worker that triggered the notification.
* @param platformTag A notification tag.
* @param platformId A notification id.
* @param builder A builder for the notification to display.
* The Trusted Web Activity client may override the small icon.
*/
public void notifyNotification(Uri scope, String platformTag, int platformId,
NotificationBuilderBase builder) {
Resources res = mContext.getResources();
String channelDisplayName = res.getString(R.string.notification_category_group_general);
mConnection.execute(scope, new Origin(scope).toString(), service -> {
int smallIconId = service.getSmallIconId();
if (smallIconId != -1) {
builder.setSmallIconForRemoteApp(smallIconId,
service.getComponentName().getPackageName());
}
boolean success =
service.notify(platformTag, platformId, builder.build(), channelDisplayName);
if (success) {
NotificationUmaTracker.getInstance().onNotificationShown(
NotificationUmaTracker.SITES, null);
}
});
}
/**
* Cancels a notification through a Trusted Web Activity client.
* @param scope The scope of the Service Worker that triggered the notification.
* @param platformTag The tag of the notification to cancel.
* @param platformId The id of the notification to cancel.
*/
public void cancelNotification(Uri scope, String platformTag, int platformId) {
mConnection.execute(scope, new Origin(scope).toString(),
service -> service.cancel(platformTag, platformId));
}
/**
* Registers the package of a Trusted Web Activity client app to be used to deal with
* notifications from the given origin. This can be called on any thread, but may hit the disk
* so should be called on a background thread if possible.
* @param context A context used to access shared preferences.
* @param origin The origin to use the client app for.
* @param clientPackage The package of the client app.
*/
public static void registerClient(Context context, Origin origin, String clientPackage) {
TrustedWebActivityServiceConnectionManager
.registerClient(context, origin.toString(), clientPackage);
}
}
...@@ -20,6 +20,7 @@ import android.graphics.PorterDuffColorFilter; ...@@ -20,6 +20,7 @@ import android.graphics.PorterDuffColorFilter;
import android.graphics.drawable.Icon; import android.graphics.drawable.Icon;
import android.os.Build; import android.os.Build;
import org.chromium.base.PackageUtils;
import org.chromium.base.VisibleForTesting; import org.chromium.base.VisibleForTesting;
import org.chromium.chrome.browser.widget.RoundedIconGenerator; import org.chromium.chrome.browser.widget.RoundedIconGenerator;
...@@ -212,6 +213,28 @@ public abstract class NotificationBuilderBase { ...@@ -212,6 +213,28 @@ public abstract class NotificationBuilderBase {
return this; return this;
} }
/**
* Sets the small icon id for a notification that will be displayed by a different Android app
* (eg a Web APK or Trusted Web Activity). Wherever the platform supports using a small icon
* bitmap, and a non-null {@code Bitmap} is provided, it will take precedence over one specified
* as a resource id.
* @param iconId An iconId for a resource in the package that will display the notification.
* @param packageName The package name of the package that will display the notification.
* @return This NotificationBuilderBase.
*/
public NotificationBuilderBase setSmallIconForRemoteApp(int iconId, String packageName) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// On Android M+, the small icon has to be from the resources of the app whose context
// is passed to the Notification.Builder constructor.
setSmallIcon(PackageUtils.decodeImageResource(packageName, iconId));
} else {
// Pre Android M, the small icon has to be from the resources of the app whose
// NotificationManager is used in NotificationManager#notify.
setSmallIcon(iconId);
}
return this;
}
/** Returns whether a small icon bitmap was set. */ /** Returns whether a small icon bitmap was set. */
public boolean hasSmallIconBitmap() { public boolean hasSmallIconBitmap() {
return mSmallIconBitmap != null; return mSmallIconBitmap != null;
......
...@@ -29,6 +29,7 @@ import org.chromium.base.metrics.RecordHistogram; ...@@ -29,6 +29,7 @@ import org.chromium.base.metrics.RecordHistogram;
import org.chromium.base.metrics.RecordUserAction; import org.chromium.base.metrics.RecordUserAction;
import org.chromium.chrome.R; import org.chromium.chrome.R;
import org.chromium.chrome.browser.ChromeFeatureList; import org.chromium.chrome.browser.ChromeFeatureList;
import org.chromium.chrome.browser.browserservices.TrustedWebActivityClient;
import org.chromium.chrome.browser.init.ChromeBrowserInitializer; import org.chromium.chrome.browser.init.ChromeBrowserInitializer;
import org.chromium.chrome.browser.notifications.channels.ChannelDefinitions; import org.chromium.chrome.browser.notifications.channels.ChannelDefinitions;
import org.chromium.chrome.browser.notifications.channels.SiteChannelsManager; import org.chromium.chrome.browser.notifications.channels.SiteChannelsManager;
...@@ -80,6 +81,8 @@ public class NotificationPlatformBridge { ...@@ -80,6 +81,8 @@ public class NotificationPlatformBridge {
private long mLastNotificationClickMs; private long mLastNotificationClickMs;
private TrustedWebActivityClient mTwaClient;
/** /**
* Creates a new instance of the NotificationPlatformBridge. * Creates a new instance of the NotificationPlatformBridge.
* *
...@@ -130,6 +133,7 @@ public class NotificationPlatformBridge { ...@@ -130,6 +133,7 @@ public class NotificationPlatformBridge {
mNotificationManager = new NotificationManagerProxyImpl( mNotificationManager = new NotificationManagerProxyImpl(
(NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE)); (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE));
} }
mTwaClient = new TrustedWebActivityClient(context);
} }
/** /**
...@@ -596,6 +600,9 @@ public class NotificationPlatformBridge { ...@@ -596,6 +600,9 @@ public class NotificationPlatformBridge {
if (forWebApk) { if (forWebApk) {
WebApkServiceClient.getInstance().notifyNotification( WebApkServiceClient.getInstance().notifyNotification(
webApkPackage, notificationBuilder, notificationId, PLATFORM_ID); webApkPackage, notificationBuilder, notificationId, PLATFORM_ID);
} else if (mTwaClient.twaExistsForScope(Uri.parse(scopeUrl))) {
mTwaClient.notifyNotification(Uri.parse(scopeUrl), notificationId, PLATFORM_ID,
notificationBuilder);
} else { } else {
// Set up a pending intent for going to the settings screen for |origin|. // Set up a pending intent for going to the settings screen for |origin|.
Intent settingsIntent = PreferencesLauncher.createIntentForSettingsPage( Intent settingsIntent = PreferencesLauncher.createIntentForSettingsPage(
...@@ -712,24 +719,37 @@ public class NotificationPlatformBridge { ...@@ -712,24 +719,37 @@ public class NotificationPlatformBridge {
@Override @Override
public void onChecked(boolean doesBrowserBackWebApk) { public void onChecked(boolean doesBrowserBackWebApk) {
closeNotificationInternal(notificationId, closeNotificationInternal(notificationId,
doesBrowserBackWebApk ? webApkPackageFound : null); doesBrowserBackWebApk ? webApkPackageFound : null,
scopeUrl);
} }
}; };
ChromeWebApkHost.checkChromeBacksWebApkAsync(webApkPackageFound, callback); ChromeWebApkHost.checkChromeBacksWebApkAsync(webApkPackageFound, callback);
return; return;
} }
} }
closeNotificationInternal(notificationId, webApkPackage); closeNotificationInternal(notificationId, webApkPackage, scopeUrl);
} }
/** Called after querying whether the browser backs the given WebAPK. */ /** Called after querying whether the browser backs the given WebAPK. */
private void closeNotificationInternal(String notificationId, String webApkPackage) { private void closeNotificationInternal(String notificationId, String webApkPackage,
if (TextUtils.isEmpty(webApkPackage)) { String scopeUrl) {
mNotificationManager.cancel(notificationId, PLATFORM_ID); if (!TextUtils.isEmpty(webApkPackage)) {
} else {
WebApkServiceClient.getInstance().cancelNotification( WebApkServiceClient.getInstance().cancelNotification(
webApkPackage, notificationId, PLATFORM_ID); webApkPackage, notificationId, PLATFORM_ID);
return;
} }
if (mTwaClient.twaExistsForScope(Uri.parse(scopeUrl))) {
mTwaClient.cancelNotification(Uri.parse(scopeUrl), notificationId, PLATFORM_ID);
// There's an edge case where a notification was displayed by Chrome, a Trusted Web
// Activity is then installed and run then the notification is cancelled by javascript.
// Chrome will attempt to close the notification through the TWA client and not itself.
// Since NotificationManager#cancel is safe to call if the requested notification
// isn't being shown, we just call that as well to ensure notifications are cleared.
}
mNotificationManager.cancel(notificationId, PLATFORM_ID);
} }
/** /**
......
...@@ -6,9 +6,6 @@ package org.chromium.chrome.browser.webapps; ...@@ -6,9 +6,6 @@ package org.chromium.chrome.browser.webapps;
import android.content.pm.ApplicationInfo; import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Build; import android.os.Build;
import android.os.IBinder; import android.os.IBinder;
import android.os.RemoteException; import android.os.RemoteException;
...@@ -84,20 +81,8 @@ public class WebApkServiceClient { ...@@ -84,20 +81,8 @@ public class WebApkServiceClient {
@Override @Override
public void useApi(IWebApkApi api) throws RemoteException { public void useApi(IWebApkApi api) throws RemoteException {
int smallIconId = api.getSmallIconId(); int smallIconId = api.getSmallIconId();
// Prior to Android M, the small icon had to be from the resources of the app whose notificationBuilder.setSmallIconForRemoteApp(smallIconId, webApkPackage);
// NotificationManager is used in {@link NotificationManager#notify()}. On Android
// M+, the small icon has to be from the resources of the app whose context is
// passed to the {@link Notification.Builder()} constructor.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// Android M+ introduced
// {@link Notification.Builder#setSmallIcon(Bitmap icon)}.
if (!notificationBuilder.hasSmallIconBitmap()) {
notificationBuilder.setSmallIcon(
decodeImageResource(webApkPackage, smallIconId));
}
} else {
notificationBuilder.setSmallIcon(smallIconId);
}
boolean notificationPermissionEnabled = api.notificationPermissionEnabled(); boolean notificationPermissionEnabled = api.notificationPermissionEnabled();
if (notificationPermissionEnabled) { if (notificationPermissionEnabled) {
String channelName = null; String channelName = null;
...@@ -138,17 +123,6 @@ public class WebApkServiceClient { ...@@ -138,17 +123,6 @@ public class WebApkServiceClient {
sInstance.mConnectionManager.disconnectAll(ContextUtils.getApplicationContext()); sInstance.mConnectionManager.disconnectAll(ContextUtils.getApplicationContext());
} }
/** Decodes bitmap from WebAPK's resources. */
private static Bitmap decodeImageResource(String webApkPackage, int resourceId) {
PackageManager packageManager = ContextUtils.getApplicationContext().getPackageManager();
try {
Resources resources = packageManager.getResourcesForApplication(webApkPackage);
return BitmapFactory.decodeResource(resources, resourceId);
} catch (PackageManager.NameNotFoundException e) {
return null;
}
}
/** Returns whether the WebAPK targets SDK 26+. */ /** Returns whether the WebAPK targets SDK 26+. */
private boolean webApkTargetsAtLeastO(String webApkPackage) { private boolean webApkTargetsAtLeastO(String webApkPackage) {
try { try {
......
...@@ -149,6 +149,7 @@ chrome_java_sources = [ ...@@ -149,6 +149,7 @@ chrome_java_sources = [
"java/src/org/chromium/chrome/browser/browserservices/OriginVerifier.java", "java/src/org/chromium/chrome/browser/browserservices/OriginVerifier.java",
"java/src/org/chromium/chrome/browser/browserservices/Origin.java", "java/src/org/chromium/chrome/browser/browserservices/Origin.java",
"java/src/org/chromium/chrome/browser/browserservices/PostMessageHandler.java", "java/src/org/chromium/chrome/browser/browserservices/PostMessageHandler.java",
"java/src/org/chromium/chrome/browser/browserservices/TrustedWebActivityClient.java",
"java/src/org/chromium/chrome/browser/browsing_data/UrlFilters.java", "java/src/org/chromium/chrome/browser/browsing_data/UrlFilters.java",
"java/src/org/chromium/chrome/browser/childaccounts/ChildAccountFeedbackReporter.java", "java/src/org/chromium/chrome/browser/childaccounts/ChildAccountFeedbackReporter.java",
"java/src/org/chromium/chrome/browser/childaccounts/ChildAccountService.java", "java/src/org/chromium/chrome/browser/childaccounts/ChildAccountService.java",
......
...@@ -63,6 +63,9 @@ android_library("custom_tabs_support_java") { ...@@ -63,6 +63,9 @@ android_library("custom_tabs_support_java") {
"src/customtabs/src/android/support/customtabs/browseractions/BrowserActionsIntent.java", "src/customtabs/src/android/support/customtabs/browseractions/BrowserActionsIntent.java",
"src/customtabs/src/android/support/customtabs/browseractions/BrowserServiceFileProvider.java", "src/customtabs/src/android/support/customtabs/browseractions/BrowserServiceFileProvider.java",
"src/customtabs/src/android/support/customtabs/browseractions/BrowserServiceImageReadTask.java", "src/customtabs/src/android/support/customtabs/browseractions/BrowserServiceImageReadTask.java",
"src/customtabs/src/android/support/customtabs/trusted/TrustedWebActivityService.java",
"src/customtabs/src/android/support/customtabs/trusted/TrustedWebActivityServiceWrapper.java",
"src/customtabs/src/android/support/customtabs/trusted/TrustedWebActivityServiceConnectionManager.java",
"src/customtabs/src/android/support/customtabs/CustomTabsCallback.java", "src/customtabs/src/android/support/customtabs/CustomTabsCallback.java",
"src/customtabs/src/android/support/customtabs/CustomTabsClient.java", "src/customtabs/src/android/support/customtabs/CustomTabsClient.java",
"src/customtabs/src/android/support/customtabs/CustomTabsIntent.java", "src/customtabs/src/android/support/customtabs/CustomTabsIntent.java",
...@@ -93,5 +96,6 @@ android_aidl("chrome_custom_tabs_service_aidl") { ...@@ -93,5 +96,6 @@ android_aidl("chrome_custom_tabs_service_aidl") {
"$java_in_dir/ICustomTabsCallback.aidl", "$java_in_dir/ICustomTabsCallback.aidl",
"$java_in_dir/ICustomTabsService.aidl", "$java_in_dir/ICustomTabsService.aidl",
"$java_in_dir/IPostMessageService.aidl", "$java_in_dir/IPostMessageService.aidl",
"$java_in_dir/trusted/ITrustedWebActivityService.aidl",
] ]
} }
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