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

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/12070682

Change-Id: Ib984a4bc34f0527c809de35919340019a04b017a
This commit is contained in:
Adrian Roos
2020-07-09 17:12:41 +00:00
committed by Automerger Merge Worker
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
+ " ic=" + mInputConnection);
// Unbind input is per process per display.
// TODO(b/150902448): free-up IME surface when target is changing.
// e.g. DisplayContent#setInputMethodTarget()
removeImeSurface();
onUnbindInput();
mInputBinding = 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
// 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 (fromIme || mState.getSource(getType()).isVisible()) {
if (fromIme || mState.getSource(getType()).isVisible() && getControl() != null) {
return ShowResult.SHOW_IMMEDIATELY;
}
return getImm().requestImeShow(null /* resultReceiver */)
return getImm().requestImeShow(mController.getHost().getWindowToken())
? ShowResult.IME_SHOW_DELAYED : ShowResult.IME_SHOW_FAILED;
}
@@ -132,12 +132,15 @@ public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer {
*/
@Override
void notifyHidden() {
getImm().notifyImeHidden();
getImm().notifyImeHidden(mController.getHost().getWindowToken());
}
@Override
public void removeSurface() {
getImm().removeImeSurface();
final IBinder window = mController.getHost().getWindowToken();
if (window != null) {
getImm().removeImeSurface(window);
}
}
@Override
@@ -146,6 +149,7 @@ public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer {
super.setControl(control, showTypes, hideTypes);
if (control == null && !mIsRequestedVisibleAwaitingControl) {
hide();
removeSurface();
}
}

View File

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

View File

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

View File

@@ -2109,28 +2109,36 @@ public final class InputMethodManager {
/**
* 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
*/
public boolean requestImeShow(ResultReceiver resultReceiver) {
public boolean requestImeShow(IBinder windowToken) {
synchronized (mH) {
final View servedView = getServedViewLocked();
if (servedView == null) {
if (servedView == null || servedView.getWindowToken() != windowToken) {
return false;
}
showSoftInput(servedView, 0 /* flags */, resultReceiver);
showSoftInput(servedView, 0 /* flags */, null /* resultReceiver */);
return true;
}
}
/**
* 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
*/
public void notifyImeHidden() {
public void notifyImeHidden(IBinder windowToken) {
synchronized (mH) {
try {
if (mCurMethod != null) {
if (mCurMethod != null && mCurRootView != null
&& mCurRootView.getWindowToken() == windowToken) {
mCurMethod.notifyImeHidden();
}
} catch (RemoteException re) {
@@ -2140,15 +2148,15 @@ public final class InputMethodManager {
/**
* 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
*/
public void removeImeSurface() {
public void removeImeSurface(IBinder windowToken) {
synchronized (mH) {
try {
if (mCurMethod != null) {
mCurMethod.removeImeSurface();
}
} catch (RemoteException re) {
mService.removeImeSurfaceFromWindow(windowToken);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
}

View File

@@ -73,5 +73,8 @@ interface IInputMethodManager {
in float[] matrixValues);
oneway void reportPerceptible(in IBinder windowToken, boolean perceptible);
/** Remove the IME surface. Requires INTERNAL_SYSTEM_WINDOW permission. */
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;
@Mock Transaction mMockTransaction;
private InsetsSource mSpyInsetsSource;
private boolean mRemoveSurfaceCalled = false;
private InsetsController mController;
private InsetsState mState;
@Before
public void setup() {
@@ -89,13 +92,19 @@ public class InsetsSourceConsumerTest {
} catch (BadTokenException e) {
// activity isn't running, lets ignore BadTokenException.
}
InsetsState state = new InsetsState();
mState = new InsetsState();
mSpyInsetsSource = Mockito.spy(new InsetsSource(ITYPE_STATUS_BAR));
state.addSource(mSpyInsetsSource);
mState.addSource(mSpyInsetsSource);
mConsumer = new InsetsSourceConsumer(ITYPE_STATUS_BAR, state,
() -> mMockTransaction,
new InsetsController(new ViewRootInsetsControllerHost(viewRootImpl)));
mController = new InsetsController(new ViewRootInsetsControllerHost(viewRootImpl));
mConsumer = new InsetsSourceConsumer(ITYPE_STATUS_BAR, mState,
() -> mMockTransaction, mController) {
@Override
public void removeSurface() {
super.removeSurface();
mRemoveSurfaceCalled = true;
}
};
});
instrumentation.waitForIdleSync();
@@ -171,6 +180,25 @@ public class InsetsSourceConsumerTest {
mConsumer.setControl(new InsetsSourceControl(ITYPE_STATUS_BAR, mLeash, new Point()),
new int[1], hideTypes);
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)
&& mAnimation != null) {
startAnimation(mImeShowing, true /* forceRestart */);
} else if (!mImeShowing) {
removeImeSurface();
}
});
}
@@ -370,16 +372,7 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
dispatchEndPositioning(mDisplayId, mCancelled, t);
if (mAnimationDirection == DIRECTION_HIDE && !mCancelled) {
t.hide(mImeSourceControl.getLeash());
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);
}
}
removeImeSurface();
}
t.apply();
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
*/

View File

@@ -119,6 +119,11 @@ public abstract class InputMethodManagerInternal {
*/
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.
*/
@@ -166,6 +171,10 @@ public abstract class InputMethodManagerInternal {
@Override
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_CREATE_SESSION = 1050;
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;
@@ -4005,6 +4006,13 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
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
private void notifyUserAction(@NonNull IBinder token) {
if (DEBUG) {
@@ -4278,11 +4286,27 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
return true;
}
case MSG_REMOVE_IME_SURFACE: {
try {
if (mEnabledSession != null && mEnabledSession.session != null) {
mEnabledSession.session.removeImeSurface();
synchronized (mMethodMap) {
try {
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;
}
@@ -5116,6 +5140,11 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
public void reportImeControl(@Nullable IBinder windowToken) {
mService.reportImeControl(windowToken);
}
@Override
public void removeImeSurface() {
mService.mHandler.sendMessage(mService.mHandler.obtainMessage(MSG_REMOVE_IME_SURFACE));
}
}
@BinderThread

View File

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

View File

@@ -3574,12 +3574,11 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
}
}
private void updateImeControlTarget() {
void updateImeControlTarget() {
mInputMethodControlTarget = computeImeControlTarget();
mInsetsStateController.onImeControlTargetChanged(mInputMethodControlTarget);
final WindowState win = mInputMethodControlTarget != null
? mInputMethodControlTarget.getWindow() : null;
final WindowState win = InsetsControlTarget.asWindowOrNull(mInputMethodControlTarget);
final IBinder token = win != null ? win.mClient.asBinder() : null;
// Note: not allowed to call into IMMS with the WM lock held, hence the post.
mWmService.mH.post(() ->
@@ -3603,6 +3602,17 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
if (!isImeControlledByApp() && mRemoteInsetsControlTarget != null) {
return mRemoteInsetsControlTarget;
} 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.
return mInputMethodInputTarget;
}

View File

@@ -18,13 +18,14 @@ package com.android.server.wm;
import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_IME;
import android.graphics.PixelFormat;
import android.view.InsetsSource;
import android.view.WindowInsets;
import com.android.internal.annotations.VisibleForTesting;
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
* {@link InsetsSource} to the client that uses it in {@link InsetsSourceConsumer}.
@@ -132,8 +133,17 @@ class ImeInsetsSourceProvider extends InsetsSourceProvider {
|| (mImeTargetFromIme != null && dcTarget.getParentWindow() == mImeTargetFromIme
&& dcTarget.mSubLayer > mImeTargetFromIme.mSubLayer)
|| mImeTargetFromIme == mDisplayContent.getImeFallback()
// If IME target is transparent but control target matches requesting window.
|| (controlTarget == mImeTargetFromIme
&& PixelFormat.formatHasAlpha(dcTarget.mAttrs.format));
|| (!mImeTargetFromIme.isClosing() && controlTarget == mImeTargetFromIme);
}
@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;
}
/** 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.WindowManager;
import com.android.server.inputmethod.InputMethodManagerInternal;
import com.android.server.protolog.common.ProtoLog;
import java.io.PrintWriter;
@@ -74,7 +75,21 @@ class InsetsStateController {
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) {
mDisplayContent = displayContent;

View File

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

View File

@@ -897,6 +897,26 @@ public class DisplayContentTests extends WindowTestsBase {
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
public void testComputeImeControlTarget_splitscreen() throws Exception {
final DisplayContent dc = createNewDisplay();