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

Commit 454d7615 authored by Kohsuke Yatoh's avatar Kohsuke Yatoh
Browse files

Add autoshow testcase.

Autoshow is a feature of InputMethodManagerService.
This feature is not documented nor guarded by CTS.

A non-CTS regression test will be useful for keeping
UX consistency across Android versions.

Bug: 195468725
Test: atest InputMethodStressTest
Change-Id: I8025ff9b7813d0d5552f1ff84fff018bd0a178fa
parent 84a40f91
Loading
Loading
Loading
Loading
+2 −1
Original line number Diff line number Diff line
@@ -19,7 +19,8 @@
    package="com.android.inputmethod.stresstest">

    <application>
        <activity android:name=".TestActivity"/>
        <activity android:name=".AutoShowTest$TestActivity"/>
        <activity android:name=".ImeOpenCloseStressTest$TestActivity"/>
    </application>

    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+83 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 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 com.android.inputmethod.stresstest;

import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED;

import static com.android.inputmethod.stresstest.ImeStressTestUtil.waitOnMainUntil;
import static com.android.inputmethod.stresstest.ImeStressTestUtil.waitOnMainUntilImeIsShown;

import android.app.Activity;
import android.app.Instrumentation;
import android.content.Intent;
import android.os.Bundle;
import android.platform.test.annotations.RootPermissionTest;
import android.widget.EditText;
import android.widget.LinearLayout;

import androidx.annotation.Nullable;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;

import org.junit.Test;
import org.junit.runner.RunWith;

@RootPermissionTest
@RunWith(AndroidJUnit4.class)
public final class AutoShowTest {

    @Test
    public void autoShow() {
        Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
        Intent intent = new Intent()
                .setAction(Intent.ACTION_MAIN)
                .setClass(instrumentation.getContext(), TestActivity.class)
                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
                .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
        TestActivity activity = (TestActivity) instrumentation.startActivitySync(intent);
        EditText editText = activity.getEditText();
        waitOnMainUntil("activity should gain focus", editText::hasWindowFocus);
        waitOnMainUntilImeIsShown(editText);
    }

    public static class TestActivity extends Activity {
        private EditText mEditText;

        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            // IME will be auto-shown if the following conditions are met:
            // 1. SoftInputMode state is SOFT_INPUT_STATE_UNSPECIFIED.
            // 2. SoftInputMode adjust is SOFT_INPUT_ADJUST_RESIZE.
            getWindow().setSoftInputMode(SOFT_INPUT_STATE_UNSPECIFIED | SOFT_INPUT_ADJUST_RESIZE);
            LinearLayout rootView = new LinearLayout(this);
            rootView.setOrientation(LinearLayout.VERTICAL);
            mEditText = new EditText(this);
            rootView.addView(mEditText, new LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT));
            setContentView(rootView);
            // 3. The focused view is a text editor (View#onCheckIsTextEditor() returns true).
            mEditText.requestFocus();
        }

        public EditText getEditText() {
            return mEditText;
        }
    }
}
+48 −31
Original line number Diff line number Diff line
@@ -16,64 +16,81 @@

package com.android.inputmethod.stresstest;

import static com.android.compatibility.common.util.SystemUtil.eventually;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;

import static com.google.common.truth.Truth.assertThat;
import static com.android.inputmethod.stresstest.ImeStressTestUtil.waitOnMainUntil;
import static com.android.inputmethod.stresstest.ImeStressTestUtil.waitOnMainUntilImeIsHidden;
import static com.android.inputmethod.stresstest.ImeStressTestUtil.waitOnMainUntilImeIsShown;

import android.app.Activity;
import android.app.Instrumentation;
import android.content.Intent;
import android.os.Bundle;
import android.platform.test.annotations.RootPermissionTest;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
import android.widget.LinearLayout;

import androidx.annotation.Nullable;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;

import org.junit.Test;
import org.junit.runner.RunWith;

import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;

@RootPermissionTest
@RunWith(AndroidJUnit4.class)
public class ImeOpenCloseStressTest {
public final class ImeOpenCloseStressTest {

    private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(5);
    private static final int NUM_TEST_ITERATIONS = 100;

    private Instrumentation mInstrumentation;

    @Test
    public void test() {
        mInstrumentation = InstrumentationRegistry.getInstrumentation();
        Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
        Intent intent = new Intent()
                .setAction(Intent.ACTION_MAIN)
                .setClass(mInstrumentation.getContext(), TestActivity.class)
                .setClass(instrumentation.getContext(), TestActivity.class)
                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
                .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
        TestActivity activity = (TestActivity) mInstrumentation.startActivitySync(intent);
        eventually(() -> assertThat(callOnMainSync(activity::hasWindowFocus)).isTrue(), TIMEOUT);
        TestActivity activity = (TestActivity) instrumentation.startActivitySync(intent);
        EditText editText = activity.getEditText();
        waitOnMainUntil("activity should gain focus", editText::hasWindowFocus);
        for (int i = 0; i < NUM_TEST_ITERATIONS; i++) {
            mInstrumentation.runOnMainSync(activity::showIme);
            eventually(() -> assertThat(callOnMainSync(activity::isImeShown)).isTrue(), TIMEOUT);
            mInstrumentation.runOnMainSync(activity::hideIme);
            eventually(() -> assertThat(callOnMainSync(activity::isImeShown)).isFalse(), TIMEOUT);
            instrumentation.runOnMainSync(activity::showIme);
            waitOnMainUntilImeIsShown(editText);
            instrumentation.runOnMainSync(activity::hideIme);
            waitOnMainUntilImeIsHidden(editText);
        }
    }

    private <V> V callOnMainSync(Callable<V> callable) {
        AtomicReference<V> result = new AtomicReference<>();
        AtomicReference<Exception> thrownException = new AtomicReference<>();
        mInstrumentation.runOnMainSync(() -> {
            try {
                result.set(callable.call());
            } catch (Exception e) {
                thrownException.set(e);
    public static class TestActivity extends Activity {

        private EditText mEditText;

        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            LinearLayout rootView = new LinearLayout(this);
            rootView.setOrientation(LinearLayout.VERTICAL);
            mEditText = new EditText(this);
            rootView.addView(mEditText, new LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT));
            setContentView(rootView);
        }
        });
        if (thrownException.get() != null) {
            throw new RuntimeException("Exception thrown from Main thread", thrownException.get());

        public EditText getEditText() {
            return mEditText;
        }

        public void showIme() {
            mEditText.requestFocus();
            InputMethodManager imm = getSystemService(InputMethodManager.class);
            imm.showSoftInput(mEditText, 0);
        }

        public void hideIme() {
            InputMethodManager imm = getSystemService(InputMethodManager.class);
            imm.hideSoftInputFromWindow(mEditText.getWindowToken(), 0);
        }
        return result.get();
    }
}
+80 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 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 com.android.inputmethod.stresstest;

import static com.android.compatibility.common.util.SystemUtil.eventually;

import static com.google.common.truth.Truth.assertWithMessage;

import android.view.View;
import android.view.WindowInsets;

import androidx.test.platform.app.InstrumentationRegistry;

import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;

/** Utility methods for IME stress test. */
public final class ImeStressTestUtil {

    private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(5);

    private ImeStressTestUtil() {
    }

    /** Checks if the IME is shown on the window that the given view belongs to. */
    public static boolean isImeShown(View view) {
        WindowInsets insets = view.getRootWindowInsets();
        return insets.isVisible(WindowInsets.Type.ime());
    }

    /** Calls the callable on the main thread and returns the result. */
    public static <V> V callOnMainSync(Callable<V> callable) {
        AtomicReference<V> result = new AtomicReference<>();
        InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
            try {
                result.set(callable.call());
            } catch (Exception e) {
                throw new RuntimeException("Exception was thrown", e);
            }
        });
        return result.get();
    }

    /**
     * Waits until {@code pred} returns true, or throws on timeout.
     *
     * <p>The given {@code pred} will be called on the main thread.
     */
    public static void waitOnMainUntil(String message, Callable<Boolean> pred) {
        eventually(() -> assertWithMessage(message).that(pred.call()).isTrue(), TIMEOUT);
    }

    /** Waits until IME is shown, or throws on timeout. */
    public static void waitOnMainUntilImeIsShown(View view) {
        eventually(() -> assertWithMessage("IME should be shown").that(
                callOnMainSync(() -> isImeShown(view))).isTrue(), TIMEOUT);
    }

    /** Waits until IME is hidden, or throws on timeout. */
    public static void waitOnMainUntilImeIsHidden(View view) {
        //eventually(() -> assertThat(callOnMainSync(() -> isImeShown(view))).isFalse(), TIMEOUT);
        eventually(() -> assertWithMessage("IME should be hidden").that(
                callOnMainSync(() -> isImeShown(view))).isFalse(), TIMEOUT);
    }
}
+0 −64
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 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 com.android.inputmethod.stresstest;

import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;

import android.app.Activity;
import android.os.Bundle;
import android.view.WindowInsets;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
import android.widget.LinearLayout;

import androidx.annotation.Nullable;

public class TestActivity extends Activity {

    private EditText mEditText;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        LinearLayout rootView = new LinearLayout(this);
        rootView.setOrientation(LinearLayout.VERTICAL);
        mEditText = new EditText(this);
        rootView.addView(mEditText, new LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT));
        setContentView(rootView);
    }

    public boolean hasWindowFocus() {
        return mEditText.hasWindowFocus();
    }

    public boolean isImeShown() {
        WindowInsets insets = mEditText.getRootWindowInsets();
        return insets.isVisible(WindowInsets.Type.ime());
    }

    public void showIme() {
        mEditText.requestFocus();
        InputMethodManager imm = getSystemService(InputMethodManager.class);
        imm.showSoftInput(mEditText, 0);
    }

    public void hideIme() {
        InputMethodManager imm = getSystemService(InputMethodManager.class);
        imm.hideSoftInputFromWindow(mEditText.getWindowToken(), 0);
    }
}