DO NOT MERGE - Add CTS test for Settings.ACTION_REQUEST_SET_AUTOFILL_SERVICE

Test: atest android.autofillservice.cts.SettingsIntentTest

Fixes: 79529318
Bug: 79615759

Change-Id: I58a402534945b28b06373e6ce3586a63903e8725
Merged-In: I58a402534945b28b06373e6ce3586a63903e8725
(cherry picked from commit 40a157245445015307bc71b9d13b60afbc596eee)
(cherry picked from commit eb038272cbbd53f9f5c7abe4755fa88ef75c7948)
diff --git a/tests/autofillservice/AndroidManifest.xml b/tests/autofillservice/AndroidManifest.xml
index 08f0754..9303955 100644
--- a/tests/autofillservice/AndroidManifest.xml
+++ b/tests/autofillservice/AndroidManifest.xml
@@ -66,6 +66,10 @@
         <activity android:name=".OutOfProcessLoginActivity"
             android:process="android.autofillservice.cts.outside"/>
         <activity android:name=".FragmentContainerActivity" />
+        <activity android:name=".WebViewActivity"/>
+        <activity android:name=".TrampolineWelcomeActivity"/>
+        <activity android:name=".AttachedContextActivity"/>
+        <activity android:name=".TrampolineForResultActivity" />
 
         <service
             android:name=".InstrumentedAutoFillService"
@@ -75,6 +79,22 @@
                 <action android:name="android.service.autofill.AutofillService" />
             </intent-filter>
         </service>
+        <service
+            android:name=".NoOpAutofillService"
+            android:label="NoOpAutofillService"
+            android:permission="android.permission.BIND_AUTOFILL_SERVICE" >
+            <intent-filter>
+                <action android:name="android.service.autofill.AutofillService" />
+            </intent-filter>
+        </service>
+        <!--  BadAutofillService does not declare the proper permission -->
+        <service
+            android:name=".BadAutofillService"
+            android:label="BadAutofillService">
+            <intent-filter>
+                <action android:name="android.service.autofill.AutofillService" />
+            </intent-filter>
+        </service>
     </application>
 
     <instrumentation
diff --git a/tests/autofillservice/src/android/autofillservice/cts/BadAutofillService.java b/tests/autofillservice/src/android/autofillservice/cts/BadAutofillService.java
new file mode 100644
index 0000000..19a8ec1
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/BadAutofillService.java
@@ -0,0 +1,49 @@
+/*
+ * 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.autofillservice.cts;
+
+import android.os.CancellationSignal;
+import android.service.autofill.AutofillService;
+import android.service.autofill.FillCallback;
+import android.service.autofill.FillRequest;
+import android.service.autofill.SaveCallback;
+import android.service.autofill.SaveRequest;
+import android.util.Log;
+
+/**
+ * An {@link AutofillService} implementation that does fails if called upon.
+ */
+public class BadAutofillService extends AutofillService {
+
+    private static final String TAG = "BadAutofillService";
+
+    static final String SERVICE_NAME = BadAutofillService.class.getPackage().getName()
+            + "/." + BadAutofillService.class.getSimpleName();
+    static final String SERVICE_LABEL = "BadAutofillService";
+
+    @Override
+    public void onFillRequest(FillRequest request, CancellationSignal cancellationSignal,
+            FillCallback callback) {
+        Log.e(TAG, "onFillRequest() should never be called");
+        throw new UnsupportedOperationException("onFillRequest() should never be called");
+    }
+
+    @Override
+    public void onSaveRequest(SaveRequest request, SaveCallback callback) {
+        Log.e(TAG, "onSaveRequest() should never be called");
+        throw new UnsupportedOperationException("onSaveRequest() should never be called");
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/Helper.java b/tests/autofillservice/src/android/autofillservice/cts/Helper.java
index 0280b44..0c384ff 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/Helper.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/Helper.java
@@ -59,6 +59,8 @@
 
     static final boolean VERBOSE = false;
 
+    static final String MY_PACKAGE = "android.autofillservice.cts";
+
     static final String ID_USERNAME_LABEL = "username_label";
     static final String ID_USERNAME = "username";
     static final String ID_PASSWORD_LABEL = "password_label";
diff --git a/tests/autofillservice/src/android/autofillservice/cts/InstrumentedAutoFillService.java b/tests/autofillservice/src/android/autofillservice/cts/InstrumentedAutoFillService.java
index 9650fff..0840f28b 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/InstrumentedAutoFillService.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/InstrumentedAutoFillService.java
@@ -53,8 +53,11 @@
  */
 public class InstrumentedAutoFillService extends AutofillService {
 
-    static final String SERVICE_NAME = InstrumentedAutoFillService.class.getPackage()
-            .getName() + "/." + InstrumentedAutoFillService.class.getSimpleName();
+    static final String SERVICE_PACKAGE = Helper.MY_PACKAGE;
+    static final String SERVICE_CLASS = "InstrumentedAutoFillService";
+
+    static final String SERVICE_NAME = SERVICE_PACKAGE + "/." + SERVICE_CLASS;
+    protected static final String sServiceLabel = SERVICE_CLASS;
 
     private static final String TAG = "InstrumentedAutoFillService";
 
diff --git a/tests/autofillservice/src/android/autofillservice/cts/NoOpAutofillService.java b/tests/autofillservice/src/android/autofillservice/cts/NoOpAutofillService.java
new file mode 100644
index 0000000..a4c2a10
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/NoOpAutofillService.java
@@ -0,0 +1,42 @@
+/*
+ * 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.autofillservice.cts;
+
+import android.os.CancellationSignal;
+import android.service.autofill.AutofillService;
+import android.service.autofill.FillCallback;
+import android.service.autofill.FillRequest;
+import android.service.autofill.SaveCallback;
+import android.service.autofill.SaveRequest;
+
+/**
+ * {@link AutofillService} implementation that does not do anything...
+ */
+public class NoOpAutofillService extends AutofillService {
+
+    static final String SERVICE_NAME = NoOpAutofillService.class.getPackage().getName()
+            + "/." + NoOpAutofillService.class.getSimpleName();
+    static final String SERVICE_LABEL = "NoOpAutofillService";
+
+    @Override
+    public void onFillRequest(FillRequest request, CancellationSignal cancellationSignal,
+            FillCallback callback) {
+    }
+
+    @Override
+    public void onSaveRequest(SaveRequest request, SaveCallback callback) {
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/SettingsIntentTest.java b/tests/autofillservice/src/android/autofillservice/cts/SettingsIntentTest.java
new file mode 100644
index 0000000..c91fd13
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/SettingsIntentTest.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 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 android.autofillservice.cts;
+
+import static android.autofillservice.cts.Helper.runShellCommand;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.net.Uri;
+import android.provider.Settings;
+import android.support.test.uiautomator.UiObject2;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+public class SettingsIntentTest extends AutoFillServiceTestCase {
+
+    private static final int MY_REQUEST_CODE = 42;
+
+    @Rule
+    public final AutofillActivityTestRule<TrampolineForResultActivity> mActivityRule =
+            new AutofillActivityTestRule<TrampolineForResultActivity>(
+                    TrampolineForResultActivity.class);
+
+    protected TrampolineForResultActivity mActivity;
+
+    @Before
+    public void setActivity() {
+        mActivity = mActivityRule.getActivity();
+    }
+
+    @After
+    public void killSettings() {
+        // Make sure there's no Settings activity left , as it could fail future tests.
+        runShellCommand("am force-stop com.android.settings");
+    }
+
+    @Test
+    public void testMultipleServicesShown() throws Exception {
+        disableService();
+
+        // Launches Settings.
+        mActivity.startForResult(newSettingsIntent(), MY_REQUEST_CODE);
+
+        // Asserts services are shown.
+        sUiBot.assertShownByText(InstrumentedAutoFillService.sServiceLabel);
+        sUiBot.assertShownByText(NoOpAutofillService.SERVICE_LABEL);
+        sUiBot.assertNotShowingForSure(BadAutofillService.SERVICE_LABEL);
+
+        // Finishes and asserts result.
+        sUiBot.pressBack();
+        mActivity.assertResult(Activity.RESULT_CANCELED);
+    }
+
+    @Test
+    public void testWarningShown_userRejectsByTappingBack() throws Exception {
+        disableService();
+
+        // Launches Settings.
+        mActivity.startForResult(newSettingsIntent(), MY_REQUEST_CODE);
+
+        // Asserts services are shown.
+        final UiObject2 object = sUiBot
+                .assertShownByText(InstrumentedAutoFillService.sServiceLabel);
+        object.click();
+
+        // TODO(b/79615759): should assert that "autofill_confirmation_message" is shown, but that
+        // string belongs to Settings - we need to move it to frameworks/base first (and/or use
+        // a resource id, also on framework).
+        // So, for now, just asserts the service name is showing again (in the popup), and the other
+        // services are not showing (because the popup hides then).
+
+        final UiObject2 msgObj = sUiBot.assertShownById("android:id/message");
+        final String msg = msgObj.getText();
+        assertWithMessage("Wrong warning message").that(msg)
+                .contains(InstrumentedAutoFillService.sServiceLabel);
+
+        // NOTE: assertion below is fine because it looks for the full text, not a substring
+        sUiBot.assertNotShowingForSure(InstrumentedAutoFillService.sServiceLabel);
+        sUiBot.assertNotShowingForSure(NoOpAutofillService.SERVICE_LABEL);
+        sUiBot.assertNotShowingForSure(BadAutofillService.SERVICE_LABEL);
+
+        // Finishes and asserts result.
+        sUiBot.pressBack();
+        mActivity.assertResult(Activity.RESULT_CANCELED);
+    }
+
+    // TODO(b/79615759): add testWarningShown_userRejectsByTappingCancel() and
+    // testWarningShown_userAccepts() - these tests would require adding the strings and resource
+    // ids to frameworks/base
+
+    private Intent newSettingsIntent() {
+        return new Intent(Settings.ACTION_REQUEST_SET_AUTOFILL_SERVICE)
+                .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+                .setData(Uri.parse("package:" + Helper.MY_PACKAGE));
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/TrampolineForResultActivity.java b/tests/autofillservice/src/android/autofillservice/cts/TrampolineForResultActivity.java
new file mode 100644
index 0000000..6cdd33e
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/TrampolineForResultActivity.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 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 android.autofillservice.cts;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.content.Intent;
+import android.util.Log;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Activity used to launch another activity for result.
+ */
+// TODO: move to common code
+public class TrampolineForResultActivity extends AbstractAutoFillActivity {
+    private static final String TAG = "TrampolineForResultActivity";
+
+    private final CountDownLatch mLatch = new CountDownLatch(1);
+
+    private int mExpectedRequestCode;
+    private int mActualRequestCode;
+    private int mActualResultCode;
+
+    /**
+     * Starts an activity for result.
+     */
+    public void startForResult(Intent intent, int requestCode) {
+        mExpectedRequestCode = requestCode;
+        startActivityForResult(intent, requestCode);
+    }
+
+    /**
+     * Asserts the activity launched by {@link #startForResult(Intent, int)} was finished with the
+     * expected result code, or fails if it times out.
+     */
+    public void assertResult(int expectedResultCode) throws Exception {
+        final boolean called = mLatch.await(1000, TimeUnit.MILLISECONDS);
+        assertWithMessage("Result not received in 1s").that(called).isTrue();
+        assertWithMessage("Wrong actual code").that(mActualRequestCode)
+            .isEqualTo(mExpectedRequestCode);
+        assertWithMessage("Wrong result code").that(mActualResultCode)
+                .isEqualTo(expectedResultCode);
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        Log.d(TAG, "onActivityResult(): req=" + requestCode + ", res=" + resultCode);
+        mActualRequestCode = requestCode;
+        mActualResultCode = resultCode;
+        mLatch.countDown();
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/UiBot.java b/tests/autofillservice/src/android/autofillservice/cts/UiBot.java
index 495b05c..774d7fe 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/UiBot.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/UiBot.java
@@ -177,8 +177,34 @@
      * {@link #assertDatasets(String...)}.
      */
     public UiObject2 assertShownByText(String text) {
-        final UiObject2 object = waitForObject(By.text(text));
-        assertWithMessage(text).that(object).isNotNull();
+        return assertShownByText(text, UI_TIMEOUT_MS);
+    }
+
+    public UiObject2 assertShownByText(String text, int timeoutMs) {
+        final UiObject2 object = waitForObject(By.text(text), timeoutMs);
+        assertWithMessage("No node with text '%s'", text).that(object).isNotNull();
+        return object;
+    }
+
+    /**
+     * Asserts that the text is not showing for sure in the screen "as is", i.e., without waiting
+     * for it.
+     *
+     * <p>Typically called after another assertion that waits for a condition to be shown.
+     */
+    public void assertNotShowingForSure(String text) throws Exception {
+        final UiObject2 object = mDevice.findObject(By.text(text));
+        assertWithMessage("Find node with text '%s'", text).that(object).isNull();
+    }
+
+    /**
+     * Asserts a node with the given content description is shown.
+     *
+     */
+    public UiObject2 assertShownByContentDescription(String contentDescription) {
+        final UiObject2 object = waitForObject(By.desc(contentDescription));
+        assertWithMessage("No node with content description '%s'", contentDescription).that(object)
+                .isNotNull();
         return object;
     }
 
@@ -204,8 +230,10 @@
     /**
      * Asserts the id is shown on the screen.
      */
-    void assertShownById(String id) {
-        assertThat(waitForObject(By.res(id))).isNotNull();
+    UiObject2 assertShownById(String id) throws Exception {
+        final UiObject2 object = waitForObject(By.res(id));
+        assertThat(object).isNotNull();
+        return object;
     }
 
     /**