diff options
7 files changed, 72 insertions, 43 deletions
diff --git a/core/java/android/view/ImeInsetsSourceConsumer.java b/core/java/android/view/ImeInsetsSourceConsumer.java index 4f741982081b..880622a1e121 100644 --- a/core/java/android/view/ImeInsetsSourceConsumer.java +++ b/core/java/android/view/ImeInsetsSourceConsumer.java @@ -221,13 +221,13 @@ public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer { @Override public boolean setControl(@Nullable InsetsSourceControl control, int[] showTypes, - int[] hideTypes, int[] cancelTypes) { + int[] hideTypes, int[] cancelTypes, int[] transientTypes) { if (Flags.refactorInsetsController()) { - return super.setControl(control, showTypes, hideTypes, cancelTypes); + return super.setControl(control, showTypes, hideTypes, cancelTypes, transientTypes); } else { ImeTracing.getInstance().triggerClientDump("ImeInsetsSourceConsumer#setControl", mController.getHost().getInputMethodManager(), null /* icProto */); - if (!super.setControl(control, showTypes, hideTypes, cancelTypes)) { + if (!super.setControl(control, showTypes, hideTypes, cancelTypes, transientTypes)) { return false; } if (control == null && !mIsRequestedVisibleAwaitingLeash) { diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java index b0813f3a98f6..c174fbe0bbcd 100644 --- a/core/java/android/view/InsetsController.java +++ b/core/java/android/view/InsetsController.java @@ -959,6 +959,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation final @InsetsType int[] showTypes = new int[1]; final @InsetsType int[] hideTypes = new int[1]; final @InsetsType int[] cancelTypes = new int[1]; + final @InsetsType int[] transientTypes = new int[1]; ImeTracker.Token statsToken = null; // Ensure to update all existing source consumers @@ -984,7 +985,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation // control may be null, but we still need to update the control to null if it got // revoked. - consumer.setControl(control, showTypes, hideTypes, cancelTypes); + consumer.setControl(control, showTypes, hideTypes, cancelTypes, transientTypes); } // Ensure to create source consumers if not available yet. @@ -992,7 +993,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation for (int i = mTmpControlArray.size() - 1; i >= 0; i--) { final InsetsSourceControl control = mTmpControlArray.valueAt(i); getSourceConsumer(control.getId(), control.getType()) - .setControl(control, showTypes, hideTypes, cancelTypes); + .setControl(control, showTypes, hideTypes, cancelTypes, transientTypes); } } @@ -1020,10 +1021,16 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation handlePendingControlRequest(statsToken); } else { if (showTypes[0] != 0) { - applyAnimation(showTypes[0], true /* show */, false /* fromIme */, statsToken); + applyAnimation(showTypes[0], true /* show */, false /* fromIme */, + false /* skipsCallbacks */, statsToken); } if (hideTypes[0] != 0) { - applyAnimation(hideTypes[0], false /* show */, false /* fromIme */, statsToken); + applyAnimation(hideTypes[0], false /* show */, false /* fromIme */, + // The animation of hiding transient types shouldn't be detected by the + // app. Otherwise, it might be able to react to the callbacks and cause + // flickering. + (hideTypes[0] & ~transientTypes[0]) == 0 /* skipsCallbacks */, + statsToken); } } } else { @@ -1033,7 +1040,8 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation ImeTracker.TYPE_SHOW, ImeTracker.ORIGIN_CLIENT, SoftInputShowHideReason.CONTROLS_CHANGED, mHost.isHandlingPointerEvent() /* fromUser */); - applyAnimation(showTypes[0], true /* show */, false /* fromIme */, newStatsToken); + applyAnimation(showTypes[0], true /* show */, false /* fromIme */, + false /* skipsCallbacks */, newStatsToken); } if (hideTypes[0] != 0) { final var newStatsToken = @@ -1041,7 +1049,12 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation ImeTracker.TYPE_HIDE, ImeTracker.ORIGIN_CLIENT, SoftInputShowHideReason.CONTROLS_CHANGED, mHost.isHandlingPointerEvent() /* fromUser */); - applyAnimation(hideTypes[0], false /* show */, false /* fromIme */, newStatsToken); + applyAnimation(hideTypes[0], false /* show */, false /* fromIme */, + // The animation of hiding transient types shouldn't be detected by the app. + // Otherwise, it might be able to react to the callbacks and cause + // flickering. + (hideTypes[0] & ~transientTypes[0]) == 0 /* skipsCallbacks */, + newStatsToken); } } @@ -1174,7 +1187,8 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation // TODO(b/353463205) check if this is needed here ImeTracker.forLatency().onShown(statsToken, ActivityThread::currentApplication); } - applyAnimation(typesReady, true /* show */, fromIme, statsToken); + applyAnimation(typesReady, true /* show */, fromIme, false /* skipsCallbacks */, + statsToken); } /** @@ -1287,7 +1301,8 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation handlePendingControlRequest(statsToken); getImeSourceConsumer().removeSurface(); } - applyAnimation(typesReady, false /* show */, fromIme, statsToken); + applyAnimation(typesReady, false /* show */, fromIme, false /* skipsCallbacks */, + statsToken); } @Override @@ -2007,24 +2022,24 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation @VisibleForTesting public void applyAnimation(@InsetsType final int types, boolean show, boolean fromIme, - @Nullable ImeTracker.Token statsToken) { + boolean skipsCallbacks, @Nullable ImeTracker.Token statsToken) { // TODO(b/166736352): We should only skip the animation of specific types, not all types. - boolean skipAnim = false; + boolean skipsAnim = false; if ((types & ime()) != 0) { final InsetsSourceControl imeControl = mImeSourceConsumer.getControl(); // Skip showing animation once that made by system for some reason. // (e.g. starting window with IME snapshot) if (imeControl != null) { - skipAnim = imeControl.getAndClearSkipAnimationOnce() && show + skipsAnim = imeControl.getAndClearSkipAnimationOnce() && show && mImeSourceConsumer.hasViewFocusWhenWindowFocusGain(); } } - applyAnimation(types, show, fromIme, skipAnim, statsToken); + applyAnimation(types, show, fromIme, skipsAnim, skipsCallbacks, statsToken); } @VisibleForTesting public void applyAnimation(@InsetsType final int types, boolean show, boolean fromIme, - boolean skipAnim, @Nullable ImeTracker.Token statsToken) { + boolean skipsAnim, boolean skipsCallbacks, @Nullable ImeTracker.Token statsToken) { if (types == 0) { // nothing to animate. if (DEBUG) Log.d(TAG, "applyAnimation, nothing to animate. Stopping here"); @@ -2040,7 +2055,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation boolean hasAnimationCallbacks = mHost.hasAnimationCallbacks(); final InternalAnimationControlListener listener = new InternalAnimationControlListener( show, hasAnimationCallbacks, types, mHost.getSystemBarsBehavior(), - skipAnim || mAnimationsDisabled, mHost.dipToPx(FLOATING_IME_BOTTOM_INSET_DP), + skipsAnim || mAnimationsDisabled, mHost.dipToPx(FLOATING_IME_BOTTOM_INSET_DP), mLoggingListener, mJankContext); // We are about to playing the default animation (show/hide). Passing a null frame indicates @@ -2050,7 +2065,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation listener /* insetsAnimationSpec */, show ? ANIMATION_TYPE_SHOW : ANIMATION_TYPE_HIDE, show ? LAYOUT_INSETS_DURING_ANIMATION_SHOWN : LAYOUT_INSETS_DURING_ANIMATION_HIDDEN, - !hasAnimationCallbacks /* useInsetsAnimationThread */, statsToken, + !hasAnimationCallbacks || skipsCallbacks /* useInsetsAnimationThread */, statsToken, false /* fromPredictiveBack */); } @@ -2173,12 +2188,12 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation new InsetsSourceControl(ID_IME_CAPTION_BAR, captionBar(), null /* leash */, false /* initialVisible */, new Point(), Insets.NONE), - new int[1], new int[1], new int[1]); + new int[1], new int[1], new int[1], new int[1]); } else { mState.removeSource(ID_IME_CAPTION_BAR); InsetsSourceConsumer sourceConsumer = mSourceConsumers.get(ID_IME_CAPTION_BAR); if (sourceConsumer != null) { - sourceConsumer.setControl(null, new int[1], new int[1], new int[1]); + sourceConsumer.setControl(null, new int[1], new int[1], new int[1], new int[1]); } } mHost.notifyInsetsChanged(); diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java index 17f33c1af4ed..e8e66210bca6 100644 --- a/core/java/android/view/InsetsSourceConsumer.java +++ b/core/java/android/view/InsetsSourceConsumer.java @@ -130,7 +130,10 @@ public class InsetsSourceConsumer { * @return Whether the control has changed from the server */ public boolean setControl(@Nullable InsetsSourceControl control, - @InsetsType int[] showTypes, @InsetsType int[] hideTypes, int[] cancelTypes) { + @InsetsType int[] showTypes, + @InsetsType int[] hideTypes, + @InsetsType int[] cancelTypes, + @InsetsType int[] transientTypes) { if (Objects.equals(mSourceControl, control)) { if (mSourceControl != null && mSourceControl != control) { mSourceControl.release(SurfaceControl::release); @@ -185,6 +188,9 @@ public class InsetsSourceConsumer { } else { hideTypes[0] |= mType; } + if (lastControl != null && lastControl.isFake()) { + transientTypes[0] |= mType; + } } else { // We are gaining control, but don't need to run an animation. // However make sure that the leash visibility is still up to date. diff --git a/core/java/android/view/InsetsSourceControl.java b/core/java/android/view/InsetsSourceControl.java index acbd95bf6810..7f2f0e8863df 100644 --- a/core/java/android/view/InsetsSourceControl.java +++ b/core/java/android/view/InsetsSourceControl.java @@ -142,6 +142,10 @@ public class InsetsSourceControl implements Parcelable { return mInsetsHint; } + public boolean isFake() { + return mLeash == null && Insets.NONE.equals(mInsetsHint); + } + public void setSkipAnimationOnce(boolean skipAnimation) { mSkipAnimationOnce = skipAnimation; } diff --git a/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java b/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java index 248db65d7435..4a54f6b12c9d 100644 --- a/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java +++ b/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java @@ -133,7 +133,7 @@ public class ImeInsetsSourceConsumerTest { // Called once through the show flow. verify(mController).applyAnimation( eq(WindowInsets.Type.ime()), eq(true) /* show */, eq(true) /* fromIme */, - eq(statsToken)); + eq(false) /* skipsCallbacks */, eq(statsToken)); // set control and verify visibility is applied. InsetsSourceControl control = new InsetsSourceControl(ID_IME, @@ -142,10 +142,10 @@ public class ImeInsetsSourceConsumerTest { // IME show animation should be triggered when control becomes available. verify(mController).applyAnimation( eq(WindowInsets.Type.ime()), eq(true) /* show */, eq(false) /* fromIme */, - and(not(eq(statsToken)), notNull())); + eq(false) /* skipsCallbacks */, and(not(eq(statsToken)), notNull())); verify(mController, never()).applyAnimation( eq(WindowInsets.Type.ime()), eq(false) /* show */, eq(false) /* fromIme */, - and(not(eq(statsToken)), notNull())); + eq(false) /* skipsCallbacks */, and(not(eq(statsToken)), notNull())); }); } @@ -163,7 +163,7 @@ public class ImeInsetsSourceConsumerTest { // Called once through the show flow. verify(mController).applyAnimation( eq(WindowInsets.Type.ime()), eq(true) /* show */, eq(true) /* fromIme */, - eq(statsToken)); + eq(false) /* skipsCallbacks */, eq(statsToken)); // Clear previous invocations to verify this is never called with control without leash. clearInvocations(mController); @@ -175,10 +175,10 @@ public class ImeInsetsSourceConsumerTest { // as we have no leash. verify(mController, never()).applyAnimation( eq(WindowInsets.Type.ime()), eq(true) /* show */, eq(false) /* fromIme */, - and(not(eq(statsToken)), notNull())); + eq(false) /* skipsCallbacks */, and(not(eq(statsToken)), notNull())); verify(mController, never()).applyAnimation( eq(WindowInsets.Type.ime()), eq(false) /* show */, eq(false) /* fromIme */, - and(not(eq(statsToken)), notNull())); + eq(false) /* skipsCallbacks */, and(not(eq(statsToken)), notNull())); // set control with leash and verify visibility is applied. InsetsSourceControl controlWithLeash = new InsetsSourceControl(ID_IME, @@ -187,10 +187,10 @@ public class ImeInsetsSourceConsumerTest { // IME show animation should be triggered when control with leash becomes available. verify(mController).applyAnimation( eq(WindowInsets.Type.ime()), eq(true) /* show */, eq(false) /* fromIme */, - and(not(eq(statsToken)), notNull())); + eq(false) /* skipsCallbacks */, and(not(eq(statsToken)), notNull())); verify(mController, never()).applyAnimation( eq(WindowInsets.Type.ime()), eq(false) /* show */, eq(false) /* fromIme */, - and(not(eq(statsToken)), notNull())); + eq(false) /* skipsCallbacks */, and(not(eq(statsToken)), notNull())); }); } @@ -223,7 +223,8 @@ public class ImeInsetsSourceConsumerTest { // Called once through the show flow. verify(mController).applyAnimation(eq(WindowInsets.Type.ime()), eq(true) /* show */, eq(true) /* fromIme */, - eq(false) /* skipAnim */, eq(statsToken)); + eq(false) /* skipsAnim */, eq(false) /* skipsCallbacks */, + eq(statsToken)); } // set control and verify visibility is applied. @@ -241,7 +242,8 @@ public class ImeInsetsSourceConsumerTest { // so the statsToken won't match. verify(mController).applyAnimation(eq(WindowInsets.Type.ime()), eq(true) /* show */, eq(false) /* fromIme */, - eq(expectSkipAnim) /* skipAnim */, and(not(eq(statsToken)), notNull())); + eq(expectSkipAnim) /* skipsAnim */, eq(false) /* skipsCallbacks */, + and(not(eq(statsToken)), notNull())); } // If previously hasViewFocus is false, verify when requesting the IME visible next @@ -252,14 +254,16 @@ public class ImeInsetsSourceConsumerTest { // Called once through the show flow. verify(mController).applyAnimation(eq(WindowInsets.Type.ime()), eq(true) /* show */, eq(true) /* fromIme */, - eq(false) /* skipAnim */, eq(statsTokenNext)); + eq(false) /* skipsAnim */, eq(false) /* skipsCallbacks */, + eq(statsTokenNext)); mController.onControlsChanged(new InsetsSourceControl[]{ control }); // Verify IME show animation should be triggered when control becomes available and // the animation will be skipped by getAndClearSkipAnimationOnce invoked. verify(control).getAndClearSkipAnimationOnce(); verify(mController).applyAnimation(eq(WindowInsets.Type.ime()), eq(true) /* show */, eq(false) /* fromIme */, - eq(true) /* skipAnim */, and(not(eq(statsToken)), notNull())); + eq(true) /* skipsAnim */, eq(false) /* skipsCallbacks */, + and(not(eq(statsToken)), notNull())); } }); } diff --git a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java index d7f6a29d7c86..905d897e9ab0 100644 --- a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java +++ b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java @@ -100,14 +100,14 @@ public class InsetsAnimationControlImplTest { topConsumer.setControl( new InsetsSourceControl(ID_STATUS_BAR, WindowInsets.Type.statusBars(), mStatusLeash, true, new Point(0, 0), Insets.of(0, 100, 0, 0)), - new int[1], new int[1], new int[1]); + new int[1], new int[1], new int[1], new int[1]); InsetsSourceConsumer navConsumer = new InsetsSourceConsumer(ID_NAVIGATION_BAR, WindowInsets.Type.navigationBars(), mInsetsState, mMockController); navConsumer.setControl( new InsetsSourceControl(ID_NAVIGATION_BAR, WindowInsets.Type.navigationBars(), mNavLeash, true, new Point(400, 0), Insets.of(0, 0, 100, 0)), - new int[1], new int[1], new int[1]); + new int[1], new int[1], new int[1], new int[1]); mMockController.setRequestedVisibleTypes(0, WindowInsets.Type.navigationBars()); navConsumer.applyLocalVisibilityOverride(); diff --git a/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java b/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java index 3a8f7ee3d7c8..45d66e8ee3a9 100644 --- a/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java +++ b/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java @@ -117,7 +117,7 @@ public class InsetsSourceConsumerTest { mConsumer.setControl( new InsetsSourceControl(ID_STATUS_BAR, statusBars(), mLeash, true /* initialVisible */, new Point(), Insets.NONE), - new int[1], new int[1], new int[1]); + new int[1], new int[1], new int[1], new int[1]); } @Test @@ -129,7 +129,7 @@ public class InsetsSourceConsumerTest { newControl.setInsetsHint(Insets.of(0, 0, 0, 100)); int[] cancelTypes = {0}; - mConsumer.setControl(newControl, new int[1], new int[1], cancelTypes); + mConsumer.setControl(newControl, new int[1], new int[1], cancelTypes, new int[1]); assertEquals(statusBars(), cancelTypes[0]); }); @@ -196,7 +196,7 @@ public class InsetsSourceConsumerTest { @Test public void testRestore() { InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { - mConsumer.setControl(null, new int[1], new int[1], new int[1]); + mConsumer.setControl(null, new int[1], new int[1], new int[1], new int[1]); mSurfaceParamsApplied = false; mController.setRequestedVisibleTypes(0 /* visibleTypes */, statusBars()); assertFalse(mSurfaceParamsApplied); @@ -204,7 +204,7 @@ public class InsetsSourceConsumerTest { mConsumer.setControl( new InsetsSourceControl(ID_STATUS_BAR, statusBars(), mLeash, true /* initialVisible */, new Point(), Insets.NONE), - new int[1], hideTypes, new int[1]); + new int[1], hideTypes, new int[1], new int[1]); assertEquals(statusBars(), hideTypes[0]); assertFalse(mRemoveSurfaceCalled); }); @@ -214,7 +214,7 @@ public class InsetsSourceConsumerTest { public void testRestore_noAnimation() { InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { mController.setRequestedVisibleTypes(0 /* visibleTypes */, statusBars()); - mConsumer.setControl(null, new int[1], new int[1], new int[1]); + mConsumer.setControl(null, new int[1], new int[1], new int[1], new int[1]); mLeash = new SurfaceControl.Builder(mSession) .setName("testSurface") .build(); @@ -223,7 +223,7 @@ public class InsetsSourceConsumerTest { mConsumer.setControl( new InsetsSourceControl(ID_STATUS_BAR, statusBars(), mLeash, false /* initialVisible */, new Point(), Insets.NONE), - new int[1], hideTypes, new int[1]); + new int[1], hideTypes, new int[1], new int[1]); assertTrue(mRemoveSurfaceCalled); assertEquals(0, hideTypes[0]); }); @@ -252,7 +252,7 @@ public class InsetsSourceConsumerTest { // Initial IME insets source control with its leash. imeConsumer.setControl(new InsetsSourceControl(ID_IME, ime(), mLeash, false /* initialVisible */, new Point(), Insets.NONE), new int[1], new int[1], - new int[1]); + new int[1], new int[1]); mSurfaceParamsApplied = false; // Verify when the app requests controlling show IME animation, the IME leash @@ -262,7 +262,7 @@ public class InsetsSourceConsumerTest { assertEquals(ANIMATION_TYPE_USER, insetsController.getAnimationType(ime())); imeConsumer.setControl(new InsetsSourceControl(ID_IME, ime(), mLeash, true /* initialVisible */, new Point(), Insets.NONE), new int[1], new int[1], - new int[1]); + new int[1], new int[1]); assertFalse(mSurfaceParamsApplied); }); } |