Add validateRelationship() to the support library.

This allows a client to request an origin to be validated as first-party
with respect to the calling app. Validation is asynchronous.

BUG=739165

Change-Id: I1a3543a773de9285217ec331759f8a94535b07a0
Review-Url: https://codereview.chromium.org/2978593002
diff --git a/Application/build.gradle b/Application/build.gradle
index c67a37e..8ac3a6b 100644
--- a/Application/build.gradle
+++ b/Application/build.gradle
@@ -2,7 +2,7 @@
 
 android {
     compileSdkVersion 23
-    buildToolsVersion "23.0.1"
+    buildToolsVersion '25.0.0'
     defaultConfig {
         applicationId "org.chromium.customtabsclient.example"
         minSdkVersion 16
diff --git a/build.gradle b/build.gradle
index 1b7886d..5966013 100644
--- a/build.gradle
+++ b/build.gradle
@@ -5,7 +5,7 @@
         jcenter()
     }
     dependencies {
-        classpath 'com.android.tools.build:gradle:1.3.0'
+        classpath 'com.android.tools.build:gradle:2.3.3'
 
         // NOTE: Do not place your application dependencies here; they belong
         // in the individual module build.gradle files
diff --git a/customtabs/build.gradle b/customtabs/build.gradle
index b75c346..a4c6f47 100644
--- a/customtabs/build.gradle
+++ b/customtabs/build.gradle
@@ -1,13 +1,21 @@
 apply plugin: 'android-library'
 
 dependencies {
-    compile 'com.android.support:support-annotations:22.2.0+'
-    compile 'com.android.support:support-v4:25.0.1'
+    compile 'com.android.support:support-annotations:24.2.0+'
+    compile 'com.android.support:support-v4:25.3.1'
+
+    androidTestCompile 'junit:junit:4.12'
+    androidTestCompile 'com.android.support.test:runner:0.5'
+    androidTestCompile 'com.android.support.test:rules:0.5'
 }
 
 android {
     compileSdkVersion 23
-    buildToolsVersion "23.0.1"
+    buildToolsVersion '25.0.0'
+
+    defaultConfig {
+        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+    }
 
     sourceSets {
         main.manifest.srcFile 'AndroidManifest.xml'
diff --git a/customtabs/src/android/support/customtabs/CustomTabsCallback.java b/customtabs/src/android/support/customtabs/CustomTabsCallback.java
index 818118a..14bec65 100644
--- a/customtabs/src/android/support/customtabs/CustomTabsCallback.java
+++ b/customtabs/src/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,15 @@
      * @param extras Reserved for future use.
      */
     public void onPostMessage(String message, Bundle extras) {}
+
+    /**
+     * Called when a relationship validation result is available.
+     *
+     * @param relation As in {@link CustomTabsSession#validateRelationship(int, Uri, Bundle)}
+     * @param requestedOrigin As in {@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/customtabs/src/android/support/customtabs/CustomTabsClient.java b/customtabs/src/android/support/customtabs/CustomTabsClient.java
index 5d7bb9b..d53d3a2 100644
--- a/customtabs/src/android/support/customtabs/CustomTabsClient.java
+++ b/customtabs/src/android/support/customtabs/CustomTabsClient.java
@@ -29,6 +29,7 @@
 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;
@@ -231,6 +232,20 @@
                     }
                 });
             }
+
+            @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/customtabs/src/android/support/customtabs/CustomTabsService.java b/customtabs/src/android/support/customtabs/CustomTabsService.java
index 5a940cf..aad174c 100644
--- a/customtabs/src/android/support/customtabs/CustomTabsService.java
+++ b/customtabs/src/android/support/customtabs/CustomTabsService.java
@@ -78,6 +78,23 @@
      */
     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 @@
             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 @@
     @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/customtabs/src/android/support/customtabs/CustomTabsSession.java b/customtabs/src/android/support/customtabs/CustomTabsSession.java
index a0df820..5275c22 100644
--- a/customtabs/src/android/support/customtabs/CustomTabsSession.java
+++ b/customtabs/src/android/support/customtabs/CustomTabsSession.java
@@ -25,6 +25,7 @@
 import android.os.RemoteException;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
+import android.support.customtabs.CustomTabsService.Relation;
 import android.support.customtabs.CustomTabsService.Result;
 import android.support.customtabs.CustomTabsSessionToken.DummyCallback;
 import android.view.View;
@@ -197,6 +198,33 @@
         }
     }
 
+    /**
+     * 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 relation Relation to check, must be one of the {@code CustomTabsService#RELATION_* }
+     *                 constants.
+     * @param origin Origin.
+     * @param extras Reserved for future use.
+     * @return true if the request has been submitted successfully.
+     */
+    public boolean validateRelationship(@Relation int relation, 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/customtabs/src/android/support/customtabs/CustomTabsSessionToken.java b/customtabs/src/android/support/customtabs/CustomTabsSessionToken.java
index 749edc2..d43349b 100644
--- a/customtabs/src/android/support/customtabs/CustomTabsSessionToken.java
+++ b/customtabs/src/android/support/customtabs/CustomTabsSessionToken.java
@@ -17,9 +17,11 @@
 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.customtabs.CustomTabsService.Relation;
 import android.support.v4.app.BundleCompat;
 import android.util.Log;
 
@@ -46,6 +48,10 @@
         public void onPostMessage(String message, Bundle extras) {}
 
         @Override
+        public void onRelationshipValidationResult(int relation, Uri origin, boolean result,
+                                                   Bundle extras) {}
+
+        @Override
         public IBinder asBinder() {
             return this;
         }
@@ -114,6 +120,17 @@
                     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/customtabs/src/android/support/customtabs/ICustomTabsCallback.aidl b/customtabs/src/android/support/customtabs/ICustomTabsCallback.aidl
index 32b6e9b..3e2c48c 100644
--- a/customtabs/src/android/support/customtabs/ICustomTabsCallback.aidl
+++ b/customtabs/src/android/support/customtabs/ICustomTabsCallback.aidl
@@ -27,4 +27,5 @@
     void extraCallback(String callbackName, in Bundle args) = 2;
     void onMessageChannelReady(in Bundle extras) = 3;
     void onPostMessage(String message, in Bundle extras) = 4;
+    void onRelationshipValidationResult(int relation, in Uri origin, boolean result, in Bundle extras) = 5;
 }
diff --git a/customtabs/src/android/support/customtabs/ICustomTabsService.aidl b/customtabs/src/android/support/customtabs/ICustomTabsService.aidl
index b24b0dd..376c2a4 100644
--- a/customtabs/src/android/support/customtabs/ICustomTabsService.aidl
+++ b/customtabs/src/android/support/customtabs/ICustomTabsService.aidl
@@ -36,4 +36,5 @@
     boolean updateVisuals(in ICustomTabsCallback callback, in Bundle bundle) = 5;
     boolean requestPostMessageChannel(in ICustomTabsCallback callback, in Uri postMessageOrigin) = 6;
     int postMessage(in ICustomTabsCallback callback, String message, in Bundle extras) = 7;
+    boolean validateRelationship(in ICustomTabsCallback callback, int relation, in Uri origin, in Bundle extras) = 8;
 }
diff --git a/customtabs/tests/src/android/support/customtabs/TestCustomTabsCallback.java b/customtabs/tests/src/android/support/customtabs/TestCustomTabsCallback.java
index 56b1817..804d354 100644
--- a/customtabs/tests/src/android/support/customtabs/TestCustomTabsCallback.java
+++ b/customtabs/tests/src/android/support/customtabs/TestCustomTabsCallback.java
@@ -16,6 +16,7 @@
 
 package android.support.customtabs;
 
+import android.net.Uri;
 import android.os.Bundle;
 import android.os.RemoteException;
 
@@ -50,6 +51,13 @@
                 throws RemoteException {
             TestCustomTabsCallback.this.onPostMessage(message, extras);
         }
+
+        @Override
+        public void onRelationshipValidationResult(int relation, Uri origin, boolean result,
+                                                   Bundle extras) throws RemoteException {
+            TestCustomTabsCallback.this.onRelationshipValidationResult(
+                    relation, origin, result, extras);
+        }
     };
 
     /* package */ ICustomTabsCallback getStub() {
diff --git a/customtabs/tests/src/android/support/customtabs/TestCustomTabsService.java b/customtabs/tests/src/android/support/customtabs/TestCustomTabsService.java
index b5c5e86..e3d5fa8 100644
--- a/customtabs/tests/src/android/support/customtabs/TestCustomTabsService.java
+++ b/customtabs/tests/src/android/support/customtabs/TestCustomTabsService.java
@@ -71,4 +71,10 @@
         if (!mPostMessageRequested) return CustomTabsService.RESULT_FAILURE_DISALLOWED;
         return CustomTabsService.RESULT_SUCCESS;
     }
+
+    @Override
+    protected boolean validateRelationship(CustomTabsSessionToken sessionToken,
+                                           @Relation int relation, Uri origin, Bundle extras) {
+        return false;
+    }
 }
diff --git a/demos/build.gradle b/demos/build.gradle
index 5271d71..690abae 100644
--- a/demos/build.gradle
+++ b/demos/build.gradle
@@ -2,7 +2,7 @@
 
 android {
     compileSdkVersion 23
-    buildToolsVersion "23.0.2"
+    buildToolsVersion '25.0.0'
 
     defaultConfig {
         applicationId "org.chromium.customtabsdemos"
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 0c71e76..91989c2 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
-#Wed Apr 10 15:27:10 PDT 2013
+#Wed Jul 12 17:48:04 CEST 2017
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip
diff --git a/shared/build.gradle b/shared/build.gradle
index b3cc1a7..b7de081 100644
--- a/shared/build.gradle
+++ b/shared/build.gradle
@@ -2,7 +2,7 @@
 
 android {
     compileSdkVersion 23
-    buildToolsVersion "23.0.2"
+    buildToolsVersion '25.0.0'
 
     defaultConfig {
         minSdkVersion 15