blob: b088802ac643247ae93d47bfe08b242cb7738cd5 [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.Parcelable;
import android.os.RemoteException;
import android.support.customtabs.trusted.ITrustedWebActivityService;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.RequiresApi;
import android.support.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 static final String REMOTE_EXCEPTION_MESSAGE = "RemoteException while trying to "
+ "communicate with the TrustedWebActivityService, this is probably because the "
+ "service died while attempting to respond. Check to see if the service crashed for "
+ "some reason.";
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.
*/
public boolean areNotificationsEnabled(@NonNull String channelName) {
try {
Bundle args = new NotificationsEnabledArgs(channelName).toBundle();
return ResultArgs.fromBundle(mService.areNotificationsEnabled(args)).success;
} catch (RemoteException e) {
throw new RuntimeException("Failure when connecting to TrustedWebActivityService", e);
}
}
/**
* 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 SecurityException If verification with the TrustedWebActivityService fails.
*/
public boolean notify(@NonNull String platformTag, int platformId,
@NonNull Notification notification, @NonNull String channel) {
try {
Bundle args = new NotifyNotificationArgs(platformTag, platformId, notification, channel)
.toBundle();
return ResultArgs.fromBundle(mService.notifyNotificationWithChannel(args)).success;
} catch (RemoteException e) {
throw new RuntimeException(REMOTE_EXCEPTION_MESSAGE, e);
}
}
/**
* Requests a notification be cancelled.
* @param platformTag The tag to identify the notification.
* @param platformId The id to identify the notification.
* @throws SecurityException If verification with the TrustedWebActivityService fails.
*/
public void cancel(@NonNull String platformTag, int platformId) {
try {
Bundle args = new CancelNotificationArgs(platformTag, platformId).toBundle();
mService.cancelNotification(args);
} catch (RemoteException e) {
throw new RuntimeException(REMOTE_EXCEPTION_MESSAGE, e);
}
}
/**
* 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 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() {
try {
Bundle notifications = mService.getActiveNotifications();
return ActiveNotificationsArgs.fromBundle(notifications).notifications;
} catch (RemoteException e) {
throw new RuntimeException(REMOTE_EXCEPTION_MESSAGE, e);
}
}
/**
* 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 SecurityException If verification with the TrustedWebActivityService fails.
*/
public int getSmallIconId() {
try {
return mService.getSmallIconId();
} catch (RemoteException e) {
throw new RuntimeException(REMOTE_EXCEPTION_MESSAGE, e);
}
}
/**
* 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 SecurityException If verification with the TrustedWebActivityService fails.
*/
@Nullable
public Bitmap getSmallIconBitmap() {
try {
return mService.getSmallIconBitmap()
.getParcelable(TrustedWebActivityService.KEY_SMALL_ICON_BITMAP);
} catch (RemoteException e) {
throw new RuntimeException(REMOTE_EXCEPTION_MESSAGE, e);
}
}
/**
* 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;
}
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);
}
}