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

Commit f5dbd94a authored by Cosmin Băieș's avatar Cosmin Băieș
Browse files

Move IME Switcher Menu Settings button tests

This moves the tests related to the IME Switcher Menu Settings button
from CTS to internal tests, as there is no hard requirement on the
button being implemented as such.

Flag: EXEMPT testfix
Bug: 416313466
Test: atest InputMethodServiceTest#testImeSwitcherMenu_openLanguageSettings
  InputMethodServiceTest#testImeSwitcherMenu_noLanguageSettingsWhenScreenLocked
  InputMethodServiceTest#testImeSwitcherMenu_noLanguageSettingsWhenDeviceNotProvisioned
Change-Id: I863791a7aac524a305e1b9c433847aff12f82fe9
parent c799c3b3
Loading
Loading
Loading
Loading
+223 −1
Original line number Diff line number Diff line
@@ -16,7 +16,10 @@

package com.android.inputmethodservice;

import static android.content.Intent.ACTION_CLOSE_SYSTEM_DIALOGS;
import static android.content.Intent.FLAG_RECEIVER_FOREGROUND;
import static android.view.WindowInsets.Type.captionBar;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG;

import static com.android.apps.inputmethod.simpleime.ims.InputMethodServiceWrapper.EVENT_CONFIG;
import static com.android.apps.inputmethod.simpleime.ims.InputMethodServiceWrapper.EVENT_HIDE;
@@ -30,19 +33,32 @@ import static com.android.internal.inputmethod.InputMethodNavButtonFlags.SHOW_IM
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeFalse;
import static org.junit.Assume.assumeTrue;

import android.app.ActivityManager;
import android.app.Instrumentation;
import android.app.KeyguardManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.graphics.Insets;
import android.os.Build;
import android.os.RemoteException;
import android.os.SystemClock;
import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.provider.Settings;
import android.server.wm.DumpOnFailure;
import android.server.wm.LockScreenSession;
import android.server.wm.WindowManagerStateHelper;
import android.util.Log;
import android.view.View;
@@ -50,6 +66,7 @@ import android.view.WindowInsets;
import android.view.WindowInsetsAnimation;
import android.view.WindowManagerGlobal;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.Flags;
import android.view.inputmethod.InputMethodManager;

import androidx.annotation.NonNull;
@@ -58,6 +75,7 @@ import androidx.test.filters.MediumTest;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.uiautomator.By;
import androidx.test.uiautomator.BySelector;
import androidx.test.uiautomator.Direction;
import androidx.test.uiautomator.UiDevice;
import androidx.test.uiautomator.UiObject2;
import androidx.test.uiautomator.Until;
@@ -81,6 +99,7 @@ import java.util.List;
import java.util.Objects;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;

@RunWith(AndroidJUnit4.class)
@MediumTest
@@ -102,6 +121,13 @@ public class InputMethodServiceTest {
    /** Timeout during which the event is not expected. */
    private static final long NOT_EXCEPT_TIMEOUT_MS = 2000L * Build.HW_TIMEOUT_MULTIPLIER;

    /** Time to wait for UiAutomator scroll to finish. */
    // TODO(b/371520375): Remove after UiAutomator scroll waits for animation to finish.
    private static final long SCROLL_TIMEOUT_MS = 500;

    /** Percentage to scroll by, to reach the top of a scrollable item. */
    private static final float SCROLL_TOP_PERCENT = 100;

    /** Command to set showing the IME when a hardware keyboard is connected. */
    private static final String SET_SHOW_IME_WITH_HARD_KEYBOARD_CMD =
            "settings put secure " + Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD;
@@ -119,7 +145,8 @@ public class InputMethodServiceTest {

    private final GestureNavSwitchHelper mGestureNavSwitchHelper = new GestureNavSwitchHelper();

    private final DeviceFlagsValueProvider mFlagsValueProvider = new DeviceFlagsValueProvider();
    @Rule
    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();

    @Rule
    public final TestName mName = new TestName();
@@ -995,6 +1022,201 @@ public class InputMethodServiceTest {
        }
    }

    /**
     * Shows the Input Method Switcher menu and verifies opening the IME Language Settings activity
     * by tapping on the button, when the device is provisioned.
     */
    @RequiresFlagsEnabled(Flags.FLAG_IME_SWITCHER_REVAMP)
    @Test
    public void testImeSwitcherMenu_openLanguageSettings() throws Exception {
        final var context = mInstrumentation.getTargetContext();
        try (var ignored = withDeviceProvisioned(context, true /* provisioned */)) {
            showImeSwitcherMenu(false /* showWhenLocked */);

            final var info = mImm.getCurrentInputMethodInfo();
            assertEquals(mInputMethodId, info != null ? info.getId() : null);

            final var container = mUiDevice.wait(Until.findObject(By.res("android:id/container")),
                    TIMEOUT_MS);
            assertNotNull("Container view should be found.", container);

            // Make sure the container starts at the top.
            container.scroll(Direction.UP, SCROLL_TOP_PERCENT);
            final var languageSettingsButtonUiObject = container.scrollUntil(Direction.DOWN,
                    Until.findObject(By.res("android:id/button1")));
            assertNotNull("Language settings button should be found",
                    languageSettingsButtonUiObject);

            // TODO(b/371520375): Remove after UiAutomator scroll waits for animation to finish.
            SystemClock.sleep(SCROLL_TIMEOUT_MS);

            // Tapping on the language settings button should dismiss the menu.
            languageSettingsButtonUiObject.click();

            final var languageSettingsComponent = new ComponentName(
                    "com.android.apps.inputmethod.simpleime",
                    "com.android.apps.inputmethod.simpleime.LanguageSettingsActivity");
            mWmState.waitAndAssert(
                    WindowManagerStateHelper.focusedActivity(languageSettingsComponent)
                            .and(WindowManagerStateHelper::activityWindowFocused),
                    "Language settings activity should have the focused window");

            assertWithMessage("Input Method Switcher Menu should no longer be shown")
                    .that(isInputMethodPickerShown(mImm)).isFalse();
        }
    }

    /**
     * Shows the Input Method Switcher menu and verifies the IME Language Settings button is not
     * visible when the screen is secure locked.
     */
    @RequiresFlagsEnabled(Flags.FLAG_IME_SWITCHER_REVAMP)
    @Test
    public void testImeSwitcherMenu_noLanguageSettingsWhenScreenLocked() throws Exception {
        final var context = mInstrumentation.getTargetContext();
        assumeFalse(context.getPackageManager().hasSystemFeature(
                PackageManager.FEATURE_AUTOMOTIVE));
        assumeFalse(context.getPackageManager().hasSystemFeature(
                PackageManager.FEATURE_LEANBACK));
        assumeTrue(context.getPackageManager().hasSystemFeature(
                PackageManager.FEATURE_INPUT_METHODS));

        try (var lockScreenSession = new LockScreenSession(mInstrumentation, mWmState);
                var ignored = withDeviceProvisioned(context, true /* provisioned */)) {
            lockScreenSession.setLockCredential().gotoKeyguard();

            final var km = context.getSystemService(KeyguardManager.class);
            assertNotNull("KeyguardManager must be found", km);
            assertTrue("keyguard is locked", km.isKeyguardLocked());
            assertTrue("keyguard is secure", km.isKeyguardSecure());

            showImeSwitcherMenu(true /* showWhenLocked */);

            final var container = mUiDevice.wait(Until.findObject(By.res("android:id/container")),
                    TIMEOUT_MS);
            assertNotNull("Container view should be found.", container);

            // Make sure the container starts at the top.
            container.scroll(Direction.UP, SCROLL_TOP_PERCENT);
            final boolean hasButton = container.scrollUntil(Direction.DOWN,
                    Until.hasObject(By.res("android:id/button1")));
            assertFalse("Language settings button should not be found", hasButton);

            context.sendBroadcast(
                    new Intent(ACTION_CLOSE_SYSTEM_DIALOGS).setFlags(FLAG_RECEIVER_FOREGROUND));

            mWmState.waitAndAssert(
                    WindowManagerStateHelper.focusedActivity(mActivity.getComponentName())
                            .and(WindowManagerStateHelper::activityWindowFocused),
                    "Test activity should have the focused window");

            assertWithMessage("Input Method Switcher Menu should no longer be shown")
                    .that(isInputMethodPickerShown(mImm)).isFalse();
        }
    }

    /**
     * Shows the Input Method Switcher menu and verifies the IME Language Settings button is not
     * visible when the device is not provisioned.
     */
    @RequiresFlagsEnabled(Flags.FLAG_IME_SWITCHER_REVAMP)
    @Test
    public void testImeSwitcherMenu_noLanguageSettingsWhenDeviceNotProvisioned() throws Exception {
        final var context = mInstrumentation.getTargetContext();
        assumeFalse(context.getPackageManager().hasSystemFeature(
                PackageManager.FEATURE_AUTOMOTIVE));
        assumeTrue(context.getPackageManager().hasSystemFeature(
                PackageManager.FEATURE_INPUT_METHODS));

        try (var ignored = withDeviceProvisioned(context, false /* provisioned */)) {
            showImeSwitcherMenu(false /* showWhenLocked */);

            final var container = mUiDevice.wait(Until.findObject(By.res("android:id/container")),
                    TIMEOUT_MS);
            assertNotNull("Container view should be found.", container);

            // Make sure the container starts at the top.
            container.scroll(Direction.UP, SCROLL_TOP_PERCENT);
            final boolean hasButton = container.scrollUntil(Direction.DOWN,
                    Until.hasObject(By.res("android:id/button1")));
            assertFalse("Language settings button should not be found", hasButton);

            context.sendBroadcast(
                    new Intent(ACTION_CLOSE_SYSTEM_DIALOGS).setFlags(FLAG_RECEIVER_FOREGROUND));

            mWmState.waitAndAssert(
                    WindowManagerStateHelper.focusedActivity(mActivity.getComponentName())
                            .and(WindowManagerStateHelper::activityWindowFocused),
                    "Test activity should have the focused window");

            assertWithMessage("Input Method Switcher Menu should no longer be shown")
                    .that(isInputMethodPickerShown(mImm)).isFalse();
        }
    }

    /**
     * Shows the IME Switcher menu on the test activity.
     *
     * @param showWhenLocked whether the test activity should be shown when the screen is locked.
     */
    private void showImeSwitcherMenu(boolean showWhenLocked) {
        mActivity.setShowWhenLocked(showWhenLocked);

        // Make sure that the IME Switcher Menu is not shown in the initial state.
        mInstrumentation.getTargetContext().sendBroadcast(new Intent(ACTION_CLOSE_SYSTEM_DIALOGS)
                .setFlags(FLAG_RECEIVER_FOREGROUND));
        mWmState.waitAndAssert(
                WindowManagerStateHelper.focusedActivity(mActivity.getComponentName())
                        .and(WindowManagerStateHelper::activityWindowFocused),
                "Test activity should have the focused window");
        assertWithMessage("Input Method Switcher Menu should not be shown")
                .that(isInputMethodPickerShown(mImm)).isFalse();

        // Test InputMethodManager#showInputMethodPicker() works as expected.
        mImm.showInputMethodPicker();
        mWmState.waitAndAssert(
                WindowManagerStateHelper.focusedActivity(mActivity.getComponentName())
                        .and(Predicate.not(WindowManagerStateHelper::activityWindowFocused))
                        .and(state -> state.getMatchingWindows(
                                        ws -> ws.isSurfaceShown()
                                                && ws.getType() == TYPE_INPUT_METHOD_DIALOG)
                                .findAny().isPresent()),
                "Input Method Dialog window should be focused on top of test activity");
        assertWithMessage("Input Method Switcher Menu should be shown")
                .that(isInputMethodPickerShown(mImm)).isTrue();
    }

    static void setDeviceProvisioned(@NonNull Context context, boolean provisioned) {
        Settings.Global.putInt(context.getContentResolver(),
                Settings.Global.DEVICE_PROVISIONED, provisioned ? 1 : 0);
    }

    static boolean getDeviceProvisioned(@NonNull Context context) {
        try {
            return Settings.Global.getInt(context.getContentResolver(),
                    Settings.Global.DEVICE_PROVISIONED) == 1;
        } catch (Settings.SettingNotFoundException e) {
            fail("Failed to get device provisioned state: " + e.getMessage());
            return false;
        }
    }

    @NonNull
    static AutoCloseable withDeviceProvisioned(@NonNull Context context, boolean provisioned) {
        final boolean initial = getDeviceProvisioned(context);
        if (initial == provisioned) {
            return () -> {};
        }
        setDeviceProvisioned(context, provisioned);
        assertWithMessage("New device provisioned state should be set")
                .that(getDeviceProvisioned(context)).isEqualTo(provisioned);
        return () -> {
            setDeviceProvisioned(context, initial);
            assertWithMessage("Initial device provisioned state should be restored")
                    .that(getDeviceProvisioned(context)).isEqualTo(initial);
        };
    }

    private void verifyInputViewStatus(@NonNull Runnable runnable, @Event int eventType,
            boolean eventExpected, boolean shown, @NonNull String message) {
        verifyInputViewStatusInternal(runnable, eventType, eventExpected,
+10 −0
Original line number Diff line number Diff line
@@ -54,5 +54,15 @@
                <action android:name="android.intent.action.MAIN" />
            </intent-filter>
        </activity>

        <activity android:name="com.android.apps.inputmethod.simpleime.SettingsActivity"
            android:label="Simple IME Settings"
            android:exported="true">
        </activity>

        <activity android:name="com.android.apps.inputmethod.simpleime.LanguageSettingsActivity"
            android:label="Simple IME Language Settings"
            android:exported="true">
        </activity>
    </application>
</manifest>
 No newline at end of file
+4 −2
Original line number Diff line number Diff line
@@ -15,7 +15,9 @@
  ~ limitations under the License.
  -->

<input-method xmlns:android="http://schemas.android.com/apk/res/android">
<input-method xmlns:android="http://schemas.android.com/apk/res/android"
    android:settingsActivity="com.android.apps.inputmethod.simpleime.SettingsActivity"
    android:languageSettingsActivity="com.android.apps.inputmethod.simpleime.LanguageSettingsActivity">
    <subtype
        android:label="SimpleIme"
        android:imeSubtypeLocale="en_US"
+23 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 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.apps.inputmethod.simpleime;

import android.app.Activity;

/** Stub activity to test out the language settings for Simple IME. */
public class LanguageSettingsActivity extends Activity {
}
+23 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 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.apps.inputmethod.simpleime;

import android.app.Activity;

/** Stub activity to test out the settings for Simple IME. */
public class SettingsActivity extends Activity {
}