Loading core/java/android/inputmethodservice/InputMethodService.java +4 −0 Original line number Diff line number Diff line Loading @@ -471,6 +471,10 @@ public class InputMethodService extends AbstractInputMethodService { final ViewTreeObserver.OnComputeInternalInsetsListener mInsetsComputer = info -> { onComputeInsets(mTmpInsets); if (!mViewsCreated) { // The IME views are not ready, keep visible insets untouched. mTmpInsets.visibleTopInsets = 0; } if (isExtractViewShown()) { // In true fullscreen mode, we just say the window isn't covering // any content so we don't impact whatever is behind. Loading core/java/android/view/InsetsController.java +64 −9 Original line number Diff line number Diff line Loading @@ -514,6 +514,12 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation /** Set of inset types for which an animation was started since last resetting this field */ private @InsetsType int mLastStartedAnimTypes; /** Set of inset types which cannot be controlled by the user animation */ private @InsetsType int mDisabledUserAnimationInsetsTypes; private Runnable mInvokeControllableInsetsChangedListeners = this::invokeControllableInsetsChangedListeners; public InsetsController(Host host) { this(host, (controller, type) -> { if (type == ITYPE_IME) { Loading Loading @@ -628,22 +634,57 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation private void updateState(InsetsState newState) { mState.setDisplayFrame(newState.getDisplayFrame()); for (int i = 0; i < InsetsState.SIZE; i++) { InsetsSource source = newState.peekSource(i); if (source == null) continue;; getSourceConsumer(source.getType()).updateSource(source); @InsetsType int disabledUserAnimationTypes = 0; @InsetsType int[] cancelledUserAnimationTypes = {0}; for (@InternalInsetsType int type = 0; type < InsetsState.SIZE; type++) { InsetsSource source = newState.peekSource(type); if (source == null) continue; @AnimationType int animationType = getAnimationType(type); if (!source.isUserControllable()) { @InsetsType int insetsType = toPublicType(type); // The user animation is not allowed when visible frame is empty. disabledUserAnimationTypes |= insetsType; if (animationType == ANIMATION_TYPE_USER) { // Existing user animation needs to be cancelled. animationType = ANIMATION_TYPE_NONE; cancelledUserAnimationTypes[0] |= insetsType; } } getSourceConsumer(type).updateSource(source, animationType); } for (int i = 0; i < InsetsState.SIZE; i++) { InsetsSource source = mState.peekSource(i); for (@InternalInsetsType int type = 0; type < InsetsState.SIZE; type++) { InsetsSource source = mState.peekSource(type); if (source == null) continue; if (newState.peekSource(source.getType()) == null) { mState.removeSource(source.getType()); if (newState.peekSource(type) == null) { mState.removeSource(type); } } if (mCaptionInsetsHeight != 0) { mState.getSource(ITYPE_CAPTION_BAR).setFrame(new Rect(mFrame.left, mFrame.top, mFrame.right, mFrame.top + mCaptionInsetsHeight)); } updateDisabledUserAnimationTypes(disabledUserAnimationTypes); if (cancelledUserAnimationTypes[0] != 0) { mHandler.post(() -> show(cancelledUserAnimationTypes[0])); } } private void updateDisabledUserAnimationTypes(@InsetsType int disabledUserAnimationTypes) { @InsetsType int diff = mDisabledUserAnimationInsetsTypes ^ disabledUserAnimationTypes; if (diff != 0) { for (int i = mSourceConsumers.size() - 1; i >= 0; i--) { InsetsSourceConsumer consumer = mSourceConsumers.valueAt(i); if (consumer.getControl() != null && (toPublicType(consumer.getType()) & diff) != 0) { mHandler.removeCallbacks(mInvokeControllableInsetsChangedListeners); mHandler.post(mInvokeControllableInsetsChangedListeners); break; } } mDisabledUserAnimationInsetsTypes = disabledUserAnimationTypes; } } private boolean captionInsetsUnchanged() { Loading Loading @@ -847,6 +888,18 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation + " while an existing " + Type.toString(mTypesBeingCancelled) + " is being cancelled."); } if (animationType == ANIMATION_TYPE_USER) { final @InsetsType int disabledTypes = types & mDisabledUserAnimationInsetsTypes; if (DEBUG) Log.d(TAG, "user animation disabled types: " + disabledTypes); types &= ~mDisabledUserAnimationInsetsTypes; if (fromIme && (disabledTypes & ime()) != 0 && !mState.getSource(ITYPE_IME).isVisible()) { // We've requested IMM to show IME, but the IME is not controllable. We need to // cancel the request. getSourceConsumer(ITYPE_IME).hide(true, animationType); } } if (types == 0) { // nothing to animate. listener.onCancelled(null); Loading Loading @@ -1320,7 +1373,8 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation @InsetsType int result = 0; for (int i = mSourceConsumers.size() - 1; i >= 0; i--) { InsetsSourceConsumer consumer = mSourceConsumers.valueAt(i); if (consumer.getControl() != null) { InsetsSource source = mState.peekSource(consumer.mType); if (consumer.getControl() != null && source != null && source.isUserControllable()) { result |= toPublicType(consumer.mType); } } Loading @@ -1331,6 +1385,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation * @return The types that are now animating due to a listener invoking control/show/hide */ private @InsetsType int invokeControllableInsetsChangedListeners() { mHandler.removeCallbacks(mInvokeControllableInsetsChangedListeners); mLastStartedAnimTypes = 0; @InsetsType int types = calculateControllableTypes(); int size = mControllableInsetsChangedListeners.size(); Loading core/java/android/view/InsetsSource.java +5 −0 Original line number Diff line number Diff line Loading @@ -92,6 +92,11 @@ public class InsetsSource implements Parcelable { return mVisible; } boolean isUserControllable() { // If mVisibleFrame is null, it will be the same area as mFrame. return mVisibleFrame == null || !mVisibleFrame.isEmpty(); } /** * Calculates the insets this source will cause to a client window. * Loading core/java/android/view/InsetsSourceConsumer.java +4 −4 Original line number Diff line number Diff line Loading @@ -18,8 +18,8 @@ package android.view; import static android.view.InsetsController.ANIMATION_TYPE_NONE; import static android.view.InsetsController.AnimationType; import static android.view.InsetsState.getDefaultVisibility; import static android.view.InsetsController.DEBUG; import static android.view.InsetsState.getDefaultVisibility; import static android.view.InsetsState.toPublicType; import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE; Loading Loading @@ -284,9 +284,9 @@ public class InsetsSourceConsumer { } @VisibleForTesting(visibility = PACKAGE) public void updateSource(InsetsSource newSource) { public void updateSource(InsetsSource newSource, @AnimationType int animationType) { InsetsSource source = mState.peekSource(mType); if (source == null || mController.getAnimationType(mType) == ANIMATION_TYPE_NONE if (source == null || animationType == ANIMATION_TYPE_NONE || source.getFrame().equals(newSource.getFrame())) { mPendingFrame = null; mPendingVisibleFrame = null; Loading @@ -295,7 +295,7 @@ public class InsetsSourceConsumer { } // Frame is changing while animating. Keep note of the new frame but keep existing frame // until animaition is finished. // until animation is finished. newSource = new InsetsSource(newSource); mPendingFrame = new Rect(newSource.getFrame()); mPendingVisibleFrame = newSource.getVisibleFrame() != null Loading core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java +4 −14 Original line number Diff line number Diff line Loading @@ -26,13 +26,11 @@ import static junit.framework.Assert.assertEquals; import static junit.framework.TestCase.assertFalse; import static junit.framework.TestCase.assertTrue; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; import android.app.Instrumentation; import android.content.Context; Loading Loading @@ -135,37 +133,29 @@ public class InsetsSourceConsumerTest { InsetsSourceConsumer consumer = new InsetsSourceConsumer( ITYPE_IME, state, null, controller); when(controller.getAnimationType(anyInt())).thenReturn(ANIMATION_TYPE_NONE); InsetsSource source = new InsetsSource(ITYPE_IME); source.setFrame(0, 1, 2, 3); consumer.updateSource(new InsetsSource(source)); when(controller.getAnimationType(anyInt())).thenReturn(ANIMATION_TYPE_USER); consumer.updateSource(new InsetsSource(source), ANIMATION_TYPE_NONE); // While we're animating, updates are delayed source.setFrame(4, 5, 6, 7); consumer.updateSource(new InsetsSource(source)); consumer.updateSource(new InsetsSource(source), ANIMATION_TYPE_USER); assertEquals(new Rect(0, 1, 2, 3), state.peekSource(ITYPE_IME).getFrame()); // Finish the animation, now the pending frame should be applied when(controller.getAnimationType(anyInt())).thenReturn(ANIMATION_TYPE_NONE); assertTrue(consumer.notifyAnimationFinished()); assertEquals(new Rect(4, 5, 6, 7), state.peekSource(ITYPE_IME).getFrame()); when(controller.getAnimationType(anyInt())).thenReturn(ANIMATION_TYPE_USER); // Animating again, updates are delayed source.setFrame(8, 9, 10, 11); consumer.updateSource(new InsetsSource(source)); consumer.updateSource(new InsetsSource(source), ANIMATION_TYPE_USER); assertEquals(new Rect(4, 5, 6, 7), state.peekSource(ITYPE_IME).getFrame()); // Updating with the current frame triggers a different code path, verify this clears // the pending 8, 9, 10, 11 frame: source.setFrame(4, 5, 6, 7); consumer.updateSource(new InsetsSource(source)); consumer.updateSource(new InsetsSource(source), ANIMATION_TYPE_USER); when(controller.getAnimationType(anyInt())).thenReturn(ANIMATION_TYPE_NONE); assertFalse(consumer.notifyAnimationFinished()); assertEquals(new Rect(4, 5, 6, 7), state.peekSource(ITYPE_IME).getFrame()); } Loading Loading
core/java/android/inputmethodservice/InputMethodService.java +4 −0 Original line number Diff line number Diff line Loading @@ -471,6 +471,10 @@ public class InputMethodService extends AbstractInputMethodService { final ViewTreeObserver.OnComputeInternalInsetsListener mInsetsComputer = info -> { onComputeInsets(mTmpInsets); if (!mViewsCreated) { // The IME views are not ready, keep visible insets untouched. mTmpInsets.visibleTopInsets = 0; } if (isExtractViewShown()) { // In true fullscreen mode, we just say the window isn't covering // any content so we don't impact whatever is behind. Loading
core/java/android/view/InsetsController.java +64 −9 Original line number Diff line number Diff line Loading @@ -514,6 +514,12 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation /** Set of inset types for which an animation was started since last resetting this field */ private @InsetsType int mLastStartedAnimTypes; /** Set of inset types which cannot be controlled by the user animation */ private @InsetsType int mDisabledUserAnimationInsetsTypes; private Runnable mInvokeControllableInsetsChangedListeners = this::invokeControllableInsetsChangedListeners; public InsetsController(Host host) { this(host, (controller, type) -> { if (type == ITYPE_IME) { Loading Loading @@ -628,22 +634,57 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation private void updateState(InsetsState newState) { mState.setDisplayFrame(newState.getDisplayFrame()); for (int i = 0; i < InsetsState.SIZE; i++) { InsetsSource source = newState.peekSource(i); if (source == null) continue;; getSourceConsumer(source.getType()).updateSource(source); @InsetsType int disabledUserAnimationTypes = 0; @InsetsType int[] cancelledUserAnimationTypes = {0}; for (@InternalInsetsType int type = 0; type < InsetsState.SIZE; type++) { InsetsSource source = newState.peekSource(type); if (source == null) continue; @AnimationType int animationType = getAnimationType(type); if (!source.isUserControllable()) { @InsetsType int insetsType = toPublicType(type); // The user animation is not allowed when visible frame is empty. disabledUserAnimationTypes |= insetsType; if (animationType == ANIMATION_TYPE_USER) { // Existing user animation needs to be cancelled. animationType = ANIMATION_TYPE_NONE; cancelledUserAnimationTypes[0] |= insetsType; } } getSourceConsumer(type).updateSource(source, animationType); } for (int i = 0; i < InsetsState.SIZE; i++) { InsetsSource source = mState.peekSource(i); for (@InternalInsetsType int type = 0; type < InsetsState.SIZE; type++) { InsetsSource source = mState.peekSource(type); if (source == null) continue; if (newState.peekSource(source.getType()) == null) { mState.removeSource(source.getType()); if (newState.peekSource(type) == null) { mState.removeSource(type); } } if (mCaptionInsetsHeight != 0) { mState.getSource(ITYPE_CAPTION_BAR).setFrame(new Rect(mFrame.left, mFrame.top, mFrame.right, mFrame.top + mCaptionInsetsHeight)); } updateDisabledUserAnimationTypes(disabledUserAnimationTypes); if (cancelledUserAnimationTypes[0] != 0) { mHandler.post(() -> show(cancelledUserAnimationTypes[0])); } } private void updateDisabledUserAnimationTypes(@InsetsType int disabledUserAnimationTypes) { @InsetsType int diff = mDisabledUserAnimationInsetsTypes ^ disabledUserAnimationTypes; if (diff != 0) { for (int i = mSourceConsumers.size() - 1; i >= 0; i--) { InsetsSourceConsumer consumer = mSourceConsumers.valueAt(i); if (consumer.getControl() != null && (toPublicType(consumer.getType()) & diff) != 0) { mHandler.removeCallbacks(mInvokeControllableInsetsChangedListeners); mHandler.post(mInvokeControllableInsetsChangedListeners); break; } } mDisabledUserAnimationInsetsTypes = disabledUserAnimationTypes; } } private boolean captionInsetsUnchanged() { Loading Loading @@ -847,6 +888,18 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation + " while an existing " + Type.toString(mTypesBeingCancelled) + " is being cancelled."); } if (animationType == ANIMATION_TYPE_USER) { final @InsetsType int disabledTypes = types & mDisabledUserAnimationInsetsTypes; if (DEBUG) Log.d(TAG, "user animation disabled types: " + disabledTypes); types &= ~mDisabledUserAnimationInsetsTypes; if (fromIme && (disabledTypes & ime()) != 0 && !mState.getSource(ITYPE_IME).isVisible()) { // We've requested IMM to show IME, but the IME is not controllable. We need to // cancel the request. getSourceConsumer(ITYPE_IME).hide(true, animationType); } } if (types == 0) { // nothing to animate. listener.onCancelled(null); Loading Loading @@ -1320,7 +1373,8 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation @InsetsType int result = 0; for (int i = mSourceConsumers.size() - 1; i >= 0; i--) { InsetsSourceConsumer consumer = mSourceConsumers.valueAt(i); if (consumer.getControl() != null) { InsetsSource source = mState.peekSource(consumer.mType); if (consumer.getControl() != null && source != null && source.isUserControllable()) { result |= toPublicType(consumer.mType); } } Loading @@ -1331,6 +1385,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation * @return The types that are now animating due to a listener invoking control/show/hide */ private @InsetsType int invokeControllableInsetsChangedListeners() { mHandler.removeCallbacks(mInvokeControllableInsetsChangedListeners); mLastStartedAnimTypes = 0; @InsetsType int types = calculateControllableTypes(); int size = mControllableInsetsChangedListeners.size(); Loading
core/java/android/view/InsetsSource.java +5 −0 Original line number Diff line number Diff line Loading @@ -92,6 +92,11 @@ public class InsetsSource implements Parcelable { return mVisible; } boolean isUserControllable() { // If mVisibleFrame is null, it will be the same area as mFrame. return mVisibleFrame == null || !mVisibleFrame.isEmpty(); } /** * Calculates the insets this source will cause to a client window. * Loading
core/java/android/view/InsetsSourceConsumer.java +4 −4 Original line number Diff line number Diff line Loading @@ -18,8 +18,8 @@ package android.view; import static android.view.InsetsController.ANIMATION_TYPE_NONE; import static android.view.InsetsController.AnimationType; import static android.view.InsetsState.getDefaultVisibility; import static android.view.InsetsController.DEBUG; import static android.view.InsetsState.getDefaultVisibility; import static android.view.InsetsState.toPublicType; import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE; Loading Loading @@ -284,9 +284,9 @@ public class InsetsSourceConsumer { } @VisibleForTesting(visibility = PACKAGE) public void updateSource(InsetsSource newSource) { public void updateSource(InsetsSource newSource, @AnimationType int animationType) { InsetsSource source = mState.peekSource(mType); if (source == null || mController.getAnimationType(mType) == ANIMATION_TYPE_NONE if (source == null || animationType == ANIMATION_TYPE_NONE || source.getFrame().equals(newSource.getFrame())) { mPendingFrame = null; mPendingVisibleFrame = null; Loading @@ -295,7 +295,7 @@ public class InsetsSourceConsumer { } // Frame is changing while animating. Keep note of the new frame but keep existing frame // until animaition is finished. // until animation is finished. newSource = new InsetsSource(newSource); mPendingFrame = new Rect(newSource.getFrame()); mPendingVisibleFrame = newSource.getVisibleFrame() != null Loading
core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java +4 −14 Original line number Diff line number Diff line Loading @@ -26,13 +26,11 @@ import static junit.framework.Assert.assertEquals; import static junit.framework.TestCase.assertFalse; import static junit.framework.TestCase.assertTrue; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; import android.app.Instrumentation; import android.content.Context; Loading Loading @@ -135,37 +133,29 @@ public class InsetsSourceConsumerTest { InsetsSourceConsumer consumer = new InsetsSourceConsumer( ITYPE_IME, state, null, controller); when(controller.getAnimationType(anyInt())).thenReturn(ANIMATION_TYPE_NONE); InsetsSource source = new InsetsSource(ITYPE_IME); source.setFrame(0, 1, 2, 3); consumer.updateSource(new InsetsSource(source)); when(controller.getAnimationType(anyInt())).thenReturn(ANIMATION_TYPE_USER); consumer.updateSource(new InsetsSource(source), ANIMATION_TYPE_NONE); // While we're animating, updates are delayed source.setFrame(4, 5, 6, 7); consumer.updateSource(new InsetsSource(source)); consumer.updateSource(new InsetsSource(source), ANIMATION_TYPE_USER); assertEquals(new Rect(0, 1, 2, 3), state.peekSource(ITYPE_IME).getFrame()); // Finish the animation, now the pending frame should be applied when(controller.getAnimationType(anyInt())).thenReturn(ANIMATION_TYPE_NONE); assertTrue(consumer.notifyAnimationFinished()); assertEquals(new Rect(4, 5, 6, 7), state.peekSource(ITYPE_IME).getFrame()); when(controller.getAnimationType(anyInt())).thenReturn(ANIMATION_TYPE_USER); // Animating again, updates are delayed source.setFrame(8, 9, 10, 11); consumer.updateSource(new InsetsSource(source)); consumer.updateSource(new InsetsSource(source), ANIMATION_TYPE_USER); assertEquals(new Rect(4, 5, 6, 7), state.peekSource(ITYPE_IME).getFrame()); // Updating with the current frame triggers a different code path, verify this clears // the pending 8, 9, 10, 11 frame: source.setFrame(4, 5, 6, 7); consumer.updateSource(new InsetsSource(source)); consumer.updateSource(new InsetsSource(source), ANIMATION_TYPE_USER); when(controller.getAnimationType(anyInt())).thenReturn(ANIMATION_TYPE_NONE); assertFalse(consumer.notifyAnimationFinished()); assertEquals(new Rect(4, 5, 6, 7), state.peekSource(ITYPE_IME).getFrame()); } Loading