1/*
2 * Copyright 2017 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
17package android.view.accessibility;
18
19import static org.hamcrest.Matchers.emptyCollectionOf;
20import static org.hamcrest.Matchers.equalTo;
21import static org.hamcrest.Matchers.hasItem;
22import static org.junit.Assert.assertThat;
23import static org.junit.Assert.fail;
24
25import android.os.Parcel;
26import android.support.test.filters.LargeTest;
27import android.support.test.runner.AndroidJUnit4;
28import android.util.ArraySet;
29import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
30
31import com.android.internal.util.CollectionUtils;
32
33import org.junit.Test;
34import org.junit.runner.RunWith;
35
36import java.util.ArrayList;
37
38@LargeTest
39@RunWith(AndroidJUnit4.class)
40public class AccessibilityNodeInfoTest {
41    // The number of fields tested in the corresponding CTS AccessibilityNodeInfoTest:
42    // See fullyPopulateAccessibilityNodeInfo, assertEqualsAccessibilityNodeInfo,
43    // and assertAccessibilityNodeInfoCleared in that class.
44    private static final int NUM_MARSHALLED_PROPERTIES = 35;
45
46    /**
47     * The number of properties that are purposely not marshalled
48     * mOriginalText - Used when resolving clickable spans; intentionally not parceled
49     */
50    private static final int NUM_NONMARSHALLED_PROPERTIES = 1;
51
52    @Test
53    public void testStandardActions_serializationFlagIsValid() {
54        AccessibilityAction brokenStandardAction = CollectionUtils.find(
55                new ArrayList<>(AccessibilityAction.sStandardActions),
56                action -> Long.bitCount(action.mSerializationFlag) != 1);
57        if (brokenStandardAction != null) {
58            String message = "Invalid serialization flag(0x"
59                    + Long.toHexString(brokenStandardAction.mSerializationFlag)
60                    + ") in " + brokenStandardAction;
61            if (brokenStandardAction.mSerializationFlag == 0L) {
62                message += "\nThis is likely due to an overflow";
63            }
64            fail(message);
65        }
66
67        brokenStandardAction = CollectionUtils.find(
68                new ArrayList<>(AccessibilityAction.sStandardActions),
69                action -> Integer.bitCount(action.getId()) == 1
70                        && action.getId() <= AccessibilityNodeInfo.LAST_LEGACY_STANDARD_ACTION
71                        && action.getId() != action.mSerializationFlag);
72        if (brokenStandardAction != null) {
73            fail("Serialization flag(0x"
74                    + Long.toHexString(brokenStandardAction.mSerializationFlag)
75                    + ") is different from legacy action id(0x"
76                    + Integer.toHexString(brokenStandardAction.getId())
77                    + ") in " + brokenStandardAction);
78        }
79    }
80
81    @Test
82    public void testStandardActions_idsAreUnique() {
83        ArraySet<AccessibilityAction> actions = AccessibilityAction.sStandardActions;
84        for (int i = 0; i < actions.size(); i++) {
85            for (int j = 0; j < i; j++) {
86                int id = actions.valueAt(i).getId();
87                if (id == actions.valueAt(j).getId()) {
88                    fail("Id 0x" + Integer.toHexString(id)
89                            + " is duplicated for standard actions #" + i + " and #" + j);
90                }
91            }
92        }
93    }
94
95    @Test
96    public void testStandardActions_allComeThroughParceling() {
97        for (AccessibilityAction action : AccessibilityAction.sStandardActions) {
98            final AccessibilityNodeInfo nodeWithAction = AccessibilityNodeInfo.obtain();
99            nodeWithAction.addAction(action);
100            assertThat(nodeWithAction.getActionList(), hasItem(action));
101            final Parcel parcel = Parcel.obtain();
102            nodeWithAction.writeToParcel(parcel, 0);
103            parcel.setDataPosition(0);
104            final AccessibilityNodeInfo unparceledNode =
105                    AccessibilityNodeInfo.CREATOR.createFromParcel(parcel);
106            assertThat(unparceledNode.getActionList(), hasItem(action));
107        }
108    }
109
110    @Test
111    public void testEmptyListOfActions_parcelsCorrectly() {
112        // Also set text, as if there's nothing else in the parcel it can unparcel even with
113        // a bug present.
114        final String text = "text";
115        final AccessibilityNodeInfo nodeWithEmptyActionList = AccessibilityNodeInfo.obtain();
116        nodeWithEmptyActionList.addAction(AccessibilityAction.ACTION_ACCESSIBILITY_FOCUS);
117        nodeWithEmptyActionList.removeAction(AccessibilityAction.ACTION_ACCESSIBILITY_FOCUS);
118        nodeWithEmptyActionList.setText(text);
119        final Parcel parcel = Parcel.obtain();
120        nodeWithEmptyActionList.writeToParcel(parcel, 0);
121        parcel.setDataPosition(0);
122        final AccessibilityNodeInfo unparceledNode =
123                AccessibilityNodeInfo.CREATOR.createFromParcel(parcel);
124        assertThat(unparceledNode.getActionList(), emptyCollectionOf(AccessibilityAction.class));
125        assertThat(unparceledNode.getText(), equalTo(text));
126    }
127
128    @Test
129    public void dontForgetToUpdateCtsParcelingTestWhenYouAddNewFields() {
130        AccessibilityEventTest.assertNoNewNonStaticFieldsAdded(AccessibilityNodeInfo.class,
131                NUM_MARSHALLED_PROPERTIES + NUM_NONMARSHALLED_PROPERTIES);
132    }
133}
134