AccessibilityServiceInfo.java revision 0f55cc33f658b2793a12d609a0989348077324a4
1/*
2 * Copyright (C) 2009 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.accessibilityservice;
18
19import android.content.ComponentName;
20import android.content.Context;
21import android.content.pm.PackageManager;
22import android.content.pm.PackageManager.NameNotFoundException;
23import android.content.pm.ResolveInfo;
24import android.content.pm.ServiceInfo;
25import android.content.res.Resources;
26import android.content.res.TypedArray;
27import android.content.res.XmlResourceParser;
28import android.os.Parcel;
29import android.os.Parcelable;
30import android.util.AttributeSet;
31import android.util.Xml;
32import android.view.accessibility.AccessibilityEvent;
33
34import org.xmlpull.v1.XmlPullParser;
35import org.xmlpull.v1.XmlPullParserException;
36
37import java.io.IOException;
38
39/**
40 * This class describes an {@link AccessibilityService}. The system notifies an
41 * {@link AccessibilityService} for {@link android.view.accessibility.AccessibilityEvent}s
42 * according to the information encapsulated in this class.
43 *
44 * @see AccessibilityService
45 * @see android.view.accessibility.AccessibilityEvent
46 * @see android.view.accessibility.AccessibilityManager
47 */
48public class AccessibilityServiceInfo implements Parcelable {
49
50    private static final String TAG_ACCESSIBILITY_SERVICE = "accessibility-service";
51
52    /**
53     * Denotes spoken feedback.
54     */
55    public static final int FEEDBACK_SPOKEN = 0x0000001;
56
57    /**
58     * Denotes haptic feedback.
59     */
60    public static final int FEEDBACK_HAPTIC =  0x0000002;
61
62    /**
63     * Denotes audible (not spoken) feedback.
64     */
65    public static final int FEEDBACK_AUDIBLE = 0x0000004;
66
67    /**
68     * Denotes visual feedback.
69     */
70    public static final int FEEDBACK_VISUAL = 0x0000008;
71
72    /**
73     * Denotes generic feedback.
74     */
75    public static final int FEEDBACK_GENERIC = 0x0000010;
76
77    /**
78     * If an {@link AccessibilityService} is the default for a given type.
79     * Default service is invoked only if no package specific one exists. In case of
80     * more than one package specific service only the earlier registered is notified.
81     */
82    public static final int DEFAULT = 0x0000001;
83
84    /**
85     * The event types an {@link AccessibilityService} is interested in.
86     * <p>
87     *   <strong>Can be dynamically set at runtime.</strong>
88     * </p>
89     * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_CLICKED
90     * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_LONG_CLICKED
91     * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_FOCUSED
92     * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_SELECTED
93     * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_TEXT_CHANGED
94     * @see android.view.accessibility.AccessibilityEvent#TYPE_WINDOW_STATE_CHANGED
95     * @see android.view.accessibility.AccessibilityEvent#TYPE_NOTIFICATION_STATE_CHANGED
96     * @see android.view.accessibility.AccessibilityEvent#TYPE_TOUCH_EXPLORATION_GESTURE_START
97     * @see android.view.accessibility.AccessibilityEvent#TYPE_TOUCH_EXPLORATION_GESTURE_END
98     * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_HOVER_ENTER
99     * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_HOVER_EXIT
100     * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_SCROLLED
101     * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_TEXT_SELECTION_CHANGED
102     * @see android.view.accessibility.AccessibilityEvent#TYPE_WINDOW_CONTENT_CHANGED
103     */
104    public int eventTypes;
105
106    /**
107     * The package names an {@link AccessibilityService} is interested in. Setting
108     * to <code>null</code> is equivalent to all packages.
109     * <p>
110     *   <strong>Can be dynamically set at runtime.</strong>
111     * </p>
112     */
113    public String[] packageNames;
114
115    /**
116     * The feedback type an {@link AccessibilityService} provides.
117     * <p>
118     *   <strong>Can be dynamically set at runtime.</strong>
119     * </p>
120     * @see #FEEDBACK_AUDIBLE
121     * @see #FEEDBACK_GENERIC
122     * @see #FEEDBACK_HAPTIC
123     * @see #FEEDBACK_SPOKEN
124     * @see #FEEDBACK_VISUAL
125     */
126    public int feedbackType;
127
128    /**
129     * The timeout after the most recent event of a given type before an
130     * {@link AccessibilityService} is notified.
131     * <p>
132     *   <strong>Can be dynamically set at runtime.</strong>.
133     * </p>
134     * <p>
135     * <strong>Note:</strong> The event notification timeout is useful to avoid propagating
136     *       events to the client too frequently since this is accomplished via an expensive
137     *       interprocess call. One can think of the timeout as a criteria to determine when
138     *       event generation has settled down.
139     */
140    public long notificationTimeout;
141
142    /**
143     * This field represents a set of flags used for configuring an
144     * {@link AccessibilityService}.
145     * <p>
146     *   <strong>Can be dynamically set at runtime.</strong>
147     * </p>
148     * @see #DEFAULT
149     */
150    public int flags;
151
152    /**
153     * The unique string Id to identify the accessibility service.
154     */
155    private String mId;
156
157    /**
158     * The Service that implements this accessibility service component.
159     */
160    private ResolveInfo mResolveInfo;
161
162    /**
163     * The accessibility service setting activity's name, used by the system
164     * settings to launch the setting activity of this accessibility service.
165     */
166    private String mSettingsActivityName;
167
168    /**
169     * Flag whether this accessibility service can retrieve window content.
170     */
171    private boolean mCanRetrieveWindowContent;
172
173    /**
174     * Creates a new instance.
175     */
176    public AccessibilityServiceInfo() {
177        /* do nothing */
178    }
179
180    /**
181     * Creates a new instance.
182     *
183     * @param resolveInfo The service resolve info.
184     * @param context Context for accessing resources.
185     * @throws XmlPullParserException If a XML parsing error occurs.
186     * @throws IOException If a XML parsing error occurs.
187     *
188     * @hide
189     */
190    public AccessibilityServiceInfo(ResolveInfo resolveInfo, Context context)
191            throws XmlPullParserException, IOException {
192        ServiceInfo serviceInfo = resolveInfo.serviceInfo;
193        mId = new ComponentName(serviceInfo.packageName, serviceInfo.name).flattenToShortString();
194        mResolveInfo = resolveInfo;
195
196        XmlResourceParser parser = null;
197
198        try {
199            PackageManager packageManager = context.getPackageManager();
200            parser = serviceInfo.loadXmlMetaData(packageManager,
201                    AccessibilityService.SERVICE_META_DATA);
202            if (parser == null) {
203                return;
204            }
205
206            int type = 0;
207            while (type != XmlPullParser.END_DOCUMENT && type != XmlPullParser.START_TAG) {
208                type = parser.next();
209            }
210
211            String nodeName = parser.getName();
212            if (!TAG_ACCESSIBILITY_SERVICE.equals(nodeName)) {
213                throw new XmlPullParserException( "Meta-data does not start with"
214                        + TAG_ACCESSIBILITY_SERVICE + " tag");
215            }
216
217            AttributeSet allAttributes = Xml.asAttributeSet(parser);
218            Resources resources = packageManager.getResourcesForApplication(
219                    serviceInfo.applicationInfo);
220            TypedArray asAttributes = resources.obtainAttributes(allAttributes,
221                    com.android.internal.R.styleable.AccessibilityService);
222            eventTypes = asAttributes.getInt(
223                    com.android.internal.R.styleable.AccessibilityService_accessibilityEventTypes,
224                    0);
225            String packageNamez = asAttributes.getString(
226                    com.android.internal.R.styleable.AccessibilityService_packageNames);
227            if (packageNamez != null) {
228                packageNames = packageNamez.split("(\\s)*,(\\s)*");
229            }
230            feedbackType = asAttributes.getInt(
231                    com.android.internal.R.styleable.AccessibilityService_accessibilityFeedbackType,
232                    0);
233            notificationTimeout = asAttributes.getInt(
234                    com.android.internal.R.styleable.AccessibilityService_notificationTimeout,
235                    0);
236            flags = asAttributes.getInt(
237                    com.android.internal.R.styleable.AccessibilityService_accessibilityFlags, 0);
238            mSettingsActivityName = asAttributes.getString(
239                    com.android.internal.R.styleable.AccessibilityService_settingsActivity);
240            mCanRetrieveWindowContent = asAttributes.getBoolean(
241                    com.android.internal.R.styleable.AccessibilityService_canRetrieveWindowContent,
242                    false);
243            asAttributes.recycle();
244        } catch (NameNotFoundException e) {
245            throw new XmlPullParserException( "Unable to create context for: "
246                    + serviceInfo.packageName);
247        } finally {
248            if (parser != null) {
249                parser.close();
250            }
251        }
252    }
253
254    /**
255     * Updates the properties that an AccessibilitySerivice can change dynamically.
256     *
257     * @param other The info from which to update the properties.
258     *
259     * @hide
260     */
261    public void updateDynamicallyConfigurableProperties(AccessibilityServiceInfo other) {
262        eventTypes = other.eventTypes;
263        packageNames = other.packageNames;
264        feedbackType = other.feedbackType;
265        notificationTimeout = other.notificationTimeout;
266        flags = other.flags;
267    }
268
269    /**
270     * The accessibility service id.
271     * <p>
272     *   <strong>Generated by the system.</strong>
273     * </p>
274     * @return The id.
275     */
276    public String getId() {
277        return mId;
278    }
279
280    /**
281     * The service {@link ResolveInfo}.
282     * <p>
283     *   <strong>Generated by the system.</strong>
284     * </p>
285     * @return The info.
286     */
287    public ResolveInfo getResolveInfo() {
288        return mResolveInfo;
289    }
290
291    /**
292     * The settings activity name.
293     * <p>
294     *    <strong>Statically set from
295     *    {@link AccessibilityService#SERVICE_META_DATA meta-data}.</strong>
296     * </p>
297     * @return The settings activity name.
298     */
299    public String getSettingsActivityName() {
300        return mSettingsActivityName;
301    }
302
303    /**
304     * Whether this service can retrieve the current window's content.
305     * <p>
306     *    <strong>Statically set from
307     *    {@link AccessibilityService#SERVICE_META_DATA meta-data}.</strong>
308     * </p>
309     * @return True window content can be retrieved.
310     */
311    public boolean getCanRetrieveWindowContent() {
312        return mCanRetrieveWindowContent;
313    }
314
315    /**
316     * {@inheritDoc}
317     */
318    public int describeContents() {
319        return 0;
320    }
321
322    public void writeToParcel(Parcel parcel, int flagz) {
323        parcel.writeInt(eventTypes);
324        parcel.writeStringArray(packageNames);
325        parcel.writeInt(feedbackType);
326        parcel.writeLong(notificationTimeout);
327        parcel.writeInt(flags);
328        parcel.writeString(mId);
329        parcel.writeParcelable(mResolveInfo, 0);
330        parcel.writeString(mSettingsActivityName);
331        parcel.writeInt(mCanRetrieveWindowContent ? 1 : 0);
332    }
333
334    private void initFromParcel(Parcel parcel) {
335        eventTypes = parcel.readInt();
336        packageNames = parcel.readStringArray();
337        feedbackType = parcel.readInt();
338        notificationTimeout = parcel.readLong();
339        flags = parcel.readInt();
340        mId = parcel.readString();
341        mResolveInfo = parcel.readParcelable(null);
342        mSettingsActivityName = parcel.readString();
343        mCanRetrieveWindowContent = (parcel.readInt() == 1);
344    }
345
346    @Override
347    public String toString() {
348        StringBuilder stringBuilder = new StringBuilder();
349        appendEventTypes(stringBuilder, eventTypes);
350        stringBuilder.append(", ");
351        appendPackageNames(stringBuilder, packageNames);
352        stringBuilder.append(", ");
353        appendFeedbackTypes(stringBuilder, feedbackType);
354        stringBuilder.append(", ");
355        stringBuilder.append("notificationTimeout: ").append(notificationTimeout);
356        stringBuilder.append(", ");
357        appendFlags(stringBuilder, flags);
358        stringBuilder.append(", ");
359        stringBuilder.append("id: ").append(mId);
360        stringBuilder.append(", ");
361        stringBuilder.append("resolveInfo: ").append(mResolveInfo);
362        stringBuilder.append(", ");
363        stringBuilder.append("settingsActivityName: ").append(mSettingsActivityName);
364        stringBuilder.append(", ");
365        stringBuilder.append("retrieveScreenContent: ").append(mCanRetrieveWindowContent);
366        return stringBuilder.toString();
367    }
368
369    private static void appendFeedbackTypes(StringBuilder stringBuilder, int feedbackTypes) {
370        stringBuilder.append("feedbackTypes:");
371        stringBuilder.append("[");
372        while (feedbackTypes != 0) {
373            final int feedbackTypeBit = (1 << Integer.numberOfTrailingZeros(feedbackTypes));
374            stringBuilder.append(feedbackTypeToString(feedbackTypeBit));
375            feedbackTypes &= ~feedbackTypeBit;
376            if (feedbackTypes != 0) {
377                stringBuilder.append(", ");
378            }
379        }
380        stringBuilder.append("]");
381    }
382
383    private static void appendPackageNames(StringBuilder stringBuilder, String[] packageNames) {
384        stringBuilder.append("packageNames:");
385        stringBuilder.append("[");
386        if (packageNames != null) {
387            final int packageNameCount = packageNames.length;
388            for (int i = 0; i < packageNameCount; i++) {
389                stringBuilder.append(packageNames[i]);
390                if (i < packageNameCount - 1) {
391                    stringBuilder.append(", ");
392                }
393            }
394        }
395        stringBuilder.append("]");
396    }
397
398    private static void appendEventTypes(StringBuilder stringBuilder, int eventTypes) {
399        stringBuilder.append("eventTypes:");
400        stringBuilder.append("[");
401        while (eventTypes != 0) {
402            final int eventTypeBit = (1 << Integer.numberOfTrailingZeros(eventTypes));
403            stringBuilder.append(AccessibilityEvent.eventTypeToString(eventTypeBit));
404            eventTypes &= ~eventTypeBit;
405            if (eventTypes != 0) {
406                stringBuilder.append(", ");
407            }
408        }
409        stringBuilder.append("]");
410    }
411
412    private static void appendFlags(StringBuilder stringBuilder, int flags) {
413        stringBuilder.append("flags:");
414        stringBuilder.append("[");
415        while (flags != 0) {
416            final int flagBit = (1 << Integer.numberOfTrailingZeros(flags));
417            stringBuilder.append(flagToString(flagBit));
418            flags &= ~flagBit;
419            if (flags != 0) {
420                stringBuilder.append(", ");
421            }
422        }
423        stringBuilder.append("]");
424    }
425
426    /**
427     * Returns the string representation of a feedback type. For example,
428     * {@link #FEEDBACK_SPOKEN} is represented by the string FEEDBACK_SPOKEN.
429     *
430     * @param feedbackType The feedback type.
431     * @return The string representation.
432     */
433    public static String feedbackTypeToString(int feedbackType) {
434        switch (feedbackType) {
435            case FEEDBACK_AUDIBLE:
436                return "FEEDBACK_AUDIBLE";
437            case FEEDBACK_HAPTIC:
438                return "FEEDBACK_HAPTIC";
439            case FEEDBACK_GENERIC:
440                return "FEEDBACK_GENERIC";
441            case FEEDBACK_SPOKEN:
442                return "FEEDBACK_SPOKEN";
443            case FEEDBACK_VISUAL:
444                return "FEEDBACK_VISUAL";
445            default:
446                return null;
447        }
448    }
449
450    /**
451     * Returns the string representation of a flag. For example,
452     * {@link #DEFAULT} is represented by the string DEFAULT.
453     *
454     * @param flag The flag.
455     * @return The string representation.
456     */
457    public static String flagToString(int flag) {
458        switch (flag) {
459            case DEFAULT:
460                return "DEFAULT";
461            default:
462                return null;
463        }
464    }
465
466    /**
467     * @see Parcelable.Creator
468     */
469    public static final Parcelable.Creator<AccessibilityServiceInfo> CREATOR =
470            new Parcelable.Creator<AccessibilityServiceInfo>() {
471        public AccessibilityServiceInfo createFromParcel(Parcel parcel) {
472            AccessibilityServiceInfo info = new AccessibilityServiceInfo();
473            info.initFromParcel(parcel);
474            return info;
475        }
476
477        public AccessibilityServiceInfo[] newArray(int size) {
478            return new AccessibilityServiceInfo[size];
479        }
480    };
481}
482