diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 80664ed0816fb..76f0a6c6bf827 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
}
@@ -3243,6 +3249,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/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
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/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) { 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 d436bace3c46a..02c0d46a34a1e 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; @@ -8634,4 +8635,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; + } + } }