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