Merge "Fix IME flicker: move hiding the surface into the control target" into rvc-dev

This commit is contained in:
Adrian Roos
2020-07-09 17:02:15 +00:00
committed by Android (Google) Code Review
17 changed files with 210 additions and 45 deletions

View File

@@ -605,9 +605,6 @@ public class InputMethodService extends AbstractInputMethodService {
if (DEBUG) Log.v(TAG, "unbindInput(): binding=" + mInputBinding if (DEBUG) Log.v(TAG, "unbindInput(): binding=" + mInputBinding
+ " ic=" + mInputConnection); + " ic=" + mInputConnection);
// Unbind input is per process per display. // Unbind input is per process per display.
// TODO(b/150902448): free-up IME surface when target is changing.
// e.g. DisplayContent#setInputMethodTarget()
removeImeSurface();
onUnbindInput(); onUnbindInput();
mInputBinding = null; mInputBinding = null;
mInputConnection = null; mInputConnection = null;

View File

@@ -119,11 +119,11 @@ public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer {
// If we had a request before to show from IME (tracked with mImeRequestedShow), reaching // If we had a request before to show from IME (tracked with mImeRequestedShow), reaching
// this code here means that we now got control, so we can start the animation immediately. // this code here means that we now got control, so we can start the animation immediately.
// If client window is trying to control IME and IME is already visible, it is immediate. // If client window is trying to control IME and IME is already visible, it is immediate.
if (fromIme || mState.getSource(getType()).isVisible()) { if (fromIme || mState.getSource(getType()).isVisible() && getControl() != null) {
return ShowResult.SHOW_IMMEDIATELY; return ShowResult.SHOW_IMMEDIATELY;
} }
return getImm().requestImeShow(null /* resultReceiver */) return getImm().requestImeShow(mController.getHost().getWindowToken())
? ShowResult.IME_SHOW_DELAYED : ShowResult.IME_SHOW_FAILED; ? ShowResult.IME_SHOW_DELAYED : ShowResult.IME_SHOW_FAILED;
} }
@@ -132,12 +132,15 @@ public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer {
*/ */
@Override @Override
void notifyHidden() { void notifyHidden() {
getImm().notifyImeHidden(); getImm().notifyImeHidden(mController.getHost().getWindowToken());
} }
@Override @Override
public void removeSurface() { public void removeSurface() {
getImm().removeImeSurface(); final IBinder window = mController.getHost().getWindowToken();
if (window != null) {
getImm().removeImeSurface(window);
}
} }
@Override @Override
@@ -146,6 +149,7 @@ public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer {
super.setControl(control, showTypes, hideTypes); super.setControl(control, showTypes, hideTypes);
if (control == null && !mIsRequestedVisibleAwaitingControl) { if (control == null && !mIsRequestedVisibleAwaitingControl) {
hide(); hide();
removeSurface();
} }
} }

View File

@@ -153,6 +153,9 @@ public class InsetsSourceConsumer {
if (oldLeash == null || newLeash == null || !oldLeash.isSameSurface(newLeash)) { if (oldLeash == null || newLeash == null || !oldLeash.isSameSurface(newLeash)) {
applyHiddenToControl(); applyHiddenToControl();
} }
if (!requestedVisible && !mIsAnimationPending) {
removeSurface();
}
} }
} }
if (lastControl != null) { if (lastControl != null) {

View File

@@ -9380,6 +9380,11 @@ public final class ViewRootImpl implements ViewParent,
return mInputEventReceiver.getToken(); return mInputEventReceiver.getToken();
} }
@NonNull
public IBinder getWindowToken() {
return mAttachInfo.mWindowToken;
}
/** /**
* Class for managing the accessibility interaction connection * Class for managing the accessibility interaction connection
* based on the global accessibility state. * based on the global accessibility state.

View File

@@ -2109,28 +2109,36 @@ public final class InputMethodManager {
/** /**
* Call showSoftInput with currently focused view. * Call showSoftInput with currently focused view.
* @return {@code true} if IME can be shown. *
* @param windowToken the window from which this request originates. If this doesn't match the
* currently served view, the request is ignored and returns {@code false}.
*
* @return {@code true} if IME can (eventually) be shown, {@code false} otherwise.
* @hide * @hide
*/ */
public boolean requestImeShow(ResultReceiver resultReceiver) { public boolean requestImeShow(IBinder windowToken) {
synchronized (mH) { synchronized (mH) {
final View servedView = getServedViewLocked(); final View servedView = getServedViewLocked();
if (servedView == null) { if (servedView == null || servedView.getWindowToken() != windowToken) {
return false; return false;
} }
showSoftInput(servedView, 0 /* flags */, resultReceiver); showSoftInput(servedView, 0 /* flags */, null /* resultReceiver */);
return true; return true;
} }
} }
/** /**
* Notify IME directly that it is no longer visible. * Notify IME directly that it is no longer visible.
*
* @param windowToken the window from which this request originates. If this doesn't match the
* currently served view, the request is ignored.
* @hide * @hide
*/ */
public void notifyImeHidden() { public void notifyImeHidden(IBinder windowToken) {
synchronized (mH) { synchronized (mH) {
try { try {
if (mCurMethod != null) { if (mCurMethod != null && mCurRootView != null
&& mCurRootView.getWindowToken() == windowToken) {
mCurMethod.notifyImeHidden(); mCurMethod.notifyImeHidden();
} }
} catch (RemoteException re) { } catch (RemoteException re) {
@@ -2140,15 +2148,15 @@ public final class InputMethodManager {
/** /**
* Notify IME directly to remove surface as it is no longer visible. * Notify IME directly to remove surface as it is no longer visible.
* @param windowToken The client window token that requests the IME to remove its surface.
* @hide * @hide
*/ */
public void removeImeSurface() { public void removeImeSurface(IBinder windowToken) {
synchronized (mH) { synchronized (mH) {
try { try {
if (mCurMethod != null) { mService.removeImeSurfaceFromWindow(windowToken);
mCurMethod.removeImeSurface(); } catch (RemoteException e) {
} throw e.rethrowFromSystemServer();
} catch (RemoteException re) {
} }
} }
} }

View File

@@ -73,5 +73,8 @@ interface IInputMethodManager {
in float[] matrixValues); in float[] matrixValues);
oneway void reportPerceptible(in IBinder windowToken, boolean perceptible); oneway void reportPerceptible(in IBinder windowToken, boolean perceptible);
/** Remove the IME surface. Requires INTERNAL_SYSTEM_WINDOW permission. */
void removeImeSurface(); void removeImeSurface();
/** Remove the IME surface. Requires passing the currently focused window. */
void removeImeSurfaceFromWindow(in IBinder windowToken);
} }

View File

@@ -71,6 +71,9 @@ public class InsetsSourceConsumerTest {
private SurfaceControl mLeash; private SurfaceControl mLeash;
@Mock Transaction mMockTransaction; @Mock Transaction mMockTransaction;
private InsetsSource mSpyInsetsSource; private InsetsSource mSpyInsetsSource;
private boolean mRemoveSurfaceCalled = false;
private InsetsController mController;
private InsetsState mState;
@Before @Before
public void setup() { public void setup() {
@@ -89,13 +92,19 @@ public class InsetsSourceConsumerTest {
} catch (BadTokenException e) { } catch (BadTokenException e) {
// activity isn't running, lets ignore BadTokenException. // activity isn't running, lets ignore BadTokenException.
} }
InsetsState state = new InsetsState(); mState = new InsetsState();
mSpyInsetsSource = Mockito.spy(new InsetsSource(ITYPE_STATUS_BAR)); mSpyInsetsSource = Mockito.spy(new InsetsSource(ITYPE_STATUS_BAR));
state.addSource(mSpyInsetsSource); mState.addSource(mSpyInsetsSource);
mConsumer = new InsetsSourceConsumer(ITYPE_STATUS_BAR, state, mController = new InsetsController(new ViewRootInsetsControllerHost(viewRootImpl));
() -> mMockTransaction, mConsumer = new InsetsSourceConsumer(ITYPE_STATUS_BAR, mState,
new InsetsController(new ViewRootInsetsControllerHost(viewRootImpl))); () -> mMockTransaction, mController) {
@Override
public void removeSurface() {
super.removeSurface();
mRemoveSurfaceCalled = true;
}
};
}); });
instrumentation.waitForIdleSync(); instrumentation.waitForIdleSync();
@@ -171,6 +180,25 @@ public class InsetsSourceConsumerTest {
mConsumer.setControl(new InsetsSourceControl(ITYPE_STATUS_BAR, mLeash, new Point()), mConsumer.setControl(new InsetsSourceControl(ITYPE_STATUS_BAR, mLeash, new Point()),
new int[1], hideTypes); new int[1], hideTypes);
assertEquals(statusBars(), hideTypes[0]); assertEquals(statusBars(), hideTypes[0]);
assertFalse(mRemoveSurfaceCalled);
}); });
} }
@Test
public void testRestore_noAnimation() {
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
mConsumer.hide();
mController.onStateChanged(mState);
mConsumer.setControl(null, new int[1], new int[1]);
reset(mMockTransaction);
verifyZeroInteractions(mMockTransaction);
mRemoveSurfaceCalled = false;
int[] hideTypes = new int[1];
mConsumer.setControl(new InsetsSourceControl(ITYPE_STATUS_BAR, mLeash, new Point()),
new int[1], hideTypes);
assertTrue(mRemoveSurfaceCalled);
assertEquals(0, hideTypes[0]);
});
}
} }

View File

@@ -226,6 +226,8 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
if (!activeControl.getSurfacePosition().equals(lastSurfacePosition) if (!activeControl.getSurfacePosition().equals(lastSurfacePosition)
&& mAnimation != null) { && mAnimation != null) {
startAnimation(mImeShowing, true /* forceRestart */); startAnimation(mImeShowing, true /* forceRestart */);
} else if (!mImeShowing) {
removeImeSurface();
} }
}); });
} }
@@ -370,16 +372,7 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
dispatchEndPositioning(mDisplayId, mCancelled, t); dispatchEndPositioning(mDisplayId, mCancelled, t);
if (mAnimationDirection == DIRECTION_HIDE && !mCancelled) { if (mAnimationDirection == DIRECTION_HIDE && !mCancelled) {
t.hide(mImeSourceControl.getLeash()); t.hide(mImeSourceControl.getLeash());
final IInputMethodManager imms = getImms(); removeImeSurface();
if (imms != null) {
try {
// Remove the IME surface to make the insets invisible for
// non-client controlled insets.
imms.removeImeSurface();
} catch (RemoteException e) {
Slog.e(TAG, "Failed to remove IME surface.", e);
}
}
} }
t.apply(); t.apply();
mTransactionPool.release(t); mTransactionPool.release(t);
@@ -402,6 +395,19 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
} }
} }
void removeImeSurface() {
final IInputMethodManager imms = getImms();
if (imms != null) {
try {
// Remove the IME surface to make the insets invisible for
// non-client controlled insets.
imms.removeImeSurface();
} catch (RemoteException e) {
Slog.e(TAG, "Failed to remove IME surface.", e);
}
}
}
/** /**
* Allows other things to synchronize with the ime position * Allows other things to synchronize with the ime position
*/ */

View File

@@ -119,6 +119,11 @@ public abstract class InputMethodManagerInternal {
*/ */
public abstract void reportImeControl(@Nullable IBinder windowToken); public abstract void reportImeControl(@Nullable IBinder windowToken);
/**
* Destroys the IME surface.
*/
public abstract void removeImeSurface();
/** /**
* Fake implementation of {@link InputMethodManagerInternal}. All the methods do nothing. * Fake implementation of {@link InputMethodManagerInternal}. All the methods do nothing.
*/ */
@@ -166,6 +171,10 @@ public abstract class InputMethodManagerInternal {
@Override @Override
public void reportImeControl(@Nullable IBinder windowToken) { public void reportImeControl(@Nullable IBinder windowToken) {
} }
@Override
public void removeImeSurface() {
}
}; };
/** /**

View File

@@ -211,6 +211,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
static final int MSG_INITIALIZE_IME = 1040; static final int MSG_INITIALIZE_IME = 1040;
static final int MSG_CREATE_SESSION = 1050; static final int MSG_CREATE_SESSION = 1050;
static final int MSG_REMOVE_IME_SURFACE = 1060; static final int MSG_REMOVE_IME_SURFACE = 1060;
static final int MSG_REMOVE_IME_SURFACE_FROM_WINDOW = 1061;
static final int MSG_START_INPUT = 2000; static final int MSG_START_INPUT = 2000;
@@ -4005,6 +4006,13 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
mHandler.sendMessage(mHandler.obtainMessage(MSG_REMOVE_IME_SURFACE)); mHandler.sendMessage(mHandler.obtainMessage(MSG_REMOVE_IME_SURFACE));
} }
@Override
public void removeImeSurfaceFromWindow(IBinder windowToken) {
// No permission check, because we'll only execute the request if the calling window is
// also the current IME client.
mHandler.obtainMessage(MSG_REMOVE_IME_SURFACE_FROM_WINDOW, windowToken).sendToTarget();
}
@BinderThread @BinderThread
private void notifyUserAction(@NonNull IBinder token) { private void notifyUserAction(@NonNull IBinder token) {
if (DEBUG) { if (DEBUG) {
@@ -4278,11 +4286,27 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
return true; return true;
} }
case MSG_REMOVE_IME_SURFACE: { case MSG_REMOVE_IME_SURFACE: {
try { synchronized (mMethodMap) {
if (mEnabledSession != null && mEnabledSession.session != null) { try {
mEnabledSession.session.removeImeSurface(); if (mEnabledSession != null && mEnabledSession.session != null
&& !mShowRequested) {
mEnabledSession.session.removeImeSurface();
}
} catch (RemoteException e) {
}
}
return true;
}
case MSG_REMOVE_IME_SURFACE_FROM_WINDOW: {
IBinder windowToken = (IBinder) msg.obj;
synchronized (mMethodMap) {
try {
if (windowToken == mCurFocusedWindow
&& mEnabledSession != null && mEnabledSession.session != null) {
mEnabledSession.session.removeImeSurface();
}
} catch (RemoteException e) {
} }
} catch (RemoteException e) {
} }
return true; return true;
} }
@@ -5116,6 +5140,11 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
public void reportImeControl(@Nullable IBinder windowToken) { public void reportImeControl(@Nullable IBinder windowToken) {
mService.reportImeControl(windowToken); mService.reportImeControl(windowToken);
} }
@Override
public void removeImeSurface() {
mService.mHandler.sendMessage(mService.mHandler.obtainMessage(MSG_REMOVE_IME_SURFACE));
}
} }
@BinderThread @BinderThread

View File

@@ -225,6 +225,11 @@ public final class MultiClientInputMethodManagerService {
@Override @Override
public void reportImeControl(@Nullable IBinder windowToken) { public void reportImeControl(@Nullable IBinder windowToken) {
} }
@Override
public void removeImeSurface() {
reportNotSupported();
}
}); });
} }
@@ -1471,6 +1476,12 @@ public final class MultiClientInputMethodManagerService {
reportNotSupported(); reportNotSupported();
} }
@BinderThread
@Override
public void removeImeSurfaceFromWindow(IBinder windowToken) {
reportNotSupported();
}
@BinderThread @BinderThread
@Override @Override
public boolean showSoftInput( public boolean showSoftInput(

View File

@@ -3574,12 +3574,11 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
} }
} }
private void updateImeControlTarget() { void updateImeControlTarget() {
mInputMethodControlTarget = computeImeControlTarget(); mInputMethodControlTarget = computeImeControlTarget();
mInsetsStateController.onImeControlTargetChanged(mInputMethodControlTarget); mInsetsStateController.onImeControlTargetChanged(mInputMethodControlTarget);
final WindowState win = mInputMethodControlTarget != null final WindowState win = InsetsControlTarget.asWindowOrNull(mInputMethodControlTarget);
? mInputMethodControlTarget.getWindow() : null;
final IBinder token = win != null ? win.mClient.asBinder() : null; final IBinder token = win != null ? win.mClient.asBinder() : null;
// Note: not allowed to call into IMMS with the WM lock held, hence the post. // Note: not allowed to call into IMMS with the WM lock held, hence the post.
mWmService.mH.post(() -> mWmService.mH.post(() ->
@@ -3603,6 +3602,17 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
if (!isImeControlledByApp() && mRemoteInsetsControlTarget != null) { if (!isImeControlledByApp() && mRemoteInsetsControlTarget != null) {
return mRemoteInsetsControlTarget; return mRemoteInsetsControlTarget;
} else { } else {
// Now, a special case -- if the last target's window is in the process of exiting, but
// not removed, keep on the last target to avoid IME flicker.
final WindowState cur = InsetsControlTarget.asWindowOrNull(mInputMethodControlTarget);
if (cur != null && !cur.mRemoved && cur.isDisplayedLw() && cur.isClosing()
&& !cur.isActivityTypeHome()) {
if (DEBUG_INPUT_METHOD) {
Slog.v(TAG_WM, "Not changing control while current window"
+ " is closing and not removed");
}
return cur;
}
// Otherwise, we just use the ime target as received from IME. // Otherwise, we just use the ime target as received from IME.
return mInputMethodInputTarget; return mInputMethodInputTarget;
} }

View File

@@ -18,13 +18,14 @@ package com.android.server.wm;
import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_IME; import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_IME;
import android.graphics.PixelFormat;
import android.view.InsetsSource; import android.view.InsetsSource;
import android.view.WindowInsets; import android.view.WindowInsets;
import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting;
import com.android.server.protolog.common.ProtoLog; import com.android.server.protolog.common.ProtoLog;
import java.io.PrintWriter;
/** /**
* Controller for IME inset source on the server. It's called provider as it provides the * Controller for IME inset source on the server. It's called provider as it provides the
* {@link InsetsSource} to the client that uses it in {@link InsetsSourceConsumer}. * {@link InsetsSource} to the client that uses it in {@link InsetsSourceConsumer}.
@@ -132,8 +133,17 @@ class ImeInsetsSourceProvider extends InsetsSourceProvider {
|| (mImeTargetFromIme != null && dcTarget.getParentWindow() == mImeTargetFromIme || (mImeTargetFromIme != null && dcTarget.getParentWindow() == mImeTargetFromIme
&& dcTarget.mSubLayer > mImeTargetFromIme.mSubLayer) && dcTarget.mSubLayer > mImeTargetFromIme.mSubLayer)
|| mImeTargetFromIme == mDisplayContent.getImeFallback() || mImeTargetFromIme == mDisplayContent.getImeFallback()
// If IME target is transparent but control target matches requesting window. || (!mImeTargetFromIme.isClosing() && controlTarget == mImeTargetFromIme);
|| (controlTarget == mImeTargetFromIme }
&& PixelFormat.formatHasAlpha(dcTarget.mAttrs.format));
@Override
public void dump(PrintWriter pw, String prefix) {
super.dump(pw, prefix);
if (mImeTargetFromIme != null) {
pw.print(prefix);
pw.print("showImePostLayout pending for mImeTargetFromIme=");
pw.print(mImeTargetFromIme);
pw.println();
}
} }
} }

View File

@@ -62,4 +62,8 @@ interface InsetsControlTarget {
return false; return false;
} }
/** Returns {@code target.getWindow()}, or null if {@code target} is {@code null}. */
static WindowState asWindowOrNull(InsetsControlTarget target) {
return target != null ? target.getWindow() : null;
}
} }

View File

@@ -44,6 +44,7 @@ import android.view.InsetsState;
import android.view.InsetsState.InternalInsetsType; import android.view.InsetsState.InternalInsetsType;
import android.view.WindowManager; import android.view.WindowManager;
import com.android.server.inputmethod.InputMethodManagerInternal;
import com.android.server.protolog.common.ProtoLog; import com.android.server.protolog.common.ProtoLog;
import java.io.PrintWriter; import java.io.PrintWriter;
@@ -74,7 +75,21 @@ class InsetsStateController {
w.notifyInsetsChanged(); w.notifyInsetsChanged();
} }
}; };
private final InsetsControlTarget mEmptyImeControlTarget = new InsetsControlTarget() { }; private final InsetsControlTarget mEmptyImeControlTarget = new InsetsControlTarget() {
@Override
public void notifyInsetsControlChanged() {
InsetsSourceControl[] controls = getControlsForDispatch(this);
if (controls == null) {
return;
}
for (InsetsSourceControl control : controls) {
if (control.getType() == ITYPE_IME) {
mDisplayContent.mWmService.mH.post(() ->
InputMethodManagerInternal.get().removeImeSurface());
}
}
}
};
InsetsStateController(DisplayContent displayContent) { InsetsStateController(DisplayContent displayContent) {
mDisplayContent = displayContent; mDisplayContent = displayContent;

View File

@@ -2170,6 +2170,9 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
if (isInputMethodTarget()) { if (isInputMethodTarget()) {
dc.computeImeTarget(true /* updateImeTarget */); dc.computeImeTarget(true /* updateImeTarget */);
} }
if (dc.mInputMethodControlTarget == this) {
dc.updateImeControlTarget();
}
final int type = mAttrs.type; final int type = mAttrs.type;
if (WindowManagerService.excludeWindowTypeFromTapOutTask(type)) { if (WindowManagerService.excludeWindowTypeFromTapOutTask(type)) {

View File

@@ -897,6 +897,26 @@ public class DisplayContentTests extends WindowTestsBase {
assertEquals(dc.mInputMethodInputTarget, dc.computeImeControlTarget()); assertEquals(dc.mInputMethodInputTarget, dc.computeImeControlTarget());
} }
@Test
public void testComputeImeControlTarget_exitingApp() throws Exception {
final DisplayContent dc = createNewDisplay();
WindowState exitingWin = createWindow(null, TYPE_BASE_APPLICATION, "exiting app");
makeWindowVisible(exitingWin);
exitingWin.mWinAnimator.mDrawState = WindowStateAnimator.HAS_DRAWN;
exitingWin.mAnimatingExit = true;
dc.mInputMethodControlTarget = exitingWin;
dc.mInputMethodTarget = dc.mInputMethodInputTarget =
createWindow(null, TYPE_BASE_APPLICATION, "starting app");
assertEquals(exitingWin, dc.computeImeControlTarget());
exitingWin.removeImmediately();
assertEquals(dc.mInputMethodInputTarget, dc.computeImeControlTarget());
}
@Test @Test
public void testComputeImeControlTarget_splitscreen() throws Exception { public void testComputeImeControlTarget_splitscreen() throws Exception {
final DisplayContent dc = createNewDisplay(); final DisplayContent dc = createNewDisplay();