1 /* 2 * Copyright (C) 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.server.wm; 18 19 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; 20 import static android.graphics.Insets.NONE; 21 import static android.view.WindowInsets.Type.ime; 22 import static android.view.WindowInsets.Type.statusBars; 23 24 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; 25 26 import static org.junit.Assert.assertEquals; 27 import static org.junit.Assume.assumeFalse; 28 import static org.junit.Assume.assumeTrue; 29 import static org.mockito.ArgumentMatchers.any; 30 import static org.mockito.ArgumentMatchers.argThat; 31 import static org.mockito.ArgumentMatchers.eq; 32 import static org.mockito.Mockito.CALLS_REAL_METHODS; 33 import static org.mockito.Mockito.inOrder; 34 import static org.mockito.Mockito.mock; 35 import static org.mockito.Mockito.withSettings; 36 37 import android.content.pm.PackageManager; 38 import android.platform.test.annotations.Presubmit; 39 import android.view.WindowInsets; 40 41 import com.android.cts.mockime.MockIme; 42 43 import org.junit.Before; 44 import org.junit.Test; 45 import org.mockito.InOrder; 46 47 /** 48 * Same as {@link WindowInsetsAnimationTests} but IME specific. 49 * 50 * Build/Install/Run: 51 * atest CtsWindowManagerDeviceTestCases:WindowInsetsAnimationImeTests 52 */ 53 @Presubmit 54 @android.server.wm.annotation.Group2 55 public class WindowInsetsAnimationImeTests extends WindowInsetsAnimationTestBase { 56 57 private static final int KEYBOARD_HEIGHT = 600; 58 59 @Before setup()60 public void setup() throws Exception { 61 super.setUp(); 62 assumeFalse( 63 "Automotive is to skip this test until showing and hiding certain insets " 64 + "simultaneously in a single request is supported", 65 mInstrumentation.getContext().getPackageManager().hasSystemFeature( 66 PackageManager.FEATURE_AUTOMOTIVE)); 67 assumeTrue("MockIme cannot be used for devices that do not support installable IMEs", 68 mInstrumentation.getContext().getPackageManager().hasSystemFeature( 69 PackageManager.FEATURE_INPUT_METHODS)); 70 } 71 initActivity(boolean useFloating)72 private void initActivity(boolean useFloating) { 73 MockImeHelper.createManagedMockImeSession(this, KEYBOARD_HEIGHT, useFloating); 74 75 // The existing IME will be replaced by MockIME. Wait for the new IME window. 76 mWmState.waitFor("MockIme must be ready.", wms -> { 77 final WindowManagerState.WindowState ime = wms.getInputMethodWindowState(); 78 return ime != null && ime.getPackageName().equals(MockIme.class.getPackageName()); 79 }); 80 81 mActivity = startActivityInWindowingMode(TestActivity.class, WINDOWING_MODE_FULLSCREEN); 82 mRootView = mActivity.getWindow().getDecorView(); 83 } 84 85 @Test testImeAnimationCallbacksShowAndHide()86 public void testImeAnimationCallbacksShowAndHide() { 87 initActivity(false /* useFloating */); 88 testShowAndHide(); 89 } 90 91 @Test testAnimationCallbacks_overlapping_opposite()92 public void testAnimationCallbacks_overlapping_opposite() { 93 initActivity(false /* useFloating */); 94 assumeTrue(hasWindowInsets(mRootView, statusBars())); 95 96 WindowInsets before = mActivity.mLastWindowInsets; 97 98 MultiAnimCallback callbackInner = new MultiAnimCallback(); 99 MultiAnimCallback callback = mock(MultiAnimCallback.class, 100 withSettings() 101 .spiedInstance(callbackInner) 102 .defaultAnswer(CALLS_REAL_METHODS) 103 .verboseLogging()); 104 mActivity.mView.setWindowInsetsAnimationCallback(callback); 105 106 getInstrumentation().runOnMainSync( 107 () -> mRootView.getWindowInsetsController().hide(statusBars())); 108 getInstrumentation().runOnMainSync( 109 () -> mRootView.getWindowInsetsController().show(ime())); 110 111 waitForOrFail("Waiting until IME animation starts", () -> callback.imeAnimStarted); 112 waitForOrFail("Waiting until animation done", () -> callback.runningAnims.isEmpty()); 113 114 WindowInsets after = mActivity.mLastWindowInsets; 115 116 // When system bar and IME are animated together, order of events cannot be predicted 117 // relative to one another: especially the end since animation durations are different. 118 // Use individual inOrder for each. 119 InOrder inOrderBar = inOrder(callback, mActivity.mListener); 120 InOrder inOrderIme = inOrder(callback, mActivity.mListener); 121 122 inOrderBar.verify(callback).onPrepare(eq(callback.statusBarAnim)); 123 124 inOrderIme.verify(mActivity.mListener).onApplyWindowInsets(any(), argThat( 125 argument -> NONE.equals(argument.getInsets(statusBars())) 126 && NONE.equals(argument.getInsets(ime())))); 127 128 inOrderBar.verify(callback).onStart(eq(callback.statusBarAnim), argThat( 129 argument -> argument.getLowerBound().equals(NONE) 130 && argument.getUpperBound().equals(before.getInsets(statusBars())))); 131 132 inOrderIme.verify(callback).onPrepare(eq(callback.imeAnim)); 133 inOrderIme.verify(mActivity.mListener).onApplyWindowInsets( 134 any(), eq(mActivity.mLastWindowInsets)); 135 136 inOrderIme.verify(callback).onStart(eq(callback.imeAnim), argThat( 137 argument -> argument.getLowerBound().equals(NONE) 138 && !argument.getUpperBound().equals(NONE))); 139 140 inOrderBar.verify(callback).onEnd(eq(callback.statusBarAnim)); 141 inOrderIme.verify(callback).onEnd(eq(callback.imeAnim)); 142 143 assertAnimationSteps(callback.statusAnimSteps, false /* showAnimation */); 144 assertAnimationSteps(callback.imeAnimSteps, true /* showAnimation */, ime()); 145 146 assertEquals(before.getInsets(statusBars()), 147 callback.statusAnimSteps.get(0).insets.getInsets(statusBars())); 148 assertEquals(after.getInsets(statusBars()), 149 callback.statusAnimSteps.get(callback.statusAnimSteps.size() - 1).insets 150 .getInsets(statusBars())); 151 152 assertEquals(before.getInsets(ime()), 153 callback.imeAnimSteps.get(0).insets.getInsets(ime())); 154 assertEquals(after.getInsets(ime()), 155 callback.imeAnimSteps.get(callback.imeAnimSteps.size() - 1).insets 156 .getInsets(ime())); 157 } 158 159 @Test testZeroInsetsImeAnimates()160 public void testZeroInsetsImeAnimates() { 161 initActivity(true /* useFloating */); 162 testShowAndHide(); 163 } 164 testShowAndHide()165 private void testShowAndHide() { 166 WindowInsets before = mActivity.mLastWindowInsets; 167 getInstrumentation().runOnMainSync( 168 () -> mRootView.getWindowInsetsController().show(ime())); 169 170 waitForOrFail("Waiting until animation done", () -> mActivity.mCallback.animationDone); 171 commonAnimationAssertions(mActivity, before, true /* show */, ime()); 172 173 mActivity.resetAnimationDone(); 174 175 before = mActivity.mLastWindowInsets; 176 177 getInstrumentation().runOnMainSync( 178 () -> mRootView.getWindowInsetsController().hide(ime())); 179 180 waitForOrFail("Waiting until animation done", () -> mActivity.mCallback.animationDone); 181 182 commonAnimationAssertions(mActivity, before, false /* show */, ime()); 183 } 184 } 185