• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2010 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.Manifest.permission.POST_NOTIFICATIONS;
20 import static android.accessibility.cts.common.InstrumentedAccessibilityService.TIMEOUT_SERVICE_ENABLE;
21 import static android.accessibility.cts.common.InstrumentedAccessibilityService.enableService;
22 import static android.accessibilityservice.AccessibilityServiceInfo.FEEDBACK_ALL_MASK;
23 import static android.accessibilityservice.cts.utils.AccessibilityEventFilterUtils.filterForEventType;
24 import static android.accessibilityservice.cts.utils.AccessibilityEventFilterUtils.filterForEventTypeWithAction;
25 import static android.accessibilityservice.cts.utils.AccessibilityEventFilterUtils.filterForEventTypeWithResource;
26 import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.findWindowByTitle;
27 import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.getActivityTitle;
28 import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.launchActivityAndWaitForItToBeOnscreen;
29 import static android.accessibilityservice.cts.utils.AsyncUtils.DEFAULT_TIMEOUT_MS;
30 import static android.accessibilityservice.cts.utils.RunOnMainUtils.getOnMain;
31 import static android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction.ACTION_HIDE_TOOLTIP;
32 import static android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction.ACTION_SHOW_TOOLTIP;
33 
34 import static org.hamcrest.Matchers.in;
35 import static org.hamcrest.Matchers.not;
36 import static org.hamcrest.core.IsEqual.equalTo;
37 import static org.hamcrest.core.IsNull.notNullValue;
38 import static org.hamcrest.core.IsNull.nullValue;
39 import static org.junit.Assert.assertEquals;
40 import static org.junit.Assert.assertFalse;
41 import static org.junit.Assert.assertNotNull;
42 import static org.junit.Assert.assertThat;
43 import static org.junit.Assert.assertTrue;
44 import static org.junit.Assert.fail;
45 import static org.mockito.ArgumentMatchers.any;
46 import static org.mockito.ArgumentMatchers.argThat;
47 import static org.mockito.ArgumentMatchers.eq;
48 import static org.mockito.Mockito.inOrder;
49 import static org.mockito.Mockito.mock;
50 import static org.mockito.Mockito.timeout;
51 import static org.mockito.Mockito.verify;
52 
53 import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
54 import android.accessibility.cts.common.InstrumentedAccessibilityService;
55 import android.accessibility.cts.common.ShellCommandBuilder;
56 import android.accessibilityservice.AccessibilityServiceInfo;
57 import android.accessibilityservice.cts.activities.AccessibilityEndToEndActivity;
58 import android.app.Activity;
59 import android.app.AlertDialog;
60 import android.app.Instrumentation;
61 import android.app.Notification;
62 import android.app.NotificationChannel;
63 import android.app.NotificationManager;
64 import android.app.PendingIntent;
65 import android.app.Service;
66 import android.app.UiAutomation;
67 import android.appwidget.AppWidgetHost;
68 import android.appwidget.AppWidgetManager;
69 import android.appwidget.AppWidgetProviderInfo;
70 import android.content.ComponentName;
71 import android.content.Context;
72 import android.content.Intent;
73 import android.content.pm.PackageManager;
74 import android.content.res.Configuration;
75 import android.content.res.Resources;
76 import android.graphics.Rect;
77 import android.graphics.Region;
78 import android.os.Process;
79 import android.os.SystemClock;
80 import android.platform.test.annotations.AppModeFull;
81 import android.platform.test.annotations.AsbSecurityTest;
82 import android.platform.test.annotations.Presubmit;
83 import android.provider.Settings;
84 import android.test.suitebuilder.annotation.MediumTest;
85 import android.text.TextUtils;
86 import android.util.Log;
87 import android.view.InputDevice;
88 import android.view.MotionEvent;
89 import android.view.TouchDelegate;
90 import android.view.View;
91 import android.view.Window;
92 import android.view.WindowManager;
93 import android.view.accessibility.AccessibilityEvent;
94 import android.view.accessibility.AccessibilityManager;
95 import android.view.accessibility.AccessibilityNodeInfo;
96 import android.view.accessibility.AccessibilityWindowInfo;
97 import android.widget.Button;
98 import android.widget.EditText;
99 import android.widget.ListView;
100 
101 import androidx.test.InstrumentationRegistry;
102 import androidx.test.rule.ActivityTestRule;
103 import androidx.test.runner.AndroidJUnit4;
104 
105 import com.android.compatibility.common.util.CtsMouseUtil;
106 import com.android.compatibility.common.util.ShellUtils;
107 import com.android.compatibility.common.util.TestUtils;
108 import com.android.sts.common.util.StsExtraBusinessLogicTestCase;
109 
110 import org.junit.After;
111 import org.junit.AfterClass;
112 import org.junit.Before;
113 import org.junit.BeforeClass;
114 import org.junit.Rule;
115 import org.junit.Test;
116 import org.junit.rules.RuleChain;
117 import org.junit.runner.RunWith;
118 
119 import java.util.Iterator;
120 import java.util.List;
121 import java.util.concurrent.TimeoutException;
122 import java.util.concurrent.atomic.AtomicBoolean;
123 
124 /**
125  * This class performs end-to-end testing of the accessibility feature by
126  * creating an {@link Activity} and poking around so {@link AccessibilityEvent}s
127  * are generated and their correct dispatch verified.
128  */
129 @RunWith(AndroidJUnit4.class)
130 public class AccessibilityEndToEndTest extends StsExtraBusinessLogicTestCase {
131 
132     private static final String LOG_TAG = "AccessibilityEndToEndTest";
133 
134     private static final String GRANT_BIND_APP_WIDGET_PERMISSION_COMMAND =
135             "appwidget grantbind --package android.accessibilityservice.cts --user ";
136 
137     private static final String REVOKE_BIND_APP_WIDGET_PERMISSION_COMMAND =
138             "appwidget revokebind --package android.accessibilityservice.cts --user ";
139 
140     private static final String APP_WIDGET_PROVIDER_PACKAGE = "foo.bar.baz";
141 
142     private static Instrumentation sInstrumentation;
143     private static UiAutomation sUiAutomation;
144 
145     private AccessibilityEndToEndActivity mActivity;
146 
147     private ActivityTestRule<AccessibilityEndToEndActivity> mActivityRule =
148             new ActivityTestRule<>(AccessibilityEndToEndActivity.class, false, false);
149 
150     private AccessibilityDumpOnFailureRule mDumpOnFailureRule =
151             new AccessibilityDumpOnFailureRule();
152 
153     @Rule
154     public final RuleChain mRuleChain = RuleChain
155             .outerRule(mActivityRule)
156             .around(mDumpOnFailureRule);
157 
158     @BeforeClass
oneTimeSetup()159     public static void oneTimeSetup() throws Exception {
160         sInstrumentation = InstrumentationRegistry.getInstrumentation();
161         sUiAutomation = sInstrumentation.getUiAutomation();
162     }
163 
164     @AfterClass
postTestTearDown()165     public static void postTestTearDown() {
166         sUiAutomation.destroy();
167     }
168 
169     @Before
setUp()170     public void setUp() throws Exception {
171         sUiAutomation.adoptShellPermissionIdentity(POST_NOTIFICATIONS);
172         mActivity = launchActivityAndWaitForItToBeOnscreen(
173                 sInstrumentation, sUiAutomation, mActivityRule);
174     }
175 
176     @After
tearDown()177     public void tearDown() throws Exception {
178         sUiAutomation.dropShellPermissionIdentity();
179     }
180 
181     @MediumTest
182     @Presubmit
183     @Test
testTypeViewSelectedAccessibilityEvent()184     public void testTypeViewSelectedAccessibilityEvent() throws Throwable {
185         // create and populate the expected event
186         final AccessibilityEvent expected = AccessibilityEvent.obtain();
187         expected.setEventType(AccessibilityEvent.TYPE_VIEW_SELECTED);
188         expected.setClassName(ListView.class.getName());
189         expected.setPackageName(mActivity.getPackageName());
190         expected.setDisplayId(mActivity.getDisplayId());
191         expected.getText().add(mActivity.getString(R.string.second_list_item));
192         expected.setItemCount(2);
193         expected.setCurrentItemIndex(1);
194         expected.setEnabled(true);
195         expected.setScrollable(false);
196         expected.setFromIndex(0);
197         expected.setToIndex(1);
198 
199         final ListView listView = (ListView) mActivity.findViewById(R.id.listview);
200 
201         AccessibilityEvent awaitedEvent =
202             sUiAutomation.executeAndWaitForEvent(
203                 new Runnable() {
204             @Override
205             public void run() {
206                 // trigger the event
207                 mActivity.runOnUiThread(new Runnable() {
208                     @Override
209                     public void run() {
210                         listView.setSelection(1);
211                     }
212                 });
213             }},
214             new UiAutomation.AccessibilityEventFilter() {
215                 // check the received event
216                 @Override
217                 public boolean accept(AccessibilityEvent event) {
218                     return equalsAccessiblityEvent(event, expected);
219                 }
220             },
221                     DEFAULT_TIMEOUT_MS);
222         assertNotNull("Did not receive expected event: " + expected, awaitedEvent);
223     }
224 
225     @MediumTest
226     @Presubmit
227     @Test
testTypeViewClickedAccessibilityEvent()228     public void testTypeViewClickedAccessibilityEvent() throws Throwable {
229         // create and populate the expected event
230         final AccessibilityEvent expected = AccessibilityEvent.obtain();
231         expected.setEventType(AccessibilityEvent.TYPE_VIEW_CLICKED);
232         expected.setClassName(Button.class.getName());
233         expected.setPackageName(mActivity.getPackageName());
234         expected.setDisplayId(mActivity.getDisplayId());
235         expected.getText().add(mActivity.getString(R.string.button_title));
236         expected.setEnabled(true);
237 
238         final Button button = (Button) mActivity.findViewById(R.id.button);
239 
240         AccessibilityEvent awaitedEvent =
241             sUiAutomation.executeAndWaitForEvent(
242                 new Runnable() {
243             @Override
244             public void run() {
245                 // trigger the event
246                 mActivity.runOnUiThread(new Runnable() {
247                     @Override
248                     public void run() {
249                         button.performClick();
250                     }
251                 });
252             }},
253             new UiAutomation.AccessibilityEventFilter() {
254                 // check the received event
255                 @Override
256                 public boolean accept(AccessibilityEvent event) {
257                     return equalsAccessiblityEvent(event, expected);
258                 }
259             },
260                     DEFAULT_TIMEOUT_MS);
261         assertNotNull("Did not receive expected event: " + expected, awaitedEvent);
262     }
263 
264     @MediumTest
265     @Presubmit
266     @Test
testTypeViewLongClickedAccessibilityEvent()267     public void testTypeViewLongClickedAccessibilityEvent() throws Throwable {
268         // create and populate the expected event
269         final AccessibilityEvent expected = AccessibilityEvent.obtain();
270         expected.setEventType(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
271         expected.setClassName(Button.class.getName());
272         expected.setPackageName(mActivity.getPackageName());
273         expected.setDisplayId(mActivity.getDisplayId());
274         expected.getText().add(mActivity.getString(R.string.button_title));
275         expected.setEnabled(true);
276 
277         final Button button = (Button) mActivity.findViewById(R.id.button);
278 
279         AccessibilityEvent awaitedEvent =
280             sUiAutomation.executeAndWaitForEvent(
281                 new Runnable() {
282             @Override
283             public void run() {
284                 // trigger the event
285                 mActivity.runOnUiThread(new Runnable() {
286                     @Override
287                     public void run() {
288                         button.performLongClick();
289                     }
290                 });
291             }},
292             new UiAutomation.AccessibilityEventFilter() {
293                 // check the received event
294                 @Override
295                 public boolean accept(AccessibilityEvent event) {
296                     return equalsAccessiblityEvent(event, expected);
297                 }
298             },
299                     DEFAULT_TIMEOUT_MS);
300         assertNotNull("Did not receive expected event: " + expected, awaitedEvent);
301     }
302 
303     @MediumTest
304     @Presubmit
305     @Test
testTypeViewFocusedAccessibilityEvent()306     public void testTypeViewFocusedAccessibilityEvent() throws Throwable {
307         // create and populate the expected event
308         final AccessibilityEvent expected = AccessibilityEvent.obtain();
309         expected.setEventType(AccessibilityEvent.TYPE_VIEW_FOCUSED);
310         expected.setClassName(Button.class.getName());
311         expected.setPackageName(mActivity.getPackageName());
312         expected.setDisplayId(mActivity.getDisplayId());
313         expected.getText().add(mActivity.getString(R.string.button_title));
314         expected.setItemCount(5);
315         expected.setCurrentItemIndex(3);
316         expected.setEnabled(true);
317 
318         final Button button = (Button) mActivity.findViewById(R.id.buttonWithTooltip);
319 
320         AccessibilityEvent awaitedEvent =
321             sUiAutomation.executeAndWaitForEvent(
322                     () -> mActivity.runOnUiThread(() -> button.requestFocus()),
323                     (event) -> equalsAccessiblityEvent(event, expected),
324                     DEFAULT_TIMEOUT_MS);
325         assertNotNull("Did not receive expected event: " + expected, awaitedEvent);
326     }
327 
328     @MediumTest
329     @Presubmit
330     @Test
testTypeViewTextChangedAccessibilityEvent()331     public void testTypeViewTextChangedAccessibilityEvent() throws Throwable {
332         // focus the edit text
333         final EditText editText = (EditText) mActivity.findViewById(R.id.edittext);
334 
335         AccessibilityEvent awaitedFocusEvent =
336             sUiAutomation.executeAndWaitForEvent(
337                 new Runnable() {
338             @Override
339             public void run() {
340                 // trigger the event
341                 mActivity.runOnUiThread(new Runnable() {
342                     @Override
343                     public void run() {
344                         editText.requestFocus();
345                     }
346                 });
347             }},
348             new UiAutomation.AccessibilityEventFilter() {
349                 // check the received event
350                 @Override
351                 public boolean accept(AccessibilityEvent event) {
352                     return event.getEventType() == AccessibilityEvent.TYPE_VIEW_FOCUSED;
353                 }
354             },
355                     DEFAULT_TIMEOUT_MS);
356         assertNotNull("Did not receive expected focuss event.", awaitedFocusEvent);
357 
358         final String beforeText = mActivity.getString(R.string.text_input_blah);
359         final String newText = mActivity.getString(R.string.text_input_blah_blah);
360         final String afterText = beforeText.substring(0, 3) + newText;
361 
362         // create and populate the expected event
363         final AccessibilityEvent expected = AccessibilityEvent.obtain();
364         expected.setEventType(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
365         expected.setClassName(EditText.class.getName());
366         expected.setPackageName(mActivity.getPackageName());
367         expected.setDisplayId(mActivity.getDisplayId());
368         expected.getText().add(afterText);
369         expected.setBeforeText(beforeText);
370         expected.setFromIndex(3);
371         expected.setAddedCount(9);
372         expected.setRemovedCount(1);
373         expected.setEnabled(true);
374 
375         AccessibilityEvent awaitedTextChangeEvent =
376             sUiAutomation.executeAndWaitForEvent(
377                 new Runnable() {
378             @Override
379             public void run() {
380                 // trigger the event
381                 mActivity.runOnUiThread(new Runnable() {
382                     @Override
383                     public void run() {
384                         editText.getEditableText().replace(3, 4, newText);
385                     }
386                 });
387             }},
388             new UiAutomation.AccessibilityEventFilter() {
389                 // check the received event
390                 @Override
391                 public boolean accept(AccessibilityEvent event) {
392                     return equalsAccessiblityEvent(event, expected);
393                 }
394             },
395                     DEFAULT_TIMEOUT_MS);
396         assertNotNull("Did not receive expected event: " + expected, awaitedTextChangeEvent);
397     }
398 
399     @MediumTest
400     @Presubmit
401     @Test
testTypeWindowStateChangedAccessibilityEvent()402     public void testTypeWindowStateChangedAccessibilityEvent() throws Throwable {
403         // create and populate the expected event
404         final AccessibilityEvent expected = AccessibilityEvent.obtain();
405         expected.setEventType(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
406         expected.setClassName(AlertDialog.class.getName());
407         expected.setPackageName(mActivity.getPackageName());
408         expected.setDisplayId(mActivity.getDisplayId());
409         expected.getText().add(mActivity.getString(R.string.alert_title));
410         expected.getText().add(mActivity.getString(R.string.alert_message));
411         expected.setEnabled(true);
412 
413         AccessibilityEvent awaitedEvent =
414             sUiAutomation.executeAndWaitForEvent(
415                 new Runnable() {
416             @Override
417             public void run() {
418                 // trigger the event
419                 mActivity.runOnUiThread(new Runnable() {
420                     @Override
421                     public void run() {
422                         (new AlertDialog.Builder(mActivity).setTitle(R.string.alert_title)
423                                 .setMessage(R.string.alert_message)).create().show();
424                     }
425                 });
426             }},
427             new UiAutomation.AccessibilityEventFilter() {
428                 // check the received event
429                 @Override
430                 public boolean accept(AccessibilityEvent event) {
431                     return equalsAccessiblityEvent(event, expected);
432                 }
433             },
434                     DEFAULT_TIMEOUT_MS);
435         assertNotNull("Did not receive expected event: " + expected, awaitedEvent);
436     }
437 
438     @MediumTest
439     @Presubmit
440     @Test
testTypeWindowsChangedAccessibilityEvent()441     public void testTypeWindowsChangedAccessibilityEvent() throws Throwable {
442         // create and populate the expected event
443         final AccessibilityEvent expected = AccessibilityEvent.obtain();
444         expected.setEventType(AccessibilityEvent.TYPE_WINDOWS_CHANGED);
445         expected.setDisplayId(mActivity.getDisplayId());
446 
447         // check the received event
448         AccessibilityEvent awaitedEvent =
449             sUiAutomation.executeAndWaitForEvent(
450                     () -> mActivity.runOnUiThread(() -> mActivity.finish()),
451                     event -> event.getWindowChanges() == AccessibilityEvent.WINDOWS_CHANGE_REMOVED
452                             && equalsAccessiblityEvent(event, expected),
453                     DEFAULT_TIMEOUT_MS);
454         assertNotNull("Did not receive expected event: " + expected, awaitedEvent);
455     }
456 
457     @MediumTest
458     @AppModeFull
459     @SuppressWarnings("deprecation")
460     @Presubmit
461     @Test
testTypeNotificationStateChangedAccessibilityEvent()462     public void testTypeNotificationStateChangedAccessibilityEvent() throws Throwable {
463         // No notification UI on televisions.
464         if ((mActivity.getResources().getConfiguration().uiMode
465                 & Configuration.UI_MODE_TYPE_MASK) == Configuration.UI_MODE_TYPE_TELEVISION) {
466             Log.i(LOG_TAG, "Skipping: testTypeNotificationStateChangedAccessibilityEvent" +
467                     " - No notification UI on televisions.");
468             return;
469         }
470         PackageManager pm = sInstrumentation.getTargetContext().getPackageManager();
471         if (pm.hasSystemFeature(pm.FEATURE_WATCH)) {
472             Log.i(LOG_TAG, "Skipping: testTypeNotificationStateChangedAccessibilityEvent" +
473                     " - Watches have different notification system.");
474             return;
475         }
476         if (pm.hasSystemFeature(pm.FEATURE_AUTOMOTIVE)) {
477             Log.i(LOG_TAG, "Skipping: testTypeNotificationStateChangedAccessibilityEvent" +
478                     " - Automotive handle notifications differently.");
479             return;
480         }
481 
482         String message = mActivity.getString(R.string.notification_message);
483 
484         final NotificationManager notificationManager =
485                 (NotificationManager) mActivity.getSystemService(Service.NOTIFICATION_SERVICE);
486         final NotificationChannel channel =
487                 new NotificationChannel("id", "name", NotificationManager.IMPORTANCE_DEFAULT);
488         try {
489             // create the notification to send
490             channel.enableVibration(true);
491             channel.enableLights(true);
492             channel.setBypassDnd(true);
493             notificationManager.createNotificationChannel(channel);
494             NotificationChannel created =
495                     notificationManager.getNotificationChannel(channel.getId());
496             final int notificationId = 1;
497             final Notification notification =
498                     new Notification.Builder(mActivity, channel.getId())
499                             .setSmallIcon(android.R.drawable.stat_notify_call_mute)
500                             .setContentIntent(PendingIntent.getActivity(mActivity, 0,
501                                     new Intent(),
502             PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE))
503                             .setTicker(message)
504                             .setContentTitle("")
505                             .setContentText("")
506                             .setPriority(Notification.PRIORITY_MAX)
507                             // Mark the notification as "interruptive" by specifying a vibration
508                             // pattern. This ensures it's announced properly on watch-type devices.
509                             .setVibrate(new long[]{})
510                             .build();
511 
512             // create and populate the expected event
513             final AccessibilityEvent expected = AccessibilityEvent.obtain();
514             expected.setEventType(AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED);
515             expected.setClassName(Notification.class.getName());
516             expected.setPackageName(mActivity.getPackageName());
517             expected.getText().add(message);
518             expected.setParcelableData(notification);
519 
520             AccessibilityEvent awaitedEvent =
521                     sUiAutomation.executeAndWaitForEvent(
522                             new Runnable() {
523                                 @Override
524                                 public void run() {
525                                     // trigger the event
526                                     mActivity.runOnUiThread(new Runnable() {
527                                         @Override
528                                         public void run() {
529                                             // trigger the event
530                                             notificationManager
531                                                     .notify(notificationId, notification);
532                                             mActivity.finish();
533                                         }
534                                     });
535                                 }
536                             },
537                             new UiAutomation.AccessibilityEventFilter() {
538                                 // check the received event
539                                 @Override
540                                 public boolean accept(AccessibilityEvent event) {
541                                     return equalsAccessiblityEvent(event, expected);
542                                 }
543                             },
544                             DEFAULT_TIMEOUT_MS);
545             assertNotNull("Did not receive expected event: " + expected, awaitedEvent);
546         } finally {
547             notificationManager.deleteNotificationChannel(channel.getId());
548         }
549     }
550 
551     @MediumTest
552     @Test
testInterrupt_notifiesService()553     public void testInterrupt_notifiesService() {
554         sInstrumentation
555                 .getUiAutomation(UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES);
556         InstrumentedAccessibilityService service =
557                 enableService(InstrumentedAccessibilityService.class);
558 
559         try {
560             assertFalse(service.wasOnInterruptCalled());
561 
562             mActivity.runOnUiThread(() -> {
563                 AccessibilityManager accessibilityManager = (AccessibilityManager) mActivity
564                         .getSystemService(Service.ACCESSIBILITY_SERVICE);
565                 accessibilityManager.interrupt();
566             });
567 
568             Object waitObject = service.getInterruptWaitObject();
569             synchronized (waitObject) {
570                 if (!service.wasOnInterruptCalled()) {
571                     try {
572                         waitObject.wait(DEFAULT_TIMEOUT_MS);
573                     } catch (InterruptedException e) {
574                         // Do nothing
575                     }
576                 }
577             }
578             assertTrue(service.wasOnInterruptCalled());
579         } finally {
580             service.disableSelfAndRemove();
581         }
582     }
583 
584     @MediumTest
585     @Test
testPackageNameCannotBeFaked()586     public void testPackageNameCannotBeFaked() throws Exception {
587         mActivity.runOnUiThread(() -> {
588             // Set the activity to report fake package for events and nodes
589             mActivity.setReportedPackageName("foo.bar.baz");
590 
591             // Make sure node package cannot be faked
592             AccessibilityNodeInfo root = sUiAutomation
593                     .getRootInActiveWindow();
594             assertPackageName(root, mActivity.getPackageName());
595         });
596 
597         // Make sure event package cannot be faked
598         try {
599             sUiAutomation.executeAndWaitForEvent(() ->
600                 sInstrumentation.runOnMainSync(() ->
601                     mActivity.findViewById(R.id.button).requestFocus())
602                 , (AccessibilityEvent event) ->
603                     event.getEventType() == AccessibilityEvent.TYPE_VIEW_FOCUSED
604                             && event.getPackageName().equals(mActivity.getPackageName())
605                 , DEFAULT_TIMEOUT_MS);
606         } catch (TimeoutException e) {
607             fail("Events from fake package should be fixed to use the correct package");
608         }
609     }
610 
611     @AppModeFull
612     @MediumTest
613     @Presubmit
614     @Test
testPackageNameCannotBeFakedAppWidget()615     public void testPackageNameCannotBeFakedAppWidget() throws Exception {
616         if (!hasAppWidgets()) {
617             return;
618         }
619 
620         sInstrumentation.runOnMainSync(() -> {
621             // Set the activity to report fake package for events and nodes
622             mActivity.setReportedPackageName(APP_WIDGET_PROVIDER_PACKAGE);
623 
624             // Make sure we cannot report nodes as if from the widget package
625             AccessibilityNodeInfo root = sUiAutomation
626                     .getRootInActiveWindow();
627             assertPackageName(root, mActivity.getPackageName());
628         });
629 
630         // Make sure we cannot send events as if from the widget package
631         try {
632             sUiAutomation.executeAndWaitForEvent(() ->
633                 sInstrumentation.runOnMainSync(() ->
634                     mActivity.findViewById(R.id.button).requestFocus())
635                 , (AccessibilityEvent event) ->
636                     event.getEventType() == AccessibilityEvent.TYPE_VIEW_FOCUSED
637                             && event.getPackageName().equals(mActivity.getPackageName())
638                 , DEFAULT_TIMEOUT_MS);
639         } catch (TimeoutException e) {
640             fail("Should not be able to send events from a widget package if no widget hosted");
641         }
642 
643         // Create a host and start listening.
644         final AppWidgetHost host = new AppWidgetHost(sInstrumentation.getTargetContext(), 0);
645         host.deleteHost();
646         host.startListening();
647 
648         // Well, app do not have this permission unless explicitly granted
649         // by the user. Now we will pretend for the user and grant it.
650         grantBindAppWidgetPermission();
651 
652         // Allocate an app widget id to bind.
653         final int appWidgetId = host.allocateAppWidgetId();
654         try {
655             // Grab a provider we defined to be bound.
656             final AppWidgetProviderInfo provider = getAppWidgetProviderInfo();
657 
658             // Bind the widget.
659             final boolean widgetBound = getAppWidgetManager().bindAppWidgetIdIfAllowed(
660                     appWidgetId, provider.getProfile(), provider.provider, null);
661             assertTrue(widgetBound);
662 
663             // Make sure the app can use the package of a widget it hosts
664             sInstrumentation.runOnMainSync(() -> {
665                 // Make sure we can report nodes as if from the widget package
666                 AccessibilityNodeInfo root = sUiAutomation
667                         .getRootInActiveWindow();
668                 assertPackageName(root, APP_WIDGET_PROVIDER_PACKAGE);
669             });
670 
671             // Make sure we can send events as if from the widget package
672             try {
673                 sUiAutomation.executeAndWaitForEvent(() ->
674                     sInstrumentation.runOnMainSync(() ->
675                         mActivity.findViewById(R.id.button).performClick())
676                     , (AccessibilityEvent event) ->
677                             event.getEventType() == AccessibilityEvent.TYPE_VIEW_CLICKED
678                                     && event.getPackageName().equals(APP_WIDGET_PROVIDER_PACKAGE)
679                     , DEFAULT_TIMEOUT_MS);
680             } catch (TimeoutException e) {
681                 fail("Should be able to send events from a widget package if widget hosted");
682             }
683         } finally {
684             // Clean up.
685             host.deleteAppWidgetId(appWidgetId);
686             host.deleteHost();
687             revokeBindAppWidgetPermission();
688         }
689     }
690 
691     @MediumTest
692     @Presubmit
693     @Test
testViewHeadingReportedToAccessibility()694     public void testViewHeadingReportedToAccessibility() throws Exception {
695         final EditText editText = (EditText) getOnMain(sInstrumentation, () -> {
696             return mActivity.findViewById(R.id.edittext);
697         });
698         // Make sure the edittext was populated properly from xml
699         final boolean editTextIsHeading = getOnMain(sInstrumentation, () -> {
700             return editText.isAccessibilityHeading();
701         });
702         assertTrue("isAccessibilityHeading not populated properly from xml", editTextIsHeading);
703 
704         final AccessibilityNodeInfo editTextNode = sUiAutomation.getRootInActiveWindow()
705                 .findAccessibilityNodeInfosByViewId(
706                         "android.accessibilityservice.cts:id/edittext")
707                 .get(0);
708         assertTrue("isAccessibilityHeading not reported to accessibility",
709                 editTextNode.isHeading());
710 
711         sUiAutomation.executeAndWaitForEvent(() -> sInstrumentation.runOnMainSync(() ->
712                         editText.setAccessibilityHeading(false)),
713                 filterForEventType(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED),
714                 DEFAULT_TIMEOUT_MS);
715         editTextNode.refresh();
716         assertFalse("isAccessibilityHeading not reported to accessibility after update",
717                 editTextNode.isHeading());
718     }
719 
720     @MediumTest
721     @Presubmit
722     @Test
testTooltipTextReportedToAccessibility()723     public void testTooltipTextReportedToAccessibility() {
724         final AccessibilityNodeInfo buttonNode = sUiAutomation.getRootInActiveWindow()
725                 .findAccessibilityNodeInfosByViewId(
726                         "android.accessibilityservice.cts:id/buttonWithTooltip")
727                 .get(0);
728         assertEquals("Tooltip text not reported to accessibility",
729                 sInstrumentation.getContext().getString(R.string.button_tooltip),
730                 buttonNode.getTooltipText());
731     }
732 
733     @MediumTest
734     @Test
testTooltipTextActionsReportedToAccessibility()735     public void testTooltipTextActionsReportedToAccessibility() throws Exception {
736         final AccessibilityNodeInfo buttonNode = sUiAutomation.getRootInActiveWindow()
737                 .findAccessibilityNodeInfosByViewId(
738                         "android.accessibilityservice.cts:id/buttonWithTooltip")
739                 .get(0);
740         assertFalse(hasTooltipShowing(R.id.buttonWithTooltip));
741         assertThat(ACTION_SHOW_TOOLTIP, in(buttonNode.getActionList()));
742         assertThat(ACTION_HIDE_TOOLTIP, not(in(buttonNode.getActionList())));
743         sUiAutomation.executeAndWaitForEvent(
744                 () -> buttonNode.performAction(ACTION_SHOW_TOOLTIP.getId()),
745                 filterForEventTypeWithAction(
746                         AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED,
747                         ACTION_SHOW_TOOLTIP.getId()),
748                 DEFAULT_TIMEOUT_MS);
749 
750         // The button should now be showing the tooltip, so it should have the option to hide it.
751         buttonNode.refresh();
752         assertThat(ACTION_HIDE_TOOLTIP, in(buttonNode.getActionList()));
753         assertThat(ACTION_SHOW_TOOLTIP, not(in(buttonNode.getActionList())));
754         assertTrue(hasTooltipShowing(R.id.buttonWithTooltip));
755     }
756 
757     @MediumTest
758     @Test
testTraversalBeforeReportedToAccessibility()759     public void testTraversalBeforeReportedToAccessibility() throws Exception {
760         final AccessibilityNodeInfo buttonNode = sUiAutomation.getRootInActiveWindow()
761                 .findAccessibilityNodeInfosByViewId(
762                         "android.accessibilityservice.cts:id/buttonWithTooltip")
763                 .get(0);
764         final AccessibilityNodeInfo beforeNode = buttonNode.getTraversalBefore();
765         assertThat(beforeNode, notNullValue());
766         assertThat(beforeNode.getViewIdResourceName(),
767                 equalTo("android.accessibilityservice.cts:id/edittext"));
768 
769         sUiAutomation.executeAndWaitForEvent(() -> sInstrumentation.runOnMainSync(
770                 () -> mActivity.findViewById(R.id.buttonWithTooltip)
771                         .setAccessibilityTraversalBefore(View.NO_ID)),
772                 filterForEventType(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED),
773                 DEFAULT_TIMEOUT_MS);
774 
775         buttonNode.refresh();
776         assertThat(buttonNode.getTraversalBefore(), nullValue());
777     }
778 
779     @MediumTest
780     @Test
testTraversalAfterReportedToAccessibility()781     public void testTraversalAfterReportedToAccessibility() throws Exception {
782         final AccessibilityNodeInfo editNode = sUiAutomation.getRootInActiveWindow()
783                 .findAccessibilityNodeInfosByViewId(
784                         "android.accessibilityservice.cts:id/edittext")
785                 .get(0);
786         final AccessibilityNodeInfo afterNode = editNode.getTraversalAfter();
787         assertThat(afterNode, notNullValue());
788         assertThat(afterNode.getViewIdResourceName(),
789                 equalTo("android.accessibilityservice.cts:id/buttonWithTooltip"));
790 
791         sUiAutomation.executeAndWaitForEvent(() -> sInstrumentation.runOnMainSync(
792                 () -> mActivity.findViewById(R.id.edittext)
793                         .setAccessibilityTraversalAfter(View.NO_ID)),
794                 filterForEventType(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED),
795                 DEFAULT_TIMEOUT_MS);
796 
797         editNode.refresh();
798         assertThat(editNode.getTraversalAfter(), nullValue());
799     }
800 
801     @MediumTest
802     @Test
testLabelForReportedToAccessibility()803     public void testLabelForReportedToAccessibility() throws Exception {
804         sUiAutomation.executeAndWaitForEvent(() -> sInstrumentation.runOnMainSync(() -> mActivity
805                 .findViewById(R.id.edittext).setLabelFor(R.id.buttonWithTooltip)),
806                 filterForEventType(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED),
807                 DEFAULT_TIMEOUT_MS);
808         // TODO: b/78022650: This code should move above the executeAndWait event. It's here because
809         // the a11y cache doesn't get notified when labelFor changes, so the node with the
810         // labledBy isn't updated.
811         final AccessibilityNodeInfo editNode = sUiAutomation.getRootInActiveWindow()
812                 .findAccessibilityNodeInfosByViewId(
813                         "android.accessibilityservice.cts:id/edittext")
814                 .get(0);
815         editNode.refresh();
816         final AccessibilityNodeInfo labelForNode = editNode.getLabelFor();
817         assertThat(labelForNode, notNullValue());
818         // Labeled node should indicate that it is labeled by the other one
819         assertThat(labelForNode.getLabeledBy(), equalTo(editNode));
820     }
821 
822     @MediumTest
823     @Test
testA11yActionTriggerMotionEventActionOutside()824     public void testA11yActionTriggerMotionEventActionOutside() throws Exception {
825         final View.OnTouchListener listener = mock(View.OnTouchListener.class);
826         final AccessibilityNodeInfo button = sUiAutomation.getRootInActiveWindow()
827                 .findAccessibilityNodeInfosByViewId(
828                         "android.accessibilityservice.cts:id/button")
829                 .get(0);
830         final String title = sInstrumentation.getContext().getString(R.string.alert_title);
831 
832         // Add a dialog that is watching outside touch
833         sUiAutomation.executeAndWaitForEvent(
834                 () -> sInstrumentation.runOnMainSync(() -> {
835                             final AlertDialog dialog = new AlertDialog.Builder(mActivity)
836                                     .setTitle(R.string.alert_title)
837                                     .setMessage(R.string.alert_message)
838                                     .create();
839                             final Window window = dialog.getWindow();
840                             window.addFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
841                                     | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH);
842                             window.getDecorView().setOnTouchListener(listener);
843                             window.setTitle(title);
844                             dialog.show();
845                     }),
846                 (event) -> {
847                     // Ensure the dialog is shown over the activity
848                     final AccessibilityWindowInfo dialog = findWindowByTitle(
849                             sUiAutomation, title);
850                     final AccessibilityWindowInfo activity = findWindowByTitle(
851                             sUiAutomation, getActivityTitle(sInstrumentation, mActivity));
852                     return (dialog != null && activity != null)
853                             && (dialog.getLayer() > activity.getLayer());
854                 }, DEFAULT_TIMEOUT_MS);
855 
856         // Perform an action and wait for an event
857         sUiAutomation.executeAndWaitForEvent(
858                 () -> button.performAction(AccessibilityNodeInfo.ACTION_CLICK),
859                 filterForEventTypeWithAction(
860                         AccessibilityEvent.TYPE_VIEW_CLICKED, AccessibilityNodeInfo.ACTION_CLICK),
861                 DEFAULT_TIMEOUT_MS);
862 
863         // Make sure the MotionEvent.ACTION_OUTSIDE is received.
864         verify(listener, timeout(DEFAULT_TIMEOUT_MS).atLeastOnce()).onTouch(any(View.class),
865                 argThat(event -> event.getActionMasked() == MotionEvent.ACTION_OUTSIDE));
866     }
867 
868     @MediumTest
869     @Test
testTouchDelegateInfoReportedToAccessibility()870     public void testTouchDelegateInfoReportedToAccessibility() {
871         final Button button = getOnMain(sInstrumentation, () -> mActivity.findViewById(
872                 R.id.button));
873         final View parent = (View) button.getParent();
874         final Rect rect = new Rect();
875         button.getHitRect(rect);
876         parent.setTouchDelegate(new TouchDelegate(rect, button));
877 
878         final AccessibilityNodeInfo nodeInfo = sUiAutomation.getRootInActiveWindow()
879                 .findAccessibilityNodeInfosByViewId(
880                         "android.accessibilityservice.cts:id/buttonLayout")
881                 .get(0);
882         AccessibilityNodeInfo.TouchDelegateInfo targetMapInfo =
883                 nodeInfo.getTouchDelegateInfo();
884         assertNotNull("Did not receive TouchDelegate target map", targetMapInfo);
885         assertEquals("Incorrect target map size", 1, targetMapInfo.getRegionCount());
886         assertEquals("Incorrect target map region", new Region(rect),
887                 targetMapInfo.getRegionAt(0));
888         final AccessibilityNodeInfo node = targetMapInfo.getTargetForRegion(
889                 targetMapInfo.getRegionAt(0));
890         assertEquals("Incorrect target map view",
891                 "android.accessibilityservice.cts:id/button",
892                 node.getViewIdResourceName());
893         node.recycle();
894     }
895 
896     @MediumTest
897     @Test
testTouchDelegateWithEbtBetweenView_ReHoverDelegate_FocusTargetAgain()898     public void testTouchDelegateWithEbtBetweenView_ReHoverDelegate_FocusTargetAgain()
899             throws Throwable {
900         mActivity.waitForEnterAnimationComplete();
901 
902         final Resources resources = sInstrumentation.getTargetContext().getResources();
903         final String buttonResourceName = resources.getResourceName(R.id.button);
904         final Button button = mActivity.findViewById(R.id.button);
905         final int[] buttonLocation = new int[2];
906         button.getLocationOnScreen(buttonLocation);
907         final int buttonX = button.getWidth() / 2;
908         final int buttonY = button.getHeight() / 2;
909         final int hoverY = buttonLocation[1] + buttonY;
910         final Button buttonWithTooltip = mActivity.findViewById(R.id.buttonWithTooltip);
911         final int[] buttonWithTooltipLocation = new int[2];
912         buttonWithTooltip.getLocationOnScreen(buttonWithTooltipLocation);
913         final int touchableSize = resources.getDimensionPixelSize(
914                 R.dimen.button_touchable_width_increment_amount);
915         final int hoverRight = buttonWithTooltipLocation[0] + touchableSize / 2;
916         final int hoverLeft = buttonLocation[0] + button.getWidth() + touchableSize / 2;
917         final int hoverMiddle = (hoverLeft + hoverRight) / 2;
918         final View.OnHoverListener listener = CtsMouseUtil.installHoverListener(button, false);
919         enableTouchExploration(sInstrumentation, true);
920 
921         try {
922             // common downTime for touch explorer injected events
923             final long downTime = SystemClock.uptimeMillis();
924             // hover through delegate, parent, 2nd view, parent and delegate again
925             sUiAutomation.executeAndWaitForEvent(
926                     () -> injectHoverEvent(downTime, false, hoverLeft, hoverY),
927                     filterForEventTypeWithResource(AccessibilityEvent.TYPE_VIEW_HOVER_ENTER,
928                             buttonResourceName), DEFAULT_TIMEOUT_MS);
929             assertTrue(button.isHovered());
930             sUiAutomation.executeAndWaitForEvent(
931                     () -> {
932                         injectHoverEvent(downTime, true, hoverMiddle, hoverY);
933                         injectHoverEvent(downTime, true, hoverRight, hoverY);
934                         injectHoverEvent(downTime, true, hoverMiddle, hoverY);
935                         injectHoverEvent(downTime, true, hoverLeft, hoverY);
936                     },
937                     filterForEventTypeWithResource(AccessibilityEvent.TYPE_VIEW_HOVER_ENTER,
938                             buttonResourceName), DEFAULT_TIMEOUT_MS);
939             // delegate target has a11y focus again
940             assertTrue(button.isHovered());
941 
942             CtsMouseUtil.clearHoverListener(button);
943             View.OnHoverListener verifier = inOrder(listener).verify(listener);
944             verifier.onHover(eq(button),
945                     matchHover(MotionEvent.ACTION_HOVER_ENTER, buttonX, buttonY));
946             verifier.onHover(eq(button),
947                     matchHover(MotionEvent.ACTION_HOVER_MOVE, buttonX, buttonY));
948             verifier.onHover(eq(button),
949                     matchHover(MotionEvent.ACTION_HOVER_MOVE, hoverMiddle, buttonY));
950             verifier.onHover(eq(button),
951                     matchHover(MotionEvent.ACTION_HOVER_EXIT, buttonX, buttonY));
952             verifier.onHover(eq(button),
953                     matchHover(MotionEvent.ACTION_HOVER_ENTER, buttonX, buttonY));
954             verifier.onHover(eq(button),
955                     matchHover(MotionEvent.ACTION_HOVER_MOVE, buttonX, buttonY));
956         } catch (TimeoutException e) {
957             fail("Accessibility events should be received as expected " + e.getMessage());
958         } finally {
959             enableTouchExploration(sInstrumentation, false);
960         }
961     }
962 
963     @MediumTest
964     @Test
testTouchDelegateCoverParentWithEbt_HoverChildAndBack_FocusTargetAgain()965     public void testTouchDelegateCoverParentWithEbt_HoverChildAndBack_FocusTargetAgain()
966             throws Throwable {
967         mActivity.waitForEnterAnimationComplete();
968 
969         final Resources resources = sInstrumentation.getTargetContext().getResources();
970         final int touchableSize = resources.getDimensionPixelSize(
971                 R.dimen.button_touchable_width_increment_amount);
972         final String targetResourceName = resources.getResourceName(R.id.buttonDelegated);
973         final View textView = mActivity.findViewById(R.id.delegateText);
974         final Button target = mActivity.findViewById(R.id.buttonDelegated);
975         int[] location = new int[2];
976         textView.getLocationOnScreen(location);
977         final int textX = location[0] + touchableSize/2;
978         final int textY = location[1] + textView.getHeight() / 2;
979         final int delegateX = location[0] - touchableSize/2;
980         final int targetX = target.getWidth() / 2;
981         final int targetY = target.getHeight() / 2;
982         final View.OnHoverListener listener = CtsMouseUtil.installHoverListener(target, false);
983         enableTouchExploration(sInstrumentation, true);
984 
985         try {
986             final long downTime = SystemClock.uptimeMillis();
987             // Like switch bar, it has a text view, a button and a delegate covers parent layout.
988             // hover the delegate, text and delegate again.
989             sUiAutomation.executeAndWaitForEvent(
990                     () -> injectHoverEvent(downTime, false, delegateX, textY),
991                     filterForEventTypeWithResource(AccessibilityEvent.TYPE_VIEW_HOVER_ENTER,
992                            targetResourceName), DEFAULT_TIMEOUT_MS);
993             assertTrue(target.isHovered());
994             sUiAutomation.executeAndWaitForEvent(
995                     () -> injectHoverEvent(downTime, true, textX, textY),
996                     filterForEventTypeWithResource(AccessibilityEvent.TYPE_VIEW_HOVER_EXIT,
997                            targetResourceName), DEFAULT_TIMEOUT_MS);
998             sUiAutomation.executeAndWaitForEvent(
999                     () -> injectHoverEvent(downTime, true, delegateX, textY),
1000                     filterForEventTypeWithResource(AccessibilityEvent.TYPE_VIEW_HOVER_ENTER,
1001                            targetResourceName), DEFAULT_TIMEOUT_MS);
1002             assertTrue(target.isHovered());
1003 
1004             CtsMouseUtil.clearHoverListener(target);
1005             View.OnHoverListener verifier = inOrder(listener).verify(listener);
1006             verifier.onHover(eq(target),
1007                     matchHover(MotionEvent.ACTION_HOVER_ENTER, targetX, targetY));
1008             verifier.onHover(eq(target),
1009                     matchHover(MotionEvent.ACTION_HOVER_MOVE, targetX, targetY));
1010             verifier.onHover(eq(target),
1011                     matchHover(MotionEvent.ACTION_HOVER_MOVE, textX, textY));
1012             verifier.onHover(eq(target),
1013                     matchHover(MotionEvent.ACTION_HOVER_EXIT, targetX, targetY));
1014             verifier.onHover(eq(target),
1015                     matchHover(MotionEvent.ACTION_HOVER_ENTER, targetX, targetY));
1016             verifier.onHover(eq(target),
1017                     matchHover(MotionEvent.ACTION_HOVER_MOVE, targetX, targetY));
1018         } catch (TimeoutException e) {
1019             fail("Accessibility events should be received as expected " + e.getMessage());
1020         } finally {
1021             enableTouchExploration(sInstrumentation, false);
1022         }
1023     }
1024 
1025     @AsbSecurityTest(cveBugId = {243378132})
1026     @Test
testUninstallPackage_DisablesMultipleServices()1027     public void testUninstallPackage_DisablesMultipleServices() throws Exception {
1028         final String apkPath =
1029                 "/data/local/tmp/cts/content/CtsAccessibilityMultipleServicesApp.apk";
1030         final String packageName = "foo.bar.multipleservices";
1031         final ComponentName service1 = ComponentName.createRelative(packageName, ".StubService1");
1032         final ComponentName service2 = ComponentName.createRelative(packageName, ".StubService2");
1033         // Match AccessibilityManagerService#COMPONENT_NAME_SEPARATOR
1034         final String componentNameSeparator = ":";
1035 
1036         final String originalEnabledServicesSetting = getEnabledServicesSetting();
1037 
1038         try {
1039             // Install the apk in this test method, instead of as part of the target preparer, to
1040             // allow repeated --iterations of the test.
1041             com.google.common.truth.Truth.assertThat(
1042                     ShellUtils.runShellCommand("pm install " + apkPath)).startsWith("Success");
1043 
1044             // Enable the two services and wait until AccessibilityManager reports them as enabled.
1045             final String servicesToEnable = getEnabledServicesSetting() + componentNameSeparator
1046                     + service1.flattenToShortString() + componentNameSeparator
1047                     + service2.flattenToShortString();
1048             ShellCommandBuilder.create(sInstrumentation)
1049                     .putSecureSetting(Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
1050                             servicesToEnable)
1051                     .putSecureSetting(Settings.Secure.ACCESSIBILITY_ENABLED, "1")
1052                     .run();
1053             TestUtils.waitUntil("Failed to enable 2 services from package " + packageName,
1054                     (int) TIMEOUT_SERVICE_ENABLE / 1000,
1055                     () -> getEnabledServices().stream().filter(
1056                             info -> info.getId().startsWith(packageName)).count() == 2);
1057 
1058             // Uninstall the package that contains the services.
1059             com.google.common.truth.Truth.assertThat(
1060                     ShellUtils.runShellCommand("pm uninstall " + packageName)).startsWith(
1061                     "Success");
1062 
1063             // Ensure the uninstall removed the services from the secure setting.
1064             TestUtils.waitUntil(
1065                     "Failed to disable services after uninstalling package " + packageName,
1066                     (int) TIMEOUT_SERVICE_ENABLE / 1000,
1067                     () -> !getEnabledServicesSetting().contains(packageName));
1068         } finally {
1069             ShellCommandBuilder.create(sInstrumentation)
1070                     .putSecureSetting(Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
1071                             originalEnabledServicesSetting)
1072                     .run();
1073             ShellUtils.runShellCommand("pm uninstall " + packageName);
1074         }
1075     }
1076 
getEnabledServices()1077     private List<AccessibilityServiceInfo> getEnabledServices() {
1078         return ((AccessibilityManager) sInstrumentation.getContext().getSystemService(
1079                 Context.ACCESSIBILITY_SERVICE)).getEnabledAccessibilityServiceList(
1080                 FEEDBACK_ALL_MASK);
1081     }
1082 
getEnabledServicesSetting()1083     private String getEnabledServicesSetting() {
1084         final String result = Settings.Secure.getString(
1085                 sInstrumentation.getContext().getContentResolver(),
1086                 Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
1087         return result != null ? result : "";
1088     }
1089 
assertPackageName(AccessibilityNodeInfo node, String packageName)1090     private static void assertPackageName(AccessibilityNodeInfo node, String packageName) {
1091         if (node == null) {
1092             return;
1093         }
1094         assertEquals(packageName, node.getPackageName());
1095         final int childCount = node.getChildCount();
1096         for (int i = 0; i < childCount; i++) {
1097             AccessibilityNodeInfo child = node.getChild(i);
1098             if (child != null) {
1099                 assertPackageName(child, packageName);
1100             }
1101         }
1102     }
1103 
enableTouchExploration(Instrumentation instrumentation, boolean enabled)1104     private static void enableTouchExploration(Instrumentation instrumentation, boolean enabled)
1105             throws InterruptedException {
1106         final int TIMEOUT_FOR_SERVICE_ENABLE = 10000; // millis; 10s
1107         final Object waitObject = new Object();
1108         final AtomicBoolean atomicBoolean = new AtomicBoolean(!enabled);
1109         AccessibilityManager.TouchExplorationStateChangeListener serviceListener = (boolean b) -> {
1110             synchronized (waitObject) {
1111                 atomicBoolean.set(b);
1112                 waitObject.notifyAll();
1113             }
1114         };
1115         final AccessibilityManager manager =
1116                 (AccessibilityManager) instrumentation.getContext().getSystemService(
1117                         Service.ACCESSIBILITY_SERVICE);
1118         manager.addTouchExplorationStateChangeListener(serviceListener);
1119 
1120         final UiAutomation uiAutomation = instrumentation.getUiAutomation();
1121         final AccessibilityServiceInfo info = uiAutomation.getServiceInfo();
1122         assert info != null;
1123         if (enabled) {
1124             info.flags |= AccessibilityServiceInfo.FLAG_REQUEST_TOUCH_EXPLORATION_MODE;
1125         } else {
1126             info.flags &= ~AccessibilityServiceInfo.FLAG_REQUEST_TOUCH_EXPLORATION_MODE;
1127         }
1128         uiAutomation.setServiceInfo(info);
1129 
1130         final long timeoutTime = System.currentTimeMillis() + TIMEOUT_FOR_SERVICE_ENABLE;
1131         synchronized (waitObject) {
1132             while ((enabled != atomicBoolean.get()) && (System.currentTimeMillis() < timeoutTime)) {
1133                 waitObject.wait(timeoutTime - System.currentTimeMillis());
1134             }
1135         }
1136         if (enabled) {
1137             assertTrue("Touch exploration state listener not called when services enabled",
1138                     atomicBoolean.get());
1139             assertTrue("Timed out enabling accessibility",
1140                     manager.isEnabled() && manager.isTouchExplorationEnabled());
1141         } else {
1142             assertFalse("Touch exploration state listener not called when services disabled",
1143                     atomicBoolean.get());
1144             assertFalse("Timed out disabling accessibility",
1145                     manager.isEnabled() && manager.isTouchExplorationEnabled());
1146         }
1147         manager.removeTouchExplorationStateChangeListener(serviceListener);
1148     }
1149 
matchHover(int action, int x, int y)1150     private static MotionEvent matchHover(int action, int x, int y) {
1151         return argThat(new CtsMouseUtil.PositionMatcher(action, x, y));
1152     }
1153 
injectHoverEvent(long downTime, boolean isFirstHoverEvent, int xOnScreen, int yOnScreen)1154     private static void injectHoverEvent(long downTime, boolean isFirstHoverEvent,
1155             int xOnScreen, int yOnScreen) {
1156         final long eventTime = isFirstHoverEvent ? SystemClock.uptimeMillis() : downTime;
1157         MotionEvent event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_HOVER_MOVE,
1158                 xOnScreen, yOnScreen, 0);
1159         event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
1160         sUiAutomation.injectInputEvent(event, true);
1161         event.recycle();
1162     }
1163 
getAppWidgetProviderInfo()1164     private AppWidgetProviderInfo getAppWidgetProviderInfo() {
1165         final ComponentName componentName = new ComponentName(
1166                 "foo.bar.baz", "foo.bar.baz.MyAppWidgetProvider");
1167         final List<AppWidgetProviderInfo> providers = getAppWidgetManager().getInstalledProviders();
1168         final int providerCount = providers.size();
1169         for (int i = 0; i < providerCount; i++) {
1170             final AppWidgetProviderInfo provider = providers.get(i);
1171             if (componentName.equals(provider.provider)
1172                     && Process.myUserHandle().equals(provider.getProfile())) {
1173                 return provider;
1174             }
1175         }
1176         return null;
1177     }
1178 
grantBindAppWidgetPermission()1179     private void grantBindAppWidgetPermission() throws Exception {
1180         ShellCommandBuilder.execShellCommand(sUiAutomation,
1181                 GRANT_BIND_APP_WIDGET_PERMISSION_COMMAND + getCurrentUser());
1182     }
1183 
revokeBindAppWidgetPermission()1184     private void revokeBindAppWidgetPermission() throws Exception {
1185         ShellCommandBuilder.execShellCommand(sUiAutomation,
1186                 REVOKE_BIND_APP_WIDGET_PERMISSION_COMMAND + getCurrentUser());
1187     }
1188 
getAppWidgetManager()1189     private AppWidgetManager getAppWidgetManager() {
1190         return (AppWidgetManager) sInstrumentation.getTargetContext()
1191                 .getSystemService(Context.APPWIDGET_SERVICE);
1192     }
1193 
hasAppWidgets()1194     private boolean hasAppWidgets() {
1195         return sInstrumentation.getTargetContext().getPackageManager()
1196                 .hasSystemFeature(PackageManager.FEATURE_APP_WIDGETS);
1197     }
1198 
1199     /**
1200      * Compares all properties of the <code>first</code> and the
1201      * <code>second</code>.
1202      */
equalsAccessiblityEvent(AccessibilityEvent first, AccessibilityEvent second)1203     private boolean equalsAccessiblityEvent(AccessibilityEvent first, AccessibilityEvent second) {
1204          return first.getEventType() == second.getEventType()
1205             && first.isChecked() == second.isChecked()
1206             && first.getCurrentItemIndex() == second.getCurrentItemIndex()
1207             && first.isEnabled() == second.isEnabled()
1208             && first.getFromIndex() == second.getFromIndex()
1209             && first.getItemCount() == second.getItemCount()
1210             && first.isPassword() == second.isPassword()
1211             && first.getRemovedCount() == second.getRemovedCount()
1212             && first.isScrollable()== second.isScrollable()
1213             && first.getToIndex() == second.getToIndex()
1214             && first.getRecordCount() == second.getRecordCount()
1215             && first.getScrollX() == second.getScrollX()
1216             && first.getScrollY() == second.getScrollY()
1217             && first.getAddedCount() == second.getAddedCount()
1218             && first.getDisplayId() == second.getDisplayId()
1219             && TextUtils.equals(first.getBeforeText(), second.getBeforeText())
1220             && TextUtils.equals(first.getClassName(), second.getClassName())
1221             && TextUtils.equals(first.getContentDescription(), second.getContentDescription())
1222             && equalsNotificationAsParcelableData(first, second)
1223             && equalsText(first, second);
1224     }
1225 
1226     /**
1227      * Compares the {@link android.os.Parcelable} data of the
1228      * <code>first</code> and <code>second</code>.
1229      */
equalsNotificationAsParcelableData(AccessibilityEvent first, AccessibilityEvent second)1230     private boolean equalsNotificationAsParcelableData(AccessibilityEvent first,
1231             AccessibilityEvent second) {
1232         Notification firstNotification = (Notification) first.getParcelableData();
1233         Notification secondNotification = (Notification) second.getParcelableData();
1234         if (firstNotification == null) {
1235             return (secondNotification == null);
1236         } else if (secondNotification == null) {
1237             return false;
1238         }
1239         return TextUtils.equals(firstNotification.tickerText, secondNotification.tickerText);
1240     }
1241 
1242     /**
1243      * Compares the text of the <code>first</code> and <code>second</code> text.
1244      */
equalsText(AccessibilityEvent first, AccessibilityEvent second)1245     private boolean equalsText(AccessibilityEvent first, AccessibilityEvent second) {
1246         List<CharSequence> firstText = first.getText();
1247         List<CharSequence> secondText = second.getText();
1248         if (firstText.size() != secondText.size()) {
1249             return false;
1250         }
1251         Iterator<CharSequence> firstIterator = firstText.iterator();
1252         Iterator<CharSequence> secondIterator = secondText.iterator();
1253         for (int i = 0; i < firstText.size(); i++) {
1254             if (!firstIterator.next().toString().equals(secondIterator.next().toString())) {
1255                 return false;
1256             }
1257         }
1258         return true;
1259     }
1260 
hasTooltipShowing(int id)1261     private boolean hasTooltipShowing(int id) {
1262         return getOnMain(sInstrumentation, () -> {
1263             final View viewWithTooltip = mActivity.findViewById(id);
1264             if (viewWithTooltip == null) {
1265                 return false;
1266             }
1267             final View tooltipView = viewWithTooltip.getTooltipView();
1268             return (tooltipView != null) && (tooltipView.getParent() != null);
1269         });
1270     }
1271 
getCurrentUser()1272     private static int getCurrentUser() {
1273         return android.os.Process.myUserHandle().getIdentifier();
1274     }
1275 }
1276