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.Display.DEFAULT_DISPLAY; 22 import static android.view.WindowInsets.Type.captionBar; 23 import static android.view.WindowInsets.Type.navigationBars; 24 import static android.view.WindowInsets.Type.statusBars; 25 import static android.view.WindowInsets.Type.systemBars; 26 import static android.view.WindowInsetsAnimation.Callback.DISPATCH_MODE_CONTINUE_ON_SUBTREE; 27 28 import static androidx.test.InstrumentationRegistry.getInstrumentation; 29 30 import static org.junit.Assert.assertEquals; 31 import static org.junit.Assert.assertFalse; 32 import static org.junit.Assume.assumeFalse; 33 import static org.junit.Assume.assumeTrue; 34 import static org.mockito.ArgumentMatchers.any; 35 import static org.mockito.ArgumentMatchers.argThat; 36 import static org.mockito.ArgumentMatchers.eq; 37 import static org.mockito.Mockito.CALLS_REAL_METHODS; 38 import static org.mockito.Mockito.atLeastOnce; 39 import static org.mockito.Mockito.inOrder; 40 import static org.mockito.Mockito.mock; 41 import static org.mockito.Mockito.verify; 42 import static org.mockito.Mockito.verifyZeroInteractions; 43 import static org.mockito.Mockito.withSettings; 44 45 import android.platform.test.annotations.Presubmit; 46 import android.view.View; 47 import android.view.WindowInsets; 48 import android.view.WindowInsetsAnimation; 49 import android.view.WindowInsetsAnimation.Bounds; 50 import android.view.WindowInsetsAnimation.Callback; 51 52 import org.junit.Before; 53 import org.junit.Test; 54 import org.mockito.InOrder; 55 56 import java.util.List; 57 58 /** 59 * Test whether {@link WindowInsetsAnimation.Callback} are properly dispatched to views. 60 * 61 * Build/Install/Run: 62 * atest CtsWindowManagerDeviceTestCases:WindowInsetsAnimationTests 63 */ 64 @Presubmit 65 public class WindowInsetsAnimationTests extends WindowInsetsAnimationTestBase { 66 67 @Before setup()68 public void setup() throws Exception { 69 super.setUp(); 70 mActivity = startActivity(TestActivity.class, DEFAULT_DISPLAY, true, 71 WINDOWING_MODE_FULLSCREEN); 72 mRootView = mActivity.getWindow().getDecorView(); 73 assumeTrue(hasWindowInsets(mRootView, systemBars())); 74 assumeFalse(isCar() && remoteInsetsControllerControlsSystemBars()); 75 } 76 77 @Test testAnimationCallbacksHide()78 public void testAnimationCallbacksHide() { 79 WindowInsets before = mActivity.mLastWindowInsets; 80 81 getInstrumentation().runOnMainSync( 82 () -> mRootView.getWindowInsetsController().hide(systemBars())); 83 84 waitForOrFail("Waiting until animation done", () -> mActivity.mCallback.animationDone); 85 86 commonAnimationAssertions(mActivity, before, false /* show */, 87 systemBars() & ~captionBar()); 88 } 89 90 @Test testAnimationCallbacksShow()91 public void testAnimationCallbacksShow() { 92 getInstrumentation().runOnMainSync( 93 () -> mRootView.getWindowInsetsController().hide(systemBars())); 94 95 waitForOrFail("Waiting until animation done", () -> mActivity.mCallback.animationDone); 96 mActivity.mCallback.animationDone = false; 97 98 WindowInsets before = mActivity.mLastWindowInsets; 99 100 getInstrumentation().runOnMainSync( 101 () -> mRootView.getWindowInsetsController().show(systemBars())); 102 103 waitForOrFail("Waiting until animation done", () -> mActivity.mCallback.animationDone); 104 105 commonAnimationAssertions(mActivity, before, true /* show */, 106 systemBars() & ~captionBar()); 107 } 108 109 @Test testAnimationCallbacks_overlapping()110 public void testAnimationCallbacks_overlapping() { 111 assumeTrue( 112 "Test requires navBar and statusBar to create overlapping animations.", 113 hasWindowInsets(mRootView, navigationBars()) 114 && hasWindowInsets(mRootView, statusBars())); 115 116 WindowInsets before = mActivity.mLastWindowInsets; 117 MultiAnimCallback callbackInner = new MultiAnimCallback(); 118 MultiAnimCallback callback = mock(MultiAnimCallback.class, 119 withSettings() 120 .spiedInstance(callbackInner) 121 .defaultAnswer(CALLS_REAL_METHODS) 122 .verboseLogging()); 123 mActivity.mView.setWindowInsetsAnimationCallback(callback); 124 callback.startRunnable = () -> mRootView.postDelayed( 125 () -> mRootView.getWindowInsetsController().hide(statusBars()), 50); 126 127 getInstrumentation().runOnMainSync( 128 () -> mRootView.getWindowInsetsController().hide(navigationBars())); 129 130 waitForOrFail("Waiting until animation done", () -> callback.animationDone); 131 132 WindowInsets after = mActivity.mLastWindowInsets; 133 134 InOrder inOrder = inOrder(callback, mActivity.mListener); 135 136 inOrder.verify(callback).onPrepare(eq(callback.navBarAnim)); 137 138 inOrder.verify(mActivity.mListener).onApplyWindowInsets(any(), argThat( 139 argument -> NONE.equals(argument.getInsets(navigationBars())) 140 && !NONE.equals(argument.getInsets(statusBars())))); 141 142 inOrder.verify(callback).onStart(eq(callback.navBarAnim), argThat( 143 argument -> argument.getLowerBound().equals(NONE) 144 && argument.getUpperBound().equals(before.getInsets(navigationBars())))); 145 146 inOrder.verify(callback).onPrepare(eq(callback.statusBarAnim)); 147 inOrder.verify(mActivity.mListener).onApplyWindowInsets( 148 any(), eq(mActivity.mLastWindowInsets)); 149 150 inOrder.verify(callback).onStart(eq(callback.statusBarAnim), argThat( 151 argument -> argument.getLowerBound().equals(NONE) 152 && argument.getUpperBound().equals(before.getInsets(statusBars())))); 153 154 inOrder.verify(callback).onEnd(eq(callback.navBarAnim)); 155 inOrder.verify(callback).onEnd(eq(callback.statusBarAnim)); 156 157 assertAnimationSteps(callback.navAnimSteps, false /* showAnimation */); 158 assertAnimationSteps(callback.statusAnimSteps, false /* showAnimation */); 159 160 assertEquals(before.getInsets(navigationBars()), 161 callback.navAnimSteps.get(0).insets.getInsets(navigationBars())); 162 assertEquals(after.getInsets(navigationBars()), 163 callback.navAnimSteps.get(callback.navAnimSteps.size() - 1).insets 164 .getInsets(navigationBars())); 165 166 assertEquals(before.getInsets(statusBars()), 167 callback.statusAnimSteps.get(0).insets.getInsets(statusBars())); 168 assertEquals(after.getInsets(statusBars()), 169 callback.statusAnimSteps.get(callback.statusAnimSteps.size() - 1).insets 170 .getInsets(statusBars())); 171 } 172 173 @Test testAnimationCallbacks_consumedByDecor()174 public void testAnimationCallbacks_consumedByDecor() { 175 getInstrumentation().runOnMainSync(() -> { 176 mActivity.getWindow().setDecorFitsSystemWindows(true); 177 mRootView.getWindowInsetsController().hide(systemBars()); 178 }); 179 180 getWmState().waitFor(state -> !state.isWindowVisible("StatusBar"), 181 "Waiting for status bar to be hidden"); 182 assertFalse(getWmState().isWindowVisible("StatusBar")); 183 184 verifyZeroInteractions(mActivity.mCallback); 185 } 186 187 @Test testAnimationCallbacks_childDoesntGetCallback()188 public void testAnimationCallbacks_childDoesntGetCallback() { 189 WindowInsetsAnimation.Callback childCallback = mock(WindowInsetsAnimation.Callback.class); 190 191 getInstrumentation().runOnMainSync(() -> { 192 mActivity.mChild.setWindowInsetsAnimationCallback(childCallback); 193 mRootView.getWindowInsetsController().hide(systemBars()); 194 }); 195 196 waitForOrFail("Waiting until animation done", () -> mActivity.mCallback.animationDone); 197 198 verifyZeroInteractions(childCallback); 199 } 200 201 @Test testAnimationCallbacks_childInsetting()202 public void testAnimationCallbacks_childInsetting() { 203 // test requires navbar. 204 assumeTrue(hasWindowInsets(mRootView, navigationBars())); 205 206 WindowInsets before = mActivity.mLastWindowInsets; 207 boolean[] done = new boolean[1]; 208 WindowInsetsAnimation.Callback childCallback = mock(WindowInsetsAnimation.Callback.class); 209 WindowInsetsAnimation.Callback callback = new Callback(DISPATCH_MODE_CONTINUE_ON_SUBTREE) { 210 211 @Override 212 public Bounds onStart(WindowInsetsAnimation animation, Bounds bounds) { 213 return bounds.inset(before.getInsets(navigationBars())); 214 } 215 216 @Override 217 public WindowInsets onProgress(WindowInsets insets, 218 List<WindowInsetsAnimation> runningAnimations) { 219 return insets.inset(insets.getInsets(navigationBars())); 220 } 221 222 @Override 223 public void onEnd(WindowInsetsAnimation animation) { 224 done[0] = true; 225 } 226 }; 227 228 getInstrumentation().runOnMainSync(() -> { 229 mActivity.mView.setWindowInsetsAnimationCallback(callback); 230 mActivity.mChild.setWindowInsetsAnimationCallback(childCallback); 231 mRootView.getWindowInsetsController().hide(systemBars()); 232 }); 233 234 waitForOrFail("Waiting until animation done", () -> done[0]); 235 236 if (hasWindowInsets(mRootView, statusBars())) { 237 verify(childCallback).onStart(any(), argThat( 238 bounds -> bounds.getUpperBound().equals(before.getInsets(statusBars())))); 239 } 240 if (hasWindowInsets(mRootView, navigationBars())) { 241 verify(childCallback, atLeastOnce()).onProgress(argThat( 242 insets -> NONE.equals(insets.getInsets(navigationBars()))), any()); 243 } 244 } 245 246 @Test testAnimationCallbacks_withLegacyFlags()247 public void testAnimationCallbacks_withLegacyFlags() { 248 getInstrumentation().runOnMainSync(() -> { 249 mActivity.getWindow().setDecorFitsSystemWindows(true); 250 mRootView.setSystemUiVisibility( 251 View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN 252 | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION 253 | View.SYSTEM_UI_FLAG_LAYOUT_STABLE); 254 mRootView.post(() -> { 255 mRootView.getWindowInsetsController().hide(systemBars()); 256 }); 257 }); 258 259 waitForOrFail("Waiting until animation done", () -> mActivity.mCallback.animationDone); 260 261 mWmState.computeState(); 262 assertFalse(mWmState.isWindowVisible("StatusBar")); 263 verify(mActivity.mCallback).onPrepare(any()); 264 verify(mActivity.mCallback).onStart(any(), any()); 265 verify(mActivity.mCallback, atLeastOnce()).onProgress(any(), any()); 266 verify(mActivity.mCallback).onEnd(any()); 267 } 268 } 269