InterrogationActivityTest.java revision 8bd69610aafc6995126965d1d23b771fe02a9084
1/**
2 * Copyright (C) 2011 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 * in compliance with the License. You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software distributed under the
10 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11 * express or implied. See the License for the specific language governing permissions and
12 * limitations under the License.
13 */
14
15package android.accessibilityservice;
16
17import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLEAR_FOCUS;
18import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLEAR_SELECTION;
19import static android.view.accessibility.AccessibilityNodeInfo.ACTION_FOCUS;
20import static android.view.accessibility.AccessibilityNodeInfo.ACTION_SELECT;
21
22import com.android.frameworks.coretests.R;
23
24import android.content.Context;
25import android.graphics.Rect;
26import android.os.RemoteException;
27import android.os.ServiceManager;
28import android.os.SystemClock;
29import android.provider.Settings;
30import android.test.ActivityInstrumentationTestCase2;
31import android.test.suitebuilder.annotation.LargeTest;
32import android.util.Log;
33import android.view.accessibility.AccessibilityEvent;
34import android.view.accessibility.AccessibilityInteractionClient;
35import android.view.accessibility.AccessibilityManager;
36import android.view.accessibility.AccessibilityNodeInfo;
37import android.view.accessibility.IAccessibilityManager;
38
39import java.lang.reflect.Method;
40import java.lang.reflect.Modifier;
41import java.util.ArrayList;
42import java.util.LinkedList;
43import java.util.List;
44import java.util.Queue;
45
46/**
47 * Activity for testing the accessibility APIs for "interrogation" of
48 * the screen content. These APIs allow exploring the screen and
49 * requesting an action to be performed on a given view from an
50 * AccessiiblityService.
51 */
52public class InterrogationActivityTest
53        extends ActivityInstrumentationTestCase2<InterrogationActivity> {
54    private static final boolean DEBUG = true;
55
56    private static String LOG_TAG = "InterrogationActivityTest";
57
58    // Timeout before give up wait for the system to process an accessibility setting change.
59    private static final int TIMEOUT_PROPAGATE_ACCESSIBLITY_SETTING = 2000;
60
61    // Helpers to figure out the first and last test methods
62    // This is a workaround for the lack of such support in JUnit3
63    private static int sTestMethodCount;
64    private static int sExecutedTestMethodCount;
65
66    // Handle to a connection to the AccessibilityManagerService
67    private static IAccessibilityServiceConnection sConnection;
68
69    // The last received accessibility event
70    private static volatile AccessibilityEvent sLastFocusAccessibilityEvent;
71
72    public InterrogationActivityTest() {
73        super(InterrogationActivity.class);
74        sTestMethodCount = getTestMethodCount();
75    }
76
77    @LargeTest
78    public void testFindAccessibilityNodeInfoByViewId() throws Exception {
79        beforeClassIfNeeded();
80        final long startTimeMillis = SystemClock.uptimeMillis();
81        try {
82            // bring up the activity
83            getActivity();
84
85            AccessibilityNodeInfo button = AccessibilityInteractionClient.getInstance()
86                .findAccessibilityNodeInfoByViewIdInActiveWindow(getConnection(), R.id.button5);
87            assertNotNull(button);
88            assertEquals(0, button.getChildCount());
89
90            // bounds
91            Rect bounds = new Rect();
92            button.getBoundsInParent(bounds);
93            assertEquals(0, bounds.left);
94            assertEquals(0, bounds.top);
95            assertEquals(160, bounds.right);
96            assertEquals(100, bounds.bottom);
97
98            // char sequence attributes
99            assertEquals("com.android.frameworks.coretests", button.getPackageName());
100            assertEquals("android.widget.Button", button.getClassName());
101            assertEquals("Button5", button.getText());
102            assertNull(button.getContentDescription());
103
104            // boolean attributes
105            assertTrue(button.isFocusable());
106            assertTrue(button.isClickable());
107            assertTrue(button.isEnabled());
108            assertFalse(button.isFocused());
109            assertTrue(button.isClickable());
110            assertFalse(button.isPassword());
111            assertFalse(button.isSelected());
112            assertFalse(button.isCheckable());
113            assertFalse(button.isChecked());
114
115            // actions
116            assertEquals(ACTION_FOCUS | ACTION_SELECT | ACTION_CLEAR_SELECTION,
117                button.getActions());
118        } finally {
119            afterClassIfNeeded();
120            if (DEBUG) {
121                final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
122                Log.i(LOG_TAG, "testFindAccessibilityNodeInfoByViewId: "
123                        + elapsedTimeMillis + "ms");
124            }
125        }
126    }
127
128    @LargeTest
129    public void testFindAccessibilityNodeInfoByViewText() throws Exception {
130        beforeClassIfNeeded();
131        final long startTimeMillis = SystemClock.uptimeMillis();
132        try {
133            // bring up the activity
134            getActivity();
135
136            // find a view by text
137            List<AccessibilityNodeInfo> buttons =  AccessibilityInteractionClient.getInstance()
138                .findAccessibilityNodeInfosByViewTextInActiveWindow(getConnection(), "butto");
139            assertEquals(9, buttons.size());
140        } finally {
141            afterClassIfNeeded();
142            if (DEBUG) {
143                final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
144                Log.i(LOG_TAG, "testFindAccessibilityNodeInfoByViewText: "
145                        + elapsedTimeMillis + "ms");
146            }
147        }
148    }
149
150    @LargeTest
151    public void testTraverseAllViews() throws Exception {
152        beforeClassIfNeeded();
153        final long startTimeMillis = SystemClock.uptimeMillis();
154        try {
155            // bring up the activity
156            getActivity();
157
158            // make list of expected nodes
159            List<String> classNameAndTextList = new ArrayList<String>();
160            classNameAndTextList.add("android.widget.LinearLayout");
161            classNameAndTextList.add("android.widget.LinearLayout");
162            classNameAndTextList.add("android.widget.LinearLayout");
163            classNameAndTextList.add("android.widget.LinearLayout");
164            classNameAndTextList.add("android.widget.ButtonButton1");
165            classNameAndTextList.add("android.widget.ButtonButton2");
166            classNameAndTextList.add("android.widget.ButtonButton3");
167            classNameAndTextList.add("android.widget.ButtonButton4");
168            classNameAndTextList.add("android.widget.ButtonButton5");
169            classNameAndTextList.add("android.widget.ButtonButton6");
170            classNameAndTextList.add("android.widget.ButtonButton7");
171            classNameAndTextList.add("android.widget.ButtonButton8");
172            classNameAndTextList.add("android.widget.ButtonButton9");
173
174            AccessibilityNodeInfo root = AccessibilityInteractionClient.getInstance()
175                .findAccessibilityNodeInfoByViewIdInActiveWindow(getConnection(), R.id.root);
176            assertNotNull("We must find the existing root.", root);
177
178            Queue<AccessibilityNodeInfo> fringe = new LinkedList<AccessibilityNodeInfo>();
179            fringe.add(root);
180
181            // do a BFS traversal and check nodes
182            while (!fringe.isEmpty()) {
183                AccessibilityNodeInfo current = fringe.poll();
184
185                CharSequence className = current.getClassName();
186                CharSequence text = current.getText();
187                String receivedClassNameAndText = className.toString()
188                   + ((text != null) ? text.toString() : "");
189                String expectedClassNameAndText = classNameAndTextList.remove(0);
190
191                assertEquals("Did not get the expected node info",
192                        expectedClassNameAndText, receivedClassNameAndText);
193
194                final int childCount = current.getChildCount();
195                for (int i = 0; i < childCount; i++) {
196                    AccessibilityNodeInfo child = current.getChild(i);
197                    fringe.add(child);
198                }
199            }
200        } finally {
201            afterClassIfNeeded();
202            if (DEBUG) {
203                final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
204                Log.i(LOG_TAG, "testTraverseAllViews: " + elapsedTimeMillis + "ms");
205            }
206        }
207    }
208
209    @LargeTest
210    public void testPerformAccessibilityActionFocus() throws Exception {
211        beforeClassIfNeeded();
212        final long startTimeMillis = SystemClock.uptimeMillis();
213        try {
214            // bring up the activity
215            getActivity();
216
217            // find a view and make sure it is not focused
218            AccessibilityNodeInfo button = AccessibilityInteractionClient.getInstance()
219                .findAccessibilityNodeInfoByViewIdInActiveWindow(getConnection(), R.id.button5);
220            assertFalse(button.isFocused());
221
222            // focus the view
223            assertTrue(button.performAction(ACTION_FOCUS));
224
225            // find the view again and make sure it is focused
226            button =  AccessibilityInteractionClient.getInstance()
227                .findAccessibilityNodeInfoByViewIdInActiveWindow(getConnection(), R.id.button5);
228            assertTrue(button.isFocused());
229        } finally {
230            afterClassIfNeeded();
231            if (DEBUG) {
232                final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
233                Log.i(LOG_TAG, "testPerformAccessibilityActionFocus: " + elapsedTimeMillis + "ms");
234            }
235        }
236    }
237
238    @LargeTest
239    public void testPerformAccessibilityActionClearFocus() throws Exception {
240        beforeClassIfNeeded();
241        final long startTimeMillis = SystemClock.uptimeMillis();
242        try {
243            // bring up the activity
244            getActivity();
245
246            // find a view and make sure it is not focused
247            AccessibilityNodeInfo button = AccessibilityInteractionClient.getInstance()
248                .findAccessibilityNodeInfoByViewIdInActiveWindow(getConnection(), R.id.button5);
249            assertFalse(button.isFocused());
250
251            // focus the view
252            assertTrue(button.performAction(ACTION_FOCUS));
253
254            // find the view again and make sure it is focused
255            button = AccessibilityInteractionClient.getInstance()
256                .findAccessibilityNodeInfoByViewIdInActiveWindow(getConnection(), R.id.button5);
257            assertTrue(button.isFocused());
258
259            // unfocus the view
260            assertTrue(button.performAction(ACTION_CLEAR_FOCUS));
261
262            // find the view again and make sure it is not focused
263            button = AccessibilityInteractionClient.getInstance()
264                .findAccessibilityNodeInfoByViewIdInActiveWindow(getConnection(), R.id.button5);
265            assertFalse(button.isFocused());
266        } finally {
267            afterClassIfNeeded();
268            if (DEBUG) {
269                final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
270                Log.i(LOG_TAG, "testPerformAccessibilityActionClearFocus: "
271                        + elapsedTimeMillis + "ms");
272            }
273        }
274    }
275
276    @LargeTest
277    public void testPerformAccessibilityActionSelect() throws Exception {
278        beforeClassIfNeeded();
279        final long startTimeMillis = SystemClock.uptimeMillis();
280        try {
281            // bring up the activity
282            getActivity();
283
284            // find a view and make sure it is not selected
285            AccessibilityNodeInfo button = AccessibilityInteractionClient.getInstance()
286                .findAccessibilityNodeInfoByViewIdInActiveWindow(getConnection(), R.id.button5);
287            assertFalse(button.isSelected());
288
289            // select the view
290            assertTrue(button.performAction(ACTION_SELECT));
291
292            // find the view again and make sure it is selected
293            button = AccessibilityInteractionClient.getInstance()
294                .findAccessibilityNodeInfoByViewIdInActiveWindow(getConnection(), R.id.button5);
295            assertTrue(button.isSelected());
296        } finally {
297            afterClassIfNeeded();
298            if (DEBUG) {
299                final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
300                Log.i(LOG_TAG, "testPerformAccessibilityActionSelect: " + elapsedTimeMillis + "ms");
301            }
302        }
303    }
304
305    @LargeTest
306    public void testPerformAccessibilityActionClearSelection() throws Exception {
307        beforeClassIfNeeded();
308        final long startTimeMillis = SystemClock.uptimeMillis();
309        try {
310            // bring up the activity
311            getActivity();
312
313            // find a view and make sure it is not selected
314            AccessibilityNodeInfo button = AccessibilityInteractionClient.getInstance()
315                .findAccessibilityNodeInfoByViewIdInActiveWindow(getConnection(), R.id.button5);
316            assertFalse(button.isSelected());
317
318            // select the view
319            assertTrue(button.performAction(ACTION_SELECT));
320
321            // find the view again and make sure it is selected
322            button = AccessibilityInteractionClient.getInstance()
323                .findAccessibilityNodeInfoByViewIdInActiveWindow(getConnection(), R.id.button5);
324            assertTrue(button.isSelected());
325
326            // unselect the view
327            assertTrue(button.performAction(ACTION_CLEAR_SELECTION));
328
329            // find the view again and make sure it is not selected
330            button =  AccessibilityInteractionClient.getInstance()
331                .findAccessibilityNodeInfoByViewIdInActiveWindow(getConnection(), R.id.button5);
332            assertFalse(button.isSelected());
333        } finally {
334            afterClassIfNeeded();
335            if (DEBUG) {
336                final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
337                Log.i(LOG_TAG, "testPerformAccessibilityActionClearSelection: "
338                        + elapsedTimeMillis + "ms");
339            }
340        }
341    }
342
343    @LargeTest
344    public void testAccessibilityEventGetSource() throws Exception {
345        beforeClassIfNeeded();
346        final long startTimeMillis = SystemClock.uptimeMillis();
347        try {
348            // bring up the activity
349            getActivity();
350
351            // find a view and make sure it is not focused
352            AccessibilityNodeInfo button = AccessibilityInteractionClient.getInstance()
353                .findAccessibilityNodeInfoByViewIdInActiveWindow(getConnection(), R.id.button5);
354            assertFalse(button.isSelected());
355
356            // focus the view
357            assertTrue(button.performAction(ACTION_FOCUS));
358
359            synchronized (sConnection) {
360                try {
361                    sConnection.wait(500);
362                } catch (InterruptedException ie) {
363                    /* ignore */
364                }
365            }
366
367            // check that last event source
368            AccessibilityNodeInfo source = sLastFocusAccessibilityEvent.getSource();
369            assertNotNull(source);
370
371            // bounds
372            Rect buttonBounds = new Rect();
373            button.getBoundsInParent(buttonBounds);
374            Rect sourceBounds = new Rect();
375            source.getBoundsInParent(sourceBounds);
376
377            assertEquals(buttonBounds.left, sourceBounds.left);
378            assertEquals(buttonBounds.right, sourceBounds.right);
379            assertEquals(buttonBounds.top, sourceBounds.top);
380            assertEquals(buttonBounds.bottom, sourceBounds.bottom);
381
382            // char sequence attributes
383            assertEquals(button.getPackageName(), source.getPackageName());
384            assertEquals(button.getClassName(), source.getClassName());
385            assertEquals(button.getText(), source.getText());
386            assertSame(button.getContentDescription(), source.getContentDescription());
387
388            // boolean attributes
389            assertSame(button.isFocusable(), source.isFocusable());
390            assertSame(button.isClickable(), source.isClickable());
391            assertSame(button.isEnabled(), source.isEnabled());
392            assertNotSame(button.isFocused(), source.isFocused());
393            assertSame(button.isLongClickable(), source.isLongClickable());
394            assertSame(button.isPassword(), source.isPassword());
395            assertSame(button.isSelected(), source.isSelected());
396            assertSame(button.isCheckable(), source.isCheckable());
397            assertSame(button.isChecked(), source.isChecked());
398        } finally {
399            afterClassIfNeeded();
400            if (DEBUG) {
401                final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
402                Log.i(LOG_TAG, "testAccessibilityEventGetSource: " + elapsedTimeMillis + "ms");
403            }
404        }
405    }
406
407    @LargeTest
408    public void testObjectContract() throws Exception {
409        beforeClassIfNeeded();
410        final long startTimeMillis = SystemClock.uptimeMillis();
411        try {
412            // bring up the activity
413            getActivity();
414
415            // find a view and make sure it is not focused
416            AccessibilityNodeInfo button = AccessibilityInteractionClient.getInstance()
417                .findAccessibilityNodeInfoByViewIdInActiveWindow(getConnection(), R.id.button5);
418            AccessibilityNodeInfo parent = button.getParent();
419            final int childCount = parent.getChildCount();
420            for (int i = 0; i < childCount; i++) {
421                AccessibilityNodeInfo child = parent.getChild(i);
422                assertNotNull(child);
423                if (child.equals(button)) {
424                    assertEquals("Equal objects must have same hasCode.", button.hashCode(),
425                            child.hashCode());
426                    return;
427                }
428            }
429            fail("Parent's children do not have the info whose parent is the parent.");
430        } finally {
431            afterClassIfNeeded();
432            if (DEBUG) {
433                final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
434                Log.i(LOG_TAG, "testObjectContract: " + elapsedTimeMillis + "ms");
435            }
436        }
437    }
438
439    @Override
440    protected void scrubClass(Class<?> testCaseClass) {
441        /* intentionally do not scrub */
442    }
443
444    /**
445     * Sets accessibility in a given state by writing the state to the
446     * settings and waiting until the accessibility manager service picks
447     * it up for max {@link #TIMEOUT_PROPAGATE_ACCESSIBLITY_SETTING}.
448     *
449     * @param state The accessibility state.
450     * @throws Exception If any error occurs.
451     */
452    private void ensureAccessibilityState(boolean state) throws Exception {
453        Context context = getInstrumentation().getContext();
454        // If the local manager ready => nothing to do.
455        AccessibilityManager accessibilityManager = AccessibilityManager.getInstance(context);
456        if (accessibilityManager.isEnabled() == state) {
457            return;
458        }
459        synchronized (this) {
460            // Check if the system already knows about the desired state.
461            final boolean currentState = Settings.Secure.getInt(context.getContentResolver(),
462                    Settings.Secure.ACCESSIBILITY_ENABLED) == 1;
463            if (currentState != state) {
464                // Make sure we wake ourselves as the desired state is propagated.
465                accessibilityManager.addAccessibilityStateChangeListener(
466                        new AccessibilityManager.AccessibilityStateChangeListener() {
467                            public void onAccessibilityStateChanged(boolean enabled) {
468                                synchronized (this) {
469                                    notifyAll();
470                                }
471                            }
472                        });
473                Settings.Secure.putInt(context.getContentResolver(),
474                        Settings.Secure.ACCESSIBILITY_ENABLED, state ? 1 : 0);
475            }
476            // No while one attempt and that is it.
477            try {
478                wait(TIMEOUT_PROPAGATE_ACCESSIBLITY_SETTING);
479            } catch (InterruptedException ie) {
480                /* ignore */
481            }
482        }
483        if (accessibilityManager.isEnabled() != state) {
484            throw new IllegalStateException("Could not set accessibility state to: " + state);
485        }
486    }
487
488    /**
489     * Execute some set up code before any test method.
490     *
491     * NOTE: I miss Junit4's @BeforeClass
492     *
493     * @throws Exception If an error occurs.
494     */
495    private void beforeClassIfNeeded() throws Exception {
496        sExecutedTestMethodCount++;
497        if (sExecutedTestMethodCount == 1) {
498            ensureAccessibilityState(true);
499        }
500    }
501
502    /**
503     * Execute some clean up code after all test methods.
504     *
505     * NOTE: I miss Junit4's @AfterClass
506     *
507     * @throws Exception If an error occurs.
508     */
509    public void afterClassIfNeeded() throws Exception {
510        if (sExecutedTestMethodCount == sTestMethodCount) {
511            sExecutedTestMethodCount = 0;
512            ensureAccessibilityState(false);
513        }
514    }
515
516    private static IAccessibilityServiceConnection getConnection() throws Exception {
517        if (sConnection == null) {
518            IEventListener listener = new IEventListener.Stub() {
519                public void setConnection(IAccessibilityServiceConnection connection)
520                        throws RemoteException {
521                    AccessibilityServiceInfo info = new AccessibilityServiceInfo();
522                    info.eventTypes = AccessibilityEvent.TYPES_ALL_MASK;
523                    info.feedbackType = AccessibilityServiceInfo.FEEDBACK_SPOKEN;
524                    info.notificationTimeout = 0;
525                    info.flags = AccessibilityServiceInfo.DEFAULT;
526                    connection.setServiceInfo(info);
527                }
528
529                public void onInterrupt() {}
530
531                public void onAccessibilityEvent(AccessibilityEvent event) {
532                    if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_FOCUSED) {
533                        sLastFocusAccessibilityEvent = AccessibilityEvent.obtain(event);
534                    }
535                    synchronized (sConnection) {
536                        sConnection.notifyAll();
537                    }
538                }
539            };
540            IAccessibilityManager manager = IAccessibilityManager.Stub.asInterface(
541                ServiceManager.getService(Context.ACCESSIBILITY_SERVICE));
542            sConnection = manager.registerEventListener(listener);
543        }
544        return sConnection;
545    }
546
547    /**
548     * @return The number of test methods.
549     */
550    private int getTestMethodCount() {
551        int testMethodCount = 0;
552        for (Method method : getClass().getMethods()) {
553            final int modifiers = method.getModifiers();
554            if (method.getName().startsWith("test")
555                    && (modifiers & Modifier.PUBLIC) != 0
556                    && (modifiers & Modifier.STATIC) == 0) {
557                testMethodCount++;
558            }
559        }
560        return testMethodCount;
561    }
562}
563