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

Commit 515308ce authored by Yohei Yukawa's avatar Yohei Yukawa
Browse files

Introduce InputMethodDialogWindowContext for testability

This is a follow up CL to our previous CLs [1], which introduced the
following classes for better testability.

 * com.android.server.inputmethod.InputMethodMenuController
 * com.android.server.wm.InputMethodMenuControllerTest

This CL only aims to clarify what we are currently testing.  Other
motivations of this CL are:

 * InputMethodMenuController is now a package-private final class,
   which is expected to be much easier to keep maintaining.
 * We can get rid of mock(InputMethodManagerService.class), which
   prevents us from making InputMethodManagerService a final class.

Anyway, this is a mechanical refactoring CL.  There should be no
observable behavior change.

 [1]: Ief3c691029b14d848f6ad4a5ec8a8743760c7cda
      c88452c3
 [2]: I8adb0c569ea6c208aee659603ee1d69df9939267
      9329fbdd

Bug: 192412909
Test: atest WmTests:InputMethodDialogWindowContextTest
Change-Id: I381b1e09823a7f8361a74d809d4b2fbd23eb556e
parent 615951d1
Loading
Loading
Loading
Loading
+58 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 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.server.inputmethod;

import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityThread;
import android.content.Context;
import android.view.ContextThemeWrapper;
import android.view.WindowManager;

import com.android.internal.annotations.VisibleForTesting;

/**
 * Provides the window context for the IME switcher dialog.
 */
@VisibleForTesting(visibility = PACKAGE)
public final class InputMethodDialogWindowContext {
    @Nullable
    private Context mDialogWindowContext;

    /**
     * Returns the window context for IME switch dialogs to receive configuration changes.
     *
     * This method initializes the window context if it was not initialized, or moves the context to
     * the targeted display if the current display of context is different from the display
     * specified by {@code displayId}.
     */
    @NonNull
    @VisibleForTesting(visibility = PACKAGE)
    public Context get(int displayId) {
        if (mDialogWindowContext == null || mDialogWindowContext.getDisplayId() != displayId) {
            final Context systemUiContext = ActivityThread.currentActivityThread()
                    .getSystemUiContext(displayId);
            final Context windowContext = systemUiContext.createWindowContext(
                    WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG, null /* options */);
            mDialogWindowContext = new ContextThemeWrapper(
                    windowContext, com.android.internal.R.style.Theme_DeviceDefault_Settings);
        }
        return mDialogWindowContext;
    }
}
+12 −34
Original line number Diff line number Diff line
@@ -16,22 +16,19 @@

package com.android.server.inputmethod;

import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
import static com.android.server.inputmethod.InputMethodManagerService.DEBUG;
import static com.android.server.inputmethod.InputMethodUtils.NOT_A_SUBTYPE_ID;

import android.app.ActivityThread;
import android.annotation.Nullable;
import android.app.AlertDialog;
import android.app.KeyguardManager;
import android.content.Context;
import android.content.DialogInterface;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.os.IBinder;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Slog;
import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -45,7 +42,6 @@ import android.widget.Switch;
import android.widget.TextView;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.LocalServices;
import com.android.server.inputmethod.InputMethodSubtypeSwitchingController.ImeSubtypeListItem;
import com.android.server.wm.WindowManagerInternal;
@@ -53,8 +49,7 @@ import com.android.server.wm.WindowManagerInternal;
import java.util.List;

/** A controller to show/hide the input method menu */
@VisibleForTesting(visibility = PACKAGE)
public class InputMethodMenuController {
final class InputMethodMenuController {
    private static final String TAG = InputMethodMenuController.class.getSimpleName();

    private final InputMethodManagerService mService;
@@ -64,17 +59,18 @@ public class InputMethodMenuController {
    private final KeyguardManager mKeyguardManager;
    private final WindowManagerInternal mWindowManagerInternal;

    private Context mSettingsContext;
    private AlertDialog.Builder mDialogBuilder;
    private AlertDialog mSwitchingDialog;
    private IBinder mSwitchingDialogToken;
    private View mSwitchingDialogTitleView;
    private InputMethodInfo[] mIms;
    private int[] mSubtypeIds;

    private boolean mShowImeWithHardKeyboard;

    @VisibleForTesting(visibility = PACKAGE)
    @GuardedBy("ImfLock.class")
    @Nullable
    private InputMethodDialogWindowContext mDialogWindowContext;

    public InputMethodMenuController(InputMethodManagerService service) {
        mService = service;
        mSettings = mService.mSettings;
@@ -132,8 +128,11 @@ public class InputMethodMenuController {
                }
            }

            final Context settingsContext = getSettingsContext(displayId);
            mDialogBuilder = new AlertDialog.Builder(settingsContext);
            if (mDialogWindowContext == null) {
                mDialogWindowContext = new InputMethodDialogWindowContext();
            }
            final Context dialogWindowContext = mDialogWindowContext.get(displayId);
            mDialogBuilder = new AlertDialog.Builder(dialogWindowContext);
            mDialogBuilder.setOnCancelListener(dialog -> hideInputMethodMenu());

            final Context dialogContext = mDialogBuilder.getContext();
@@ -199,7 +198,7 @@ public class InputMethodMenuController {
            // Use an alternate token for the dialog for that window manager can group the token
            // with other IME windows based on type vs. grouping based on whichever token happens
            // to get selected by the system later on.
            attrs.token = mSwitchingDialogToken;
            attrs.token = dialogWindowContext.getWindowContextToken();
            attrs.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
            attrs.setTitle("Select input method");
            w.setAttributes(attrs);
@@ -208,27 +207,6 @@ public class InputMethodMenuController {
        }
    }

    /**
     * Returns the window context for IME switch dialogs to receive configuration changes.
     *
     * This method initializes the window context if it was not initialized. This method also moves
     * the context to the targeted display if the current display of context is different than
     * the display specified by {@code displayId}.
     */
    @VisibleForTesting
    public Context getSettingsContext(int displayId) {
        if (mSettingsContext == null || mSettingsContext.getDisplayId() != displayId) {
            final Context systemUiContext = ActivityThread.currentActivityThread()
                    .getSystemUiContext(displayId);
            final Context windowContext = systemUiContext.createWindowContext(
                    WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG, null /* options */);
            mSettingsContext = new ContextThemeWrapper(
                    windowContext, com.android.internal.R.style.Theme_DeviceDefault_Settings);
            mSwitchingDialogToken = mSettingsContext.getWindowContextToken();
        }
        return mSettingsContext;
    }

    private boolean isScreenLocked() {
        return mKeyguardManager != null && mKeyguardManager.isKeyguardLocked()
                && mKeyguardManager.isKeyguardSecure();
+9 −11
Original line number Diff line number Diff line
@@ -31,7 +31,6 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;

@@ -48,8 +47,7 @@ import android.view.WindowManager;
import android.view.WindowManagerGlobal;
import android.window.WindowTokenClient;

import com.android.server.inputmethod.InputMethodManagerService;
import com.android.server.inputmethod.InputMethodMenuController;
import com.android.server.inputmethod.InputMethodDialogWindowContext;

import org.junit.After;
import org.junit.Before;
@@ -61,13 +59,13 @@ import org.mockito.Mockito;
//  scenario there.
/**
 * Build/Install/Run:
 *  atest WmTests:InputMethodMenuControllerTest
 *  atest WmTests:InputMethodDialogWindowContextTest
 */
@Presubmit
@RunWith(WindowTestRunner.class)
public class InputMethodMenuControllerTest extends WindowTestsBase {
public class InputMethodDialogWindowContextTest extends WindowTestsBase {

    private InputMethodMenuController mController;
    private InputMethodDialogWindowContext mWindowContext;
    private DualDisplayAreaGroupPolicyTest.DualDisplayContent mSecondaryDisplay;

    private IWindowManager mIWindowManager;
@@ -80,7 +78,7 @@ public class InputMethodMenuControllerTest extends WindowTestsBase {
                new DualDisplayAreaGroupPolicyTest.DualDisplayTestPolicyProvider();
        Mockito.doReturn(policyProvider).when(mWm).getDisplayAreaPolicyProvider();

        mController = new InputMethodMenuController(mock(InputMethodManagerService.class));
        mWindowContext = new InputMethodDialogWindowContext();
        mSecondaryDisplay = new DualDisplayAreaGroupPolicyTest.DualDisplayContent
                .Builder(mAtm, 1000, 1000).build();
        mSecondaryDisplay.getDisplayInfo().state = STATE_ON;
@@ -119,21 +117,21 @@ public class InputMethodMenuControllerTest extends WindowTestsBase {

    @Test
    public void testGetSettingsContext() {
        final Context contextOnDefaultDisplay = mController.getSettingsContext(DEFAULT_DISPLAY);
        final Context contextOnDefaultDisplay = mWindowContext.get(DEFAULT_DISPLAY);

        assertImeSwitchContextMetricsValidity(contextOnDefaultDisplay, mDefaultDisplay);

        // Obtain the context again and check if the window metrics match the IME container bounds
        // of the secondary display.
        final Context contextOnSecondaryDisplay = mController.getSettingsContext(
                mSecondaryDisplay.getDisplayId());
        final Context contextOnSecondaryDisplay =
                mWindowContext.get(mSecondaryDisplay.getDisplayId());

        assertImeSwitchContextMetricsValidity(contextOnSecondaryDisplay, mSecondaryDisplay);
    }

    @Test
    public void testGetSettingsContextOnDualDisplayContent() {
        final Context context = mController.getSettingsContext(mSecondaryDisplay.getDisplayId());
        final Context context = mWindowContext.get(mSecondaryDisplay.getDisplayId());
        final WindowTokenClient tokenClient = (WindowTokenClient) context.getWindowContextToken();
        assertNotNull(tokenClient);
        spyOn(tokenClient);