• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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