1 /*
2  * Copyright (C) 2016 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.accessibilityservice.cts;
18 
19 import static android.accessibilityservice.cts.utils.AccessibilityEventFilterUtils.filterWaitForAll;
20 import static android.accessibilityservice.cts.utils.AccessibilityEventFilterUtils.filterWindowsChangeTypesAndWindowTitle;
21 import static android.accessibilityservice.cts.utils.AccessibilityEventFilterUtils.filterWindowsChangedWithChangeTypes;
22 import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.findWindowByTitle;
23 import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.findWindowByTitleAndDisplay;
24 import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.getActivityTitle;
25 import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.launchActivityAndWaitForItToBeOnscreen;
26 import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.launchActivityOnSpecifiedDisplayAndWaitForItToBeOnscreen;
27 import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.supportsMultiDisplay;
28 import static android.accessibilityservice.cts.utils.CtsTestUtils.isAutomotive;
29 import static android.accessibilityservice.cts.utils.DisplayUtils.VirtualDisplaySession;
30 import static android.accessibilityservice.cts.utils.WindowCreationUtils.TOP_WINDOW_TITLE;
31 import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE;
32 import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOWS_CHANGED;
33 import static android.view.accessibility.AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED;
34 import static android.view.accessibility.AccessibilityEvent.WINDOWS_CHANGE_ACTIVE;
35 import static android.view.accessibility.AccessibilityEvent.WINDOWS_CHANGE_ADDED;
36 import static android.view.accessibility.AccessibilityEvent.WINDOWS_CHANGE_BOUNDS;
37 import static android.view.accessibility.AccessibilityEvent.WINDOWS_CHANGE_CHILDREN;
38 import static android.view.accessibility.AccessibilityEvent.WINDOWS_CHANGE_FOCUSED;
39 import static android.view.accessibility.AccessibilityEvent.WINDOWS_CHANGE_TITLE;
40 
41 import static com.google.common.truth.Truth.assertThat;
42 import static com.google.common.truth.Truth.assertWithMessage;
43 
44 import static org.junit.Assume.assumeFalse;
45 import static org.junit.Assume.assumeTrue;
46 
47 import android.Manifest;
48 import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
49 import android.accessibilityservice.AccessibilityServiceInfo;
50 import android.accessibilityservice.cts.activities.AccessibilityWindowReportingActivity;
51 import android.accessibilityservice.cts.activities.NonDefaultDisplayActivity;
52 import android.accessibilityservice.cts.activities.NotTouchableWindowTestActivity;
53 import android.accessibilityservice.cts.utils.ActivityLaunchUtils;
54 import android.accessibilityservice.cts.utils.DisplayUtils;
55 import android.accessibilityservice.cts.utils.WindowCreationUtils;
56 import android.app.Activity;
57 import android.app.Instrumentation;
58 import android.app.UiAutomation;
59 import android.content.ComponentName;
60 import android.content.Context;
61 import android.content.Intent;
62 import android.platform.test.annotations.AppModeFull;
63 import android.platform.test.annotations.Presubmit;
64 import android.provider.Settings;
65 import android.view.Gravity;
66 import android.view.View;
67 import android.view.WindowManager;
68 import android.view.accessibility.AccessibilityNodeInfo;
69 import android.view.accessibility.AccessibilityWindowInfo;
70 import android.widget.ArrayAdapter;
71 import android.widget.AutoCompleteTextView;
72 import android.widget.Button;
73 
74 import androidx.test.InstrumentationRegistry;
75 import androidx.test.rule.ActivityTestRule;
76 import androidx.test.runner.AndroidJUnit4;
77 
78 import com.android.compatibility.common.util.CddTest;
79 import com.android.compatibility.common.util.SystemUtil;
80 
81 import org.junit.AfterClass;
82 import org.junit.Before;
83 import org.junit.BeforeClass;
84 import org.junit.Ignore;
85 import org.junit.Rule;
86 import org.junit.Test;
87 import org.junit.rules.RuleChain;
88 import org.junit.runner.RunWith;
89 
90 import java.io.IOException;
91 import java.util.List;
92 import java.util.concurrent.TimeoutException;
93 
94 /**
95  * Tests that window changes produce the correct events and that AccessibilityWindowInfos are
96  * properly populated
97  */
98 @RunWith(AndroidJUnit4.class)
99 @CddTest(requirements = {"3.10/C-1-1,C-1-2"})
100 public class AccessibilityWindowReportingTest {
101     private static final int TIMEOUT_ASYNC_PROCESSING = 5000;
102     private static Instrumentation sInstrumentation;
103     private static UiAutomation sUiAutomation;
104     private Activity mActivity;
105     private CharSequence mActivityTitle;
106 
107     private final ActivityTestRule<AccessibilityWindowReportingActivity> mActivityRule =
108             new ActivityTestRule<>(AccessibilityWindowReportingActivity.class, false, false);
109 
110     private final AccessibilityDumpOnFailureRule mDumpOnFailureRule =
111             new AccessibilityDumpOnFailureRule();
112 
113     @Rule
114     public final RuleChain mRuleChain = RuleChain
115             .outerRule(mActivityRule)
116             .around(mDumpOnFailureRule);
117 
118     @BeforeClass
oneTimeSetup()119     public static void oneTimeSetup() throws Exception {
120         sInstrumentation = InstrumentationRegistry.getInstrumentation();
121         sUiAutomation = sInstrumentation.getUiAutomation();
122         AccessibilityServiceInfo info = sUiAutomation.getServiceInfo();
123         info.flags |= AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS;
124         sUiAutomation.setServiceInfo(info);
125     }
126 
127     @AfterClass
finalTearDown()128     public static void finalTearDown() {
129         sUiAutomation.destroy();
130     }
131 
132     @Before
setUp()133     public void setUp() throws Exception {
134         mActivity = launchActivityAndWaitForItToBeOnscreen(
135                 sInstrumentation, sUiAutomation, mActivityRule);
136         mActivityTitle = getActivityTitle(sInstrumentation, mActivity);
137     }
138 
perDisplayFocusEnabled()139     private static boolean perDisplayFocusEnabled() {
140         return sInstrumentation.getTargetContext().getResources()
141                 .getBoolean(android.R.bool.config_perDisplayFocusEnabled);
142     }
143 
144     @Test
145     @Presubmit
testUpdatedWindowTitle_generatesEventAndIsReturnedByGetTitle()146     public void testUpdatedWindowTitle_generatesEventAndIsReturnedByGetTitle() {
147         final String updatedTitle = "Updated Title";
148         try {
149             sUiAutomation.executeAndWaitForEvent(
150                     () -> sInstrumentation.runOnMainSync(() -> mActivity.setTitle(updatedTitle)),
151                     filterWindowsChangedWithChangeTypes(WINDOWS_CHANGE_TITLE),
152                     TIMEOUT_ASYNC_PROCESSING);
153         } catch (TimeoutException exception) {
154             throw new RuntimeException(
155                     "Failed to get windows changed event for title update", exception);
156         }
157         final AccessibilityWindowInfo window = findWindowByTitle(sUiAutomation, updatedTitle);
158         assertWithMessage("Updated window title not reported to accessibility")
159                 .that(window).isNotNull();
160         window.recycle();
161     }
162 
163     @Test
164     @Presubmit
testWindowAddedMovedAndRemoved_generatesEventsForAllThree()165     public void testWindowAddedMovedAndRemoved_generatesEventsForAllThree() throws Exception {
166         final WindowManager.LayoutParams paramsForTop =
167                 WindowCreationUtils.layoutParamsForWindowOnTop(
168                         sInstrumentation, mActivity, TOP_WINDOW_TITLE);
169         final WindowManager.LayoutParams paramsForBottom = layoutParamsForWindowOnBottom();
170         final Button button = new Button(mActivity);
171         button.setText(R.string.button1);
172 
173         WindowCreationUtils.addWindowAndWaitForEvent(sUiAutomation, sInstrumentation, mActivity,
174                 button, paramsForTop, filterWindowsChangedWithChangeTypes(WINDOWS_CHANGE_ADDED));
175 
176         // Move window from top to bottom
177         sUiAutomation.executeAndWaitForEvent(() -> sInstrumentation.runOnMainSync(
178                 () -> mActivity.getWindowManager().updateViewLayout(button, paramsForBottom)),
179                 filterWindowsChangedWithChangeTypes(WINDOWS_CHANGE_BOUNDS),
180                 TIMEOUT_ASYNC_PROCESSING);
181         // Remove the view
182         WindowCreationUtils.removeWindow(sUiAutomation, sInstrumentation, mActivity, button);
183     }
184 
185     @Test
186     @Ignore("b/325640120")
putWindowInPictureInPicture_generatesEventAndReportsProperty()187     public void putWindowInPictureInPicture_generatesEventAndReportsProperty() throws Exception {
188         if (!sInstrumentation.getContext().getPackageManager()
189                 .hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)) {
190             return;
191         }
192         sUiAutomation.executeAndWaitForEvent(
193                 () -> sInstrumentation.runOnMainSync(() -> mActivity.enterPictureInPictureMode()),
194                 (event) -> {
195                     if (event.getEventType() != TYPE_WINDOWS_CHANGED) return false;
196                     // Look for a picture-in-picture window
197                     final List<AccessibilityWindowInfo> windows = sUiAutomation.getWindows();
198                     final int windowCount = windows.size();
199                     for (int i = 0; i < windowCount; i++) {
200                         if (windows.get(i).isInPictureInPictureMode()) {
201                             return true;
202                         }
203                     }
204                     return false;
205                 }, TIMEOUT_ASYNC_PROCESSING);
206 
207         // There should be exactly one picture-in-picture window now
208         int numPictureInPictureWindows = 0;
209         final List<AccessibilityWindowInfo> windows = sUiAutomation.getWindows();
210         final int windowCount = windows.size();
211         for (int i = 0; i < windowCount; i++) {
212             final AccessibilityWindowInfo window = windows.get(i);
213             if (window.isInPictureInPictureMode()) {
214                 numPictureInPictureWindows++;
215             }
216         }
217         assertThat(numPictureInPictureWindows).isAtLeast(1);
218     }
219 
220     @Test
221     @Presubmit
moveFocusToAnotherWindow_generatesEventsAndMovesActiveAndFocus()222     public void moveFocusToAnotherWindow_generatesEventsAndMovesActiveAndFocus() throws Exception {
223         final View topWindowView = showTopWindowAndWaitForItToShowUp();
224         final AccessibilityWindowInfo topWindow =
225                 findWindowByTitle(sUiAutomation, TOP_WINDOW_TITLE);
226 
227         AccessibilityWindowInfo activityWindow = findWindowByTitle(sUiAutomation, mActivityTitle);
228         final AccessibilityNodeInfo buttonNode =
229                 topWindow.getRoot().findAccessibilityNodeInfosByText(
230                         sInstrumentation.getContext().getString(R.string.button1)).get(0);
231 
232         // Make sure activityWindow is not focused
233         if (activityWindow.isFocused()) {
234             sUiAutomation.executeAndWaitForEvent(
235                     () -> buttonNode.performAction(AccessibilityNodeInfo.ACTION_FOCUS),
236                     filterWindowsChangedWithChangeTypes(WINDOWS_CHANGE_FOCUSED),
237                     TIMEOUT_ASYNC_PROCESSING);
238         }
239 
240         // Windows may have changed - refresh
241         activityWindow = findWindowByTitle(sUiAutomation, mActivityTitle);
242         assertThat(activityWindow.isActive()).isFalse();
243         assertThat(activityWindow.isFocused()).isFalse();
244 
245         // Find a focusable view in the main activity menu
246         final AccessibilityNodeInfo autoCompleteTextInfo = activityWindow.getRoot()
247                 .findAccessibilityNodeInfosByViewId(
248                         "android.accessibilityservice.cts:id/autoCompleteLayout")
249                 .get(0);
250         assertThat(autoCompleteTextInfo).isNotNull();
251 
252         // Remove the top window and focus on the main activity
253         sUiAutomation.executeAndWaitForEvent(
254                 () -> {
255                     sInstrumentation.runOnMainSync(
256                             () -> mActivity.getWindowManager().removeView(topWindowView));
257                     buttonNode.performAction(AccessibilityNodeInfo.ACTION_FOCUS);
258                 },
259                 filterWindowsChangedWithChangeTypes(WINDOWS_CHANGE_FOCUSED | WINDOWS_CHANGE_ACTIVE),
260                 TIMEOUT_ASYNC_PROCESSING);
261     }
262 
263     @Test
264     @Presubmit
moveFocusToAnotherDisplay_movesActiveAndFocusWindow()265     public void moveFocusToAnotherDisplay_movesActiveAndFocusWindow() throws Exception {
266         assumeTrue(supportsMultiDisplay(sInstrumentation.getContext()));
267 
268         // Makes sure activityWindow on default display is focused
269         AccessibilityWindowInfo activityWindow = findWindowByTitle(sUiAutomation, mActivityTitle);
270         assertThat(activityWindow.isActive()).isTrue();
271         assertThat(activityWindow.isFocused()).isTrue();
272 
273         // Creates a virtual display.
274         try (VirtualDisplaySession displaySession = new VirtualDisplaySession()) {
275             final int virtualDisplayId =
276                     displaySession.createDisplayWithDefaultDisplayMetricsAndWait(
277                             sInstrumentation.getContext(), false).getDisplayId();
278             // Launches an activity on virtual display.
279             final Activity activityOnVirtualDisplay =
280                     launchActivityOnSpecifiedDisplayAndWaitForItToBeOnscreen(sInstrumentation,
281                             sUiAutomation,
282                             NonDefaultDisplayActivity.class,
283                             virtualDisplayId);
284 
285             final CharSequence activityTitle = getActivityTitle(sInstrumentation,
286                     activityOnVirtualDisplay);
287 
288             // Window manager changed the behavior of focused window at a virtual display. A window
289             // at virtual display needs to be touched then it becomes to be focused one. Adding this
290             // touch event on the activity window of the virtual display to pass this test case.
291             sUiAutomation.executeAndWaitForEvent(
292                     () -> DisplayUtils.touchDisplay(sUiAutomation, virtualDisplayId, activityTitle),
293                     filterWindowsChangedWithChangeTypes(WINDOWS_CHANGE_FOCUSED |
294                             WINDOWS_CHANGE_ACTIVE),
295                     TIMEOUT_ASYNC_PROCESSING);
296 
297             // Make sure activityWindow on virtual display is focused.
298             AccessibilityWindowInfo activityWindowOnVirtualDisplay =
299                     findWindowByTitleAndDisplay(sUiAutomation, activityTitle, virtualDisplayId);
300             // Windows may have changed - refresh.
301             activityWindow = findWindowByTitle(sUiAutomation, mActivityTitle);
302             try {
303                 if (!perDisplayFocusEnabled()) {
304                     assertThat(activityWindow.isActive()).isFalse();
305                     assertThat(activityWindow.isFocused()).isFalse();
306                 } else {
307                     assertThat(activityWindow.isActive()).isTrue();
308                     assertThat(activityWindow.isFocused()).isTrue();
309                 }
310                 assertThat(activityWindowOnVirtualDisplay.isActive()).isTrue();
311                 assertThat(activityWindowOnVirtualDisplay.isFocused()).isTrue();
312             } finally {
313                 sUiAutomation.executeAndWaitForEvent(
314                         () -> sInstrumentation.runOnMainSync(activityOnVirtualDisplay::finish),
315                         filterWaitForAll(
316                                 filterWindowsChangedWithChangeTypes(
317                                         WINDOWS_CHANGE_FOCUSED | WINDOWS_CHANGE_ACTIVE),
318                                 event -> {
319                                     // The focused window should be returned to activity at
320                                     // default display after
321                                     // the activity at virtual display is destroyed.
322                                     AccessibilityWindowInfo window = findWindowByTitle(
323                                             sUiAutomation, mActivityTitle);
324                                     return window.isActive() && window.isFocused();
325                                 }),
326                         TIMEOUT_ASYNC_PROCESSING);
327             }
328         }
329     }
330 
331     @Test
332     @Presubmit
testChangeAccessibilityFocusWindow_getEvent()333     public void testChangeAccessibilityFocusWindow_getEvent() throws Exception {
334         final AccessibilityServiceInfo info = sUiAutomation.getServiceInfo();
335         info.flags |= AccessibilityServiceInfo.FLAG_REQUEST_TOUCH_EXPLORATION_MODE;
336         sUiAutomation.setServiceInfo(info);
337         View topWindowView = null;
338         try {
339             topWindowView = showTopWindowAndWaitForItToShowUp();
340 
341             final AccessibilityWindowInfo activityWindow =
342                     findWindowByTitle(sUiAutomation, mActivityTitle);
343             final AccessibilityWindowInfo topWindow =
344                     findWindowByTitle(sUiAutomation, TOP_WINDOW_TITLE);
345             final AccessibilityNodeInfo win2Node =
346                     topWindow.getRoot().findAccessibilityNodeInfosByText(
347                             sInstrumentation.getContext().getString(R.string.button1)).get(0);
348             final AccessibilityNodeInfo win1Node = activityWindow.getRoot()
349                     .findAccessibilityNodeInfosByViewId(
350                             "android.accessibilityservice.cts:id/autoCompleteLayout")
351                     .get(0);
352 
353             sUiAutomation.executeAndWaitForEvent(
354                     () -> {
355                         win2Node.performAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
356                         win1Node.performAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
357                     },
358                     filterWindowsChangedWithChangeTypes(WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED),
359                     TIMEOUT_ASYNC_PROCESSING);
360         } finally {
361             info.flags &= ~AccessibilityServiceInfo.FLAG_REQUEST_TOUCH_EXPLORATION_MODE;
362             sUiAutomation.setServiceInfo(info);
363             // Remove the view
364             if (topWindowView != null) {
365                 WindowCreationUtils.removeWindow(sUiAutomation, sInstrumentation, mActivity,
366                         topWindowView);
367             }
368         }
369     }
370 
371     @Test
testGetAnchorForDropDownForAutoCompleteTextView_returnsTextViewNode()372     public void testGetAnchorForDropDownForAutoCompleteTextView_returnsTextViewNode() {
373         final AutoCompleteTextView autoCompleteTextView =
374                 mActivity.findViewById(R.id.autoCompleteLayout);
375         final AccessibilityNodeInfo autoCompleteTextInfo = sUiAutomation.getRootInActiveWindow()
376                 .findAccessibilityNodeInfosByViewId(
377                         "android.accessibilityservice.cts:id/autoCompleteLayout")
378                 .get(0);
379 
380         // For the drop-down
381         final String[] countries = new String[]{"Belgium", "France", "Italy", "Germany", "Spain"};
382 
383         try {
384             sUiAutomation.executeAndWaitForEvent(() -> sInstrumentation.runOnMainSync(
385                     () -> {
386                         final ArrayAdapter<String> adapter = new ArrayAdapter<>(
387                                 mActivity, android.R.layout.simple_dropdown_item_1line, countries);
388                         autoCompleteTextView.setAdapter(adapter);
389                         autoCompleteTextView.showDropDown();
390                     }),
391                     filterWindowsChangeTypesAndWindowTitle(sUiAutomation, WINDOWS_CHANGE_CHILDREN,
392                             mActivityTitle.toString()), TIMEOUT_ASYNC_PROCESSING);
393         } catch (TimeoutException exception) {
394             throw new RuntimeException(
395                     "Failed to get window changed event when showing dropdown", exception);
396         }
397 
398         // Find the pop-up window
399         boolean foundPopup = false;
400         final List<AccessibilityWindowInfo> windows = sUiAutomation.getWindows();
401         for (int i = 0; i < windows.size(); i++) {
402             final AccessibilityWindowInfo window = windows.get(i);
403             if (window.getAnchor() == null) {
404                 continue;
405             }
406             assertThat(window.getAnchor()).isEqualTo(autoCompleteTextInfo);
407             assertWithMessage("Found multiple pop-ups anchored to one text view")
408                     .that(foundPopup).isFalse();
409             foundPopup = true;
410         }
411         assertWithMessage("Failed to find accessibility window for auto-complete pop-up")
412                 .that(foundPopup).isTrue();
413     }
414 
415     @AppModeFull
416     @Test
showNotTouchableWindow_activityWindowIsNotVisible()417     public void showNotTouchableWindow_activityWindowIsNotVisible() throws TimeoutException {
418         // TODO: b/336552993 - Investigate and re-enable this test on Android Auto.
419         assumeFalse(isAutomotive(sInstrumentation.getTargetContext()));
420         try {
421             launchNotTouchableWindowTestActivityFromShell();
422 
423             Intent intent = new Intent();
424             intent.setAction(NotTouchableWindowTestActivity.ADD_WINDOW);
425             intent.setPackage(sInstrumentation.getContext().getPackageName());
426 
427             // Waits for two events, whose order is nondeterministic:
428             //  (1) the test activity is covered by the untrusted non-touchable window.
429             //  (2) the untrusted non-touchable window is added.
430             sendIntentAndWaitForEvent(intent,
431                     filterWaitForAll(
432                             event -> {
433                                 final AccessibilityWindowInfo coveredWindow =
434                                         findWindowByTitle(sUiAutomation,
435                                                 NotTouchableWindowTestActivity.TITLE);
436                                 return coveredWindow == null;
437                             },
438                             filterWindowsChangeTypesAndWindowTitle(sUiAutomation,
439                                     WINDOWS_CHANGE_ADDED,
440                                     NotTouchableWindowTestActivity.NON_TOUCHABLE_WINDOW_TITLE)
441                     ));
442         } finally {
443             closeNotTouchableWindowTestActivity();
444         }
445     }
446 
447     @AppModeFull
448     @Test
showNotTouchableTrustedWindow_activityWindowIsVisible()449     public void showNotTouchableTrustedWindow_activityWindowIsVisible() {
450         // TODO: b/336552993 - Investigate and re-enable this test on Android Auto.
451         assumeFalse(isAutomotive(sInstrumentation.getTargetContext()));
452         try {
453             launchNotTouchableWindowTestActivityFromShell();
454 
455             Intent intent = new Intent();
456             intent.setAction(NotTouchableWindowTestActivity.ADD_TRUSTED_WINDOW);
457             intent.setPackage(sInstrumentation.getContext().getPackageName());
458 
459             SystemUtil.runWithShellPermissionIdentity(sUiAutomation,
460                     () -> sendIntentAndWaitForEvent(intent,
461                             filterWindowsChangeTypesAndWindowTitle(sUiAutomation,
462                                     WINDOWS_CHANGE_ADDED,
463                                     NotTouchableWindowTestActivity.NON_TOUCHABLE_WINDOW_TITLE)),
464                     Manifest.permission.INTERNAL_SYSTEM_WINDOW);
465 
466             assertThat(findWindowByTitle(sUiAutomation, NotTouchableWindowTestActivity.TITLE))
467                     .isNotNull();
468         } finally {
469             closeNotTouchableWindowTestActivity();
470         }
471     }
472 
473     // We want to test WindowState#isTrustedOverlay which refers to flag stored in the
474     // Session class and is not updated since the Session is created.
475     // Use shell command instead of ActivityLaunchUtils to get INTERNAL_SYSTEM_WINDOW
476     // permission when the Session is created.
launchNotTouchableWindowTestActivityFromShell()477     private void launchNotTouchableWindowTestActivityFromShell() {
478         SystemUtil.runWithShellPermissionIdentity(sUiAutomation,
479                 () -> sUiAutomation.executeAndWaitForEvent(
480                         () -> {
481                             final ComponentName componentName = new ComponentName(
482                                     sInstrumentation.getContext(),
483                                     NotTouchableWindowTestActivity.class);
484 
485                             String command = "am start -n " + componentName.flattenToString();
486                             try {
487                                 SystemUtil.runShellCommand(sInstrumentation, command);
488                             } catch (IOException e) {
489                                 throw new RuntimeException(e);
490                             }
491                         },
492                         (event) -> {
493                             final AccessibilityWindowInfo window =
494                                     findWindowByTitleAndDisplay(sUiAutomation,
495                                             NotTouchableWindowTestActivity.TITLE, 0);
496                             return window != null;
497                         }, TIMEOUT_ASYNC_PROCESSING), Manifest.permission.INTERNAL_SYSTEM_WINDOW);
498     }
499 
closeNotTouchableWindowTestActivity()500     private void closeNotTouchableWindowTestActivity() {
501         final Intent intent = new Intent();
502         intent.setAction(NotTouchableWindowTestActivity.FINISH_ACTIVITY);
503         intent.setPackage(sInstrumentation.getContext().getPackageName());
504         // Call finish() on the window. This is required to launch more activities in any subsequent
505         // tests from this same app process.
506         sInstrumentation.runOnMainSync(() -> sInstrumentation.getContext().sendBroadcast(intent));
507         // Ensure we're at the home screen before continuing to other tests.
508         // finish() should do this, but sometimes takes longer than expected.
509         ActivityLaunchUtils.homeScreenOrBust(sInstrumentation.getContext(), sUiAutomation);
510     }
511 
512     /**
513      * Test whether we can successfully enable and disable window animations.
514      */
515     @Test
testDisableWindowAnimations()516     public void testDisableWindowAnimations() {
517         setAndAssertAnimationScale(0.0f);
518         setAndAssertAnimationScale(0.5f);
519         setAndAssertAnimationScale(1.0f);
520     }
521 
522     /** Sets the animation scale to a specified value and asserts that the value has been set. */
setAndAssertAnimationScale(float value)523     private void setAndAssertAnimationScale(float value) {
524         Context context = sInstrumentation.getContext();
525         sUiAutomation.setAnimationScale(value);
526         assertThat(getGlobalFloat(context, Settings.Global.WINDOW_ANIMATION_SCALE))
527                 .isEqualTo(value);
528         assertThat(getGlobalFloat(context, Settings.Global.TRANSITION_ANIMATION_SCALE))
529                 .isEqualTo(value);
530         assertThat(getGlobalFloat(context, Settings.Global.ANIMATOR_DURATION_SCALE))
531                 .isEqualTo(value);
532     }
533 
534     /** Returns value of constants in Settings.Global. */
getGlobalFloat(Context context, String constantName)535     private static float getGlobalFloat(Context context, String constantName) {
536         return Settings.Global.getFloat(context.getContentResolver(), constantName, -1);
537     }
538 
showTopWindowAndWaitForItToShowUp()539     private View showTopWindowAndWaitForItToShowUp() throws TimeoutException {
540         final WindowManager.LayoutParams paramsForTop =
541                 WindowCreationUtils.layoutParamsForWindowOnTop(
542                         sInstrumentation, mActivity, TOP_WINDOW_TITLE);
543         final Button button = new Button(mActivity);
544         button.setText(R.string.button1);
545 
546         WindowCreationUtils.addWindowAndWaitForEvent(sUiAutomation, sInstrumentation, mActivity,
547                 button, paramsForTop, (event) -> (event.getEventType() == TYPE_WINDOWS_CHANGED)
548                         && (findWindowByTitle(sUiAutomation, mActivityTitle) != null)
549                         && (findWindowByTitle(sUiAutomation, TOP_WINDOW_TITLE) != null));
550         return button;
551     }
552 
layoutParamsForWindowOnBottom()553     private WindowManager.LayoutParams layoutParamsForWindowOnBottom() {
554         final WindowManager.LayoutParams params = WindowCreationUtils.layoutParamsForTestWindow(
555                 sInstrumentation, mActivity);
556         params.gravity = Gravity.BOTTOM;
557         return params;
558     }
559 
sendIntentAndWaitForEvent(Intent intent, UiAutomation.AccessibilityEventFilter filter)560     private void sendIntentAndWaitForEvent(Intent intent,
561             UiAutomation.AccessibilityEventFilter filter) throws TimeoutException {
562         sUiAutomation.executeAndWaitForEvent(() -> sInstrumentation.runOnMainSync(
563                 () -> sInstrumentation.getContext().sendBroadcast(intent)),
564                 filter,
565                 TIMEOUT_ASYNC_PROCESSING);
566     }
567 
568 }
569