Loading core/java/android/view/InsetsAnimationControlImpl.java +51 −4 Original line number Diff line number Diff line Loading @@ -20,6 +20,7 @@ import static android.view.InsetsState.INSET_SIDE_BOTTOM; import static android.view.InsetsState.INSET_SIDE_LEFT; import static android.view.InsetsState.INSET_SIDE_RIGHT; import static android.view.InsetsState.INSET_SIDE_TOP; import static android.view.InsetsState.toPublicType; import android.annotation.Nullable; import android.graphics.Insets; Loading @@ -33,6 +34,7 @@ import android.util.SparseSetArray; import android.view.InsetsState.InsetSide; import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams; import android.view.WindowInsets.Type.InsetType; import android.view.WindowInsetsAnimationListener.InsetsAnimation; import android.view.WindowManager.LayoutParams; import com.android.internal.annotations.VisibleForTesting; Loading Loading @@ -66,8 +68,12 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll private final Supplier<SyncRtSurfaceTransactionApplier> mTransactionApplierSupplier; private final InsetsController mController; private final WindowInsetsAnimationListener.InsetsAnimation mAnimation; private final Rect mFrame; private Insets mCurrentInsets; private Insets mPendingInsets; private boolean mFinished; private boolean mCancelled; private int mFinishedShownTypes; @VisibleForTesting public InsetsAnimationControlImpl(SparseArray<InsetsSourceConsumer> consumers, Rect frame, Loading @@ -86,6 +92,7 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll null /* typeSideMap */); mShownInsets = calculateInsets(mInitialInsetsState, frame, consumers, true /* shown */, mTypeSideMap); mFrame = new Rect(frame); buildTypeSourcesMap(mTypeSideMap, mSideSourceMap, mConsumers); // TODO: Check for controllability first and wait for IME if needed. Loading Loading @@ -119,12 +126,26 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll @Override public void changeInsets(Insets insets) { if (mFinished) { throw new IllegalStateException( "Can't change insets on an animation that is finished."); } if (mCancelled) { throw new IllegalStateException( "Can't change insets on an animation that is cancelled."); } mPendingInsets = sanitize(insets); mController.scheduleApplyChangeInsets(); } @VisibleForTesting public void applyChangeInsets(InsetsState state) { /** * @return Whether the finish callback of this animation should be invoked. */ public boolean applyChangeInsets(InsetsState state) { if (mCancelled) { return false; } final Insets offset = Insets.subtract(mShownInsets, mPendingInsets); ArrayList<SurfaceParams> params = new ArrayList<>(); if (offset.left != 0) { Loading @@ -144,13 +165,40 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll SyncRtSurfaceTransactionApplier applier = mTransactionApplierSupplier.get(); applier.scheduleApply(params.toArray(new SurfaceParams[params.size()])); mCurrentInsets = mPendingInsets; if (mFinished) { mController.notifyFinished(this, mFinishedShownTypes); } return mFinished; } @Override public void finish(int shownTypes) { // TODO if (mCancelled) { return; } InsetsState state = new InsetsState(mController.getState()); for (int i = mConsumers.size() - 1; i >= 0; i--) { InsetsSourceConsumer consumer = mConsumers.valueAt(i); boolean visible = (shownTypes & toPublicType(consumer.getType())) != 0; state.getSource(consumer.getType()).setVisible(visible); } Insets insets = getInsetsFromState(state, mFrame, null /* typeSideMap */); changeInsets(insets); mFinished = true; mFinishedShownTypes = shownTypes; } mController.dispatchAnimationFinished(mAnimation); @VisibleForTesting public void onCancelled() { if (mFinished) { return; } mCancelled = true; mListener.onCancelled(); } InsetsAnimation getAnimation() { return mAnimation; } private Insets calculateInsets(InsetsState state, Rect frame, Loading Loading @@ -225,4 +273,3 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll } } } core/java/android/view/InsetsController.java +58 −17 Original line number Diff line number Diff line Loading @@ -17,6 +17,8 @@ package android.view; import static android.view.InsetsState.TYPE_IME; import static android.view.InsetsState.toPublicType; import static android.view.WindowInsets.Type.all; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; Loading Loading @@ -99,6 +101,7 @@ public class InsetsController implements WindowInsetsController { private final SparseArray<InsetsSourceControl> mTmpControlArray = new SparseArray<>(); private final ArrayList<InsetsAnimationControlImpl> mAnimationControls = new ArrayList<>(); private final ArrayList<InsetsAnimationControlImpl> mTmpFinishedControls = new ArrayList<>(); private WindowInsets mLastInsets; private boolean mAnimCallbackScheduled; Loading @@ -107,7 +110,6 @@ public class InsetsController implements WindowInsetsController { private final Rect mLastLegacyContentInsets = new Rect(); private final Rect mLastLegacyStableInsets = new Rect(); private ObjectAnimator mAnimator; private @AnimationDirection int mAnimationDirection; private int mPendingTypesToShow; Loading @@ -122,19 +124,29 @@ public class InsetsController implements WindowInsetsController { return; } mTmpFinishedControls.clear(); InsetsState state = new InsetsState(mState, true /* copySources */); for (int i = mAnimationControls.size() - 1; i >= 0; i--) { mAnimationControls.get(i).applyChangeInsets(state); InsetsAnimationControlImpl control = mAnimationControls.get(i); if (mAnimationControls.get(i).applyChangeInsets(state)) { mTmpFinishedControls.add(control); } } WindowInsets insets = state.calculateInsets(mFrame, mLastInsets.isRound(), mLastInsets.shouldAlwaysConsumeNavBar(), mLastInsets.getDisplayCutout(), mLastLegacyContentInsets, mLastLegacyStableInsets, mLastLegacySoftInputMode, null /* typeSideMap */); mViewRoot.mView.dispatchWindowInsetsAnimationProgress(insets); for (int i = mTmpFinishedControls.size() - 1; i >= 0; i--) { dispatchAnimationFinished(mTmpFinishedControls.get(i).getAnimation()); } }; } void onFrameChanged(Rect frame) { @VisibleForTesting public void onFrameChanged(Rect frame) { if (mFrame.equals(frame)) { return; } Loading Loading @@ -279,7 +291,8 @@ public class InsetsController implements WindowInsetsController { // nothing to animate. return; } // TODO: Check whether we already have a controller. cancelExistingControllers(types); final ArraySet<Integer> internalTypes = mState.toInternalType(types); final SparseArray<InsetsSourceConsumer> consumers = new SparseArray<>(); Loading Loading @@ -321,7 +334,7 @@ public class InsetsController implements WindowInsetsController { // Show request switch(consumer.requestShow(fromIme)) { case ShowResult.SHOW_IMMEDIATELY: typesReady |= InsetsState.toPublicType(TYPE_IME); typesReady |= InsetsState.toPublicType(consumer.getType()); break; case ShowResult.SHOW_DELAYED: isReady = false; Loading Loading @@ -365,6 +378,36 @@ public class InsetsController implements WindowInsetsController { return typesReady; } private void cancelExistingControllers(@InsetType int types) { for (int i = mAnimationControls.size() - 1; i >= 0; i--) { InsetsAnimationControlImpl control = mAnimationControls.get(i); if ((control.getTypes() & types) != 0) { cancelAnimation(control); } } } @VisibleForTesting public void notifyFinished(InsetsAnimationControlImpl controller, int shownTypes) { mAnimationControls.remove(controller); hideDirectly(controller.getTypes() & ~shownTypes); showDirectly(controller.getTypes() & shownTypes); } void notifyControlRevoked(InsetsSourceConsumer consumer) { for (int i = mAnimationControls.size() - 1; i >= 0; i--) { InsetsAnimationControlImpl control = mAnimationControls.get(i); if ((control.getTypes() & toPublicType(consumer.getType())) != 0) { cancelAnimation(control); } } } private void cancelAnimation(InsetsAnimationControlImpl control) { control.onCancelled(); mAnimationControls.remove(control); } private void applyLocalVisibilityOverride() { for (int i = mSourceConsumers.size() - 1; i >= 0; i--) { final InsetsSourceConsumer controller = mSourceConsumers.valueAt(i); Loading Loading @@ -455,8 +498,13 @@ public class InsetsController implements WindowInsetsController { } WindowInsetsAnimationControlListener listener = new WindowInsetsAnimationControlListener() { private WindowInsetsAnimationController mController; private ObjectAnimator mAnimator; @Override public void onReady(WindowInsetsAnimationController controller, int types) { mController = controller; if (show) { showDirectly(types); } else { Loading @@ -474,10 +522,6 @@ public class InsetsController implements WindowInsetsController { : ANIMATION_DURATION_HIDE_MS); mAnimator.setInterpolator(INTERPOLATOR); mAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationCancel(Animator animation) { onAnimationFinish(); } @Override public void onAnimationEnd(Animator animation) { Loading @@ -488,15 +532,15 @@ public class InsetsController implements WindowInsetsController { } @Override public void onCancelled() {} public void onCancelled() { mAnimator.cancel(); } private void onAnimationFinish() { mAnimationDirection = DIRECTION_NONE; mController.finish(show ? types : 0); } }; // TODO: Instead of clearing this here, properly wire up // InsetsAnimationControlImpl.finish() to remove this from mAnimationControls. mAnimationControls.clear(); // Show/hide animations always need to be relative to the display frame, in order that shown // and hidden state insets are correct. Loading @@ -522,10 +566,7 @@ public class InsetsController implements WindowInsetsController { */ @VisibleForTesting public void cancelExistingAnimation() { mAnimationDirection = DIRECTION_NONE; if (mAnimator != null) { mAnimator.cancel(); } cancelExistingControllers(all()); } void dump(String prefix, PrintWriter pw) { Loading core/java/android/view/InsetsSourceConsumer.java +3 −0 Original line number Diff line number Diff line Loading @@ -77,6 +77,9 @@ public class InsetsSourceConsumer { if (applyLocalVisibilityOverride()) { mController.notifyVisibilityChanged(); } if (mSourceControl == null) { mController.notifyControlRevoked(this); } } @VisibleForTesting Loading core/java/android/view/View.java +1 −1 Original line number Diff line number Diff line Loading @@ -10968,7 +10968,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } void dispatchWindowInsetsAnimationFinished(InsetsAnimation animation) { if (mListenerInfo != null && mListenerInfo.mOnApplyWindowInsetsListener != null) { if (mListenerInfo != null && mListenerInfo.mWindowInsetsAnimationListener != null) { mListenerInfo.mWindowInsetsAnimationListener.onFinished(animation); } } core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java +23 −0 Original line number Diff line number Diff line Loading @@ -129,6 +129,29 @@ public class InsetsAnimationControlImplTest { assertPosition(navParams.matrix, new Rect(400, 0, 500, 500), new Rect(460, 0, 560, 500)); } @Test public void testFinishing() { when(mMockController.getState()).thenReturn(mInsetsState); mController.finish(sideBars()); mController.applyChangeInsets(mInsetsState); assertFalse(mInsetsState.getSource(TYPE_TOP_BAR).isVisible()); assertTrue(mInsetsState.getSource(TYPE_NAVIGATION_BAR).isVisible()); assertEquals(Insets.of(0, 0, 100, 0), mController.getCurrentInsets()); verify(mMockController).notifyFinished(eq(mController), eq(sideBars())); } @Test public void testCancelled() { mController.onCancelled(); try { mController.changeInsets(Insets.NONE); fail("Expected exception to be thrown"); } catch (IllegalStateException ignored) { } verify(mMockListener).onCancelled(); mController.finish(sideBars()); } private void assertPosition(Matrix m, Rect original, Rect transformed) { RectF rect = new RectF(original); rect.offsetTo(0, 0); Loading Loading
core/java/android/view/InsetsAnimationControlImpl.java +51 −4 Original line number Diff line number Diff line Loading @@ -20,6 +20,7 @@ import static android.view.InsetsState.INSET_SIDE_BOTTOM; import static android.view.InsetsState.INSET_SIDE_LEFT; import static android.view.InsetsState.INSET_SIDE_RIGHT; import static android.view.InsetsState.INSET_SIDE_TOP; import static android.view.InsetsState.toPublicType; import android.annotation.Nullable; import android.graphics.Insets; Loading @@ -33,6 +34,7 @@ import android.util.SparseSetArray; import android.view.InsetsState.InsetSide; import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams; import android.view.WindowInsets.Type.InsetType; import android.view.WindowInsetsAnimationListener.InsetsAnimation; import android.view.WindowManager.LayoutParams; import com.android.internal.annotations.VisibleForTesting; Loading Loading @@ -66,8 +68,12 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll private final Supplier<SyncRtSurfaceTransactionApplier> mTransactionApplierSupplier; private final InsetsController mController; private final WindowInsetsAnimationListener.InsetsAnimation mAnimation; private final Rect mFrame; private Insets mCurrentInsets; private Insets mPendingInsets; private boolean mFinished; private boolean mCancelled; private int mFinishedShownTypes; @VisibleForTesting public InsetsAnimationControlImpl(SparseArray<InsetsSourceConsumer> consumers, Rect frame, Loading @@ -86,6 +92,7 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll null /* typeSideMap */); mShownInsets = calculateInsets(mInitialInsetsState, frame, consumers, true /* shown */, mTypeSideMap); mFrame = new Rect(frame); buildTypeSourcesMap(mTypeSideMap, mSideSourceMap, mConsumers); // TODO: Check for controllability first and wait for IME if needed. Loading Loading @@ -119,12 +126,26 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll @Override public void changeInsets(Insets insets) { if (mFinished) { throw new IllegalStateException( "Can't change insets on an animation that is finished."); } if (mCancelled) { throw new IllegalStateException( "Can't change insets on an animation that is cancelled."); } mPendingInsets = sanitize(insets); mController.scheduleApplyChangeInsets(); } @VisibleForTesting public void applyChangeInsets(InsetsState state) { /** * @return Whether the finish callback of this animation should be invoked. */ public boolean applyChangeInsets(InsetsState state) { if (mCancelled) { return false; } final Insets offset = Insets.subtract(mShownInsets, mPendingInsets); ArrayList<SurfaceParams> params = new ArrayList<>(); if (offset.left != 0) { Loading @@ -144,13 +165,40 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll SyncRtSurfaceTransactionApplier applier = mTransactionApplierSupplier.get(); applier.scheduleApply(params.toArray(new SurfaceParams[params.size()])); mCurrentInsets = mPendingInsets; if (mFinished) { mController.notifyFinished(this, mFinishedShownTypes); } return mFinished; } @Override public void finish(int shownTypes) { // TODO if (mCancelled) { return; } InsetsState state = new InsetsState(mController.getState()); for (int i = mConsumers.size() - 1; i >= 0; i--) { InsetsSourceConsumer consumer = mConsumers.valueAt(i); boolean visible = (shownTypes & toPublicType(consumer.getType())) != 0; state.getSource(consumer.getType()).setVisible(visible); } Insets insets = getInsetsFromState(state, mFrame, null /* typeSideMap */); changeInsets(insets); mFinished = true; mFinishedShownTypes = shownTypes; } mController.dispatchAnimationFinished(mAnimation); @VisibleForTesting public void onCancelled() { if (mFinished) { return; } mCancelled = true; mListener.onCancelled(); } InsetsAnimation getAnimation() { return mAnimation; } private Insets calculateInsets(InsetsState state, Rect frame, Loading Loading @@ -225,4 +273,3 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll } } }
core/java/android/view/InsetsController.java +58 −17 Original line number Diff line number Diff line Loading @@ -17,6 +17,8 @@ package android.view; import static android.view.InsetsState.TYPE_IME; import static android.view.InsetsState.toPublicType; import static android.view.WindowInsets.Type.all; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; Loading Loading @@ -99,6 +101,7 @@ public class InsetsController implements WindowInsetsController { private final SparseArray<InsetsSourceControl> mTmpControlArray = new SparseArray<>(); private final ArrayList<InsetsAnimationControlImpl> mAnimationControls = new ArrayList<>(); private final ArrayList<InsetsAnimationControlImpl> mTmpFinishedControls = new ArrayList<>(); private WindowInsets mLastInsets; private boolean mAnimCallbackScheduled; Loading @@ -107,7 +110,6 @@ public class InsetsController implements WindowInsetsController { private final Rect mLastLegacyContentInsets = new Rect(); private final Rect mLastLegacyStableInsets = new Rect(); private ObjectAnimator mAnimator; private @AnimationDirection int mAnimationDirection; private int mPendingTypesToShow; Loading @@ -122,19 +124,29 @@ public class InsetsController implements WindowInsetsController { return; } mTmpFinishedControls.clear(); InsetsState state = new InsetsState(mState, true /* copySources */); for (int i = mAnimationControls.size() - 1; i >= 0; i--) { mAnimationControls.get(i).applyChangeInsets(state); InsetsAnimationControlImpl control = mAnimationControls.get(i); if (mAnimationControls.get(i).applyChangeInsets(state)) { mTmpFinishedControls.add(control); } } WindowInsets insets = state.calculateInsets(mFrame, mLastInsets.isRound(), mLastInsets.shouldAlwaysConsumeNavBar(), mLastInsets.getDisplayCutout(), mLastLegacyContentInsets, mLastLegacyStableInsets, mLastLegacySoftInputMode, null /* typeSideMap */); mViewRoot.mView.dispatchWindowInsetsAnimationProgress(insets); for (int i = mTmpFinishedControls.size() - 1; i >= 0; i--) { dispatchAnimationFinished(mTmpFinishedControls.get(i).getAnimation()); } }; } void onFrameChanged(Rect frame) { @VisibleForTesting public void onFrameChanged(Rect frame) { if (mFrame.equals(frame)) { return; } Loading Loading @@ -279,7 +291,8 @@ public class InsetsController implements WindowInsetsController { // nothing to animate. return; } // TODO: Check whether we already have a controller. cancelExistingControllers(types); final ArraySet<Integer> internalTypes = mState.toInternalType(types); final SparseArray<InsetsSourceConsumer> consumers = new SparseArray<>(); Loading Loading @@ -321,7 +334,7 @@ public class InsetsController implements WindowInsetsController { // Show request switch(consumer.requestShow(fromIme)) { case ShowResult.SHOW_IMMEDIATELY: typesReady |= InsetsState.toPublicType(TYPE_IME); typesReady |= InsetsState.toPublicType(consumer.getType()); break; case ShowResult.SHOW_DELAYED: isReady = false; Loading Loading @@ -365,6 +378,36 @@ public class InsetsController implements WindowInsetsController { return typesReady; } private void cancelExistingControllers(@InsetType int types) { for (int i = mAnimationControls.size() - 1; i >= 0; i--) { InsetsAnimationControlImpl control = mAnimationControls.get(i); if ((control.getTypes() & types) != 0) { cancelAnimation(control); } } } @VisibleForTesting public void notifyFinished(InsetsAnimationControlImpl controller, int shownTypes) { mAnimationControls.remove(controller); hideDirectly(controller.getTypes() & ~shownTypes); showDirectly(controller.getTypes() & shownTypes); } void notifyControlRevoked(InsetsSourceConsumer consumer) { for (int i = mAnimationControls.size() - 1; i >= 0; i--) { InsetsAnimationControlImpl control = mAnimationControls.get(i); if ((control.getTypes() & toPublicType(consumer.getType())) != 0) { cancelAnimation(control); } } } private void cancelAnimation(InsetsAnimationControlImpl control) { control.onCancelled(); mAnimationControls.remove(control); } private void applyLocalVisibilityOverride() { for (int i = mSourceConsumers.size() - 1; i >= 0; i--) { final InsetsSourceConsumer controller = mSourceConsumers.valueAt(i); Loading Loading @@ -455,8 +498,13 @@ public class InsetsController implements WindowInsetsController { } WindowInsetsAnimationControlListener listener = new WindowInsetsAnimationControlListener() { private WindowInsetsAnimationController mController; private ObjectAnimator mAnimator; @Override public void onReady(WindowInsetsAnimationController controller, int types) { mController = controller; if (show) { showDirectly(types); } else { Loading @@ -474,10 +522,6 @@ public class InsetsController implements WindowInsetsController { : ANIMATION_DURATION_HIDE_MS); mAnimator.setInterpolator(INTERPOLATOR); mAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationCancel(Animator animation) { onAnimationFinish(); } @Override public void onAnimationEnd(Animator animation) { Loading @@ -488,15 +532,15 @@ public class InsetsController implements WindowInsetsController { } @Override public void onCancelled() {} public void onCancelled() { mAnimator.cancel(); } private void onAnimationFinish() { mAnimationDirection = DIRECTION_NONE; mController.finish(show ? types : 0); } }; // TODO: Instead of clearing this here, properly wire up // InsetsAnimationControlImpl.finish() to remove this from mAnimationControls. mAnimationControls.clear(); // Show/hide animations always need to be relative to the display frame, in order that shown // and hidden state insets are correct. Loading @@ -522,10 +566,7 @@ public class InsetsController implements WindowInsetsController { */ @VisibleForTesting public void cancelExistingAnimation() { mAnimationDirection = DIRECTION_NONE; if (mAnimator != null) { mAnimator.cancel(); } cancelExistingControllers(all()); } void dump(String prefix, PrintWriter pw) { Loading
core/java/android/view/InsetsSourceConsumer.java +3 −0 Original line number Diff line number Diff line Loading @@ -77,6 +77,9 @@ public class InsetsSourceConsumer { if (applyLocalVisibilityOverride()) { mController.notifyVisibilityChanged(); } if (mSourceControl == null) { mController.notifyControlRevoked(this); } } @VisibleForTesting Loading
core/java/android/view/View.java +1 −1 Original line number Diff line number Diff line Loading @@ -10968,7 +10968,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } void dispatchWindowInsetsAnimationFinished(InsetsAnimation animation) { if (mListenerInfo != null && mListenerInfo.mOnApplyWindowInsetsListener != null) { if (mListenerInfo != null && mListenerInfo.mWindowInsetsAnimationListener != null) { mListenerInfo.mWindowInsetsAnimationListener.onFinished(animation); } }
core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java +23 −0 Original line number Diff line number Diff line Loading @@ -129,6 +129,29 @@ public class InsetsAnimationControlImplTest { assertPosition(navParams.matrix, new Rect(400, 0, 500, 500), new Rect(460, 0, 560, 500)); } @Test public void testFinishing() { when(mMockController.getState()).thenReturn(mInsetsState); mController.finish(sideBars()); mController.applyChangeInsets(mInsetsState); assertFalse(mInsetsState.getSource(TYPE_TOP_BAR).isVisible()); assertTrue(mInsetsState.getSource(TYPE_NAVIGATION_BAR).isVisible()); assertEquals(Insets.of(0, 0, 100, 0), mController.getCurrentInsets()); verify(mMockController).notifyFinished(eq(mController), eq(sideBars())); } @Test public void testCancelled() { mController.onCancelled(); try { mController.changeInsets(Insets.NONE); fail("Expected exception to be thrown"); } catch (IllegalStateException ignored) { } verify(mMockListener).onCancelled(); mController.finish(sideBars()); } private void assertPosition(Matrix m, Rect original, Rect transformed) { RectF rect = new RectF(original); rect.offsetTo(0, 0); Loading