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.doubleClick;
22 import static androidx.test.espresso.action.ViewActions.scrollTo;
23 import static androidx.test.espresso.action.ViewActions.swipeUp;
24 import static androidx.test.espresso.assertion.ViewAssertions.doesNotExist;
25 import static androidx.test.espresso.assertion.ViewAssertions.matches;
26 import static androidx.test.espresso.matcher.RootMatchers.isDialog;
27 import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
28 import static androidx.test.espresso.matcher.ViewMatchers.withClassName;
29 import static androidx.test.espresso.matcher.ViewMatchers.withId;
30 import static androidx.test.espresso.matcher.ViewMatchers.withText;
31 
32 import static org.hamcrest.Matchers.allOf;
33 import static org.hamcrest.Matchers.endsWith;
34 import static org.mockito.ArgumentMatchers.any;
35 import static org.mockito.ArgumentMatchers.anyInt;
36 import static org.mockito.ArgumentMatchers.anyString;
37 import static org.mockito.ArgumentMatchers.eq;
38 import static org.mockito.Mockito.verify;
39 import static org.mockito.Mockito.when;
40 
41 import android.accessibilityservice.AccessibilityServiceInfo;
42 import android.content.ComponentName;
43 import android.content.Context;
44 import android.content.pm.ApplicationInfo;
45 import android.content.pm.PackageManager;
46 import android.content.pm.ResolveInfo;
47 import android.content.pm.ServiceInfo;
48 import android.os.Handler;
49 import android.support.test.uiautomator.By;
50 import android.support.test.uiautomator.UiDevice;
51 import android.support.test.uiautomator.UiObject2;
52 import android.support.test.uiautomator.Until;
53 import android.view.accessibility.AccessibilityManager;
54 import android.view.accessibility.IAccessibilityManager;
55 
56 import androidx.lifecycle.Lifecycle;
57 import androidx.test.core.app.ActivityScenario;
58 import androidx.test.platform.app.InstrumentationRegistry;
59 import androidx.test.runner.AndroidJUnit4;
60 
61 import com.android.internal.R;
62 import com.android.internal.accessibility.dialog.AccessibilityShortcutChooserActivity;
63 
64 import org.junit.After;
65 import org.junit.Before;
66 import org.junit.Rule;
67 import org.junit.Test;
68 import org.junit.runner.RunWith;
69 import org.mockito.Mock;
70 import org.mockito.junit.MockitoJUnit;
71 import org.mockito.junit.MockitoRule;
72 
73 import java.util.Collections;
74 
75 /**
76  * Tests for {@link AccessibilityShortcutChooserActivity}.
77  */
78 @RunWith(AndroidJUnit4.class)
79 public class AccessibilityShortcutChooserActivityTest {
80     private static final String ONE_HANDED_MODE = "One-Handed mode";
81     private static final String DENY_LABEL = "Deny";
82     private static final String EDIT_LABEL = "Edit shortcuts";
83     private static final String LIST_TITLE_LABEL = "Choose features to use";
84     private static final String TEST_LABEL = "TEST_LABEL";
85     private static final ComponentName TEST_COMPONENT_NAME = new ComponentName("package", "class");
86     private static final long UI_TIMEOUT_MS = 1000;
87     private UiDevice mDevice;
88     private ActivityScenario<TestAccessibilityShortcutChooserActivity> mScenario;
89     private TestAccessibilityShortcutChooserActivity mActivity;
90 
91     @Rule
92     public final MockitoRule mMockitoRule = MockitoJUnit.rule();
93     @Mock
94     private AccessibilityServiceInfo mAccessibilityServiceInfo;
95     @Mock
96     private ResolveInfo mResolveInfo;
97     @Mock
98     private ServiceInfo mServiceInfo;
99     @Mock
100     private ApplicationInfo mApplicationInfo;
101     @Mock
102     private IAccessibilityManager mAccessibilityManagerService;
103 
104     @Before
setUp()105     public void setUp() throws Exception {
106         mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
107         mDevice.wakeUp();
108         when(mAccessibilityServiceInfo.getResolveInfo()).thenReturn(mResolveInfo);
109         mResolveInfo.serviceInfo = mServiceInfo;
110         mServiceInfo.applicationInfo = mApplicationInfo;
111         when(mResolveInfo.loadLabel(any(PackageManager.class))).thenReturn(TEST_LABEL);
112         when(mAccessibilityServiceInfo.getComponentName()).thenReturn(TEST_COMPONENT_NAME);
113         when(mAccessibilityManagerService.getInstalledAccessibilityServiceList(
114                 anyInt())).thenReturn(Collections.singletonList(mAccessibilityServiceInfo));
115         when(mAccessibilityManagerService.isAccessibilityTargetAllowed(
116                 anyString(), anyInt(), anyInt())).thenReturn(true);
117         TestAccessibilityShortcutChooserActivity.setupForTesting(mAccessibilityManagerService);
118         mScenario = ActivityScenario.launch(TestAccessibilityShortcutChooserActivity.class);
119         mScenario.onActivity(activity -> mActivity = activity);
120         mScenario.moveToState(Lifecycle.State.CREATED);
121         mScenario.moveToState(Lifecycle.State.STARTED);
122         mScenario.moveToState(Lifecycle.State.RESUMED);
123         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
124     }
125 
126     @After
cleanUp()127     public void cleanUp() {
128         mScenario.moveToState(Lifecycle.State.DESTROYED);
129     }
130 
131     @Test
doubleClickTestServiceAndClickDenyButton_permissionDialogDoesNotExist()132     public void doubleClickTestServiceAndClickDenyButton_permissionDialogDoesNotExist() {
133         openShortcutsList();
134 
135         // Performing the double-click is flaky so retry if needed.
136         for (int attempt = 1; attempt <= 2; attempt++) {
137             onView(withText(TEST_LABEL)).perform(scrollTo(), doubleClick());
138             if (mDevice.wait(Until.hasObject(By.text(DENY_LABEL)), UI_TIMEOUT_MS)) {
139                 break;
140             }
141         }
142 
143         onView(withText(DENY_LABEL)).perform(scrollTo(), click());
144         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
145 
146         onView(withId(R.id.accessibility_permissionDialog_title)).inRoot(isDialog()).check(
147                 doesNotExist());
148     }
149 
150     @Test
clickServiceTarget_notPermittedByAdmin_sendRestrictedDialogIntent()151     public void clickServiceTarget_notPermittedByAdmin_sendRestrictedDialogIntent()
152             throws Exception {
153         when(mAccessibilityManagerService.isAccessibilityTargetAllowed(
154                 eq(TEST_COMPONENT_NAME.getPackageName()), anyInt(), anyInt())).thenReturn(false);
155         openShortcutsList();
156 
157         onView(withText(TEST_LABEL)).perform(scrollTo(), click());
158 
159         verify(mAccessibilityManagerService).sendRestrictedDialogIntent(
160                 eq(TEST_COMPONENT_NAME.getPackageName()), anyInt(), anyInt());
161     }
162 
163     @Test
popEditShortcutMenuList_oneHandedModeEnabled_shouldBeInListView()164     public void popEditShortcutMenuList_oneHandedModeEnabled_shouldBeInListView() {
165         TestUtils.setOneHandedModeEnabled(this, /* enabled= */ true);
166         openShortcutsList();
167 
168         onView(allOf(withClassName(endsWith("ListView")), isDisplayed())).perform(swipeUp());
169         mDevice.wait(Until.hasObject(By.text(ONE_HANDED_MODE)), UI_TIMEOUT_MS);
170 
171         onView(withText(ONE_HANDED_MODE)).inRoot(isDialog()).check(matches(isDisplayed()));
172     }
173 
174     @Test
popEditShortcutMenuList_oneHandedModeDisabled_shouldNotBeInListView()175     public void popEditShortcutMenuList_oneHandedModeDisabled_shouldNotBeInListView() {
176         TestUtils.setOneHandedModeEnabled(this, /* enabled= */ false);
177         openShortcutsList();
178 
179         onView(allOf(withClassName(endsWith("ListView")), isDisplayed())).perform(swipeUp());
180         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
181 
182         onView(withText(ONE_HANDED_MODE)).inRoot(isDialog()).check(doesNotExist());
183     }
184 
openShortcutsList()185     private void openShortcutsList() {
186         UiObject2 editButton = mDevice.findObject(By.text(EDIT_LABEL));
187         if (editButton != null) {
188             editButton.click();
189         }
190         mDevice.wait(Until.hasObject(By.textStartsWith(LIST_TITLE_LABEL)), UI_TIMEOUT_MS);
191     }
192 
193     /**
194      * Used for testing.
195      */
196     public static class TestAccessibilityShortcutChooserActivity extends
197             AccessibilityShortcutChooserActivity {
198         private static IAccessibilityManager sAccessibilityManagerService;
199 
setupForTesting(IAccessibilityManager accessibilityManagerService)200         public static void setupForTesting(IAccessibilityManager accessibilityManagerService) {
201             sAccessibilityManagerService = accessibilityManagerService;
202         }
203 
204         @Override
getSystemService(String name)205         public Object getSystemService(String name) {
206             if (Context.ACCESSIBILITY_SERVICE.equals(name)
207                     && sAccessibilityManagerService != null) {
208                 return new AccessibilityManager(this, new Handler(getMainLooper()),
209                         sAccessibilityManagerService, /* userId= */ 0, /* serviceConnect= */ true);
210             }
211 
212             return super.getSystemService(name);
213         }
214     }
215 }
216