Loading core/java/android/view/InsetsAnimationControlImpl.java +6 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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 */, Loading Loading @@ -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) { Loading core/java/android/view/InsetsController.java +87 −49 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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 Loading Loading @@ -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. */ Loading Loading @@ -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(), Loading Loading @@ -176,7 +192,6 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation } private void onAnimationFinish() { mAnimationDirection = DIRECTION_NONE; mController.finish(mShow); } Loading @@ -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(); Loading @@ -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; Loading @@ -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; Loading @@ -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) { Loading @@ -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); } } Loading Loading @@ -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()); Loading @@ -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; } Loading @@ -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)) { Loading @@ -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. Loading Loading @@ -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)); } /** Loading Loading @@ -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 */); } } } Loading @@ -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 { Loading @@ -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) { private void cancelAnimation(InsetsAnimationControlImpl control, boolean invokeCallback) { if (invokeCallback) { control.onCancelled(); mAnimationControls.remove(control); } for (int i = mRunningAnimations.size() - 1; i >= 0; i--) { if (mRunningAnimations.get(i).control == control) { mRunningAnimations.remove(i); break; } } } private void applyLocalVisibilityOverride() { Loading Loading @@ -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) { Loading Loading @@ -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); } Loading core/java/android/view/InsetsSourceConsumer.java +3 −1 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; } Loading core/tests/coretests/src/android/view/InsetsControllerTest.java +51 −15 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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() { Loading @@ -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, Loading @@ -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(); } Loading Loading @@ -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(); Loading Loading @@ -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()); Loading Loading
core/java/android/view/InsetsAnimationControlImpl.java +6 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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 */, Loading Loading @@ -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) { Loading
core/java/android/view/InsetsController.java +87 −49 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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 Loading Loading @@ -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. */ Loading Loading @@ -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(), Loading Loading @@ -176,7 +192,6 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation } private void onAnimationFinish() { mAnimationDirection = DIRECTION_NONE; mController.finish(mShow); } Loading @@ -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(); Loading @@ -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; Loading @@ -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; Loading @@ -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) { Loading @@ -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); } } Loading Loading @@ -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()); Loading @@ -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; } Loading @@ -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)) { Loading @@ -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. Loading Loading @@ -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)); } /** Loading Loading @@ -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 */); } } } Loading @@ -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 { Loading @@ -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) { private void cancelAnimation(InsetsAnimationControlImpl control, boolean invokeCallback) { if (invokeCallback) { control.onCancelled(); mAnimationControls.remove(control); } for (int i = mRunningAnimations.size() - 1; i >= 0; i--) { if (mRunningAnimations.get(i).control == control) { mRunningAnimations.remove(i); break; } } } private void applyLocalVisibilityOverride() { Loading Loading @@ -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) { Loading Loading @@ -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); } Loading
core/java/android/view/InsetsSourceConsumer.java +3 −1 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; } Loading
core/tests/coretests/src/android/view/InsetsControllerTest.java +51 −15 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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() { Loading @@ -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, Loading @@ -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(); } Loading Loading @@ -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(); Loading Loading @@ -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()); Loading