diff options
Diffstat (limited to 'android/support/customtabs')
-rw-r--r-- | android/support/customtabs/CustomTabsCallback.java | 16 | ||||
-rw-r--r-- | android/support/customtabs/CustomTabsClient.java | 15 | ||||
-rw-r--r-- | android/support/customtabs/CustomTabsService.java | 43 | ||||
-rw-r--r-- | android/support/customtabs/CustomTabsSession.java | 50 | ||||
-rw-r--r-- | android/support/customtabs/CustomTabsSessionToken.java | 49 | ||||
-rw-r--r-- | android/support/customtabs/TrustedWebUtils.java | 82 |
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); + } +} |