Disable user animations on insets whose visible frame is empty

Floating IME or fullscreen IME won't cause insets (except the area
overlapped with navigation bar). It doesn't make much sense to let
apps move the IME at these cases.

Fix: 157777145
Test: atest InsetsSourceConsumerTest
Change-Id: Ibdf5454843c880d7e726a66a8f1107ca511e5025
This commit is contained in:
Tiger Huang
2020-06-05 18:38:53 +08:00
parent 06174db579
commit 0b9450bab9
4 changed files with 66 additions and 20 deletions

View File

@@ -485,6 +485,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 mLastDisabledUserAnimationInsetsTypes;
private Runnable mInvokeControllableInsetsChangedListeners =
this::invokeControllableInsetsChangedListeners;
public InsetsController(Host host) {
this(host, (controller, type) -> {
if (type == ITYPE_IME) {
@@ -597,9 +603,23 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
private void updateState(InsetsState newState) {
mState.setDisplayFrame(newState.getDisplayFrame());
@InsetsType int disabledUserAnimationTypes = 0;
@InsetsType int[] cancelledUserAnimationTypes = {0};
for (int i = newState.getSourcesCount() - 1; i >= 0; i--) {
InsetsSource source = newState.sourceAt(i);
getSourceConsumer(source.getType()).updateSource(source);
@InternalInsetsType int internalInsetsType = source.getType();
@AnimationType int animationType = getAnimationType(internalInsetsType);
if (source.isVisibleFrameEmpty()) {
@InsetsType int insetsType = toPublicType(internalInsetsType);
// 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(internalInsetsType).updateSource(source, animationType);
}
for (int i = mState.getSourcesCount() - 1; i >= 0; i--) {
InsetsSource source = mState.sourceAt(i);
@@ -611,6 +631,27 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
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 = mLastDisabledUserAnimationInsetsTypes ^ disabledUserAnimationTypes;
if (diff != 0) {
for (int i = mSourceConsumers.size() - 1; i >= 0; i--) {
InsetsSourceConsumer consumer = mSourceConsumers.valueAt(i);
if (consumer.getControl() != null && (toPublicType(consumer.mType) & diff) != 0) {
mHandler.removeCallbacks(mInvokeControllableInsetsChangedListeners);
mHandler.post(mInvokeControllableInsetsChangedListeners);
break;
}
}
mLastDisabledUserAnimationInsetsTypes = disabledUserAnimationTypes;
}
}
private boolean captionInsetsUnchanged() {
@@ -901,6 +942,15 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
boolean imeReady = true;
for (int i = internalTypes.size() - 1; i >= 0; i--) {
final InsetsSourceConsumer consumer = getSourceConsumer(internalTypes.valueAt(i));
if (animationType == ANIMATION_TYPE_USER) {
final InsetsSource source = mState.peekSource(consumer.getType());
if (source != null && source.isVisibleFrameEmpty()) {
if (WARN) Log.w(TAG, String.format(
"collectSourceControls can't run user animation for type: %s",
InsetsState.typeToString(consumer.getType())));
continue;
}
}
boolean show = animationType == ANIMATION_TYPE_SHOW
|| animationType == ANIMATION_TYPE_USER;
boolean canRun = false;
@@ -1291,7 +1341,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.isVisibleFrameEmpty()) {
result |= toPublicType(consumer.mType);
}
}
@@ -1302,6 +1353,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();

View File

@@ -92,6 +92,10 @@ public class InsetsSource implements Parcelable {
return mVisible;
}
public boolean isVisibleFrameEmpty() {
return mVisibleFrame != null && mVisibleFrame.isEmpty();
}
/**
* Calculates the insets this source will cause to a client window.
*

View File

@@ -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;
@@ -274,9 +274,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;
@@ -285,7 +285,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

View File

@@ -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;
@@ -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());
}