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
This commit is contained in:
@@ -1270,6 +1270,12 @@ package android.hardware.soundtrigger {
|
|||||||
|
|
||||||
package android.inputmethodservice {
|
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 {
|
@UiContext public class InputMethodService extends android.inputmethodservice.AbstractInputMethodService {
|
||||||
field public static final long FINISH_INPUT_NO_FALLBACK_CONNECTION = 156215187L; // 0x94fa793L
|
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 {
|
@UiContext public abstract class WindowProviderService extends android.app.Service {
|
||||||
ctor public WindowProviderService();
|
ctor public WindowProviderService();
|
||||||
method public final void attachToWindowToken(@NonNull android.os.IBinder);
|
method public final void attachToWindowToken(@NonNull android.os.IBinder);
|
||||||
|
method @NonNull public int getInitialDisplayId();
|
||||||
method @Nullable public android.os.Bundle getWindowContextOptions();
|
method @Nullable public android.os.Bundle getWindowContextOptions();
|
||||||
method public abstract int getWindowType();
|
method public abstract int getWindowType();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3194,12 +3194,6 @@ class ContextImpl extends Context {
|
|||||||
@UnsupportedAppUsage
|
@UnsupportedAppUsage
|
||||||
final void setOuterContext(@NonNull Context context) {
|
final void setOuterContext(@NonNull Context context) {
|
||||||
mOuterContext = 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
|
@UnsupportedAppUsage
|
||||||
|
|||||||
@@ -18,16 +18,23 @@ package android.inputmethodservice;
|
|||||||
|
|
||||||
import android.annotation.MainThread;
|
import android.annotation.MainThread;
|
||||||
import android.annotation.NonNull;
|
import android.annotation.NonNull;
|
||||||
import android.app.Service;
|
import android.annotation.Nullable;
|
||||||
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.content.res.Configuration;
|
||||||
|
import android.os.Bundle;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
|
import android.os.RemoteException;
|
||||||
import android.util.proto.ProtoOutputStream;
|
import android.util.proto.ProtoOutputStream;
|
||||||
import android.view.KeyEvent;
|
import android.view.KeyEvent;
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
|
import android.view.WindowManager;
|
||||||
|
import android.view.WindowManagerGlobal;
|
||||||
import android.view.inputmethod.InputConnection;
|
import android.view.inputmethod.InputConnection;
|
||||||
import android.view.inputmethod.InputContentInfo;
|
import android.view.inputmethod.InputContentInfo;
|
||||||
import android.view.inputmethod.InputMethod;
|
import android.view.inputmethod.InputMethod;
|
||||||
import android.view.inputmethod.InputMethodSession;
|
import android.view.inputmethod.InputMethodSession;
|
||||||
|
import android.window.WindowProviderService;
|
||||||
|
|
||||||
import java.io.FileDescriptor;
|
import java.io.FileDescriptor;
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
@@ -44,9 +51,22 @@ import java.io.PrintWriter;
|
|||||||
* implement. This base class takes care of reporting your InputMethod from
|
* implement. This base class takes care of reporting your InputMethod from
|
||||||
* the service when clients bind to it, but provides no standard implementation
|
* the service when clients bind to it, but provides no standard implementation
|
||||||
* of the InputMethod interface itself. Derived classes must implement that
|
* of the InputMethod interface itself. Derived classes must implement that
|
||||||
* interface.
|
* interface.</p>
|
||||||
|
*
|
||||||
|
* <p>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.</p>
|
||||||
|
*
|
||||||
|
* <p>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
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @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 {
|
implements KeyEvent.Callback {
|
||||||
private InputMethod mInputMethod;
|
private InputMethod mInputMethod;
|
||||||
|
|
||||||
@@ -272,9 +292,33 @@ public abstract class AbstractInputMethodService extends Service
|
|||||||
public void notifyUserActionIfNecessary() {
|
public void notifyUserActionIfNecessary() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO(b/149463653): remove it in T. We missed the API deadline in S.
|
||||||
/** @hide */
|
/** @hide */
|
||||||
@Override
|
@Override
|
||||||
public final boolean isUiContext() {
|
public final boolean isUiContext() {
|
||||||
return true;
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -629,10 +629,13 @@ public class InputMethodService extends AbstractInputMethodService {
|
|||||||
throw new IllegalStateException(
|
throw new IllegalStateException(
|
||||||
"attachToken() must be called at most once. token=" + token);
|
"attachToken() must be called at most once. token=" + token);
|
||||||
}
|
}
|
||||||
|
attachToWindowToken(token);
|
||||||
mToken = token;
|
mToken = token;
|
||||||
mWindow.setToken(token);
|
mWindow.setToken(token);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO(b/149463653): remove updateInputMethodDisplay(int displayId) since we'll get the
|
||||||
|
// right display by attachToWindowToken
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
* @hide
|
* @hide
|
||||||
|
|||||||
@@ -865,4 +865,11 @@ interface IWindowManager
|
|||||||
void unregisterCrossWindowBlurEnabledListener(ICrossWindowBlurEnabledListener listener);
|
void unregisterCrossWindowBlurEnabledListener(ICrossWindowBlurEnabledListener listener);
|
||||||
|
|
||||||
boolean isTaskSnapshotSupported();
|
boolean isTaskSnapshotSupported();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the preferred display ID to show software keyboard.
|
||||||
|
*
|
||||||
|
* @see android.window.WindowProviderService#getLaunchedDisplayId
|
||||||
|
*/
|
||||||
|
int getImeDisplayId();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,6 @@ import android.view.WindowManager;
|
|||||||
import android.view.WindowManager.LayoutParams.WindowType;
|
import android.view.WindowManager.LayoutParams.WindowType;
|
||||||
import android.view.WindowManagerImpl;
|
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
|
* 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
|
* accessibility overlay windows. This {@link Service} has similar behavior to
|
||||||
@@ -89,6 +88,20 @@ public abstract class WindowProviderService extends Service {
|
|||||||
return null;
|
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}.
|
* Attaches this WindowProviderService to the {@code windowToken}.
|
||||||
*
|
*
|
||||||
@@ -104,10 +117,9 @@ public abstract class WindowProviderService extends Service {
|
|||||||
public final Context createServiceBaseContext(ActivityThread mainThread,
|
public final Context createServiceBaseContext(ActivityThread mainThread,
|
||||||
LoadedApk packageInfo) {
|
LoadedApk packageInfo) {
|
||||||
final Context context = super.createServiceBaseContext(mainThread, packageInfo);
|
final Context context = super.createServiceBaseContext(mainThread, packageInfo);
|
||||||
// Always associate with the default display at initialization.
|
final Display display = context.getSystemService(DisplayManager.class)
|
||||||
final Display defaultDisplay = context.getSystemService(DisplayManager.class)
|
.getDisplay(getInitialDisplayId());
|
||||||
.getDisplay(DEFAULT_DISPLAY);
|
return context.createTokenContext(mWindowToken, display);
|
||||||
return context.createTokenContext(mWindowToken, defaultDisplay);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@CallSuper
|
@CallSuper
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ import android.content.res.Configuration;
|
|||||||
import android.graphics.PixelFormat;
|
import android.graphics.PixelFormat;
|
||||||
import android.hardware.display.DisplayManager;
|
import android.hardware.display.DisplayManager;
|
||||||
import android.hardware.display.VirtualDisplay;
|
import android.hardware.display.VirtualDisplay;
|
||||||
import android.inputmethodservice.InputMethodService;
|
|
||||||
import android.media.ImageReader;
|
import android.media.ImageReader;
|
||||||
import android.os.UserHandle;
|
import android.os.UserHandle;
|
||||||
import android.view.Display;
|
import android.view.Display;
|
||||||
@@ -139,13 +138,6 @@ public class ContextTest {
|
|||||||
assertThat(systemUiContext.isUiContext()).isTrue();
|
assertThat(systemUiContext.isUiContext()).isTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testIsUiContext_InputMethodService_returnsTrue() {
|
|
||||||
final InputMethodService ims = new InputMethodService();
|
|
||||||
|
|
||||||
assertTrue(ims.isUiContext());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetDisplayFromDisplayContextDerivedContextOnPrimaryDisplay() {
|
public void testGetDisplayFromDisplayContextDerivedContextOnPrimaryDisplay() {
|
||||||
verifyGetDisplayFromDisplayContextDerivedContext(false /* onSecondaryDisplay */);
|
verifyGetDisplayFromDisplayContextDerivedContext(false /* onSecondaryDisplay */);
|
||||||
|
|||||||
@@ -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.DEFAULT_DISPLAY;
|
||||||
import static android.view.Display.INVALID_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_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_APPLICATION_WINDOW;
|
||||||
import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
|
import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
|
||||||
import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
|
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();
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user