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