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
      c88452c3ee
 [2]: I8adb0c569ea6c208aee659603ee1d69df9939267
      9329fbdd72

Bug: 192412909
Test: atest WmTests:InputMethodDialogWindowContextTest
Change-Id: I381b1e09823a7f8361a74d809d4b2fbd23eb556e
This commit is contained in:
Yohei Yukawa
2022-01-24 21:08:46 -08:00
parent 615951d16e
commit 515308cea0
3 changed files with 79 additions and 45 deletions

View File

@@ -0,0 +1,58 @@
/*
* 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;
}
}

View File

@@ -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();

View File

@@ -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);