diff options
author | Felipe Leme <felipeal@google.com> | 2018-05-04 11:26:41 -0700 |
---|---|---|
committer | Felipe Leme <felipeal@google.com> | 2018-05-04 11:42:46 -0700 |
commit | 660327dda6e1e87a995d9afd7dab37cf50b7381a (patch) | |
tree | 5c07ec0f28e41b331d7ca380edacd88b8d8f37e9 /apct-tests/perftests/autofill | |
parent | e145f4d7ff988f2aa61f5fba1f15300f3f093942 (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')
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; + } +} |