1/*
2 * Copyright (C) 2010 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 com.android.server;
18
19import android.accessibilityservice.AccessibilityService;
20import android.accessibilityservice.AccessibilityServiceInfo;
21import android.content.Intent;
22import android.os.Message;
23import android.view.accessibility.AccessibilityEvent;
24
25import java.util.Iterator;
26import java.util.LinkedList;
27import java.util.List;
28import java.util.Queue;
29
30import junit.framework.TestCase;
31
32/**
33 * This is the base class for mock {@link AccessibilityService}s.
34 */
35public abstract class MockAccessibilityService extends AccessibilityService {
36
37    /**
38     * The event this service expects to receive.
39     */
40    private final Queue<AccessibilityEvent> mExpectedEvents = new LinkedList<AccessibilityEvent>();
41
42    /**
43     * Interruption call this service expects to receive.
44     */
45    private boolean mExpectedInterrupt;
46
47    /**
48     * Flag if the mock is currently replaying.
49     */
50    private boolean mReplaying;
51
52    /**
53     * Flag if the system is bound as a client to this service.
54     */
55    private boolean mIsSystemBoundAsClient;
56
57    /**
58     * Creates an {@link AccessibilityServiceInfo} populated with default
59     * values.
60     *
61     * @return The default info.
62     */
63    public static AccessibilityServiceInfo createDefaultInfo() {
64        AccessibilityServiceInfo defaultInfo = new AccessibilityServiceInfo();
65        defaultInfo.eventTypes = AccessibilityEvent.TYPE_VIEW_CLICKED;
66        defaultInfo.feedbackType = AccessibilityServiceInfo.FEEDBACK_AUDIBLE;
67        defaultInfo.flags = 0;
68        defaultInfo.notificationTimeout = 0;
69        defaultInfo.packageNames = new String[] {
70            "foo.bar.baz"
71        };
72
73        return defaultInfo;
74    }
75
76    /**
77     * Starts replaying the mock.
78     */
79    public void replay() {
80        mReplaying = true;
81    }
82
83    /**
84     * Verifies if all expected service methods have been called.
85     */
86    public void verify() {
87        if (!mReplaying) {
88            throw new IllegalStateException("Did you forget to call replay()");
89        }
90
91        if (mExpectedInterrupt) {
92            throw new IllegalStateException("Expected call to #interrupt() not received");
93        }
94        if (!mExpectedEvents.isEmpty()) {
95            throw new IllegalStateException("Expected a call to onAccessibilityEvent() for "
96                    + "events \"" + mExpectedEvents + "\" not received");
97        }
98    }
99
100    /**
101     * Resets this instance so it can be reused.
102     */
103    public void reset() {
104        mExpectedEvents.clear();
105        mExpectedInterrupt = false;
106        mReplaying = false;
107    }
108
109    /**
110     * Sets an expected call to
111     * {@link #onAccessibilityEvent(AccessibilityEvent)} with given event as
112     * argument.
113     *
114     * @param expectedEvent The expected event argument.
115     */
116    public void expectEvent(AccessibilityEvent expectedEvent) {
117        mExpectedEvents.add(expectedEvent);
118    }
119
120    /**
121     * Sets an expected call of {@link #onInterrupt()}.
122     */
123    public void expectInterrupt() {
124        mExpectedInterrupt = true;
125    }
126
127    @Override
128    public void onAccessibilityEvent(AccessibilityEvent receivedEvent) {
129        if (!mReplaying) {
130            return;
131        }
132
133        if (mExpectedEvents.isEmpty()) {
134            throw new IllegalStateException("Unexpected event: " + receivedEvent);
135        }
136
137        AccessibilityEvent expectedEvent = mExpectedEvents.poll();
138        assertEqualsAccessiblityEvent(expectedEvent, receivedEvent);
139    }
140
141    @Override
142    public void onInterrupt() {
143        if (!mReplaying) {
144            return;
145        }
146
147        if (!mExpectedInterrupt) {
148            throw new IllegalStateException("Unexpected call to onInterrupt()");
149        }
150
151        mExpectedInterrupt = false;
152    }
153
154    @Override
155    protected void onServiceConnected() {
156        mIsSystemBoundAsClient = true;
157    }
158
159    @Override
160    public boolean onUnbind(Intent intent) {
161        mIsSystemBoundAsClient = false;
162        return false;
163    }
164
165    /**
166     * Returns if the system is bound as client to this service.
167     *
168     * @return True if the system is bound, false otherwise.
169     */
170    public boolean isSystemBoundAsClient() {
171        return mIsSystemBoundAsClient;
172    }
173
174    /**
175     * Compares all properties of the <code>expectedEvent</code> and the
176     * <code>receviedEvent</code> to verify that the received event is the one
177     * that is expected.
178     */
179    private void assertEqualsAccessiblityEvent(AccessibilityEvent expectedEvent,
180            AccessibilityEvent receivedEvent) {
181        TestCase.assertEquals("addedCount has incorrect value", expectedEvent.getAddedCount(),
182                receivedEvent.getAddedCount());
183        TestCase.assertEquals("beforeText has incorrect value", expectedEvent.getBeforeText(),
184                receivedEvent.getBeforeText());
185        TestCase.assertEquals("checked has incorrect value", expectedEvent.isChecked(),
186                receivedEvent.isChecked());
187        TestCase.assertEquals("className has incorrect value", expectedEvent.getClassName(),
188                receivedEvent.getClassName());
189        TestCase.assertEquals("contentDescription has incorrect value", expectedEvent
190                .getContentDescription(), receivedEvent.getContentDescription());
191        TestCase.assertEquals("currentItemIndex has incorrect value", expectedEvent
192                .getCurrentItemIndex(), receivedEvent.getCurrentItemIndex());
193        TestCase.assertEquals("enabled has incorrect value", expectedEvent.isEnabled(),
194                receivedEvent.isEnabled());
195        TestCase.assertEquals("eventType has incorrect value", expectedEvent.getEventType(),
196                receivedEvent.getEventType());
197        TestCase.assertEquals("fromIndex has incorrect value", expectedEvent.getFromIndex(),
198                receivedEvent.getFromIndex());
199        TestCase.assertEquals("fullScreen has incorrect value", expectedEvent.isFullScreen(),
200                receivedEvent.isFullScreen());
201        TestCase.assertEquals("itemCount has incorrect value", expectedEvent.getItemCount(),
202                receivedEvent.getItemCount());
203        assertEqualsNotificationAsParcelableData(expectedEvent, receivedEvent);
204        TestCase.assertEquals("password has incorrect value", expectedEvent.isPassword(),
205                receivedEvent.isPassword());
206        TestCase.assertEquals("removedCount has incorrect value", expectedEvent.getRemovedCount(),
207                receivedEvent.getRemovedCount());
208        assertEqualsText(expectedEvent, receivedEvent);
209    }
210
211    /**
212     * Compares the {@link android.os.Parcelable} data of the
213     * <code>expectedEvent</code> and <code>receivedEvent</code> to verify that
214     * the received event is the one that is expected.
215     */
216    private void assertEqualsNotificationAsParcelableData(AccessibilityEvent expectedEvent,
217            AccessibilityEvent receivedEvent) {
218        String message = "parcelableData has incorrect value";
219        Message expectedMessage = (Message) expectedEvent.getParcelableData();
220        Message receivedMessage = (Message) receivedEvent.getParcelableData();
221
222        if (expectedMessage == null) {
223            if (receivedMessage == null) {
224                return;
225            }
226        }
227
228        TestCase.assertNotNull(message, receivedMessage);
229
230        // we do a very simple sanity check since we do not test Message
231        TestCase.assertEquals(message, expectedMessage.what, receivedMessage.what);
232    }
233
234    /**
235     * Compares the text of the <code>expectedEvent</code> and
236     * <code>receivedEvent</code> by comparing the string representation of the
237     * corresponding {@link CharSequence}s.
238     */
239    private void assertEqualsText(AccessibilityEvent expectedEvent,
240            AccessibilityEvent receivedEvent) {
241        String message = "text has incorrect value";
242        List<CharSequence> expectedText = expectedEvent.getText();
243        List<CharSequence> receivedText = receivedEvent.getText();
244
245        TestCase.assertEquals(message, expectedText.size(), receivedText.size());
246
247        Iterator<CharSequence> expectedTextIterator = expectedText.iterator();
248        Iterator<CharSequence> receivedTextIterator = receivedText.iterator();
249
250        for (int i = 0; i < expectedText.size(); i++) {
251            // compare the string representation
252            TestCase.assertEquals(message, expectedTextIterator.next().toString(),
253                    receivedTextIterator.next().toString());
254        }
255    }
256}
257