1 /* 2 * Copyright (C) 2022 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.view.Display.DEFAULT_DISPLAY; 20 21 import static junit.framework.Assert.assertEquals; 22 import static junit.framework.Assert.fail; 23 24 import static org.junit.Assert.assertTrue; 25 26 import android.app.Instrumentation; 27 import android.view.KeyEvent; 28 import android.view.MotionEvent; 29 import android.window.BackEvent; 30 import android.window.OnBackAnimationCallback; 31 32 import androidx.test.platform.app.InstrumentationRegistry; 33 import androidx.test.uiautomator.UiDevice; 34 35 import com.android.compatibility.common.util.CddTest; 36 37 import org.junit.Before; 38 import org.junit.Test; 39 40 import java.util.ArrayList; 41 import java.util.List; 42 import java.util.concurrent.CountDownLatch; 43 import java.util.concurrent.TimeUnit; 44 45 /** 46 * Integration test for back navigation 47 */ 48 public class OnBackInvokedCallbackGestureTest extends ActivityManagerTestBase { 49 private static final int PROGRESS_SWIPE_STEPS = 10; 50 51 private Instrumentation mInstrumentation; 52 private UiDevice mUiDevice; 53 private BackInvocationTracker mTracker = new BackInvocationTracker(); 54 private BackNavigationActivity mActivity; 55 56 private final OnBackAnimationCallback mAnimationCallback = new OnBackAnimationCallback() { 57 @Override 58 public void onBackStarted(BackEvent e) { 59 mTracker.trackBackStarted(); 60 } 61 62 @Override 63 public void onBackInvoked() { 64 mTracker.trackBackInvoked(); 65 } 66 67 @Override 68 public void onBackCancelled() { 69 mTracker.trackBackCancelled(); 70 } 71 72 @Override 73 public void onBackProgressed(BackEvent e) { 74 mTracker.trackBackProgressed(e); 75 } 76 }; 77 78 @Before setup()79 public void setup() throws Exception { 80 super.setUp(); 81 mInstrumentation = InstrumentationRegistry.getInstrumentation(); 82 mUiDevice = UiDevice.getInstance(mInstrumentation); 83 enableAndAssumeGestureNavigationMode(); 84 85 mInstrumentation.getUiAutomation().adoptShellPermissionIdentity(); 86 mTracker.reset(); 87 88 final TestActivitySession<BackNavigationActivity> activitySession = 89 createManagedTestActivitySession(); 90 activitySession.launchTestActivityOnDisplaySync( 91 BackNavigationActivity.class, DEFAULT_DISPLAY); 92 mWmState.waitForAppTransitionIdleOnDisplay(DEFAULT_DISPLAY); 93 mInstrumentation.getUiAutomation().syncInputTransactions(); 94 95 mActivity = activitySession.getActivity(); 96 registerBackCallback(mActivity); 97 } 98 99 @Test 100 @CddTest(requirements = { "7.2.3/H-0-5,H-0-6,H-0-8" }) invokesCallback_invoked()101 public void invokesCallback_invoked() throws InterruptedException { 102 int midHeight = mUiDevice.getDisplayHeight() / 2; 103 int midWidth = mUiDevice.getDisplayWidth() / 2; 104 105 final TouchHelper.SwipeSession touchSession = new TouchHelper.SwipeSession( 106 DEFAULT_DISPLAY, true, false); 107 long startDownTime = touchSession.beginSwipe(0, midHeight); 108 // Inject another move event to trigger back start. 109 TouchHelper.injectMotion(startDownTime, startDownTime, MotionEvent.ACTION_MOVE, 0, 110 midHeight, DEFAULT_DISPLAY, true, false); 111 assertInvoked(mTracker.mStartLatch); 112 assertNotInvoked(mTracker.mProgressLatch); 113 assertNotInvoked(mTracker.mInvokeLatch); 114 assertNotInvoked(mTracker.mCancelLatch); 115 116 touchSession.continueSwipe(midWidth, midHeight, PROGRESS_SWIPE_STEPS); 117 assertInvoked(mTracker.mProgressLatch); 118 assertNotInvoked(mTracker.mInvokeLatch); 119 assertNotInvoked(mTracker.mCancelLatch); 120 List<BackEvent> events = mTracker.mProgressEvents; 121 assertTrue(events.size() > 0); 122 for (int i = 0; i < events.size() - 1; i++) { 123 // Check that progress events report increasing progress values. 124 // TODO(b/258817762): Verify more once the progress clamping behavior is implemented. 125 BackEvent event = events.get(i); 126 assertTrue(event.getProgress() <= events.get(i + 1).getProgress()); 127 assertTrue(event.getTouchX() <= events.get(i + 1).getTouchX()); 128 assertEquals(midHeight, midHeight, event.getTouchY()); 129 assertEquals(BackEvent.EDGE_LEFT, event.getSwipeEdge()); 130 } 131 132 touchSession.finishSwipe(); 133 assertInvoked(mTracker.mInvokeLatch); 134 assertNotInvoked(mTracker.mCancelLatch); 135 } 136 137 @Test 138 @CddTest(requirements = { "7.2.3/H-0-7" }) invokesCallback_cancelled()139 public void invokesCallback_cancelled() throws InterruptedException { 140 int midHeight = mUiDevice.getDisplayHeight() / 2; 141 int midWidth = mUiDevice.getDisplayWidth() / 2; 142 143 final TouchHelper.SwipeSession touchSession = new TouchHelper.SwipeSession( 144 DEFAULT_DISPLAY, true, false); 145 long startDownTime = touchSession.beginSwipe(0, midHeight); 146 // Inject another move event to trigger back start. 147 TouchHelper.injectMotion(startDownTime, startDownTime, MotionEvent.ACTION_MOVE, 0, 148 midHeight, DEFAULT_DISPLAY, true, false); 149 touchSession.continueSwipe(midWidth, midHeight, PROGRESS_SWIPE_STEPS); 150 assertInvoked(mTracker.mProgressLatch); 151 152 mTracker.reset(); 153 mTracker.mIsCancelRequested = true; 154 touchSession.cancelSwipe(); 155 156 assertInvoked(mTracker.mCancelLatch); 157 assertNotInvoked(mTracker.mInvokeLatch); 158 assertInvoked(mTracker.mCancelProgressLatch); 159 List<BackEvent> events = mTracker.mProgressEvents; 160 assertTrue(events.size() > 0); 161 assertTrue(events.get(events.size() - 1).getProgress() == 0); 162 } 163 164 @Test 165 @CddTest(requirements = { "7.2.3/H-0-5,H-0-6" }) invokesCallbackInButtonsNav_invoked()166 public void invokesCallbackInButtonsNav_invoked() throws InterruptedException { 167 long downTime = TouchHelper.injectKeyActionDown(KeyEvent.KEYCODE_BACK, 168 /* longpress = */ false, 169 /* sync = */ true); 170 171 assertInvoked(mTracker.mStartLatch); 172 assertNotInvoked(mTracker.mProgressLatch); 173 assertNotInvoked(mTracker.mInvokeLatch); 174 assertNotInvoked(mTracker.mCancelLatch); 175 176 TouchHelper.injectKeyActionUp(KeyEvent.KEYCODE_BACK, 177 /* downTime = */ downTime, 178 /* cancelled = */ false, 179 /* sync = */ true); 180 181 assertInvoked(mTracker.mInvokeLatch); 182 assertNotInvoked(mTracker.mProgressLatch); 183 assertNotInvoked(mTracker.mCancelLatch); 184 } 185 186 @Test 187 @CddTest(requirements = { "7.2.3/H-0-7" }) invokesCallbackInButtonsNav_cancelled()188 public void invokesCallbackInButtonsNav_cancelled() throws InterruptedException { 189 long downTime = TouchHelper.injectKeyActionDown(KeyEvent.KEYCODE_BACK, 190 /* longpress = */ false, 191 /* sync = */ true); 192 193 TouchHelper.injectKeyActionUp(KeyEvent.KEYCODE_BACK, 194 /* downTime = */ downTime, 195 /* cancelled = */ true, 196 /* sync = */ true); 197 198 assertInvoked(mTracker.mCancelLatch); 199 assertNotInvoked(mTracker.mProgressLatch); 200 assertNotInvoked(mTracker.mInvokeLatch); 201 } 202 203 @Test constructsEvent()204 public void constructsEvent() { 205 final float x = 200; 206 final float y = 300; 207 final float progress = 0.5f; 208 final int swipeEdge = BackEvent.EDGE_RIGHT; 209 BackEvent event = new BackEvent(x, y, progress, swipeEdge); 210 assertEquals(x, event.getTouchX()); 211 assertEquals(y, event.getTouchY()); 212 assertEquals(progress, event.getProgress()); 213 assertEquals(swipeEdge, event.getSwipeEdge()); 214 } 215 assertInvoked(CountDownLatch latch)216 private void assertInvoked(CountDownLatch latch) throws InterruptedException { 217 assertTrue(latch.await(500, TimeUnit.MILLISECONDS)); 218 } 219 assertNotInvoked(CountDownLatch latch)220 private void assertNotInvoked(CountDownLatch latch) { 221 assertTrue(latch.getCount() >= 1); 222 } 223 registerBackCallback(BackNavigationActivity activity)224 private void registerBackCallback(BackNavigationActivity activity) { 225 CountDownLatch backRegisteredLatch = new CountDownLatch(1); 226 activity.getOnBackInvokedDispatcher().registerOnBackInvokedCallback( 227 0, mAnimationCallback); 228 backRegisteredLatch.countDown(); 229 try { 230 if (!backRegisteredLatch.await(100, TimeUnit.MILLISECONDS)) { 231 fail("Back callback was not registered on the Activity thread. This might be " 232 + "an error with the test itself."); 233 } 234 } catch (InterruptedException e) { 235 fail(e.getMessage()); 236 } 237 } 238 239 /** Helper class to track {@link android.window.OnBackAnimationCallback} invocations. */ 240 static class BackInvocationTracker { 241 private CountDownLatch mStartLatch; 242 private CountDownLatch mInvokeLatch; 243 private CountDownLatch mProgressLatch; 244 private CountDownLatch mCancelLatch; 245 private CountDownLatch mCancelProgressLatch; 246 private boolean mIsCancelRequested = false; 247 private final ArrayList<BackEvent> mProgressEvents = new ArrayList<>(); 248 reset()249 private void reset() { 250 mStartLatch = new CountDownLatch(1); 251 mInvokeLatch = new CountDownLatch(1); 252 mProgressLatch = new CountDownLatch(PROGRESS_SWIPE_STEPS); 253 mCancelLatch = new CountDownLatch(1); 254 mCancelProgressLatch = new CountDownLatch(1); 255 mIsCancelRequested = false; 256 mProgressEvents.clear(); 257 } 258 trackBackStarted()259 private void trackBackStarted() { 260 mStartLatch.countDown(); 261 } 262 trackBackProgressed(BackEvent e)263 private void trackBackProgressed(BackEvent e) { 264 mProgressEvents.add(e); 265 if (mIsCancelRequested && 0 == e.getProgress()) { 266 // Ensure the progress could reach to 0 for cancel animation. 267 mCancelProgressLatch.countDown(); 268 } else { 269 mProgressLatch.countDown(); 270 } 271 } 272 trackBackCancelled()273 private void trackBackCancelled() { 274 mCancelLatch.countDown(); 275 } 276 trackBackInvoked()277 private void trackBackInvoked() { 278 mInvokeLatch.countDown(); 279 } 280 } 281 } 282