From fc7a1b5b34ad15bee492deab8123d7a548c8b8b3 Mon Sep 17 00:00:00 2001
From: Charles Chen
Date: Wed, 16 Jun 2021 20:26:34 +0800
Subject: [PATCH 1/2] 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;
+ }
+ }
}
From 79cb3c52888096eb9f4522f73158d29daa6c156b Mon Sep 17 00:00:00 2001
From: Charles Chen
Date: Wed, 16 Jun 2021 20:28:35 +0800
Subject: [PATCH 2/2] Move IME log to WindowTokenClient
IMF has the requirement to debug if the configuration is dispatched
to InputMethodService. Since we migrated InputMethodService to
WindowProviderService, we should also move the debug logic to
WindowContext module because config updates will be no longer from
ActivityThread.
Test: build & run
Bug: 135719017
Change-Id: I248c90f36249a6a4537228f7b0204f0d4466ebc7
---
core/java/android/app/ActivityThread.java | 15 ------------
.../android/app/ActivityThreadInternal.java | 2 --
.../android/app/ConfigurationController.java | 21 -----------------
.../android/window/WindowTokenClient.java | 23 +++++++++++++++++++
4 files changed, 23 insertions(+), 38 deletions(-)
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 3de9a5b1252cb..8662bb4b5dbb0 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -96,7 +96,6 @@ import android.graphics.Canvas;
import android.graphics.HardwareRenderer;
import android.graphics.Typeface;
import android.hardware.display.DisplayManagerGlobal;
-import android.inputmethodservice.InputMethodService;
import android.media.MediaFrameworkInitializer;
import android.media.MediaFrameworkPlatformInitializer;
import android.media.MediaServiceManager;
@@ -5779,9 +5778,6 @@ public final class ActivityThread extends ClientTransactionHandler
final int NSVC = mServices.size();
for (int i=0; i {
synchronized (mResourcesManager) {
- // TODO (b/135719017): Temporary log for debugging IME service.
- if (Build.IS_DEBUGGABLE && mHasImeComponent) {
- Log.d(TAG, "ViewRootImpl.ConfigChangedCallback for IME, "
- + "config=" + globalConfig);
- }
-
// We need to apply this change to the resources immediately, because upon returning
// the view hierarchy will be informed about it.
if (mResourcesManager.applyConfigurationToResources(globalConfig,
@@ -7884,11 +7874,6 @@ public final class ActivityThread extends ClientTransactionHandler
return mDensityCompatMode;
}
- @Override
- public boolean hasImeComponent() {
- return mHasImeComponent;
- }
-
// ------------------ Regular JNI ------------------------
private native void nPurgePendingResources();
private native void nDumpGraphicsInfo(FileDescriptor fd);
diff --git a/core/java/android/app/ActivityThreadInternal.java b/core/java/android/app/ActivityThreadInternal.java
index bc471c4dca187..bc698f6573055 100644
--- a/core/java/android/app/ActivityThreadInternal.java
+++ b/core/java/android/app/ActivityThreadInternal.java
@@ -32,8 +32,6 @@ interface ActivityThreadInternal {
boolean isInDensityCompatMode();
- boolean hasImeComponent();
-
boolean isCachedProcessState();
Application getApplication();
diff --git a/core/java/android/app/ConfigurationController.java b/core/java/android/app/ConfigurationController.java
index c7702663ec9b9..8637e31eb122c 100644
--- a/core/java/android/app/ConfigurationController.java
+++ b/core/java/android/app/ConfigurationController.java
@@ -28,12 +28,9 @@ import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.HardwareRenderer;
-import android.inputmethodservice.InputMethodService;
-import android.os.Build;
import android.os.LocaleList;
import android.os.Trace;
import android.util.DisplayMetrics;
-import android.util.Log;
import android.util.Slog;
import android.view.ContextThemeWrapper;
import android.view.WindowManagerGlobal;
@@ -168,12 +165,7 @@ class ConfigurationController {
mPendingConfiguration = null;
}
- final boolean hasIme = mActivityThread.hasImeComponent();
if (config == null) {
- // TODO (b/135719017): Temporary log for debugging IME service.
- if (Build.IS_DEBUGGABLE && hasIme) {
- Log.w(TAG, "handleConfigurationChanged for IME app but config is null");
- }
return;
}
@@ -204,12 +196,6 @@ class ConfigurationController {
mConfiguration = new Configuration();
}
if (!mConfiguration.isOtherSeqNewer(config) && compat == null) {
- // TODO (b/135719017): Temporary log for debugging IME service.
- if (Build.IS_DEBUGGABLE && hasIme) {
- Log.w(TAG, "handleConfigurationChanged for IME app but config seq is obsolete "
- + ", config=" + config
- + ", mConfiguration=" + mConfiguration);
- }
return;
}
@@ -237,13 +223,6 @@ class ConfigurationController {
ComponentCallbacks2 cb = callbacks.get(i);
if (!equivalent) {
performConfigurationChanged(cb, config);
- } else {
- // TODO (b/135719017): Temporary log for debugging IME service.
- if (Build.IS_DEBUGGABLE && cb instanceof InputMethodService) {
- Log.w(TAG, "performConfigurationChanged didn't callback to IME "
- + ", configDiff=" + configDiff
- + ", mConfiguration=" + mConfiguration);
- }
}
}
}
diff --git a/core/java/android/window/WindowTokenClient.java b/core/java/android/window/WindowTokenClient.java
index 04ac044aeff10..c32a268cef5c0 100644
--- a/core/java/android/window/WindowTokenClient.java
+++ b/core/java/android/window/WindowTokenClient.java
@@ -26,8 +26,11 @@ import android.app.IWindowToken;
import android.app.ResourcesManager;
import android.content.Context;
import android.content.res.Configuration;
+import android.inputmethodservice.AbstractInputMethodService;
+import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
+import android.util.Log;
import java.lang.ref.WeakReference;
@@ -43,6 +46,8 @@ import java.lang.ref.WeakReference;
* @hide
*/
public class WindowTokenClient extends IWindowToken.Stub {
+ private static final String TAG = WindowTokenClient.class.getSimpleName();
+
/**
* Attached {@link Context} for this window token to update configuration and resources.
* Initialized by {@link #attachContext(Context)}.
@@ -53,6 +58,8 @@ public class WindowTokenClient extends IWindowToken.Stub {
private final Configuration mConfiguration = new Configuration();
+ private boolean mShouldDumpConfigForIme;
+
/**
* Attaches {@code context} to this {@link WindowTokenClient}. Each {@link WindowTokenClient}
* can only attach one {@link Context}.
@@ -69,6 +76,8 @@ public class WindowTokenClient extends IWindowToken.Stub {
}
mContextRef = new WeakReference<>(context);
mConfiguration.setTo(context.getResources().getConfiguration());
+ mShouldDumpConfigForIme = Build.IS_DEBUGGABLE
+ && context instanceof AbstractInputMethodService;
}
@Override
@@ -82,6 +91,13 @@ public class WindowTokenClient extends IWindowToken.Stub {
newConfig, newConfig /* overrideConfig */, displayChanged,
null /* configChanged */);
+ if (!shouldUpdateResources && mShouldDumpConfigForIme) {
+ Log.d(TAG, "Configuration not dispatch to IME because configuration is up"
+ + " to date. Current config=" + context.getResources().getConfiguration()
+ + ", reported config=" + mConfiguration
+ + ", updated config=" + newConfig);
+ }
+
if (shouldUpdateResources) {
// TODO(ag/9789103): update resource manager logic to track non-activity tokens
mResourcesManager.updateResourcesForActivity(this, newConfig, newDisplayId);
@@ -104,6 +120,13 @@ public class WindowTokenClient extends IWindowToken.Stub {
((WindowProviderService) context).onConfigurationChanged(newConfig));
}
freeTextLayoutCachesIfNeeded(diff);
+ if (diff == 0 && mShouldDumpConfigForIme) {
+ Log.d(TAG, "Configuration not dispatch to IME because configuration has no "
+ + " public difference with updated config. "
+ + " Current config=" + context.getResources().getConfiguration()
+ + ", reported config=" + mConfiguration
+ + ", updated config=" + newConfig);
+ }
mConfiguration.setTo(newConfig);
}
if (displayChanged) {