summaryrefslogtreecommitdiff
path: root/apct-tests/perftests/autofill
diff options
context:
space:
mode:
authorFelipe Leme <felipeal@google.com>2018-05-04 11:26:41 -0700
committerFelipe Leme <felipeal@google.com>2018-05-04 11:42:46 -0700
commit660327dda6e1e87a995d9afd7dab37cf50b7381a (patch)
tree5c07ec0f28e41b331d7ca380edacd88b8d8f37e9 /apct-tests/perftests/autofill
parente145f4d7ff988f2aa61f5fba1f15300f3f093942 (diff)
Spun off Autofill PERF tests on its own package...
...so bugs on it (like starving the UI Thread) don't impact other tests Test: mmma -j ./frameworks/base/apct-tests/perftests/autofill/ && \ adb install -r $OUT/data/app/AutofillPerfTests/AutofillPerfTests.apk && \ adb shell am instrument -w -e class android.view.autofill.LoginTest \ com.android.perftests.autofill/android.support.test.runner.AndroidJUnitRunner Bug: 38345816 Change-Id: I76346f1d1c45790788400fcd9765f6b424d99f4c
Diffstat (limited to 'apct-tests/perftests/autofill')
-rw-r--r--apct-tests/perftests/autofill/Android.mk34
-rw-r--r--apct-tests/perftests/autofill/AndroidManifest.xml40
-rw-r--r--apct-tests/perftests/autofill/AndroidTest.xml28
-rw-r--r--apct-tests/perftests/autofill/res/layout/autofill_dataset_picker_text_only.xml25
-rw-r--r--apct-tests/perftests/autofill/res/layout/test_autofill_login.xml94
-rw-r--r--apct-tests/perftests/autofill/src/android/view/autofill/AbstractAutofillPerfTestCase.java125
-rw-r--r--apct-tests/perftests/autofill/src/android/view/autofill/LoginTest.java243
-rw-r--r--apct-tests/perftests/autofill/src/android/view/autofill/MyAutofillCallback.java127
-rw-r--r--apct-tests/perftests/autofill/src/android/view/autofill/MyAutofillService.java219
9 files changed, 935 insertions, 0 deletions
diff --git a/apct-tests/perftests/autofill/Android.mk b/apct-tests/perftests/autofill/Android.mk
new file mode 100644
index 000000000000..28555a0d5ca5
--- /dev/null
+++ b/apct-tests/perftests/autofill/Android.mk
@@ -0,0 +1,34 @@
+# 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.
+
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ android-support-test \
+ androidx.annotation_annotation \
+ apct-perftests-utils
+
+LOCAL_PACKAGE_NAME := AutofillPerfTests
+LOCAL_PRIVATE_PLATFORM_APIS := true
+
+LOCAL_COMPATIBILITY_SUITE += device-tests
+
+include $(BUILD_PACKAGE)
diff --git a/apct-tests/perftests/autofill/AndroidManifest.xml b/apct-tests/perftests/autofill/AndroidManifest.xml
new file mode 100644
index 000000000000..e706a32ad0c8
--- /dev/null
+++ b/apct-tests/perftests/autofill/AndroidManifest.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.perftests.autofill">
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ <activity android:name="android.perftests.utils.StubActivity">
+ <intent-filter>
+ <action android:name="com.android.perftests.core.PERFTEST" />
+ </intent-filter>
+ </activity>
+
+ <service
+ android:name="android.view.autofill.MyAutofillService"
+ android:label="PERF AutofillService"
+ android:permission="android.permission.BIND_AUTOFILL_SERVICE" >
+ <intent-filter>
+ <action android:name="android.service.autofill.AutofillService" />
+ </intent-filter>
+ </service>
+
+ </application>
+
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.perftests.autofill"/>
+</manifest>
diff --git a/apct-tests/perftests/autofill/AndroidTest.xml b/apct-tests/perftests/autofill/AndroidTest.xml
new file mode 100644
index 000000000000..29f9f94bcac7
--- /dev/null
+++ b/apct-tests/perftests/autofill/AndroidTest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<configuration description="Runs AutofillPerfTests metric instrumentation.">
+ <option name="test-suite-tag" value="apct" />
+ <option name="test-suite-tag" value="apct-metric-instrumentation" />
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="AutofillPerfTests.apk" />
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="com.android.perftests.autofill" />
+ <option name="hidden-api-checks" value="false"/>
+ </test>
+</configuration>
diff --git a/apct-tests/perftests/autofill/res/layout/autofill_dataset_picker_text_only.xml b/apct-tests/perftests/autofill/res/layout/autofill_dataset_picker_text_only.xml
new file mode 100644
index 000000000000..5e6b277a0a08
--- /dev/null
+++ b/apct-tests/perftests/autofill/res/layout/autofill_dataset_picker_text_only.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ * 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.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="horizontal">
+
+ <TextView
+ android:id="@+id/text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+</LinearLayout> \ No newline at end of file
diff --git a/apct-tests/perftests/autofill/res/layout/test_autofill_login.xml b/apct-tests/perftests/autofill/res/layout/test_autofill_login.xml
new file mode 100644
index 000000000000..b35bdf1b757b
--- /dev/null
+++ b/apct-tests/perftests/autofill/res/layout/test_autofill_login.xml
@@ -0,0 +1,94 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * 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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:focusable="true"
+ android:focusableInTouchMode="true"
+ android:orientation="vertical" >
+
+ <LinearLayout
+ android:id="@+id/username_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal" >
+
+ <TextView
+ android:id="@+id/username_label"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Username" />
+
+ <EditText
+ android:id="@+id/username"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:imeOptions="flagNoFullscreen" />
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal" >
+
+ <TextView
+ android:id="@+id/password_label"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Password" />
+
+ <EditText
+ android:id="@+id/password"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:inputType="textPassword"
+ android:imeOptions="flagNoFullscreen" />
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal" >
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal" >
+
+ <Button
+ android:id="@+id/login"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Login" />
+
+ <Button
+ android:id="@+id/cancel"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Cancel" />
+ </LinearLayout>
+
+ <TextView
+ android:id="@+id/output"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+
+</LinearLayout>
diff --git a/apct-tests/perftests/autofill/src/android/view/autofill/AbstractAutofillPerfTestCase.java b/apct-tests/perftests/autofill/src/android/view/autofill/AbstractAutofillPerfTestCase.java
new file mode 100644
index 000000000000..86a5c109f95a
--- /dev/null
+++ b/apct-tests/perftests/autofill/src/android/view/autofill/AbstractAutofillPerfTestCase.java
@@ -0,0 +1,125 @@
+/*
+ * 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.view.autofill;
+
+import android.os.Looper;
+import android.perftests.utils.PerfStatusReporter;
+import android.perftests.utils.SettingsHelper;
+import android.perftests.utils.SettingsStateKeeperRule;
+import android.perftests.utils.ShellHelper;
+import android.view.View;
+import android.perftests.utils.StubActivity;
+import android.provider.Settings;
+import android.support.test.filters.LargeTest;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.InstrumentationRegistry;
+
+import com.android.perftests.autofill.R;
+
+import java.util.Locale;
+import java.util.Collection;
+import java.util.Arrays;
+
+import org.junit.Test;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Base class for all autofill tests.
+ */
+@LargeTest
+public abstract class AbstractAutofillPerfTestCase {
+
+ @ClassRule
+ public static final SettingsStateKeeperRule mServiceSettingsKeeper =
+ new SettingsStateKeeperRule(InstrumentationRegistry.getTargetContext(),
+ Settings.Secure.AUTOFILL_SERVICE);
+
+ @Rule
+ public ActivityTestRule<StubActivity> mActivityRule =
+ new ActivityTestRule<StubActivity>(StubActivity.class);
+
+ @Rule
+ public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+ private final int mLayoutId;
+
+ protected AbstractAutofillPerfTestCase(int layoutId) {
+ mLayoutId = layoutId;
+ }
+
+ /**
+ * Prepares the activity so that by the time the test is run it has reference to its fields.
+ */
+ @Before
+ public void prepareActivity() throws Throwable {
+ mActivityRule.runOnUiThread(() -> {
+ assertTrue("We should be running on the main thread",
+ Looper.getMainLooper().getThread() == Thread.currentThread());
+ assertTrue("We should be running on the main thread",
+ Looper.myLooper() == Looper.getMainLooper());
+ StubActivity activity = mActivityRule.getActivity();
+ activity.setContentView(mLayoutId);
+ onCreate(activity);
+ });
+ }
+
+ @Before
+ public void enableService() {
+ MyAutofillService.resetStaticState();
+ MyAutofillService.setEnabled(true);
+ }
+
+ @After
+ public void disableService() {
+ // Must disable service so calls are ignored in case of errors during the test case;
+ // otherwise, other tests will fail because these calls are made in the UI thread (as both
+ // the service, the tests, and the app run in the same process).
+ MyAutofillService.setEnabled(false);
+ }
+
+ /**
+ * Initializes the {@link StubActivity} after it was launched.
+ */
+ protected abstract void onCreate(StubActivity activity);
+
+ /**
+ * Uses the {@code settings} binary to set the autofill service.
+ */
+ protected void setService() {
+ SettingsHelper.syncSet(InstrumentationRegistry.getTargetContext(),
+ SettingsHelper.NAMESPACE_SECURE,
+ Settings.Secure.AUTOFILL_SERVICE,
+ MyAutofillService.COMPONENT_NAME);
+ }
+
+ /**
+ * Uses the {@code settings} binary to reset the autofill service.
+ */
+ protected void resetService() {
+ SettingsHelper.syncDelete(InstrumentationRegistry.getTargetContext(),
+ SettingsHelper.NAMESPACE_SECURE,
+ Settings.Secure.AUTOFILL_SERVICE);
+ }
+}
diff --git a/apct-tests/perftests/autofill/src/android/view/autofill/LoginTest.java b/apct-tests/perftests/autofill/src/android/view/autofill/LoginTest.java
new file mode 100644
index 000000000000..a93fa3256802
--- /dev/null
+++ b/apct-tests/perftests/autofill/src/android/view/autofill/LoginTest.java
@@ -0,0 +1,243 @@
+/*
+ * 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.view.autofill;
+
+import android.app.Activity;
+import android.os.Looper;
+import android.os.Bundle;
+import android.perftests.utils.PerfStatusReporter;
+import android.util.Log;
+import android.view.View;
+import android.widget.EditText;
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.StubActivity;
+import android.provider.Settings;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.InstrumentationRegistry;
+import com.android.perftests.autofill.R;
+
+import java.util.Locale;
+import java.util.Collection;
+import java.util.Arrays;
+
+import org.junit.Test;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.runner.RunWith;
+
+import static android.view.autofill.AutofillManager.AutofillCallback.EVENT_INPUT_HIDDEN;
+import static android.view.autofill.AutofillManager.AutofillCallback.EVENT_INPUT_SHOWN;
+
+public class LoginTest extends AbstractAutofillPerfTestCase {
+
+ private EditText mUsername;
+ private EditText mPassword;
+ private AutofillManager mAfm;
+
+ public LoginTest() {
+ super(R.layout.test_autofill_login);
+ }
+
+ @Override
+ protected void onCreate(StubActivity activity) {
+ View root = activity.getWindow().getDecorView();
+ mUsername = root.findViewById(R.id.username);
+ mPassword = root.findViewById(R.id.password);
+ mAfm = activity.getSystemService(AutofillManager.class);
+ }
+
+ /**
+ * This is the baseline test for focusing the 2 views when autofill is disabled.
+ */
+ @Test
+ public void testFocus_noService() throws Throwable {
+ resetService();
+
+ focusTest(false);
+ }
+
+ /**
+ * This time the service is called, but it returns a {@code null} response so the UI behaves
+ * as if autofill was disabled.
+ */
+ @Test
+ public void testFocus_serviceDoesNotAutofill() throws Throwable {
+ MyAutofillService.newCannedResponse().reply();
+ setService();
+
+ focusTest(true);
+ }
+
+ /**
+ * Now the service returns autofill data, for both username and password.
+ */
+ @Test
+ public void testFocus_autofillBothFields() throws Throwable {
+ MyAutofillService.newCannedResponse()
+ .setUsername(mUsername.getAutofillId(), "user")
+ .setPassword(mPassword.getAutofillId(), "pass")
+ .reply();
+ setService();
+
+ focusTest(true);
+ }
+
+ /**
+ * Now the service returns autofill data, but just for username.
+ */
+ @Test
+ public void testFocus_autofillUsernameOnly() throws Throwable {
+ // Must set ignored ids so focus on password does not trigger new requests
+ MyAutofillService.newCannedResponse()
+ .setUsername(mUsername.getAutofillId(), "user")
+ .setIgnored(mPassword.getAutofillId())
+ .reply();
+ setService();
+
+ focusTest(true);
+ }
+
+ private void focusTest(boolean waitForService) throws Throwable {
+ // Must first focus in a field to trigger autofill and wait for service response
+ // outside the loop
+ mActivityRule.runOnUiThread(() -> mUsername.requestFocus());
+ if (waitForService) {
+ MyAutofillService.getLastFillRequest();
+ }
+ mActivityRule.runOnUiThread(() -> {
+ BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ mUsername.requestFocus();
+ mPassword.requestFocus();
+ }
+ });
+ }
+
+ /**
+ * This is the baseline test for changing the 2 views when autofill is disabled.
+ */
+ @Test
+ public void testChange_noService() throws Throwable {
+ resetService();
+
+ changeTest(false);
+ }
+
+ /**
+ * This time the service is called, but it returns a {@code null} response so the UI behaves
+ * as if autofill was disabled.
+ */
+ @Test
+ public void testChange_serviceDoesNotAutofill() throws Throwable {
+ MyAutofillService.newCannedResponse().reply();
+ setService();
+
+ changeTest(true);
+ }
+
+ /**
+ * Now the service returns autofill data, for both username and password.
+ */
+ @Test
+ public void testChange_autofillBothFields() throws Throwable {
+ MyAutofillService.newCannedResponse()
+ .setUsername(mUsername.getAutofillId(), "user")
+ .setPassword(mPassword.getAutofillId(), "pass")
+ .reply();
+ setService();
+
+ changeTest(true);
+ }
+
+ /**
+ * Now the service returns autofill data, but just for username.
+ */
+ @Test
+ public void testChange_autofillUsernameOnly() throws Throwable {
+ // Must set ignored ids so focus on password does not trigger new requests
+ MyAutofillService.newCannedResponse()
+ .setUsername(mUsername.getAutofillId(), "user")
+ .setIgnored(mPassword.getAutofillId())
+ .reply();
+ setService();
+
+ changeTest(true);
+ }
+
+ private void changeTest(boolean waitForService) throws Throwable {
+ // Must first focus in a field to trigger autofill and wait for service response
+ // outside the loop
+ mActivityRule.runOnUiThread(() -> mUsername.requestFocus());
+ if (waitForService) {
+ MyAutofillService.getLastFillRequest();
+ }
+ mActivityRule.runOnUiThread(() -> {
+
+ BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ mUsername.setText("");
+ mUsername.setText("a");
+ mPassword.setText("");
+ mPassword.setText("x");
+ }
+ });
+ }
+
+ @Test
+ public void testCallbacks() throws Throwable {
+ MyAutofillService.newCannedResponse()
+ .setUsername(mUsername.getAutofillId(), "user")
+ .setPassword(mPassword.getAutofillId(), "pass")
+ .reply();
+ setService();
+
+ MyAutofillCallback callback = new MyAutofillCallback();
+ mAfm.registerCallback(callback);
+
+ // Must first focus in a field to trigger autofill and wait for service response
+ // outside the loop
+ mActivityRule.runOnUiThread(() -> mUsername.requestFocus());
+ MyAutofillService.getLastFillRequest();
+ callback.expectEvent(mUsername, EVENT_INPUT_SHOWN);
+
+ // Now focus on password to prepare loop state
+ mActivityRule.runOnUiThread(() -> mPassword.requestFocus());
+ callback.expectEvent(mUsername, EVENT_INPUT_HIDDEN);
+ callback.expectEvent(mPassword, EVENT_INPUT_SHOWN);
+
+ // NOTE: we cannot run the whole loop inside the UI thread, because the autofill callback
+ // is called on it, which would cause a deadlock on expectEvent().
+ try {
+ BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ mActivityRule.runOnUiThread(() -> mUsername.requestFocus());
+ callback.expectEvent(mPassword, EVENT_INPUT_HIDDEN);
+ callback.expectEvent(mUsername, EVENT_INPUT_SHOWN);
+ mActivityRule.runOnUiThread(() -> mPassword.requestFocus());
+ callback.expectEvent(mUsername, EVENT_INPUT_HIDDEN);
+ callback.expectEvent(mPassword, EVENT_INPUT_SHOWN);
+ }
+
+ // Sanity check
+ callback.assertNoAsyncErrors();
+ } finally {
+ mAfm.unregisterCallback(callback);
+ }
+ }
+}
diff --git a/apct-tests/perftests/autofill/src/android/view/autofill/MyAutofillCallback.java b/apct-tests/perftests/autofill/src/android/view/autofill/MyAutofillCallback.java
new file mode 100644
index 000000000000..208d632f4f69
--- /dev/null
+++ b/apct-tests/perftests/autofill/src/android/view/autofill/MyAutofillCallback.java
@@ -0,0 +1,127 @@
+/*
+ * 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.view.autofill;
+
+import android.view.View;
+import android.view.autofill.AutofillManager.AutofillCallback;
+
+import androidx.annotation.NonNull;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+
+import static android.view.autofill.AutofillManager.AutofillCallback.EVENT_INPUT_HIDDEN;
+import static android.view.autofill.AutofillManager.AutofillCallback.EVENT_INPUT_SHOWN;
+import static android.view.autofill.AutofillManager.AutofillCallback.EVENT_INPUT_UNAVAILABLE;
+
+import android.os.CancellationSignal;
+import android.service.autofill.FillCallback;
+import android.service.autofill.FillRequest;
+import android.util.Log;
+
+/**
+ * Custom {@link AutofillCallback} used to recover events during tests.
+ */
+public final class MyAutofillCallback extends AutofillCallback {
+
+ private static final String TAG = "MyAutofillCallback";
+ private static final int TIMEOUT_MS = 5000;
+
+ private final BlockingQueue<MyEvent> mEvents = new LinkedBlockingQueue<>(2);
+ private final List<String> mAsyncErrors = new ArrayList<>();
+
+ @Override
+ public void onAutofillEvent(View view, int event) {
+ boolean offered = false;
+ try {
+ offered = mEvents.offer(new MyEvent(view, event), TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ if (!offered) {
+ String error = "could not offer " + toString(view, event) + " in " + TIMEOUT_MS + "ms";
+ Log.e(TAG, error);
+ mAsyncErrors.add(error);
+ }
+ }
+
+ /**
+ * Asserts the callback is called for the given view and event, or fail if it times out.
+ */
+ public void expectEvent(@NonNull View view, int event) {
+ MyEvent myEvent;
+ try {
+ myEvent = mEvents.poll(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ if (myEvent == null) {
+ throw new IllegalStateException("no event received in " + TIMEOUT_MS
+ + "ms while waiting for " + toString(view, event));
+ }
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ throw new IllegalStateException("interrupted waiting for " + toString(view, event));
+ }
+ if (!myEvent.view.equals(view) || myEvent.event != event) {
+ throw new AssertionError("Invalid event: expected " + myEvent + ", got "
+ + toString(view, event));
+ }
+ }
+
+ /**
+ * Throws an exception if an error happened asynchronously while handing
+ * {@link #onAutofillEvent(View, int)}.
+ */
+ public void assertNoAsyncErrors() {
+ if (!mAsyncErrors.isEmpty()) {
+ throw new IllegalStateException(mAsyncErrors.size() + " errors: " + mAsyncErrors);
+ }
+ }
+
+ private static String eventToString(int event) {
+ switch (event) {
+ case EVENT_INPUT_HIDDEN:
+ return "HIDDEN";
+ case EVENT_INPUT_SHOWN:
+ return "SHOWN";
+ case EVENT_INPUT_UNAVAILABLE:
+ return "UNAVAILABLE";
+ default:
+ throw new IllegalArgumentException("invalid event: " + event);
+ }
+ }
+
+ private static String toString(View view, int event) {
+ return eventToString(event) + ": " + view + ")";
+ }
+
+ private static final class MyEvent {
+ public final View view;
+ public final int event;
+
+ MyEvent(View view, int event) {
+ this.view = view;
+ this.event = event;
+ }
+
+ @Override
+ public String toString() {
+ return MyAutofillCallback.toString(view, event);
+ }
+ }
+}
diff --git a/apct-tests/perftests/autofill/src/android/view/autofill/MyAutofillService.java b/apct-tests/perftests/autofill/src/android/view/autofill/MyAutofillService.java
new file mode 100644
index 000000000000..7060233fc80f
--- /dev/null
+++ b/apct-tests/perftests/autofill/src/android/view/autofill/MyAutofillService.java
@@ -0,0 +1,219 @@
+/*
+ * 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.view.autofill;
+
+import android.os.CancellationSignal;
+import android.service.autofill.AutofillService;
+import android.service.autofill.Dataset;
+import android.service.autofill.FillCallback;
+import android.service.autofill.FillRequest;
+import android.service.autofill.FillResponse;
+import android.service.autofill.SaveCallback;
+import android.service.autofill.SaveRequest;
+import android.util.Log;
+import android.util.Pair;
+import android.widget.RemoteViews;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.perftests.autofill.R;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * An {@link AutofillService} implementation whose replies can be programmed by the test case.
+ */
+public class MyAutofillService extends AutofillService {
+
+ private static final String TAG = "MyAutofillService";
+ private static final int TIMEOUT_MS = 5000;
+
+ private static final String PACKAGE_NAME = "com.android.perftests.autofill";
+ static final String COMPONENT_NAME = PACKAGE_NAME + "/android.view.autofill.MyAutofillService";
+
+ private static final BlockingQueue<FillRequest> sFillRequests = new LinkedBlockingQueue<>();
+ private static final BlockingQueue<CannedResponse> sCannedResponses =
+ new LinkedBlockingQueue<>();
+
+ private static boolean sEnabled;
+
+ /**
+ * Resets the static state associated with the service.
+ */
+ static void resetStaticState() {
+ sFillRequests.clear();
+ sCannedResponses.clear();
+ sEnabled = false;
+ }
+
+ /**
+ * Sets whether the service is enabled or not - when disabled, calls to
+ * {@link #onFillRequest(FillRequest, CancellationSignal, FillCallback)} will be ignored.
+ */
+ static void setEnabled(boolean enabled) {
+ sEnabled = enabled;
+ }
+
+ /**
+ * Gets the the last {@link FillRequest} passed to
+ * {@link #onFillRequest(FillRequest, CancellationSignal, FillCallback)} or throws an
+ * exception if that method was not called.
+ */
+ @NonNull
+ static FillRequest getLastFillRequest() {
+ FillRequest request = null;
+ try {
+ request = sFillRequests.poll(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ throw new IllegalStateException("onFillRequest() interrupted");
+ }
+ if (request == null) {
+ throw new IllegalStateException("onFillRequest() not called in " + TIMEOUT_MS + "ms");
+ }
+ return request;
+ }
+
+ @Override
+ public void onFillRequest(FillRequest request, CancellationSignal cancellationSignal,
+ FillCallback callback) {
+ try {
+ handleRequest(request, callback);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ onError("onFillRequest() interrupted", e, callback);
+ } catch (Exception e) {
+ onError("exception on onFillRequest()", e, callback);
+ }
+ }
+
+
+ private void handleRequest(FillRequest request, FillCallback callback) throws Exception {
+ if (!sEnabled) {
+ onError("ignoring onFillRequest(): service is disabled", callback);
+ return;
+ }
+ CannedResponse response = sCannedResponses.poll(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ if (response == null) {
+ onError("ignoring onFillRequest(): response not set", callback);
+ return;
+ }
+ Dataset.Builder dataset = new Dataset.Builder(newDatasetPresentation("dataset"));
+ boolean hasData = false;
+ if (response.mUsername != null) {
+ hasData = true;
+ dataset.setValue(response.mUsername.first,
+ AutofillValue.forText(response.mUsername.second));
+ }
+ if (response.mPassword != null) {
+ hasData = true;
+ dataset.setValue(response.mPassword.first,
+ AutofillValue.forText(response.mPassword.second));
+ }
+ if (hasData) {
+ FillResponse.Builder fillResponse = new FillResponse.Builder();
+ if (response.mIgnoredIds != null) {
+ fillResponse.setIgnoredIds(response.mIgnoredIds);
+ }
+
+ callback.onSuccess(fillResponse.addDataset(dataset.build()).build());
+ } else {
+ callback.onSuccess(null);
+ }
+ if (!sFillRequests.offer(request, TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+ Log.w(TAG, "could not offer request in " + TIMEOUT_MS + "ms");
+ }
+ }
+
+ @Override
+ public void onSaveRequest(SaveRequest request, SaveCallback callback) {
+ // No current test should have triggered it...
+ Log.e(TAG, "onSaveRequest() should not have been called");
+ callback.onFailure("onSaveRequest() should not have been called");
+ }
+
+ static final class CannedResponse {
+ private final Pair<AutofillId, String> mUsername;
+ private final Pair<AutofillId, String> mPassword;
+ private final AutofillId[] mIgnoredIds;
+
+ private CannedResponse(@NonNull Builder builder) {
+ mUsername = builder.mUsername;
+ mPassword = builder.mPassword;
+ mIgnoredIds = builder.mIgnoredIds;
+ }
+
+ static class Builder {
+ private Pair<AutofillId, String> mUsername;
+ private Pair<AutofillId, String> mPassword;
+ private AutofillId[] mIgnoredIds;
+
+ @NonNull
+ Builder setUsername(@NonNull AutofillId id, @NonNull String value) {
+ mUsername = new Pair<>(id, value);
+ return this;
+ }
+
+ @NonNull
+ Builder setPassword(@NonNull AutofillId id, @NonNull String value) {
+ mPassword = new Pair<>(id, value);
+ return this;
+ }
+
+ @NonNull
+ Builder setIgnored(AutofillId... ids) {
+ mIgnoredIds = ids;
+ return this;
+ }
+
+ void reply() {
+ sCannedResponses.add(new CannedResponse(this));
+ }
+ }
+ }
+
+ /**
+ * Sets the expected canned {@link FillResponse} for the next
+ * {@link AutofillService#onFillRequest(FillRequest, CancellationSignal, FillCallback)}.
+ */
+ static CannedResponse.Builder newCannedResponse() {
+ return new CannedResponse.Builder();
+ }
+
+ private void onError(@NonNull String msg, @NonNull FillCallback callback) {
+ Log.e(TAG, msg);
+ callback.onFailure(msg);
+ }
+
+ private void onError(@NonNull String msg, @NonNull Exception e,
+ @NonNull FillCallback callback) {
+ Log.e(TAG, msg, e);
+ callback.onFailure(msg);
+ }
+
+ @NonNull
+ private static RemoteViews newDatasetPresentation(@NonNull CharSequence text) {
+ RemoteViews presentation =
+ new RemoteViews(PACKAGE_NAME, R.layout.autofill_dataset_picker_text_only);
+ presentation.setTextViewText(R.id.text, text);
+ return presentation;
+ }
+}