Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 6378b48f authored by Felipe Leme's avatar Felipe Leme
Browse files

Initial PERF tests for autofill.

These tests render an activity that has 2 autofillable views (username and
password) and keep focusing between then, observing what happens in 4 scenarios:

1. No autofill service (baseline)
2. Autofill service returning no datasets.
3. Autofill service returning a dataset with username and password.
4. Autofill service returning a dataset with just username.

Because this change introduced a helper class to run shell commands without
needing the UiAutomator package, it also changed the MultiUserPerfTests to use
such helper.

Test: mmma -j ./frameworks/base/apct-tests/perftests/core/ &&  \
  adb install -r $OUT/data/app/CorePerfTests/CorePerfTests.apk && \
  adb shell am instrument -w -e class android.view.autofill.AutofillPerfTest \
  com.android.perftests.core/android.support.test.runner.AndroidJUnitRunner
Test: mmma -j ./frameworks/base/apct-tests/perftests/multiuser && \
  adb install -r $OUT/data/app/MultiUserPerfTests/MultiUserPerfTests.apk && \
  adb shell am instrument -w -e class android.multiuser.UserLifecycleTests \
  com.android.perftests.multiuser/android.support.test.runner.AndroidJUnitRunner

Bug: 38345816

Change-Id: Ie283dff8dd19c38ea829de9164b23aae2bfeb015
parent 633c4d98
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -10,6 +10,7 @@ LOCAL_SRC_FILES := \

LOCAL_STATIC_JAVA_LIBRARIES := \
    android-support-test \
    androidx.annotation_annotation \
    apct-perftests-utils \
    guava

+10 −0
Original line number Diff line number Diff line
@@ -16,6 +16,16 @@
          </intent-filter>
        </activity>
        <service android:name="android.os.SomeService" android:exported="false" android:process=":some_service" />

        <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"
+25 −0
Original line number Diff line number Diff line
<?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
+94 −0
Original line number Diff line number Diff line
<?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>
+212 −0
Original line number Diff line number Diff line
/*
 * 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.perftests.utils.SettingsHelper;
import android.perftests.utils.ShellHelper;
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.filters.LargeTest;
import android.support.test.runner.AndroidJUnit4;
import android.support.test.rule.ActivityTestRule;
import android.support.test.InstrumentationRegistry;

import com.android.perftests.core.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.Rule;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import org.junit.runner.RunWith;

import static org.junit.Assert.assertTrue;

@RunWith(Parameterized.class)
public class AutofillPerfTest {
    @Parameters(name = "{0}")
    @SuppressWarnings("rawtypes")
    public static Collection layouts() {
        return Arrays.asList(new Object[][] {
                { "Simple login", R.layout.test_autofill_login}
        });
    }

    private final int mLayoutId;
    private EditText mUsername;
    private EditText mPassword;

    public AutofillPerfTest(String key, int layoutId) {
        mLayoutId = layoutId;
    }

    @Rule
    public ActivityTestRule<StubActivity> mActivityRule =
            new ActivityTestRule<StubActivity>(StubActivity.class);

    @Rule
    public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();

    /**
     * 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());
            Activity activity = mActivityRule.getActivity();
            activity.setContentView(mLayoutId);
            View root = activity.getWindow().getDecorView();
            mUsername = root.findViewById(R.id.username);
            mPassword = root.findViewById(R.id.password);
        });
    }

    @Before
    public void resetStaticState() {
        MyAutofillService.resetStaticState();
    }

    @After
    public void cleanup() {
        resetService();
    }

    /**
     * This is the baseline test for focusing the 2 views when autofill is disabled.
     */
    @Test
    public void testFocus_noService() throws Throwable {
        resetService();

        focusTest();
    }

    /**
     * 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();

        // Must first focus in a field to trigger autofill and wait for service response
        // outside the loop
        mActivityRule.runOnUiThread(() -> mUsername.requestFocus());
        MyAutofillService.getLastFillRequest();

        // Test properly speaking
        focusTest();

        // Sanity check
        MyAutofillService.assertNoAsyncErrors();
    }

    /**
     * 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();

        // Must first focus in a field to trigger autofill and wait for service response
        // outside the loop
        mActivityRule.runOnUiThread(() -> mUsername.requestFocus());
        MyAutofillService.getLastFillRequest();

        // Test properly speaking
        focusTest();

        // Sanity check
        MyAutofillService.assertNoAsyncErrors();
    }

    /**
     * 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();

        // Must first focus in a field to trigger autofill and wait for service response
        // outside the loop
        mActivityRule.runOnUiThread(() -> mUsername.requestFocus());
        MyAutofillService.getLastFillRequest();

        focusTest();

        // Sanity check
        MyAutofillService.assertNoAsyncErrors();
    }

    private void focusTest() throws Throwable {
        mActivityRule.runOnUiThread(() -> {
            BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
            while (state.keepRunning()) {
                mUsername.requestFocus();
                mPassword.requestFocus();
            }
        });
    }

    // TODO: add tests for changing value of the fields

    /**
     * Uses the {@code settings} binary to set the autofill service.
     */
    private 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.
     */
    private void resetService() {
        SettingsHelper.syncDelete(InstrumentationRegistry.getTargetContext(),
                SettingsHelper.NAMESPACE_SECURE,
                Settings.Secure.AUTOFILL_SERVICE);
    }
}
Loading