From fc7a1b5b34ad15bee492deab8123d7a548c8b8b3 Mon Sep 17 00:00:00 2001 From: Charles Chen Date: Wed, 16 Jun 2021 20:26:34 +0800 Subject: [PATCH] Migrate InputMethodService to WindowProviderService Also introduce IWindowManager#getDisplayIdToLaunchIme to make IMS be aware of the launched display to prevent extra onConfigurationChanged callback Bug: 149463653 Test: atest MultiDisplaySystemDecorationTests CtsInputMethodTestCases Test: atest ContextTest ContextIsUiContextTest Test: manual - moving IME between 2 displays and displayArea within display - config change received Test: manual - the app to show IME crashed and focus is set to the next task - no config change Change-Id: Ie565e30ed5dd3f2cfe27355a6dded76dc3adc14b --- core/api/test-current.txt | 7 +++ core/java/android/app/ContextImpl.java | 6 --- .../AbstractInputMethodService.java | 50 +++++++++++++++++-- .../InputMethodService.java | 3 ++ core/java/android/view/IWindowManager.aidl | 7 +++ .../android/window/WindowProviderService.java | 22 ++++++-- .../src/android/content/ContextTest.java | 8 --- .../server/wm/WindowManagerService.java | 20 ++++++++ 8 files changed, 101 insertions(+), 22 deletions(-) diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 42708f9d2e158..23926a3c042e6 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -1270,6 +1270,12 @@ package android.hardware.soundtrigger { package android.inputmethodservice { + public abstract class AbstractInputMethodService extends android.window.WindowProviderService implements android.view.KeyEvent.Callback { + method public final int getInitialDisplayId(); + method @Nullable public final android.os.Bundle getWindowContextOptions(); + method public final int getWindowType(); + } + @UiContext public class InputMethodService extends android.inputmethodservice.AbstractInputMethodService { field public static final long FINISH_INPUT_NO_FALLBACK_CONNECTION = 156215187L; // 0x94fa793L } @@ -3242,6 +3248,7 @@ package android.window { @UiContext public abstract class WindowProviderService extends android.app.Service { ctor public WindowProviderService(); method public final void attachToWindowToken(@NonNull android.os.IBinder); + method @NonNull public int getInitialDisplayId(); method @Nullable public android.os.Bundle getWindowContextOptions(); method public abstract int getWindowType(); } diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index 16b6ea5bcf42b..c42c38be0ae82 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -3194,12 +3194,6 @@ class ContextImpl extends Context { @UnsupportedAppUsage final void setOuterContext(@NonNull Context context) { mOuterContext = context; - // TODO(b/149463653): check if we still need this method after migrating IMS to - // WindowContext. - if (mOuterContext.isUiContext() && mContextType <= CONTEXT_TYPE_DISPLAY_CONTEXT) { - mContextType = CONTEXT_TYPE_WINDOW_CONTEXT; - mIsConfigurationBasedContext = true; - } } @UnsupportedAppUsage diff --git a/core/java/android/inputmethodservice/AbstractInputMethodService.java b/core/java/android/inputmethodservice/AbstractInputMethodService.java index 3cd13a212a4b8..17d4ae6205caa 100644 --- a/core/java/android/inputmethodservice/AbstractInputMethodService.java +++ b/core/java/android/inputmethodservice/AbstractInputMethodService.java @@ -18,16 +18,23 @@ package android.inputmethodservice; import android.annotation.MainThread; import android.annotation.NonNull; -import android.app.Service; +import android.annotation.Nullable; +import android.content.Context; import android.content.Intent; +import android.content.res.Configuration; +import android.os.Bundle; import android.os.IBinder; +import android.os.RemoteException; import android.util.proto.ProtoOutputStream; import android.view.KeyEvent; import android.view.MotionEvent; +import android.view.WindowManager; +import android.view.WindowManagerGlobal; import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputContentInfo; import android.view.inputmethod.InputMethod; import android.view.inputmethod.InputMethodSession; +import android.window.WindowProviderService; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -44,9 +51,22 @@ import java.io.PrintWriter; * implement. This base class takes care of reporting your InputMethod from * the service when clients bind to it, but provides no standard implementation * of the InputMethod interface itself. Derived classes must implement that - * interface. + * interface.

+ * + *

After {@link android.os.Build.VERSION_CODES#S}, the maximum possible area to show the soft + * input may not be the entire screen. For example, some devices may support to show the soft input + * on only half of screen.

+ * + *

In that case, moving the soft input from one half screen to another will trigger a + * {@link android.content.res.Resources} update to match the new {@link Configuration} and + * this {@link AbstractInputMethodService} may also receive a + * {@link #onConfigurationChanged(Configuration)} callback if there's notable configuration changes + *

+ * + * @see android.content.ComponentCallbacks#onConfigurationChanged(Configuration) + * @see Context#isUiContext Context#isUiContext to see the concept of UI Context. */ -public abstract class AbstractInputMethodService extends Service +public abstract class AbstractInputMethodService extends WindowProviderService implements KeyEvent.Callback { private InputMethod mInputMethod; @@ -272,9 +292,33 @@ public abstract class AbstractInputMethodService extends Service public void notifyUserActionIfNecessary() { } + // TODO(b/149463653): remove it in T. We missed the API deadline in S. /** @hide */ @Override public final boolean isUiContext() { return true; } + + /** @hide */ + @Override + public final int getWindowType() { + return WindowManager.LayoutParams.TYPE_INPUT_METHOD; + } + + /** @hide */ + @Override + @Nullable + public final Bundle getWindowContextOptions() { + return null; + } + + /** @hide */ + @Override + public final int getInitialDisplayId() { + try { + return WindowManagerGlobal.getWindowManagerService().getImeDisplayId(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } } diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index 881e0cfb58d7d..18c6256dff80f 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -629,10 +629,13 @@ public class InputMethodService extends AbstractInputMethodService { throw new IllegalStateException( "attachToken() must be called at most once. token=" + token); } + attachToWindowToken(token); mToken = token; mWindow.setToken(token); } + // TODO(b/149463653): remove updateInputMethodDisplay(int displayId) since we'll get the + // right display by attachToWindowToken /** * {@inheritDoc} * @hide diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index 802163617b3b9..305cb97dbc775 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -865,4 +865,11 @@ interface IWindowManager void unregisterCrossWindowBlurEnabledListener(ICrossWindowBlurEnabledListener listener); boolean isTaskSnapshotSupported(); + + /** + * Returns the preferred display ID to show software keyboard. + * + * @see android.window.WindowProviderService#getLaunchedDisplayId + */ + int getImeDisplayId(); } diff --git a/core/java/android/window/WindowProviderService.java b/core/java/android/window/WindowProviderService.java index b8619fbcf3348..c861dbd89e15b 100644 --- a/core/java/android/window/WindowProviderService.java +++ b/core/java/android/window/WindowProviderService.java @@ -36,7 +36,6 @@ import android.view.WindowManager; import android.view.WindowManager.LayoutParams.WindowType; import android.view.WindowManagerImpl; -// TODO(b/159767464): handle #onConfigurationChanged(Configuration) /** * A {@link Service} responsible for showing a non-activity window, such as software keyboards or * accessibility overlay windows. This {@link Service} has similar behavior to @@ -89,6 +88,20 @@ public abstract class WindowProviderService extends Service { return null; } + /** + * Returns the display ID to launch this {@link WindowProviderService}. + * + * @hide + */ + @TestApi + @SuppressLint({"OnNameExpected"}) + // Suppress the lint because it is not a callback and users may override this API to provide + // display. + @NonNull + public int getInitialDisplayId() { + return DEFAULT_DISPLAY; + } + /** * Attaches this WindowProviderService to the {@code windowToken}. * @@ -104,10 +117,9 @@ public abstract class WindowProviderService extends Service { public final Context createServiceBaseContext(ActivityThread mainThread, LoadedApk packageInfo) { final Context context = super.createServiceBaseContext(mainThread, packageInfo); - // Always associate with the default display at initialization. - final Display defaultDisplay = context.getSystemService(DisplayManager.class) - .getDisplay(DEFAULT_DISPLAY); - return context.createTokenContext(mWindowToken, defaultDisplay); + final Display display = context.getSystemService(DisplayManager.class) + .getDisplay(getInitialDisplayId()); + return context.createTokenContext(mWindowToken, display); } @CallSuper diff --git a/core/tests/coretests/src/android/content/ContextTest.java b/core/tests/coretests/src/android/content/ContextTest.java index d1776fb7e5c12..3d7d807ca53d2 100644 --- a/core/tests/coretests/src/android/content/ContextTest.java +++ b/core/tests/coretests/src/android/content/ContextTest.java @@ -32,7 +32,6 @@ import android.content.res.Configuration; import android.graphics.PixelFormat; import android.hardware.display.DisplayManager; import android.hardware.display.VirtualDisplay; -import android.inputmethodservice.InputMethodService; import android.media.ImageReader; import android.os.UserHandle; import android.view.Display; @@ -139,13 +138,6 @@ public class ContextTest { assertThat(systemUiContext.isUiContext()).isTrue(); } - @Test - public void testIsUiContext_InputMethodService_returnsTrue() { - final InputMethodService ims = new InputMethodService(); - - assertTrue(ims.isUiContext()); - } - @Test public void testGetDisplayFromDisplayContextDerivedContextOnPrimaryDisplay() { verifyGetDisplayFromDisplayContextDerivedContext(false /* onSecondaryDisplay */); diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 339134486b0aa..ff99920a4b9ad 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -46,6 +46,7 @@ import static android.provider.Settings.Global.DEVELOPMENT_WM_DISPLAY_SETTINGS_P import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.INVALID_DISPLAY; import static android.view.WindowManager.DISPLAY_IME_POLICY_FALLBACK_DISPLAY; +import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL; import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW; import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW; import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; @@ -8632,4 +8633,23 @@ public class WindowManagerService extends IWindowManager.Stub return snapshot != null && snapshot.hasImeSurface(); } } + + @Override + public int getImeDisplayId() { + // TODO(b/189805422): Add a toast to notify users that IMS may get extra + // onConfigurationChanged callback when perDisplayFocus is enabled. + // Enabling perDisplayFocus means that we track focus on each display, so we don't have + // the "top focus" display and getTopFocusedDisplayContent returns the default display + // as the fallback. It leads to InputMethodService receives an extra onConfiguration + // callback when InputMethodService move from a secondary display to another display + // with the same display metrics because InputMethodService will always associate with + // the ImeContainer on the default display in onCreate and receive a configuration update + // to match default display ImeContainer and then receive another configuration update + // from attachToWindowToken. + synchronized (mGlobalLock) { + final DisplayContent dc = mRoot.getTopFocusedDisplayContent(); + return dc.getImePolicy() == DISPLAY_IME_POLICY_LOCAL ? dc.getDisplayId() + : DEFAULT_DISPLAY; + } + } }