diff --git a/services/core/java/com/android/server/inputmethod/InputMethodDialogWindowContext.java b/services/core/java/com/android/server/inputmethod/InputMethodDialogWindowContext.java new file mode 100644 index 0000000000000..1779333e1479c --- /dev/null +++ b/services/core/java/com/android/server/inputmethod/InputMethodDialogWindowContext.java @@ -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; + } +} diff --git a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java index fcb1be0fc88ca..348bb2d868a25 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java @@ -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(); diff --git a/services/tests/wmtests/src/com/android/server/wm/InputMethodMenuControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/InputMethodDialogWindowContextTest.java similarity index 90% rename from services/tests/wmtests/src/com/android/server/wm/InputMethodMenuControllerTest.java rename to services/tests/wmtests/src/com/android/server/wm/InputMethodDialogWindowContextTest.java index d64bf121736f8..670eb756db0b1 100644 --- a/services/tests/wmtests/src/com/android/server/wm/InputMethodMenuControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/InputMethodDialogWindowContextTest.java @@ -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);