• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 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.view;
18 
19 import static com.google.common.truth.Truth.assertThat;
20 import static com.google.common.truth.Truth.assertWithMessage;
21 
22 import android.accessibilityservice.AccessibilityServiceInfo;
23 import android.app.Activity;
24 import android.app.Instrumentation;
25 import android.app.Service;
26 import android.app.UiAutomation;
27 import android.graphics.Rect;
28 import android.os.Process;
29 import android.os.SystemClock;
30 import android.text.TextUtils;
31 import android.view.accessibility.AccessibilityEvent;
32 import android.view.accessibility.AccessibilityManager;
33 import android.view.accessibility.AccessibilityNodeInfo;
34 import android.view.accessibility.AccessibilityTestActivity;
35 import android.view.accessibility.AccessibilityWindowInfo;
36 import android.view.accessibility.IWindowSurfaceInfoCallback;
37 
38 import androidx.test.InstrumentationRegistry;
39 import androidx.test.ext.junit.runners.AndroidJUnit4;
40 import androidx.test.rule.ActivityTestRule;
41 
42 import com.android.compatibility.common.util.TestUtils;
43 import com.android.frameworks.coretests.R;
44 
45 import org.junit.After;
46 import org.junit.AfterClass;
47 import org.junit.Before;
48 import org.junit.BeforeClass;
49 import org.junit.Rule;
50 import org.junit.Test;
51 import org.junit.runner.RunWith;
52 import org.mockito.Mockito;
53 
54 import java.util.List;
55 import java.util.concurrent.TimeoutException;
56 
57 @RunWith(AndroidJUnit4.class)
58 public class AccessibilityInteractionControllerTest {
59     static final long TIMEOUT_DEFAULT = 10000; // 10 seconds
60 
61     private static Instrumentation sInstrumentation;
62     private static UiAutomation sUiAutomation;
63 
64     @Rule
65     public ActivityTestRule<AccessibilityTestActivity> mActivityRule = new ActivityTestRule<>(
66             AccessibilityTestActivity.class, false, false);
67 
68     private AccessibilityInteractionController mAccessibilityInteractionController;
69     private ViewRootImpl mViewRootImpl;
70     private View mButton;
71 
72     @BeforeClass
oneTimeSetup()73     public static void oneTimeSetup() {
74         sInstrumentation = InstrumentationRegistry.getInstrumentation();
75         sUiAutomation = sInstrumentation.getUiAutomation();
76     }
77 
78     @AfterClass
postTestTearDown()79     public static void postTestTearDown() {
80         sUiAutomation.destroy();
81     }
82 
83     @Before
setUp()84     public void setUp() throws Throwable {
85         launchActivity();
86         enableTouchExploration(true);
87         mActivityRule.runOnUiThread(() -> {
88             mViewRootImpl = mActivityRule.getActivity().getWindow().getDecorView()
89                     .getViewRootImpl();
90             mButton = mActivityRule.getActivity().findViewById(R.id.appNameBtn);
91         });
92         mAccessibilityInteractionController =
93                 mViewRootImpl.getAccessibilityInteractionController();
94     }
95 
96     @After
tearDown()97     public void tearDown() {
98         enableTouchExploration(false);
99     }
100 
101     @Test
clearAccessibilityFocus_shouldClearFocus()102     public void clearAccessibilityFocus_shouldClearFocus() throws Exception {
103         performAccessibilityFocus("com.android.frameworks.coretests:id/appNameBtn");
104         assertWithMessage("Button should have a11y focus").that(
105                 mButton.isAccessibilityFocused()).isTrue();
106         mAccessibilityInteractionController.clearAccessibilityFocusClientThread();
107         sInstrumentation.waitForIdleSync();
108         assertWithMessage("Button should not have a11y focus").that(
109                 mButton.isAccessibilityFocused()).isFalse();
110     }
111 
112     @Test
clearAccessibilityFocus_uiThread_shouldClearFocus()113     public void clearAccessibilityFocus_uiThread_shouldClearFocus() throws Exception {
114         performAccessibilityFocus("com.android.frameworks.coretests:id/appNameBtn");
115         assertWithMessage("Button should have a11y focus").that(
116                 mButton.isAccessibilityFocused()).isTrue();
117         sInstrumentation.runOnMainSync(() ->
118                 mAccessibilityInteractionController.clearAccessibilityFocusClientThread());
119         assertWithMessage("Button should not have a11y focus").that(
120                 mButton.isAccessibilityFocused()).isFalse();
121     }
122 
123     @Test
clearAccessibilityFocus_sensitiveRootView_shouldClearFocus()124     public void clearAccessibilityFocus_sensitiveRootView_shouldClearFocus()
125             throws Exception {
126         final View rootView = mButton.getRootView();
127         assertThat(rootView).isNotNull();
128         try {
129             rootView.setAccessibilityDataSensitive(View.ACCESSIBILITY_DATA_SENSITIVE_YES);
130             performAccessibilityFocus("com.android.frameworks.coretests:id/appNameBtn");
131             assertWithMessage("Button should have a11y focus").that(
132                     mButton.isAccessibilityFocused()).isTrue();
133             sInstrumentation.runOnMainSync(() ->
134                     mAccessibilityInteractionController.clearAccessibilityFocusClientThread());
135             assertWithMessage("Button should not have a11y focus").that(
136                     mButton.isAccessibilityFocused()).isFalse();
137         } finally {
138             rootView.setAccessibilityDataSensitive(View.ACCESSIBILITY_DATA_SENSITIVE_AUTO);
139         }
140     }
141 
142     @Test
getWindowSurfaceInfo_shouldCallCallbackWithWindowSurfaceDataFromVri()143     public void getWindowSurfaceInfo_shouldCallCallbackWithWindowSurfaceDataFromVri()
144             throws Exception {
145         final ViewRootImpl vri = mButton.getRootView().getViewRootImpl();
146         IWindowSurfaceInfoCallback callback = Mockito.mock(IWindowSurfaceInfoCallback.class);
147 
148         sInstrumentation.runOnMainSync(() ->
149                 mAccessibilityInteractionController.getWindowSurfaceInfoClientThread(callback));
150         sInstrumentation.waitForIdleSync();
151 
152         Mockito.verify(callback).provideWindowSurfaceInfo(
153                 vri.getWindowFlags(), Process.myUid(), vri.getSurfaceControl());
154     }
155 
launchActivity()156     private void launchActivity() {
157         final Object waitObject = new Object();
158         final int[] location = new int[2];
159         final StringBuilder activityPackage = new StringBuilder();
160         final Rect bounds = new Rect();
161         final StringBuilder activityTitle = new StringBuilder();
162         try {
163             final long executionStartTimeMillis = SystemClock.uptimeMillis();
164             sUiAutomation.setOnAccessibilityEventListener((event) -> {
165                 if (event.getEventTime() < executionStartTimeMillis) {
166                     return;
167                 }
168                 synchronized (waitObject) {
169                     waitObject.notifyAll();
170                 }
171             });
172             enableRetrieveAccessibilityWindows();
173 
174             final Activity activity = mActivityRule.launchActivity(null);
175             sInstrumentation.runOnMainSync(() -> {
176                 activity.getWindow().getDecorView().getLocationOnScreen(location);
177                 activityPackage.append(activity.getPackageName());
178                 activityTitle.append(activity.getTitle());
179             });
180             sInstrumentation.waitForIdleSync();
181 
182             TestUtils.waitOn(waitObject, () -> {
183                 final AccessibilityWindowInfo window = findWindowByTitle(activityTitle);
184                 if (window == null) return false;
185                 window.getBoundsInScreen(bounds);
186                 activity.getWindow().getDecorView().getLocationOnScreen(location);
187                 if (bounds.isEmpty()) {
188                     return false;
189                 }
190                 return (!bounds.isEmpty())
191                         && (bounds.left == location[0]) && (bounds.top == location[1]);
192             }, TIMEOUT_DEFAULT, "Launch Activity");
193         } finally {
194             sUiAutomation.setOnAccessibilityEventListener(null);
195         }
196     }
197 
enableRetrieveAccessibilityWindows()198     private void enableRetrieveAccessibilityWindows() {
199         AccessibilityServiceInfo info = sUiAutomation.getServiceInfo();
200         info.flags |= AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS;
201         sUiAutomation.setServiceInfo(info);
202     }
203 
enableTouchExploration(boolean enabled)204     private void enableTouchExploration(boolean enabled) {
205         final Object waitObject = new Object();
206         final AccessibilityManager accessibilityManager =
207                 (AccessibilityManager) sInstrumentation.getContext().getSystemService(
208                         Service.ACCESSIBILITY_SERVICE);
209         final AccessibilityManager.TouchExplorationStateChangeListener listener = status -> {
210             synchronized (waitObject) {
211                 waitObject.notifyAll();
212             }
213         };
214         try {
215             accessibilityManager.addTouchExplorationStateChangeListener(listener);
216             final AccessibilityServiceInfo info = sUiAutomation.getServiceInfo();
217             if (enabled) {
218                 info.flags |= AccessibilityServiceInfo.FLAG_REQUEST_TOUCH_EXPLORATION_MODE;
219             } else {
220                 info.flags &= ~AccessibilityServiceInfo.FLAG_REQUEST_TOUCH_EXPLORATION_MODE;
221             }
222             sUiAutomation.setServiceInfo(info);
223             TestUtils.waitOn(waitObject,
224                     () -> accessibilityManager.isTouchExplorationEnabled() == enabled,
225                     TIMEOUT_DEFAULT,
226                     (enabled ? "Enable" : "Disable") + "touch exploration");
227         } finally {
228             accessibilityManager.removeTouchExplorationStateChangeListener(listener);
229         }
230     }
231 
performAccessibilityFocus(String viewId)232     private void performAccessibilityFocus(String viewId) throws TimeoutException {
233         final AccessibilityNodeInfo node = sUiAutomation.getRootInActiveWindow()
234                 .findAccessibilityNodeInfosByViewId(viewId).get(0);
235         // Perform an action and wait for an event
236         sUiAutomation.executeAndWaitForEvent(
237                 () -> node.performAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS),
238                 event -> event.getEventType() == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED,
239                 TIMEOUT_DEFAULT);
240         node.refresh();
241     }
242 
findWindowByTitle(CharSequence title)243     private AccessibilityWindowInfo findWindowByTitle(CharSequence title) {
244         final List<AccessibilityWindowInfo> windows = sUiAutomation.getWindows();
245         AccessibilityWindowInfo returnValue = null;
246         for (int i = 0; i < windows.size(); i++) {
247             final AccessibilityWindowInfo window = windows.get(i);
248             if (TextUtils.equals(title, window.getTitle())) {
249                 returnValue = window;
250             } else {
251                 window.recycle();
252             }
253         }
254         return returnValue;
255     }
256 }
257