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