diff options
4 files changed, 147 insertions, 65 deletions
diff --git a/core/java/android/view/InsetsAnimationControlImpl.java b/core/java/android/view/InsetsAnimationControlImpl.java index 6589e75c7bc2..69d01050801f 100644 --- a/core/java/android/view/InsetsAnimationControlImpl.java +++ b/core/java/android/view/InsetsAnimationControlImpl.java @@ -34,6 +34,7 @@ import android.util.SparseIntArray; import android.util.SparseSetArray; import android.view.InsetsController.LayoutInsetsDuringAnimation; import android.view.InsetsState.InternalInsetsSide; +import android.view.InsetsState.InternalInsetsType; import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams; import android.view.WindowInsets.Type.InsetsType; import android.view.WindowInsetsAnimationCallback.AnimationBounds; @@ -92,6 +93,7 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll mController = controller; mInitialInsetsState = new InsetsState(state, true /* copySources */); mCurrentInsets = getInsetsFromState(mInitialInsetsState, frame, null /* typeSideMap */); + mPendingInsets = mCurrentInsets; mHiddenInsets = calculateInsets(mInitialInsetsState, frame, controls, false /* shown */, null /* typeSideMap */); mShownInsets = calculateInsets(mInitialInsetsState, frame, controls, true /* shown */, @@ -131,6 +133,10 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll return mTypes; } + boolean controlsInternalType(@InternalInsetsType int type) { + return InsetsState.toInternalType(mTypes).contains(type); + } + @Override public void setInsetsAndAlpha(Insets insets, float alpha, float fraction) { if (mFinished) { diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java index 775490c757d4..e2739c469e5f 100644 --- a/core/java/android/view/InsetsController.java +++ b/core/java/android/view/InsetsController.java @@ -28,6 +28,7 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.graphics.Insets; import android.graphics.Rect; +import android.net.InvalidPacketException.ErrorCode; import android.os.RemoteException; import android.util.ArraySet; import android.util.Log; @@ -61,15 +62,9 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation private static final int ANIMATION_DURATION_SHOW_MS = 275; private static final int ANIMATION_DURATION_HIDE_MS = 340; - private static final int DIRECTION_NONE = 0; - private static final int DIRECTION_SHOW = 1; - private static final int DIRECTION_HIDE = 2; static final Interpolator INTERPOLATOR = new PathInterpolator(0.4f, 0f, 0.2f, 1f); - @IntDef ({DIRECTION_NONE, DIRECTION_SHOW, DIRECTION_HIDE}) - private @interface AnimationDirection{} - /** * Layout mode during insets animation: The views should be laid out as if the changing inset * types are fully shown. Before starting the animation, {@link View#onApplyWindowInsets} will @@ -101,6 +96,28 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation @interface LayoutInsetsDuringAnimation { } + /** Not running an animation. */ + @VisibleForTesting + public static final int ANIMATION_TYPE_NONE = -1; + + /** Running animation will show insets */ + @VisibleForTesting + public static final int ANIMATION_TYPE_SHOW = 0; + + /** Running animation will hide insets */ + @VisibleForTesting + public static final int ANIMATION_TYPE_HIDE = 1; + + /** Running animation is controlled by user via {@link #controlWindowInsetsAnimation} */ + @VisibleForTesting + public static final int ANIMATION_TYPE_USER = 2; + + @Retention(RetentionPolicy.SOURCE) + @IntDef(value = {ANIMATION_TYPE_NONE, ANIMATION_TYPE_SHOW, ANIMATION_TYPE_HIDE, + ANIMATION_TYPE_USER}) + @interface AnimationType { + } + /** * Translation animation evaluator. */ @@ -145,7 +162,6 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation public void onReady(WindowInsetsAnimationController controller, int types) { mController = controller; - mAnimationDirection = mShow ? DIRECTION_SHOW : DIRECTION_HIDE; mAnimator = ObjectAnimator.ofObject( controller, new InsetsProperty(), @@ -176,7 +192,6 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation } private void onAnimationFinish() { - mAnimationDirection = DIRECTION_NONE; mController.finish(mShow); } @@ -193,6 +208,20 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation } } + /** + * Represents a running animation + */ + private static class RunningAnimation { + + RunningAnimation(InsetsAnimationControlImpl control, int type) { + this.control = control; + this.type = type; + } + + final InsetsAnimationControlImpl control; + final @AnimationType int type; + } + private final String TAG = "InsetsControllerImpl"; private final InsetsState mState = new InsetsState(); @@ -203,7 +232,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation private final ViewRootImpl mViewRoot; private final SparseArray<InsetsSourceControl> mTmpControlArray = new SparseArray<>(); - private final ArrayList<InsetsAnimationControlImpl> mAnimationControls = new ArrayList<>(); + private final ArrayList<RunningAnimation> mRunningAnimations = new ArrayList<>(); private final ArrayList<InsetsAnimationControlImpl> mTmpFinishedControls = new ArrayList<>(); private WindowInsets mLastInsets; @@ -213,7 +242,6 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation private final Rect mLastLegacyContentInsets = new Rect(); private final Rect mLastLegacyStableInsets = new Rect(); - private @AnimationDirection int mAnimationDirection; private int mPendingTypesToShow; @@ -226,7 +254,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation mViewRoot = viewRoot; mAnimCallback = () -> { mAnimCallbackScheduled = false; - if (mAnimationControls.isEmpty()) { + if (mRunningAnimations.isEmpty()) { return; } if (mViewRoot.mView == null) { @@ -236,9 +264,9 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation mTmpFinishedControls.clear(); InsetsState state = new InsetsState(mState, true /* copySources */); - for (int i = mAnimationControls.size() - 1; i >= 0; i--) { - InsetsAnimationControlImpl control = mAnimationControls.get(i); - if (mAnimationControls.get(i).applyChangeInsets(state)) { + for (int i = mRunningAnimations.size() - 1; i >= 0; i--) { + InsetsAnimationControlImpl control = mRunningAnimations.get(i).control; + if (control.applyChangeInsets(state)) { mTmpFinishedControls.add(control); } } @@ -349,18 +377,13 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation int typesReady = 0; final ArraySet<Integer> internalTypes = InsetsState.toInternalType(types); for (int i = internalTypes.size() - 1; i >= 0; i--) { - InsetsSourceConsumer consumer = getSourceConsumer(internalTypes.valueAt(i)); - if (mAnimationDirection == DIRECTION_HIDE) { - // Only one animator (with multiple InsetsType) can run at a time. - // previous one should be cancelled for simplicity. - cancelExistingAnimation(); - } else if (consumer.isRequestedVisible() - && (mAnimationDirection == DIRECTION_NONE - || mAnimationDirection == DIRECTION_HIDE)) { + @InternalInsetsType int internalType = internalTypes.valueAt(i); + @AnimationType int animationType = getAnimationType(internalType); + InsetsSourceConsumer consumer = getSourceConsumer(internalType); + if (mState.getSource(internalType).isVisible() && animationType == ANIMATION_TYPE_NONE + || animationType == ANIMATION_TYPE_SHOW) { // no-op: already shown or animating in (because window visibility is // applied before starting animation). - // TODO: When we have more than one types: handle specific case when - // show animation is going on, but the current type is not becoming visible. continue; } typesReady |= InsetsState.toPublicType(consumer.getType()); @@ -377,12 +400,11 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation int typesReady = 0; final ArraySet<Integer> internalTypes = InsetsState.toInternalType(types); for (int i = internalTypes.size() - 1; i >= 0; i--) { - InsetsSourceConsumer consumer = getSourceConsumer(internalTypes.valueAt(i)); - if (mAnimationDirection == DIRECTION_SHOW) { - cancelExistingAnimation(); - } else if (!consumer.isRequestedVisible() - && (mAnimationDirection == DIRECTION_NONE - || mAnimationDirection == DIRECTION_HIDE)) { + @InternalInsetsType int internalType = internalTypes.valueAt(i); + @AnimationType int animationType = getAnimationType(internalType); + InsetsSourceConsumer consumer = getSourceConsumer(internalType); + if (!mState.getSource(internalType).isVisible() && animationType == ANIMATION_TYPE_NONE + || animationType == ANIMATION_TYPE_HIDE) { // no-op: already hidden or animating out. continue; } @@ -394,11 +416,13 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation @Override public void controlWindowInsetsAnimation(@InsetsType int types, long durationMs, WindowInsetsAnimationControlListener listener) { - controlWindowInsetsAnimation(types, listener, false /* fromIme */, durationMs); + controlWindowInsetsAnimation(types, listener, false /* fromIme */, durationMs, + ANIMATION_TYPE_USER); } private void controlWindowInsetsAnimation(@InsetsType int types, - WindowInsetsAnimationControlListener listener, boolean fromIme, long durationMs) { + WindowInsetsAnimationControlListener listener, boolean fromIme, long durationMs, + @AnimationType int animationType) { // If the frame of our window doesn't span the entire display, the control API makes very // little sense, as we don't deal with negative insets. So just cancel immediately. if (!mState.getDisplayFrame().equals(mFrame)) { @@ -406,12 +430,12 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation return; } controlAnimationUnchecked(types, listener, mFrame, fromIme, durationMs, false /* fade */, - getLayoutInsetsDuringAnimationMode(types)); + animationType, getLayoutInsetsDuringAnimationMode(types)); } private void controlAnimationUnchecked(@InsetsType int types, WindowInsetsAnimationControlListener listener, Rect frame, boolean fromIme, - long durationMs, boolean fade, + long durationMs, boolean fade, @AnimationType int animationType, @LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation) { if (types == 0) { // nothing to animate. @@ -444,7 +468,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation final InsetsAnimationControlImpl controller = new InsetsAnimationControlImpl(controls, frame, mState, listener, typesReady, this, durationMs, fade, layoutInsetsDuringAnimation); - mAnimationControls.add(controller); + mRunningAnimations.add(new RunningAnimation(controller, animationType)); } /** @@ -523,10 +547,10 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation } private void cancelExistingControllers(@InsetsType int types) { - for (int i = mAnimationControls.size() - 1; i >= 0; i--) { - InsetsAnimationControlImpl control = mAnimationControls.get(i); + for (int i = mRunningAnimations.size() - 1; i >= 0; i--) { + InsetsAnimationControlImpl control = mRunningAnimations.get(i).control; if ((control.getTypes() & types) != 0) { - cancelAnimation(control); + cancelAnimation(control, true /* invokeCallback */); } } } @@ -534,7 +558,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation @VisibleForTesting @Override public void notifyFinished(InsetsAnimationControlImpl controller, boolean shown) { - mAnimationControls.remove(controller); + cancelAnimation(controller, false /* invokeCallback */); if (shown) { showDirectly(controller.getTypes()); } else { @@ -554,17 +578,24 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation } void notifyControlRevoked(InsetsSourceConsumer consumer) { - for (int i = mAnimationControls.size() - 1; i >= 0; i--) { - InsetsAnimationControlImpl control = mAnimationControls.get(i); + for (int i = mRunningAnimations.size() - 1; i >= 0; i--) { + InsetsAnimationControlImpl control = mRunningAnimations.get(i).control; if ((control.getTypes() & toPublicType(consumer.getType())) != 0) { - cancelAnimation(control); + cancelAnimation(control, true /* invokeCallback */); } } } - private void cancelAnimation(InsetsAnimationControlImpl control) { - control.onCancelled(); - mAnimationControls.remove(control); + private void cancelAnimation(InsetsAnimationControlImpl control, boolean invokeCallback) { + if (invokeCallback) { + control.onCancelled(); + } + for (int i = mRunningAnimations.size() - 1; i >= 0; i--) { + if (mRunningAnimations.get(i).control == control) { + mRunningAnimations.remove(i); + break; + } + } } private void applyLocalVisibilityOverride() { @@ -622,8 +653,15 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation } } - boolean isAnimating() { - return mAnimationDirection != DIRECTION_NONE; + @VisibleForTesting + public @AnimationType int getAnimationType(@InternalInsetsType int type) { + for (int i = mRunningAnimations.size() - 1; i >= 0; i--) { + InsetsAnimationControlImpl control = mRunningAnimations.get(i).control; + if (control.controlsInternalType(type)) { + return mRunningAnimations.get(i).type; + } + } + return ANIMATION_TYPE_NONE; } private InsetsSourceConsumer createConsumerOfType(int type) { @@ -665,8 +703,8 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation // and hidden state insets are correct. controlAnimationUnchecked( types, listener, mState.getDisplayFrame(), fromIme, listener.getDurationMs(), - true /* fade */, show - ? LAYOUT_INSETS_DURING_ANIMATION_SHOWN + true /* fade */, show ? ANIMATION_TYPE_SHOW : ANIMATION_TYPE_HIDE, + show ? LAYOUT_INSETS_DURING_ANIMATION_SHOWN : LAYOUT_INSETS_DURING_ANIMATION_HIDDEN); } diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java index b2a5d915c2a6..8a1b45a3a411 100644 --- a/core/java/android/view/InsetsSourceConsumer.java +++ b/core/java/android/view/InsetsSourceConsumer.java @@ -16,6 +16,8 @@ package android.view; +import static android.view.InsetsController.ANIMATION_TYPE_NONE; + import android.annotation.IntDef; import android.annotation.Nullable; import android.view.InsetsState.InternalInsetsType; @@ -172,7 +174,7 @@ public class InsetsSourceConsumer { private void applyHiddenToControl() { if (mSourceControl == null || mSourceControl.getLeash() == null - || mController.isAnimating()) { + || mController.getAnimationType(mType) != ANIMATION_TYPE_NONE) { return; } diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java index 1db96b15f83a..628f7ecce9f1 100644 --- a/core/tests/coretests/src/android/view/InsetsControllerTest.java +++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java @@ -16,9 +16,13 @@ package android.view; +import static android.view.InsetsController.ANIMATION_TYPE_HIDE; +import static android.view.InsetsController.ANIMATION_TYPE_NONE; +import static android.view.InsetsController.ANIMATION_TYPE_SHOW; import static android.view.InsetsState.ITYPE_IME; import static android.view.InsetsState.ITYPE_NAVIGATION_BAR; import static android.view.InsetsState.ITYPE_STATUS_BAR; +import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL; import static android.view.WindowInsets.Type.statusBars; import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; @@ -40,12 +44,15 @@ import android.platform.test.annotations.Presubmit; import android.view.WindowInsets.Type; import android.view.WindowManager.BadTokenException; import android.view.WindowManager.LayoutParams; +import android.view.test.InsetsModeSession; import android.widget.TextView; import androidx.test.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; +import org.junit.AfterClass; import org.junit.Before; +import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; @@ -69,6 +76,17 @@ public class InsetsControllerTest { private SurfaceSession mSession = new SurfaceSession(); private SurfaceControl mLeash; private ViewRootImpl mViewRoot; + private static InsetsModeSession sInsetsModeSession; + + @BeforeClass + public static void setupOnce() { + sInsetsModeSession = new InsetsModeSession(NEW_INSETS_MODE_FULL); + } + + @AfterClass + public static void tearDownOnce() { + sInsetsModeSession.close(); + } @Before public void setup() { @@ -86,6 +104,11 @@ public class InsetsControllerTest { } mController = new InsetsController(mViewRoot); final Rect rect = new Rect(5, 5, 5, 5); + mController.getState().getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 100, 10)); + mController.getState().getSource(ITYPE_NAVIGATION_BAR).setFrame( + new Rect(0, 90, 100, 100)); + mController.getState().getSource(ITYPE_IME).setFrame(new Rect(0, 50, 100, 100)); + mController.getState().setDisplayFrame(new Rect(0, 0, 100, 100)); mController.calculateInsets( false, false, @@ -93,7 +116,6 @@ public class InsetsControllerTest { Insets.of(10, 10, 10, 10), rect, rect, rect, rect), rect, rect, SOFT_INPUT_ADJUST_RESIZE); mController.onFrameChanged(new Rect(0, 0, 100, 100)); - mController.getState().setDisplayFrame(new Rect(0, 0, 100, 100)); }); InstrumentationRegistry.getInstrumentation().waitForIdleSync(); } @@ -205,18 +227,24 @@ public class InsetsControllerTest { InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { int types = Type.navigationBars() | Type.systemBars(); - // test show select types. - mController.show(types); + // test hide select types. + mController.hide(types); + assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(ITYPE_NAVIGATION_BAR)); + assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(ITYPE_STATUS_BAR)); mController.cancelExistingAnimation(); - assertTrue(mController.getSourceConsumer(navBar.getType()).isRequestedVisible()); - assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible()); + assertEquals(ANIMATION_TYPE_NONE, mController.getAnimationType(ITYPE_NAVIGATION_BAR)); + assertEquals(ANIMATION_TYPE_NONE, mController.getAnimationType(ITYPE_STATUS_BAR)); + assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible()); + assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible()); assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible()); // test hide all - mController.hide(types); + mController.show(types); + assertEquals(ANIMATION_TYPE_SHOW, mController.getAnimationType(ITYPE_NAVIGATION_BAR)); + assertEquals(ANIMATION_TYPE_SHOW, mController.getAnimationType(ITYPE_STATUS_BAR)); mController.cancelExistingAnimation(); - assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible()); - assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible()); + assertTrue(mController.getSourceConsumer(navBar.getType()).isRequestedVisible()); + assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible()); assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible()); }); InstrumentationRegistry.getInstrumentation().waitForIdleSync(); @@ -271,30 +299,38 @@ public class InsetsControllerTest { InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { // start two animations and see if previous is cancelled and final state is reached. - mController.show(Type.navigationBars()); - mController.show(Type.systemBars()); - mController.cancelExistingAnimation(); - assertTrue(mController.getSourceConsumer(navBar.getType()).isRequestedVisible()); - assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible()); - assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible()); - mController.hide(Type.navigationBars()); mController.hide(Type.systemBars()); + assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(ITYPE_NAVIGATION_BAR)); + assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(ITYPE_STATUS_BAR)); mController.cancelExistingAnimation(); assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible()); assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible()); assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible()); + mController.show(Type.navigationBars()); + mController.show(Type.systemBars()); + assertEquals(ANIMATION_TYPE_SHOW, mController.getAnimationType(ITYPE_NAVIGATION_BAR)); + assertEquals(ANIMATION_TYPE_SHOW, mController.getAnimationType(ITYPE_STATUS_BAR)); + mController.cancelExistingAnimation(); + assertTrue(mController.getSourceConsumer(navBar.getType()).isRequestedVisible()); + assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible()); + assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible()); + int types = Type.navigationBars() | Type.systemBars(); // show two at a time and hide one by one. mController.show(types); mController.hide(Type.navigationBars()); + assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(ITYPE_NAVIGATION_BAR)); + assertEquals(ANIMATION_TYPE_NONE, mController.getAnimationType(ITYPE_STATUS_BAR)); mController.cancelExistingAnimation(); assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible()); assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible()); assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible()); mController.hide(Type.systemBars()); + assertEquals(ANIMATION_TYPE_NONE, mController.getAnimationType(ITYPE_NAVIGATION_BAR)); + assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(ITYPE_STATUS_BAR)); mController.cancelExistingAnimation(); assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible()); assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible()); |