blob: 56b62f174ca02aad1c24faf28c2be4ea959952f2 [file] [log] [blame]
/*
* Copyright 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.browser.trusted;
import android.app.Notification;
import android.content.ComponentName;
import android.graphics.Bitmap;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Parcelable;
import android.os.RemoteException;
import android.support.customtabs.trusted.ITrustedWebActivityCallback;
import android.support.customtabs.trusted.ITrustedWebActivityService;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.annotation.RestrictTo;
/**
* TrustedWebActivityServiceConnection is used by a Trusted Web Activity provider to wrap calls to
* the {@link TrustedWebActivityService} in the client app.
* All of these calls except {@link #getComponentName()} forward over IPC
* to corresponding calls on {@link TrustedWebActivityService}, eg {@link #getSmallIconId()}
* forwards to {@link TrustedWebActivityService#onGetSmallIconId()}.
* <p>
* These IPC calls are synchronous, though the {@link TrustedWebActivityService} method may hit the
* disk. Therefore it is recommended to call them on a background thread (without StrictMode).
*/
public final class TrustedWebActivityServiceConnection {
// Inputs.
private static final String KEY_PLATFORM_TAG =
"android.support.customtabs.trusted.PLATFORM_TAG";
private static final String KEY_PLATFORM_ID =
"android.support.customtabs.trusted.PLATFORM_ID";
private static final String KEY_NOTIFICATION =
"android.support.customtabs.trusted.NOTIFICATION";
private static final String KEY_CHANNEL_NAME =
"android.support.customtabs.trusted.CHANNEL_NAME";
private static final String KEY_ACTIVE_NOTIFICATIONS =
"android.support.customtabs.trusted.ACTIVE_NOTIFICATIONS";
// Outputs.
private static final String KEY_NOTIFICATION_SUCCESS =
"android.support.customtabs.trusted.NOTIFICATION_SUCCESS";
private final ITrustedWebActivityService mService;
private final ComponentName mComponentName;
TrustedWebActivityServiceConnection(@NonNull ITrustedWebActivityService service,
@NonNull ComponentName componentName) {
mService = service;
mComponentName = componentName;
}
/**
* Checks whether notifications are enabled.
* @param channelName The name of the channel to check enabled status. Only used on Android O+.
* @return Whether notifications or the notification channel is blocked for the client app.
* @throws RemoteException If the Service dies while responding to the request.
* @throws SecurityException If verification with the TrustedWebActivityService fails.
*/
public boolean areNotificationsEnabled(@NonNull String channelName) throws RemoteException {
Bundle args = new NotificationsEnabledArgs(channelName).toBundle();
return ResultArgs.fromBundle(mService.areNotificationsEnabled(args)).success;
}
/**
* Requests a notification be shown.
* @param platformTag The tag to identify the notification.
* @param platformId The id to identify the notification.
* @param notification The notification.
* @param channel The name of the channel in the Trusted Web Activity client app to display the
* notification on.
* @return Whether notifications or the notification channel are blocked for the client app.
* @throws RemoteException If the Service dies while responding to the request.
* @throws SecurityException If verification with the TrustedWebActivityService fails.
*/
public boolean notify(@NonNull String platformTag, int platformId,
@NonNull Notification notification, @NonNull String channel) throws RemoteException {
Bundle args = new NotifyNotificationArgs(platformTag, platformId, notification, channel)
.toBundle();
return ResultArgs.fromBundle(mService.notifyNotificationWithChannel(args)).success;
}
/**
* Requests a notification be cancelled.
* @param platformTag The tag to identify the notification.
* @param platformId The id to identify the notification.
* @throws RemoteException If the Service dies while responding to the request.
* @throws SecurityException If verification with the TrustedWebActivityService fails.
*/
public void cancel(@NonNull String platformTag, int platformId) throws RemoteException {
Bundle args = new CancelNotificationArgs(platformTag, platformId).toBundle();
mService.cancelNotification(args);
}
/**
* Gets the notifications shown by the Trusted Web Activity client. Can only be called on
* Android M and above.
* @return An StatusBarNotification[] as a Parcelable[]. This is so this code can compile for
* Jellybean (even if it must not be called for pre-Marshmallow).
* @throws RemoteException If the Service dies while responding to the request.
* @throws SecurityException If verification with the TrustedWebActivityService fails.
* @throws IllegalStateException If called on Android pre-M.
*
* @hide
*/
@RestrictTo(RestrictTo.Scope.LIBRARY)
@RequiresApi(Build.VERSION_CODES.M)
@NonNull
public Parcelable[] getActiveNotifications() throws RemoteException {
Bundle notifications = mService.getActiveNotifications();
return ActiveNotificationsArgs.fromBundle(notifications).notifications;
}
/**
* Requests an Android resource id to be used for the notification small icon.
* @return An Android resource id for the notification small icon. -1 if non found.
* @throws RemoteException If the Service dies while responding to the request.
* @throws SecurityException If verification with the TrustedWebActivityService fails.
*/
public int getSmallIconId() throws RemoteException {
return mService.getSmallIconId();
}
/**
* Requests a bitmap of a small icon to be used for the notification
* small icon. The bitmap is decoded on the side of Trusted Web Activity client using
* the resource id from {@link TrustedWebActivityService#onGetSmallIconId}.
* @return A {@link Bitmap} to be used for the small icon.
* @throws RemoteException If the Service dies while responding to the request.
* @throws SecurityException If verification with the TrustedWebActivityService fails.
*/
@Nullable
public Bitmap getSmallIconBitmap() throws RemoteException {
return mService.getSmallIconBitmap()
.getParcelable(TrustedWebActivityService.KEY_SMALL_ICON_BITMAP);
}
/**
* Gets the {@link ComponentName} of the connected Trusted Web Activity client app.
* @return The Trusted Web Activity client app component name.
*/
@NonNull
public ComponentName getComponentName() {
return mComponentName;
}
/**
* Passes a free-form command to the client.
* {@link TrustedWebActivityService#onExtraCommand} will be called.
* The client may not know how to deal with the command, in which case {@code null} may be
* returned.
*
* @param commandName Name of the command to execute.
* @param args Arguments to the command.
* @param callback Callback that may be used to return data, depending on the command.
* @return The result {@link Bundle}, or {@code null} if the command could not be executed.
* @throws RemoteException If the Service dies while responding to the request.
* @throws SecurityException If verification with the TrustedWebActivityService fails.
*/
@SuppressWarnings("NullAway") // TODO: b/142938599
@Nullable
public Bundle sendExtraCommand(@NonNull String commandName, @NonNull Bundle args,
@Nullable TrustedWebActivityCallback callback) throws RemoteException {
ITrustedWebActivityCallback callbackBinder = wrapCallback(callback);
IBinder binder = callbackBinder == null ? null : callbackBinder.asBinder();
return mService.extraCommand(commandName, args, binder);
}
static class NotifyNotificationArgs {
public final String platformTag;
public final int platformId;
public final Notification notification;
public final String channelName;
NotifyNotificationArgs(String platformTag, int platformId,
Notification notification, String channelName) {
this.platformTag = platformTag;
this.platformId = platformId;
this.notification = notification;
this.channelName = channelName;
}
public static NotifyNotificationArgs fromBundle(Bundle bundle) {
ensureBundleContains(bundle, KEY_PLATFORM_TAG);
ensureBundleContains(bundle, KEY_PLATFORM_ID);
ensureBundleContains(bundle, KEY_NOTIFICATION);
ensureBundleContains(bundle, KEY_CHANNEL_NAME);
return new NotifyNotificationArgs(bundle.getString(KEY_PLATFORM_TAG),
bundle.getInt(KEY_PLATFORM_ID),
(Notification) bundle.getParcelable(KEY_NOTIFICATION),
bundle.getString(KEY_CHANNEL_NAME));
}
public Bundle toBundle() {
Bundle args = new Bundle();
args.putString(KEY_PLATFORM_TAG, platformTag);
args.putInt(KEY_PLATFORM_ID, platformId);
args.putParcelable(KEY_NOTIFICATION, notification);
args.putString(KEY_CHANNEL_NAME, channelName);
return args;
}
}
static class CancelNotificationArgs {
public final String platformTag;
public final int platformId;
CancelNotificationArgs(String platformTag, int platformId) {
this.platformTag = platformTag;
this.platformId = platformId;
}
public static CancelNotificationArgs fromBundle(Bundle bundle) {
ensureBundleContains(bundle, KEY_PLATFORM_TAG);
ensureBundleContains(bundle, KEY_PLATFORM_ID);
return new CancelNotificationArgs(bundle.getString(KEY_PLATFORM_TAG),
bundle.getInt(KEY_PLATFORM_ID));
}
public Bundle toBundle() {
Bundle args = new Bundle();
args.putString(KEY_PLATFORM_TAG, platformTag);
args.putInt(KEY_PLATFORM_ID, platformId);
return args;
}
}
static class ResultArgs {
public final boolean success;
ResultArgs(boolean success) {
this.success = success;
}
public static ResultArgs fromBundle(Bundle bundle) {
ensureBundleContains(bundle, KEY_NOTIFICATION_SUCCESS);
return new ResultArgs(bundle.getBoolean(KEY_NOTIFICATION_SUCCESS));
}
public Bundle toBundle() {
Bundle args = new Bundle();
args.putBoolean(KEY_NOTIFICATION_SUCCESS, success);
return args;
}
}
static class ActiveNotificationsArgs {
public final Parcelable[] notifications;
ActiveNotificationsArgs(Parcelable[] notifications) {
this.notifications = notifications;
}
public static ActiveNotificationsArgs fromBundle(Bundle bundle) {
ensureBundleContains(bundle, KEY_ACTIVE_NOTIFICATIONS);
return new ActiveNotificationsArgs(bundle.getParcelableArray(KEY_ACTIVE_NOTIFICATIONS));
}
public Bundle toBundle() {
Bundle args = new Bundle();
args.putParcelableArray(KEY_ACTIVE_NOTIFICATIONS, notifications);
return args;
}
}
static class NotificationsEnabledArgs {
public final String channelName;
NotificationsEnabledArgs(String channelName) {
this.channelName = channelName;
}
public static NotificationsEnabledArgs fromBundle(Bundle bundle) {
ensureBundleContains(bundle, KEY_CHANNEL_NAME);
return new NotificationsEnabledArgs(bundle.getString(KEY_CHANNEL_NAME));
}
public Bundle toBundle() {
Bundle args = new Bundle();
args.putString(KEY_CHANNEL_NAME, channelName);
return args;
}
}
@SuppressWarnings("WeakerAccess") /* synthetic access */
static void ensureBundleContains(Bundle args, String key) {
if (args.containsKey(key)) return;
throw new IllegalArgumentException("Bundle must contain " + key);
}
@Nullable
private static ITrustedWebActivityCallback wrapCallback(
@Nullable TrustedWebActivityCallback callback) {
if (callback == null) return null;
return new ITrustedWebActivityCallback.Stub() {
@Override
public void onExtraCallback(String callbackName, Bundle args)
throws RemoteException {
callback.onExtraCallback(callbackName, args);
}
};
}
}