summaryrefslogtreecommitdiff
path: root/android/support/customtabs
diff options
context:
space:
mode:
Diffstat (limited to 'android/support/customtabs')
-rw-r--r--android/support/customtabs/CustomTabsCallback.java16
-rw-r--r--android/support/customtabs/CustomTabsClient.java15
-rw-r--r--android/support/customtabs/CustomTabsService.java43
-rw-r--r--android/support/customtabs/CustomTabsSession.java50
-rw-r--r--android/support/customtabs/CustomTabsSessionToken.java49
-rw-r--r--android/support/customtabs/TrustedWebUtils.java82
6 files changed, 255 insertions, 0 deletions
diff --git a/android/support/customtabs/CustomTabsCallback.java b/android/support/customtabs/CustomTabsCallback.java
index 818118a0..f8d349a8 100644
--- a/android/support/customtabs/CustomTabsCallback.java
+++ b/android/support/customtabs/CustomTabsCallback.java
@@ -16,7 +16,9 @@
package android.support.customtabs;
+import android.net.Uri;
import android.os.Bundle;
+import android.support.customtabs.CustomTabsService.Relation;
/**
* A callback class for custom tabs client to get messages regarding events in their custom tabs. In
@@ -98,4 +100,18 @@ public class CustomTabsCallback {
* @param extras Reserved for future use.
*/
public void onPostMessage(String message, Bundle extras) {}
+
+ /**
+ * Called when a relationship validation result is available.
+ *
+ * @param relation Relation for which the result is available. Value previously passed to
+ * {@link CustomTabsSession#validateRelationship(int, Uri, Bundle)}. Must be one
+ * of the {@code CustomTabsService#RELATION_* } constants.
+ * @param requestedOrigin Origin requested. Value previously passed to
+ * {@link CustomTabsSession#validateRelationship(int, Uri, Bundle)}.
+ * @param result Whether the relation was validated.
+ * @param extras Reserved for future use.
+ */
+ public void onRelationshipValidationResult(@Relation int relation, Uri requestedOrigin,
+ boolean result, Bundle extras) {}
}
diff --git a/android/support/customtabs/CustomTabsClient.java b/android/support/customtabs/CustomTabsClient.java
index 09f31109..2e955cbe 100644
--- a/android/support/customtabs/CustomTabsClient.java
+++ b/android/support/customtabs/CustomTabsClient.java
@@ -31,6 +31,7 @@ import android.os.Looper;
import android.os.RemoteException;
import android.support.annotation.Nullable;
import android.support.annotation.RestrictTo;
+import android.support.customtabs.CustomTabsService.Relation;
import android.text.TextUtils;
import java.util.ArrayList;
@@ -234,6 +235,20 @@ public class CustomTabsClient {
}
});
}
+
+ @Override
+ public void onRelationshipValidationResult(
+ final @Relation int relation, final Uri requestedOrigin, final boolean result,
+ final @Nullable Bundle extras) throws RemoteException {
+ if (callback == null) return;
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ callback.onRelationshipValidationResult(
+ relation, requestedOrigin, result, extras);
+ }
+ });
+ }
};
try {
diff --git a/android/support/customtabs/CustomTabsService.java b/android/support/customtabs/CustomTabsService.java
index 5a940cf4..aad174c0 100644
--- a/android/support/customtabs/CustomTabsService.java
+++ b/android/support/customtabs/CustomTabsService.java
@@ -78,6 +78,23 @@ public abstract class CustomTabsService extends Service {
*/
public static final int RESULT_FAILURE_MESSAGING_ERROR = -3;
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({RELATION_USE_AS_ORIGIN, RELATION_HANDLE_ALL_URLS})
+ public @interface Relation {
+ }
+
+ /**
+ * Used for {@link CustomTabsSession#validateRelationship(int, Uri, Bundle)}. For
+ * App -> Web transitions, requests the app to use the declared origin to be used as origin for
+ * the client app in the web APIs context.
+ */
+ public static final int RELATION_USE_AS_ORIGIN = 1;
+ /**
+ * Used for {@link CustomTabsSession#validateRelationship(int, Uri, Bundle)}. Requests the
+ * ability to handle all URLs from a given origin.
+ */
+ public static final int RELATION_HANDLE_ALL_URLS = 2;
+
private final Map<IBinder, DeathRecipient> mDeathRecipientMap = new ArrayMap<>();
private ICustomTabsService.Stub mBinder = new ICustomTabsService.Stub() {
@@ -137,6 +154,13 @@ public abstract class CustomTabsService extends Service {
return CustomTabsService.this.postMessage(
new CustomTabsSessionToken(callback), message, extras);
}
+
+ @Override
+ public boolean validateRelationship(
+ ICustomTabsCallback callback, @Relation int relation, Uri origin, Bundle extras) {
+ return CustomTabsService.this.validateRelationship(
+ new CustomTabsSessionToken(callback), relation, origin, extras);
+ }
};
@Override
@@ -268,4 +292,23 @@ public abstract class CustomTabsService extends Service {
@Result
protected abstract int postMessage(
CustomTabsSessionToken sessionToken, String message, Bundle extras);
+
+ /**
+ * Request to validate a relationship between the application and an origin.
+ *
+ * If this method returns true, the validation result will be provided through
+ * {@link CustomTabsCallback#onRelationshipValidationResult(int, Uri, boolean, Bundle)}.
+ * Otherwise the request didn't succeed. The client must call
+ * {@link CustomTabsClient#warmup(long)} before this.
+ *
+ * @param sessionToken The unique identifier for the session. Can not be null.
+ * @param relation Relation to check, must be one of the {@code CustomTabsService#RELATION_* }
+ * constants.
+ * @param origin Origin for the relation query.
+ * @param extras Reserved for future use.
+ * @return true if the request has been submitted successfully.
+ */
+ protected abstract boolean validateRelationship(
+ CustomTabsSessionToken sessionToken, @Relation int relation, Uri origin,
+ Bundle extras);
}
diff --git a/android/support/customtabs/CustomTabsSession.java b/android/support/customtabs/CustomTabsSession.java
index cad897c8..a84d63c7 100644
--- a/android/support/customtabs/CustomTabsSession.java
+++ b/android/support/customtabs/CustomTabsSession.java
@@ -25,6 +25,8 @@ import android.os.IBinder;
import android.os.RemoteException;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
+import android.support.annotation.VisibleForTesting;
+import android.support.customtabs.CustomTabsService.Relation;
import android.support.customtabs.CustomTabsService.Result;
import android.view.View;
import android.widget.RemoteViews;
@@ -42,6 +44,21 @@ public final class CustomTabsSession {
private final ICustomTabsCallback mCallback;
private final ComponentName mComponentName;
+ /**
+ * Provides browsers a way to generate a mock {@link CustomTabsSession} for testing
+ * purposes.
+ *
+ * @param componentName The component the session should be created for.
+ * @return A mock session with no functionality.
+ */
+ @VisibleForTesting
+ @NonNull
+ public static CustomTabsSession createMockSessionForTesting(
+ @NonNull ComponentName componentName) {
+ return new CustomTabsSession(
+ null, new CustomTabsSessionToken.MockCallback(), componentName);
+ }
+
/* package */ CustomTabsSession(
ICustomTabsService service, ICustomTabsCallback callback, ComponentName componentName) {
mService = service;
@@ -185,6 +202,39 @@ public final class CustomTabsSession {
}
}
+ /**
+ * Requests to validate a relationship between the application and an origin.
+ *
+ * <p>
+ * See <a href="https://developers.google.com/digital-asset-links/v1/getting-started">here</a>
+ * for documentation about Digital Asset Links. This methods requests the browser to verify
+ * a relation with the calling application, to grant the associated rights.
+ *
+ * <p>
+ * If this method returns {@code true}, the validation result will be provided through
+ * {@link CustomTabsCallback#onRelationshipValidationResult(int, Uri, boolean, Bundle)}.
+ * Otherwise the request didn't succeed. The client must call
+ * {@link CustomTabsClient#warmup(long)} before this.
+ *
+ * @param relation Relation to check, must be one of the {@code CustomTabsService#RELATION_* }
+ * constants.
+ * @param origin Origin.
+ * @param extras Reserved for future use.
+ * @return {@code true} if the request has been submitted successfully.
+ */
+ public boolean validateRelationship(@Relation int relation, @NonNull Uri origin,
+ @Nullable Bundle extras) {
+ if (relation < CustomTabsService.RELATION_USE_AS_ORIGIN
+ || relation > CustomTabsService.RELATION_HANDLE_ALL_URLS) {
+ return false;
+ }
+ try {
+ return mService.validateRelationship(mCallback, relation, origin, extras);
+ } catch (RemoteException e) {
+ return false;
+ }
+ }
+
/* package */ IBinder getBinder() {
return mCallback.asBinder();
}
diff --git a/android/support/customtabs/CustomTabsSessionToken.java b/android/support/customtabs/CustomTabsSessionToken.java
index adfadd92..5a9e1b66 100644
--- a/android/support/customtabs/CustomTabsSessionToken.java
+++ b/android/support/customtabs/CustomTabsSessionToken.java
@@ -17,9 +17,12 @@
package android.support.customtabs;
import android.content.Intent;
+import android.net.Uri;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
+import android.support.annotation.NonNull;
+import android.support.customtabs.CustomTabsService.Relation;
import android.support.v4.app.BundleCompat;
import android.util.Log;
@@ -32,6 +35,29 @@ public class CustomTabsSessionToken {
private final ICustomTabsCallback mCallbackBinder;
private final CustomTabsCallback mCallback;
+ /* package */ static class MockCallback extends ICustomTabsCallback.Stub {
+ @Override
+ public void onNavigationEvent(int navigationEvent, Bundle extras) {}
+
+ @Override
+ public void extraCallback(String callbackName, Bundle args) {}
+
+ @Override
+ public void onMessageChannelReady(Bundle extras) {}
+
+ @Override
+ public void onPostMessage(String message, Bundle extras) {}
+
+ @Override
+ public void onRelationshipValidationResult(@Relation int relation, Uri requestedOrigin,
+ boolean result, Bundle extras) {}
+
+ @Override
+ public IBinder asBinder() {
+ return this;
+ }
+ }
+
/**
* Obtain a {@link CustomTabsSessionToken} from an intent. See {@link CustomTabsIntent.Builder}
* for ways to generate an intent for custom tabs.
@@ -46,6 +72,17 @@ public class CustomTabsSessionToken {
return new CustomTabsSessionToken(ICustomTabsCallback.Stub.asInterface(binder));
}
+ /**
+ * Provides browsers a way to generate a mock {@link CustomTabsSessionToken} for testing
+ * purposes.
+ *
+ * @return A mock token with no functionality.
+ */
+ @NonNull
+ public static CustomTabsSessionToken createMockSessionTokenForTesting() {
+ return new CustomTabsSessionToken(new MockCallback());
+ }
+
CustomTabsSessionToken(ICustomTabsCallback callbackBinder) {
mCallbackBinder = callbackBinder;
mCallback = new CustomTabsCallback() {
@@ -85,6 +122,18 @@ public class CustomTabsSessionToken {
Log.e(TAG, "RemoteException during ICustomTabsCallback transaction");
}
}
+
+ @Override
+ public void onRelationshipValidationResult(@Relation int relation, Uri origin,
+ boolean result, Bundle extras) {
+ try {
+ mCallbackBinder.onRelationshipValidationResult(
+ relation, origin, result, extras);
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException during ICustomTabsCallback transaction");
+ }
+ }
+
};
}
diff --git a/android/support/customtabs/TrustedWebUtils.java b/android/support/customtabs/TrustedWebUtils.java
new file mode 100644
index 00000000..e9a22332
--- /dev/null
+++ b/android/support/customtabs/TrustedWebUtils.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2017 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 android.support.customtabs;
+
+import android.content.Context;
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.v4.app.BundleCompat;
+
+/**
+ * Class for utilities and convenience calls for opening a qualifying web page as a
+ * Trusted Web Activity.
+ *
+ * Trusted Web Activity is a fullscreen UI with no visible browser controls that hosts web pages
+ * meeting certain criteria. The full list of qualifications is at the implementing browser's
+ * discretion, but minimum recommended set is for the web page :
+ * <ul>
+ * <li>To have declared delegate_permission/common.handle_all_urls relationship with the
+ * launching client application ensuring 1:1 trust between the Android native and web
+ * components. See https://developers.google.com/digital-asset-links/ for details.</li>
+ * <li>To work as a reliable, fast and engaging standalone component within the launching app's
+ * flow.</li>
+ * <li>To be accessible and operable even when offline.</li>
+ * </ul>
+ *
+ * Fallback behaviors may also differ with implementation. Possibilities are launching the page in
+ * a custom tab, or showing it in browser UI. Browsers are encouraged to use
+ * {@link CustomTabsCallback#onRelationshipValidationResult(int, Uri, boolean, Bundle)}
+ * for sending details of the verification results.
+ */
+public class TrustedWebUtils {
+
+ /**
+ * Boolean extra that triggers a {@link CustomTabsIntent} launch to be in a fullscreen UI with
+ * no browser controls.
+ *
+ * @see TrustedWebUtils#launchAsTrustedWebActivity(Context, CustomTabsIntent, Uri).
+ */
+ public static final String EXTRA_LAUNCH_AS_TRUSTED_WEB_ACTIVITY =
+ "android.support.customtabs.extra.LAUNCH_AS_TRUSTED_WEB_ACTIVITY";
+
+ private TrustedWebUtils() {}
+
+ /**
+ * Launch the given {@link CustomTabsIntent} as a Trusted Web Activity. The given
+ * {@link CustomTabsIntent} should have a valid {@link CustomTabsSession} associated with it
+ * during construction. Once the Trusted Web Activity is launched, browser side implementations
+ * may have their own fallback behavior (e.g. Showing the page in a custom tab UI with toolbar)
+ * based on qualifications listed above or more.
+ *
+ * @param context {@link Context} to use while launching the {@link CustomTabsIntent}.
+ * @param customTabsIntent The {@link CustomTabsIntent} to use for launching the
+ * Trusted Web Activity. Note that all customizations in the given
+ * associated with browser toolbar controls will be ignored.
+ * @param uri The web page to launch as Trusted Web Activity.
+ */
+ public static void launchAsTrustedWebActivity(@NonNull Context context,
+ @NonNull CustomTabsIntent customTabsIntent, @NonNull Uri uri) {
+ if (BundleCompat.getBinder(
+ customTabsIntent.intent.getExtras(), CustomTabsIntent.EXTRA_SESSION) == null) {
+ throw new IllegalArgumentException(
+ "Given CustomTabsIntent should be associated with a valid CustomTabsSession");
+ }
+ customTabsIntent.intent.putExtra(EXTRA_LAUNCH_AS_TRUSTED_WEB_ACTIVITY, true);
+ customTabsIntent.launchUrl(context, uri);
+ }
+}