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