• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.internal.accessibility;
18 
19 import static androidx.test.espresso.Espresso.onView;
20 import static androidx.test.espresso.action.ViewActions.click;
21 import static androidx.test.espresso.action.ViewActions.scrollTo;
22 import static androidx.test.espresso.action.ViewActions.swipeUp;
23 import static androidx.test.espresso.assertion.ViewAssertions.doesNotExist;
24 import static androidx.test.espresso.assertion.ViewAssertions.matches;
25 import static androidx.test.espresso.matcher.RootMatchers.isDialog;
26 import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
27 import static androidx.test.espresso.matcher.ViewMatchers.withClassName;
28 import static androidx.test.espresso.matcher.ViewMatchers.withText;
29 
30 import static com.google.common.truth.Truth.assertThat;
31 
32 import static org.hamcrest.Matchers.allOf;
33 import static org.hamcrest.Matchers.endsWith;
34 import static org.junit.Assume.assumeFalse;
35 import static org.mockito.ArgumentMatchers.any;
36 import static org.mockito.ArgumentMatchers.anyInt;
37 import static org.mockito.ArgumentMatchers.anyString;
38 import static org.mockito.ArgumentMatchers.eq;
39 import static org.mockito.Mockito.verify;
40 import static org.mockito.Mockito.when;
41 
42 import android.accessibilityservice.AccessibilityServiceInfo;
43 import android.app.AlertDialog;
44 import android.app.Dialog;
45 import android.app.KeyguardManager;
46 import android.content.ComponentName;
47 import android.content.Context;
48 import android.content.pm.ApplicationInfo;
49 import android.content.pm.PackageInstaller;
50 import android.content.pm.PackageManager;
51 import android.content.pm.ParceledListSlice;
52 import android.content.pm.ResolveInfo;
53 import android.content.pm.ServiceInfo;
54 import android.os.Bundle;
55 import android.os.Handler;
56 import android.platform.test.flag.junit.CheckFlagsRule;
57 import android.platform.test.flag.junit.DeviceFlagsValueProvider;
58 import android.support.test.uiautomator.By;
59 import android.support.test.uiautomator.UiDevice;
60 import android.support.test.uiautomator.UiObject2;
61 import android.support.test.uiautomator.Until;
62 import android.view.View;
63 import android.view.WindowManager;
64 import android.view.accessibility.AccessibilityManager;
65 import android.view.accessibility.IAccessibilityManager;
66 import android.widget.Button;
67 
68 import androidx.lifecycle.Lifecycle;
69 import androidx.test.core.app.ActivityScenario;
70 import androidx.test.ext.junit.runners.AndroidJUnit4;
71 import androidx.test.platform.app.InstrumentationRegistry;
72 
73 import com.android.internal.R;
74 import com.android.internal.accessibility.dialog.AccessibilityShortcutChooserActivity;
75 
76 import org.junit.After;
77 import org.junit.Before;
78 import org.junit.Rule;
79 import org.junit.Test;
80 import org.junit.runner.RunWith;
81 import org.mockito.Mock;
82 import org.mockito.Mockito;
83 import org.mockito.junit.MockitoJUnit;
84 import org.mockito.junit.MockitoRule;
85 import org.mockito.stubbing.Answer;
86 
87 import java.util.Collections;
88 import java.util.concurrent.atomic.AtomicBoolean;
89 
90 /**
91  * Tests for {@link AccessibilityShortcutChooserActivity}.
92  */
93 @RunWith(AndroidJUnit4.class)
94 public class AccessibilityShortcutChooserActivityTest {
95     private static final String ONE_HANDED_MODE = "One-Handed mode";
96     private static final String ALLOW_LABEL = "Allow";
97     private static final String DENY_LABEL = "Deny";
98     private static final String UNINSTALL_LABEL = "Uninstall";
99     private static final String EDIT_LABEL = "Edit shortcuts";
100     private static final String LIST_TITLE_LABEL = "Choose features to use";
101     private static final String TEST_LABEL = "TEST_LABEL";
102     private static final String TEST_PACKAGE = "TEST_LABEL";
103     private static final ComponentName TEST_COMPONENT_NAME = new ComponentName(TEST_PACKAGE,
104             "class");
105     private static final long UI_TIMEOUT_MS = 1000;
106     private UiDevice mDevice;
107     private ActivityScenario<TestAccessibilityShortcutChooserActivity> mScenario;
108     private TestAccessibilityShortcutChooserActivity mActivity;
109 
110     @Rule
111     public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
112 
113     @Rule
114     public final MockitoRule mMockitoRule = MockitoJUnit.rule();
115     @Mock
116     private AccessibilityServiceInfo mAccessibilityServiceInfo;
117     @Mock
118     private ResolveInfo mResolveInfo;
119     @Mock
120     private ServiceInfo mServiceInfo;
121     @Mock
122     private ApplicationInfo mApplicationInfo;
123     @Mock
124     private IAccessibilityManager mAccessibilityManagerService;
125     @Mock
126     private KeyguardManager mKeyguardManager;
127     @Mock
128     private PackageManager mPackageManager;
129     @Mock
130     private PackageInstaller mPackageInstaller;
131 
132     @Before
setUp()133     public void setUp() throws Exception {
134         final PackageManager pm =
135                 InstrumentationRegistry.getInstrumentation().getContext().getPackageManager();
136         assumeFalse("AccessibilityShortcutChooserActivity not supported on watch",
137                 pm.hasSystemFeature(PackageManager.FEATURE_WATCH));
138 
139         mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
140         mDevice.wakeUp();
141         when(mAccessibilityServiceInfo.getResolveInfo()).thenReturn(mResolveInfo);
142         mResolveInfo.serviceInfo = mServiceInfo;
143         mServiceInfo.applicationInfo = mApplicationInfo;
144         when(mResolveInfo.loadLabel(any(PackageManager.class))).thenReturn(TEST_LABEL);
145         when(mAccessibilityServiceInfo.getComponentName()).thenReturn(TEST_COMPONENT_NAME);
146         when(mAccessibilityManagerService.getInstalledAccessibilityServiceList(
147                 anyInt())).thenReturn(new ParceledListSlice<>(
148                 Collections.singletonList(mAccessibilityServiceInfo)));
149         when(mAccessibilityManagerService.isAccessibilityServiceWarningRequired(any()))
150                 .thenReturn(true);
151         when(mAccessibilityManagerService.isAccessibilityTargetAllowed(
152                 anyString(), anyInt(), anyInt())).thenReturn(true);
153         when(mKeyguardManager.isKeyguardLocked()).thenReturn(false);
154         when(mPackageManager.getPackageInstaller()).thenReturn(mPackageInstaller);
155 
156         TestAccessibilityShortcutChooserActivity.setupForTesting(
157                 mAccessibilityManagerService, mKeyguardManager,
158                 mPackageManager);
159     }
160 
161     @After
cleanUp()162     public void cleanUp() {
163         if (mScenario != null) {
164             mScenario.close();
165         }
166         if (mActivity != null) {
167             Dialog permissionDialog = mActivity.getPermissionDialog();
168             if (permissionDialog != null && permissionDialog.isShowing()) {
169                 permissionDialog.dismiss();
170             }
171         }
172     }
173 
174     @Test
selectTestService_permissionDialog_allow_rowChecked()175     public void selectTestService_permissionDialog_allow_rowChecked() {
176         launchActivity();
177         openShortcutsList();
178 
179         mDevice.findObject(By.text(TEST_LABEL)).clickAndWait(Until.newWindow(), UI_TIMEOUT_MS);
180         clickPermissionDialogButton(R.id.accessibility_permission_enable_allow_button);
181 
182         assertThat(mDevice.wait(Until.hasObject(By.checked(true)), UI_TIMEOUT_MS)).isTrue();
183     }
184 
185     @Test
selectTestService_permissionDialog_deny_rowNotChecked()186     public void selectTestService_permissionDialog_deny_rowNotChecked() {
187         launchActivity();
188         openShortcutsList();
189 
190         mDevice.findObject(By.text(TEST_LABEL)).clickAndWait(Until.newWindow(), UI_TIMEOUT_MS);
191         clickPermissionDialogButton(R.id.accessibility_permission_enable_deny_button);
192 
193         assertThat(mDevice.wait(Until.hasObject(By.checked(true)), UI_TIMEOUT_MS)).isFalse();
194     }
195 
196     @Test
selectTestService_permissionDialog_uninstall_callsUninstaller_rowRemoved()197     public void selectTestService_permissionDialog_uninstall_callsUninstaller_rowRemoved() {
198         launchActivity();
199         openShortcutsList();
200 
201         mDevice.findObject(By.text(TEST_LABEL)).clickAndWait(Until.newWindow(), UI_TIMEOUT_MS);
202         clickPermissionDialogButton(R.id.accessibility_permission_enable_uninstall_button);
203 
204         verify(mPackageInstaller).uninstall(eq(TEST_PACKAGE), any());
205         assertThat(mDevice.wait(Until.hasObject(By.textStartsWith(TEST_LABEL)),
206                 UI_TIMEOUT_MS)).isFalse();
207     }
208 
209     @Test
selectTestService_permissionDialog_notShownWhenNotRequired()210     public void selectTestService_permissionDialog_notShownWhenNotRequired() throws Exception {
211         when(mAccessibilityManagerService.isAccessibilityServiceWarningRequired(any()))
212                 .thenReturn(false);
213         launchActivity();
214         openShortcutsList();
215 
216         // Clicking the test service should not show a permission dialog window,
217         assertThat(mDevice.findObject(By.text(TEST_LABEL)).clickAndWait(
218                 Until.newWindow(), UI_TIMEOUT_MS)).isFalse();
219         // and should become checked.
220         assertThat(mDevice.findObject(By.checked(true))).isNotNull();
221     }
222 
223     @Test
selectTestService_notPermittedByAdmin_blockedEvenIfNoWarningRequired()224     public void selectTestService_notPermittedByAdmin_blockedEvenIfNoWarningRequired()
225             throws Exception {
226         when(mAccessibilityManagerService.isAccessibilityServiceWarningRequired(any()))
227                 .thenReturn(false);
228         when(mAccessibilityManagerService.isAccessibilityTargetAllowed(
229                 eq(TEST_COMPONENT_NAME.getPackageName()), anyInt(), anyInt())).thenReturn(false);
230         // This test class mocks AccessibilityManagerService, so the restricted dialog window
231         // will not actually appear and therefore cannot be used for a wait Until.newWindow().
232         // To still allow smart waiting in this test we can instead set up the mocked method
233         // to update an atomic boolean and wait for that to be set.
234         final Object waitObject = new Object();
235         final AtomicBoolean calledSendRestrictedDialogIntent = new AtomicBoolean(false);
236         Mockito.doAnswer((Answer<Void>) invocation -> {
237             synchronized (waitObject) {
238                 calledSendRestrictedDialogIntent.set(true);
239                 waitObject.notify();
240             }
241             return null;
242         }).when(mAccessibilityManagerService).sendRestrictedDialogIntent(
243                 eq(TEST_COMPONENT_NAME.getPackageName()), anyInt(), anyInt());
244         launchActivity();
245         openShortcutsList();
246 
247         mDevice.findObject(By.text(TEST_LABEL)).click();
248         final long timeout = System.currentTimeMillis() + UI_TIMEOUT_MS;
249         synchronized (waitObject) {
250             while (!calledSendRestrictedDialogIntent.get() &&
251                     (System.currentTimeMillis() < timeout)) {
252                 waitObject.wait(timeout - System.currentTimeMillis());
253             }
254         }
255 
256         assertThat(calledSendRestrictedDialogIntent.get()).isTrue();
257         assertThat(mDevice.findObject(By.checked(true))).isNull();
258     }
259 
260     @Test
clickServiceTarget_notPermittedByAdmin_sendRestrictedDialogIntent()261     public void clickServiceTarget_notPermittedByAdmin_sendRestrictedDialogIntent()
262             throws Exception {
263         when(mAccessibilityManagerService.isAccessibilityTargetAllowed(
264                 eq(TEST_COMPONENT_NAME.getPackageName()), anyInt(), anyInt())).thenReturn(false);
265         launchActivity();
266         openShortcutsList();
267 
268         onView(withText(TEST_LABEL)).perform(scrollTo(), click());
269 
270         verify(mAccessibilityManagerService).sendRestrictedDialogIntent(
271                 eq(TEST_COMPONENT_NAME.getPackageName()), anyInt(), anyInt());
272     }
273 
274     @Test
popEditShortcutMenuList_oneHandedModeEnabled_shouldBeInListView()275     public void popEditShortcutMenuList_oneHandedModeEnabled_shouldBeInListView() {
276         TestUtils.setOneHandedModeEnabled(this, /* enabled= */ true);
277         launchActivity();
278         openShortcutsList();
279 
280         onView(allOf(withClassName(endsWith("ListView")), isDisplayed())).perform(swipeUp());
281         mDevice.wait(Until.hasObject(By.text(ONE_HANDED_MODE)), UI_TIMEOUT_MS);
282 
283         onView(withText(ONE_HANDED_MODE)).inRoot(isDialog()).check(matches(isDisplayed()));
284     }
285 
286     @Test
popEditShortcutMenuList_oneHandedModeDisabled_shouldNotBeInListView()287     public void popEditShortcutMenuList_oneHandedModeDisabled_shouldNotBeInListView() {
288         TestUtils.setOneHandedModeEnabled(this, /* enabled= */ false);
289         launchActivity();
290         openShortcutsList();
291 
292         onView(allOf(withClassName(endsWith("ListView")), isDisplayed())).perform(swipeUp());
293         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
294 
295         onView(withText(ONE_HANDED_MODE)).inRoot(isDialog()).check(doesNotExist());
296     }
297 
298     @Test
createDialog_onLockscreen_hasExpectedContent()299     public void createDialog_onLockscreen_hasExpectedContent() {
300         when(mKeyguardManager.isKeyguardLocked()).thenReturn(true);
301         launchActivity();
302 
303         final AlertDialog dialog = mActivity.getMenuDialog();
304 
305         assertThat(dialog.getButton(AlertDialog.BUTTON_POSITIVE).getVisibility())
306                 .isEqualTo(View.GONE);
307         assertThat(dialog.getWindow().getAttributes().flags
308                 & WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED)
309                 .isEqualTo(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
310     }
311 
launchActivity()312     private void launchActivity() {
313         mScenario = ActivityScenario.launch(TestAccessibilityShortcutChooserActivity.class);
314         mScenario.onActivity(activity -> mActivity = activity);
315         mScenario.moveToState(Lifecycle.State.CREATED);
316         mScenario.moveToState(Lifecycle.State.STARTED);
317         mScenario.moveToState(Lifecycle.State.RESUMED);
318         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
319     }
320 
openShortcutsList()321     private void openShortcutsList() {
322         UiObject2 editButton = mDevice.findObject(By.text(EDIT_LABEL));
323         if (editButton != null) {
324             editButton.click();
325         }
326         mDevice.wait(Until.hasObject(By.textStartsWith(LIST_TITLE_LABEL)), UI_TIMEOUT_MS);
327     }
328 
clickPermissionDialogButton(int buttonId)329     private void clickPermissionDialogButton(int buttonId) {
330         Button button = mActivity.getPermissionDialog().findViewById(buttonId);
331         mActivity.runOnUiThread(button::performClick);
332         // Wait for the dialog to go away by waiting for the shortcut chooser
333         // to become visible again.
334         assertThat(mDevice.wait(Until.hasObject(By.textStartsWith(LIST_TITLE_LABEL)),
335                 UI_TIMEOUT_MS)).isTrue();
336     }
337 
338     /**
339      * Used for testing.
340      */
341     public static class TestAccessibilityShortcutChooserActivity extends
342             AccessibilityShortcutChooserActivity {
343         private static IAccessibilityManager sAccessibilityManagerService;
344         private static KeyguardManager sKeyguardManager;
345         private static PackageManager sPackageManager;
346 
setupForTesting( IAccessibilityManager accessibilityManagerService, KeyguardManager keyguardManager, PackageManager packageManager)347         public static void setupForTesting(
348                 IAccessibilityManager accessibilityManagerService,
349                 KeyguardManager keyguardManager,
350                 PackageManager packageManager) {
351             sAccessibilityManagerService = accessibilityManagerService;
352             sKeyguardManager = keyguardManager;
353             sPackageManager = packageManager;
354         }
355 
356         @Override
onCreate(Bundle savedInstanceState)357         public void onCreate(Bundle savedInstanceState) {
358             super.onCreate(savedInstanceState);
359             // Setting the Theme is necessary here for the dialog to use the proper style
360             // resources as designated in its layout XML.
361             setTheme(R.style.Theme_DeviceDefault_DayNight);
362         }
363 
364         @Override
getPackageManager()365         public PackageManager getPackageManager() {
366             return sPackageManager;
367         }
368 
369         @Override
getSystemService(String name)370         public Object getSystemService(String name) {
371             if (Context.ACCESSIBILITY_SERVICE.equals(name)
372                     && sAccessibilityManagerService != null) {
373                 // Warning: This new AccessibilityManager complicates UI inspection
374                 // because it breaks the expected "singleton per process" quality of
375                 // AccessibilityManager. Debug here if observing unexpected issues
376                 // with UI inspection or interaction.
377                 return new AccessibilityManager(this, new Handler(getMainLooper()),
378                         sAccessibilityManagerService, /* userId= */ 0, /* serviceConnect= */ true);
379             }
380             if (Context.KEYGUARD_SERVICE.equals(name)) {
381                 return sKeyguardManager;
382             }
383 
384             return super.getSystemService(name);
385         }
386     }
387 }
388