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

Commit 813c9541 authored by Cosmin Băieș's avatar Cosmin Băieș Committed by Android (Google) Code Review
Browse files

Merge "Move IME Switcher Menu Settings button tests" into main

parents 46777a7f f5dbd94a
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 {
}