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