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