blob: 44bb0620b8067923798f2799705665874303c1af [file] [log] [blame]
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License
*/
package android.view;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.view.InsetsState.ITYPE_IME;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import android.content.Context;
import android.graphics.Insets;
import android.graphics.Point;
import android.graphics.Rect;
import android.platform.test.annotations.Presubmit;
import android.view.WindowManager.BadTokenException;
import android.view.WindowManager.LayoutParams;
import android.widget.TextView;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.FlakyTest;
import androidx.test.platform.app.InstrumentationRegistry;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.mockito.Spy;
/**
* Test {@link InsetsSourceConsumer} with IME type.
*
* Build/Install/Run:
* atest FrameworksCoreTests:ImeInsetsSourceConsumerTest
*/
@Presubmit
@FlakyTest(detail = "Promote once confirmed non-flaky")
@RunWith(AndroidJUnit4.class)
public class ImeInsetsSourceConsumerTest {
Context mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
ImeInsetsSourceConsumer mImeConsumer;
@Spy InsetsController mController;
SurfaceControl mLeash;
@Before
public void setup() {
mLeash = new SurfaceControl.Builder(new SurfaceSession())
.setName("testSurface")
.build();
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
ViewRootImpl viewRootImpl = new ViewRootImpl(mContext, mContext.getDisplayNoVerify());
try {
viewRootImpl.setView(new TextView(mContext), new LayoutParams(), null);
} catch (BadTokenException e) {
// activity isn't running, we will ignore BadTokenException.
}
mController = Mockito.spy(new InsetsController(
new ViewRootInsetsControllerHost(viewRootImpl)));
final Rect rect = new Rect(5, 5, 5, 5);
mController.getState().setDisplayCutout(new DisplayCutout(
Insets.of(10, 10, 10, 10), rect, rect, rect, rect));
mController.calculateInsets(
false,
false,
TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED,
SOFT_INPUT_ADJUST_RESIZE, 0, 0);
mImeConsumer = (ImeInsetsSourceConsumer) mController.getSourceConsumer(ITYPE_IME);
});
}
@Test
public void testImeVisibility() {
final InsetsSourceControl ime =
new InsetsSourceControl(ITYPE_IME, mLeash, false, new Point(), Insets.NONE);
mController.onControlsChanged(new InsetsSourceControl[] { ime });
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
// test if setVisibility can show IME
mImeConsumer.onWindowFocusGained(true);
mController.show(WindowInsets.Type.ime(), true /* fromIme */);
mController.cancelExistingAnimations();
assertTrue(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
// test if setVisibility can hide IME
mController.hide(WindowInsets.Type.ime(), true /* fromIme */);
mController.cancelExistingAnimations();
assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
});
}
@Test
public void testImeRequestedVisibleAwaitingControl() {
// Set null control and then request show.
mController.onControlsChanged(new InsetsSourceControl[] { null });
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
// Request IME visible before control is available.
mImeConsumer.onWindowFocusGained(true);
mController.show(WindowInsets.Type.ime(), true /* fromIme */);
// set control and verify visibility is applied.
InsetsSourceControl control =
new InsetsSourceControl(ITYPE_IME, mLeash, false, new Point(), Insets.NONE);
mController.onControlsChanged(new InsetsSourceControl[] { control });
// IME show animation should be triggered when control becomes available.
verify(mController).applyAnimation(
eq(WindowInsets.Type.ime()), eq(true) /* show */, eq(true) /* fromIme */);
verify(mController, never()).applyAnimation(
eq(WindowInsets.Type.ime()), eq(false) /* show */, eq(true) /* fromIme */);
});
}
@Test
public void testImeGetAndClearSkipAnimationOnce_expectSkip() {
// Expect IME animation will skipped when the IME is visible at first place.
verifyImeGetAndClearSkipAnimationOnce(true /* hasWindowFocus */, true /* hasViewFocus */,
true /* expectSkipAnim */);
}
@Test
public void testImeGetAndClearSkipAnimationOnce_expectNoSkip() {
// Expect IME animation will not skipped if previously no view focused when gained the
// window focus and requesting the IME visible next time.
verifyImeGetAndClearSkipAnimationOnce(true /* hasWindowFocus */, false /* hasViewFocus */,
false /* expectSkipAnim */);
}
private void verifyImeGetAndClearSkipAnimationOnce(boolean hasWindowFocus, boolean hasViewFocus,
boolean expectSkipAnim) {
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
// Request IME visible before control is available.
mImeConsumer.onWindowFocusGained(hasWindowFocus);
final boolean imeVisible = hasWindowFocus && hasViewFocus;
if (imeVisible) {
mController.show(WindowInsets.Type.ime(), true /* fromIme */);
}
// set control and verify visibility is applied.
InsetsSourceControl control = Mockito.spy(
new InsetsSourceControl(ITYPE_IME, mLeash, false, new Point(), Insets.NONE));
// Simulate IME source control set this flag when the target has starting window.
control.setSkipAnimationOnce(true);
if (imeVisible) {
// Verify IME applyAnimation should be triggered when control becomes available,
// and expect skip animation state after getAndClearSkipAnimationOnce invoked.
mController.onControlsChanged(new InsetsSourceControl[]{ control });
verify(control).getAndClearSkipAnimationOnce();
verify(mController).applyAnimation(eq(WindowInsets.Type.ime()),
eq(true) /* show */, eq(false) /* fromIme */,
eq(expectSkipAnim) /* skipAnim */);
}
// If previously hasViewFocus is false, verify when requesting the IME visible next
// time will not skip animation.
if (!hasViewFocus) {
mController.show(WindowInsets.Type.ime(), true);
mController.onControlsChanged(new InsetsSourceControl[]{ control });
// Verify IME show animation should be triggered when control becomes available and
// the animation will be skipped by getAndClearSkipAnimationOnce invoked.
verify(control).getAndClearSkipAnimationOnce();
verify(mController).applyAnimation(eq(WindowInsets.Type.ime()),
eq(true) /* show */, eq(true) /* fromIme */,
eq(false) /* skipAnim */);
}
});
}
}