AccessibilityServiceInfo.java revision fefd20e927b7252d63acb7bb1852c5188e3c1b2e
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.Build;
29import android.os.Parcel;
30import android.os.Parcelable;
31import android.util.AttributeSet;
32import android.util.TypedValue;
33import android.util.Xml;
34import android.view.View;
35import android.view.accessibility.AccessibilityEvent;
36
37import org.xmlpull.v1.XmlPullParser;
38import org.xmlpull.v1.XmlPullParserException;
39
40import java.io.IOException;
41
42/**
43 * This class describes an {@link AccessibilityService}. The system notifies an
44 * {@link AccessibilityService} for {@link android.view.accessibility.AccessibilityEvent}s
45 * according to the information encapsulated in this class.
46 *
47 * <div class="special reference">
48 * <h3>Developer Guides</h3>
49 * <p>For more information about creating AccessibilityServices, read the
50 * <a href="{@docRoot}guide/topics/ui/accessibility/index.html">Accessibility</a>
51 * developer guide.</p>
52 * </div>
53 *
54 * @see AccessibilityService
55 * @see android.view.accessibility.AccessibilityEvent
56 * @see android.view.accessibility.AccessibilityManager
57 */
58public class AccessibilityServiceInfo implements Parcelable {
59
60    private static final String TAG_ACCESSIBILITY_SERVICE = "accessibility-service";
61
62    /**
63     * Denotes spoken feedback.
64     */
65    public static final int FEEDBACK_SPOKEN = 0x0000001;
66
67    /**
68     * Denotes haptic feedback.
69     */
70    public static final int FEEDBACK_HAPTIC =  0x0000002;
71
72    /**
73     * Denotes audible (not spoken) feedback.
74     */
75    public static final int FEEDBACK_AUDIBLE = 0x0000004;
76
77    /**
78     * Denotes visual feedback.
79     */
80    public static final int FEEDBACK_VISUAL = 0x0000008;
81
82    /**
83     * Denotes generic feedback.
84     */
85    public static final int FEEDBACK_GENERIC = 0x0000010;
86
87    /**
88     * Mask for all feedback types.
89     *
90     * @see #FEEDBACK_SPOKEN
91     * @see #FEEDBACK_HAPTIC
92     * @see #FEEDBACK_AUDIBLE
93     * @see #FEEDBACK_VISUAL
94     * @see #FEEDBACK_GENERIC
95     */
96    public static final int FEEDBACK_ALL_MASK = 0xFFFFFFFF;
97
98    /**
99     * If an {@link AccessibilityService} is the default for a given type.
100     * Default service is invoked only if no package specific one exists. In case of
101     * more than one package specific service only the earlier registered is notified.
102     */
103    public static final int DEFAULT = 0x0000001;
104
105    /**
106     * If this flag is set the system will regard views that are not important
107     * for accessibility in addition to the ones that are important for accessibility.
108     * That is, views that are marked as not important for accessibility via
109     * {@link View#IMPORTANT_FOR_ACCESSIBILITY_NO} and views that are marked as
110     * potentially important for accessibility via
111     * {@link View#IMPORTANT_FOR_ACCESSIBILITY_AUTO} for which the system has determined
112     * that are not important for accessibility, are both reported while querying the
113     * window content and also the accessibility service will receive accessibility events
114     * from them.
115     * <p>
116     * <strong>Note:</strong> For accessibility services targeting API version
117     * {@link Build.VERSION_CODES#JELLY_BEAN} or higher this flag has to be explicitly
118     * set for the system to regard views that are not important for accessibility. For
119     * accessibility services targeting API version lower than
120     * {@link Build.VERSION_CODES#JELLY_BEAN} this flag is ignored and all views are
121     * regarded for accessibility purposes.
122     * </p>
123     * <p>
124     * Usually views not important for accessibility are layout managers that do not
125     * react to user actions, do not draw any content, and do not have any special
126     * semantics in the context of the screen content. For example, a three by three
127     * grid can be implemented as three horizontal linear layouts and one vertical,
128     * or three vertical linear layouts and one horizontal, or one grid layout, etc.
129     * In this context the actual layout mangers used to achieve the grid configuration
130     * are not important, rather it is important that there are nine evenly distributed
131     * elements.
132     * </p>
133     */
134    public static final int INCLUDE_NOT_IMPORTANT_VIEWS = 0x0000002;
135
136    /**
137     * The event types an {@link AccessibilityService} is interested in.
138     * <p>
139     *   <strong>Can be dynamically set at runtime.</strong>
140     * </p>
141     * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_CLICKED
142     * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_LONG_CLICKED
143     * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_FOCUSED
144     * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_SELECTED
145     * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_TEXT_CHANGED
146     * @see android.view.accessibility.AccessibilityEvent#TYPE_WINDOW_STATE_CHANGED
147     * @see android.view.accessibility.AccessibilityEvent#TYPE_NOTIFICATION_STATE_CHANGED
148     * @see android.view.accessibility.AccessibilityEvent#TYPE_TOUCH_EXPLORATION_GESTURE_START
149     * @see android.view.accessibility.AccessibilityEvent#TYPE_TOUCH_EXPLORATION_GESTURE_END
150     * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_HOVER_ENTER
151     * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_HOVER_EXIT
152     * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_SCROLLED
153     * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_TEXT_SELECTION_CHANGED
154     * @see android.view.accessibility.AccessibilityEvent#TYPE_WINDOW_CONTENT_CHANGED
155     */
156    public int eventTypes;
157
158    /**
159     * The package names an {@link AccessibilityService} is interested in. Setting
160     * to <code>null</code> is equivalent to all packages.
161     * <p>
162     *   <strong>Can be dynamically set at runtime.</strong>
163     * </p>
164     */
165    public String[] packageNames;
166
167    /**
168     * The feedback type an {@link AccessibilityService} provides.
169     * <p>
170     *   <strong>Can be dynamically set at runtime.</strong>
171     * </p>
172     * @see #FEEDBACK_AUDIBLE
173     * @see #FEEDBACK_GENERIC
174     * @see #FEEDBACK_HAPTIC
175     * @see #FEEDBACK_SPOKEN
176     * @see #FEEDBACK_VISUAL
177     */
178    public int feedbackType;
179
180    /**
181     * The timeout after the most recent event of a given type before an
182     * {@link AccessibilityService} is notified.
183     * <p>
184     *   <strong>Can be dynamically set at runtime.</strong>.
185     * </p>
186     * <p>
187     * <strong>Note:</strong> The event notification timeout is useful to avoid propagating
188     *       events to the client too frequently since this is accomplished via an expensive
189     *       interprocess call. One can think of the timeout as a criteria to determine when
190     *       event generation has settled down.
191     */
192    public long notificationTimeout;
193
194    /**
195     * This field represents a set of flags used for configuring an
196     * {@link AccessibilityService}.
197     * <p>
198     *   <strong>Can be dynamically set at runtime.</strong>
199     * </p>
200     * @see #DEFAULT
201     * @see #INCLUDE_NOT_IMPORTANT_VIEWS
202     */
203    public int flags;
204
205    /**
206     * The unique string Id to identify the accessibility service.
207     */
208    private String mId;
209
210    /**
211     * The Service that implements this accessibility service component.
212     */
213    private ResolveInfo mResolveInfo;
214
215    /**
216     * The accessibility service setting activity's name, used by the system
217     * settings to launch the setting activity of this accessibility service.
218     */
219    private String mSettingsActivityName;
220
221    /**
222     * Flag whether this accessibility service can retrieve window content.
223     */
224    private boolean mCanRetrieveWindowContent;
225
226    /**
227     * Flag whether this accessibility service can handle gestures.
228     */
229    private boolean mCanHandleGestures;
230
231    /**
232     * Resource id of the description of the accessibility service.
233     */
234    private int mDescriptionResId;
235
236    /**
237     * Non localized description of the accessibility service.
238     */
239    private String mNonLocalizedDescription;
240
241    /**
242     * Creates a new instance.
243     */
244    public AccessibilityServiceInfo() {
245        /* do nothing */
246    }
247
248    /**
249     * Creates a new instance.
250     *
251     * @param resolveInfo The service resolve info.
252     * @param context Context for accessing resources.
253     * @throws XmlPullParserException If a XML parsing error occurs.
254     * @throws IOException If a XML parsing error occurs.
255     *
256     * @hide
257     */
258    public AccessibilityServiceInfo(ResolveInfo resolveInfo, Context context)
259            throws XmlPullParserException, IOException {
260        ServiceInfo serviceInfo = resolveInfo.serviceInfo;
261        mId = new ComponentName(serviceInfo.packageName, serviceInfo.name).flattenToShortString();
262        mResolveInfo = resolveInfo;
263
264        XmlResourceParser parser = null;
265
266        try {
267            PackageManager packageManager = context.getPackageManager();
268            parser = serviceInfo.loadXmlMetaData(packageManager,
269                    AccessibilityService.SERVICE_META_DATA);
270            if (parser == null) {
271                return;
272            }
273
274            int type = 0;
275            while (type != XmlPullParser.END_DOCUMENT && type != XmlPullParser.START_TAG) {
276                type = parser.next();
277            }
278
279            String nodeName = parser.getName();
280            if (!TAG_ACCESSIBILITY_SERVICE.equals(nodeName)) {
281                throw new XmlPullParserException( "Meta-data does not start with"
282                        + TAG_ACCESSIBILITY_SERVICE + " tag");
283            }
284
285            AttributeSet allAttributes = Xml.asAttributeSet(parser);
286            Resources resources = packageManager.getResourcesForApplication(
287                    serviceInfo.applicationInfo);
288            TypedArray asAttributes = resources.obtainAttributes(allAttributes,
289                    com.android.internal.R.styleable.AccessibilityService);
290            eventTypes = asAttributes.getInt(
291                    com.android.internal.R.styleable.AccessibilityService_accessibilityEventTypes,
292                    0);
293            String packageNamez = asAttributes.getString(
294                    com.android.internal.R.styleable.AccessibilityService_packageNames);
295            if (packageNamez != null) {
296                packageNames = packageNamez.split("(\\s)*,(\\s)*");
297            }
298            feedbackType = asAttributes.getInt(
299                    com.android.internal.R.styleable.AccessibilityService_accessibilityFeedbackType,
300                    0);
301            notificationTimeout = asAttributes.getInt(
302                    com.android.internal.R.styleable.AccessibilityService_notificationTimeout,
303                    0);
304            flags = asAttributes.getInt(
305                    com.android.internal.R.styleable.AccessibilityService_accessibilityFlags, 0);
306            mSettingsActivityName = asAttributes.getString(
307                    com.android.internal.R.styleable.AccessibilityService_settingsActivity);
308            mCanRetrieveWindowContent = asAttributes.getBoolean(
309                    com.android.internal.R.styleable.AccessibilityService_canRetrieveWindowContent,
310                    false);
311            mCanHandleGestures = asAttributes.getBoolean(
312                    com.android.internal.R.styleable.AccessibilityService_canHandleGestures, false);
313            TypedValue peekedValue = asAttributes.peekValue(
314                    com.android.internal.R.styleable.AccessibilityService_description);
315            if (peekedValue != null) {
316                mDescriptionResId = peekedValue.resourceId;
317                CharSequence nonLocalizedDescription = peekedValue.coerceToString();
318                if (nonLocalizedDescription != null) {
319                    mNonLocalizedDescription = nonLocalizedDescription.toString().trim();
320                }
321            }
322            asAttributes.recycle();
323        } catch (NameNotFoundException e) {
324            throw new XmlPullParserException( "Unable to create context for: "
325                    + serviceInfo.packageName);
326        } finally {
327            if (parser != null) {
328                parser.close();
329            }
330        }
331    }
332
333    /**
334     * Updates the properties that an AccessibilitySerivice can change dynamically.
335     *
336     * @param other The info from which to update the properties.
337     *
338     * @hide
339     */
340    public void updateDynamicallyConfigurableProperties(AccessibilityServiceInfo other) {
341        eventTypes = other.eventTypes;
342        packageNames = other.packageNames;
343        feedbackType = other.feedbackType;
344        notificationTimeout = other.notificationTimeout;
345        flags = other.flags;
346    }
347
348    /**
349     * The accessibility service id.
350     * <p>
351     *   <strong>Generated by the system.</strong>
352     * </p>
353     * @return The id.
354     */
355    public String getId() {
356        return mId;
357    }
358
359    /**
360     * The service {@link ResolveInfo}.
361     * <p>
362     *   <strong>Generated by the system.</strong>
363     * </p>
364     * @return The info.
365     */
366    public ResolveInfo getResolveInfo() {
367        return mResolveInfo;
368    }
369
370    /**
371     * The settings activity name.
372     * <p>
373     *    <strong>Statically set from
374     *    {@link AccessibilityService#SERVICE_META_DATA meta-data}.</strong>
375     * </p>
376     * @return The settings activity name.
377     */
378    public String getSettingsActivityName() {
379        return mSettingsActivityName;
380    }
381
382    /**
383     * Whether this service can retrieve the current window's content.
384     * <p>
385     *    <strong>Statically set from
386     *    {@link AccessibilityService#SERVICE_META_DATA meta-data}.</strong>
387     * </p>
388     * @return True if window content can be retrieved.
389     */
390    public boolean getCanRetrieveWindowContent() {
391        return mCanRetrieveWindowContent;
392    }
393
394    /**
395     * Whether this service can handle gestures.
396     * <p>
397     *    <strong>Statically set from
398     *    {@link AccessibilityService#SERVICE_META_DATA meta-data}.</strong>
399     * </p>
400     * @return True if the service can handle gestures.
401     */
402    public boolean getCanHandleGestures() {
403        return mCanHandleGestures;
404    }
405
406    /**
407     * Gets the non-localized description of the accessibility service.
408     * <p>
409     *    <strong>Statically set from
410     *    {@link AccessibilityService#SERVICE_META_DATA meta-data}.</strong>
411     * </p>
412     * @return The description.
413     *
414     * @deprecated Use {@link #loadDescription(PackageManager)}.
415     */
416    public String getDescription() {
417        return mNonLocalizedDescription;
418    }
419
420    /**
421     * The localized description of the accessibility service.
422     * <p>
423     *    <strong>Statically set from
424     *    {@link AccessibilityService#SERVICE_META_DATA meta-data}.</strong>
425     * </p>
426     * @return The localized description.
427     */
428    public String loadDescription(PackageManager packageManager) {
429        if (mDescriptionResId == 0) {
430            return mNonLocalizedDescription;
431        }
432        ServiceInfo serviceInfo = mResolveInfo.serviceInfo;
433        CharSequence description = packageManager.getText(serviceInfo.packageName,
434                mDescriptionResId, serviceInfo.applicationInfo);
435        if (description != null) {
436            return description.toString().trim();
437        }
438        return null;
439    }
440
441    /**
442     * {@inheritDoc}
443     */
444    public int describeContents() {
445        return 0;
446    }
447
448    public void writeToParcel(Parcel parcel, int flagz) {
449        parcel.writeInt(eventTypes);
450        parcel.writeStringArray(packageNames);
451        parcel.writeInt(feedbackType);
452        parcel.writeLong(notificationTimeout);
453        parcel.writeInt(flags);
454        parcel.writeString(mId);
455        parcel.writeParcelable(mResolveInfo, 0);
456        parcel.writeString(mSettingsActivityName);
457        parcel.writeInt(mCanRetrieveWindowContent ? 1 : 0);
458        parcel.writeInt(mDescriptionResId);
459        parcel.writeString(mNonLocalizedDescription);
460    }
461
462    private void initFromParcel(Parcel parcel) {
463        eventTypes = parcel.readInt();
464        packageNames = parcel.readStringArray();
465        feedbackType = parcel.readInt();
466        notificationTimeout = parcel.readLong();
467        flags = parcel.readInt();
468        mId = parcel.readString();
469        mResolveInfo = parcel.readParcelable(null);
470        mSettingsActivityName = parcel.readString();
471        mCanRetrieveWindowContent = (parcel.readInt() == 1);
472        mDescriptionResId = parcel.readInt();
473        mNonLocalizedDescription = parcel.readString();
474    }
475
476    @Override
477    public String toString() {
478        StringBuilder stringBuilder = new StringBuilder();
479        appendEventTypes(stringBuilder, eventTypes);
480        stringBuilder.append(", ");
481        appendPackageNames(stringBuilder, packageNames);
482        stringBuilder.append(", ");
483        appendFeedbackTypes(stringBuilder, feedbackType);
484        stringBuilder.append(", ");
485        stringBuilder.append("notificationTimeout: ").append(notificationTimeout);
486        stringBuilder.append(", ");
487        appendFlags(stringBuilder, flags);
488        stringBuilder.append(", ");
489        stringBuilder.append("id: ").append(mId);
490        stringBuilder.append(", ");
491        stringBuilder.append("resolveInfo: ").append(mResolveInfo);
492        stringBuilder.append(", ");
493        stringBuilder.append("settingsActivityName: ").append(mSettingsActivityName);
494        stringBuilder.append(", ");
495        stringBuilder.append("retrieveScreenContent: ").append(mCanRetrieveWindowContent);
496        return stringBuilder.toString();
497    }
498
499    private static void appendFeedbackTypes(StringBuilder stringBuilder, int feedbackTypes) {
500        stringBuilder.append("feedbackTypes:");
501        stringBuilder.append("[");
502        while (feedbackTypes != 0) {
503            final int feedbackTypeBit = (1 << Integer.numberOfTrailingZeros(feedbackTypes));
504            stringBuilder.append(feedbackTypeToString(feedbackTypeBit));
505            feedbackTypes &= ~feedbackTypeBit;
506            if (feedbackTypes != 0) {
507                stringBuilder.append(", ");
508            }
509        }
510        stringBuilder.append("]");
511    }
512
513    private static void appendPackageNames(StringBuilder stringBuilder, String[] packageNames) {
514        stringBuilder.append("packageNames:");
515        stringBuilder.append("[");
516        if (packageNames != null) {
517            final int packageNameCount = packageNames.length;
518            for (int i = 0; i < packageNameCount; i++) {
519                stringBuilder.append(packageNames[i]);
520                if (i < packageNameCount - 1) {
521                    stringBuilder.append(", ");
522                }
523            }
524        }
525        stringBuilder.append("]");
526    }
527
528    private static void appendEventTypes(StringBuilder stringBuilder, int eventTypes) {
529        stringBuilder.append("eventTypes:");
530        stringBuilder.append("[");
531        while (eventTypes != 0) {
532            final int eventTypeBit = (1 << Integer.numberOfTrailingZeros(eventTypes));
533            stringBuilder.append(AccessibilityEvent.eventTypeToString(eventTypeBit));
534            eventTypes &= ~eventTypeBit;
535            if (eventTypes != 0) {
536                stringBuilder.append(", ");
537            }
538        }
539        stringBuilder.append("]");
540    }
541
542    private static void appendFlags(StringBuilder stringBuilder, int flags) {
543        stringBuilder.append("flags:");
544        stringBuilder.append("[");
545        while (flags != 0) {
546            final int flagBit = (1 << Integer.numberOfTrailingZeros(flags));
547            stringBuilder.append(flagToString(flagBit));
548            flags &= ~flagBit;
549            if (flags != 0) {
550                stringBuilder.append(", ");
551            }
552        }
553        stringBuilder.append("]");
554    }
555
556    /**
557     * Returns the string representation of a feedback type. For example,
558     * {@link #FEEDBACK_SPOKEN} is represented by the string FEEDBACK_SPOKEN.
559     *
560     * @param feedbackType The feedback type.
561     * @return The string representation.
562     */
563    public static String feedbackTypeToString(int feedbackType) {
564        StringBuilder builder = new StringBuilder();
565        builder.append("[");
566        while (feedbackType != 0) {
567            final int feedbackTypeFlag = 1 << Integer.numberOfTrailingZeros(feedbackType);
568            feedbackType &= ~feedbackTypeFlag;
569            switch (feedbackTypeFlag) {
570                case FEEDBACK_AUDIBLE:
571                    if (builder.length() > 1) {
572                        builder.append(", ");
573                    }
574                    builder.append("FEEDBACK_AUDIBLE");
575                    break;
576                case FEEDBACK_HAPTIC:
577                    if (builder.length() > 1) {
578                        builder.append(", ");
579                    }
580                    builder.append("FEEDBACK_HAPTIC");
581                    break;
582                case FEEDBACK_GENERIC:
583                    if (builder.length() > 1) {
584                        builder.append(", ");
585                    }
586                    builder.append("FEEDBACK_GENERIC");
587                    break;
588                case FEEDBACK_SPOKEN:
589                    if (builder.length() > 1) {
590                        builder.append(", ");
591                    }
592                    builder.append("FEEDBACK_SPOKEN");
593                    break;
594                case FEEDBACK_VISUAL:
595                    if (builder.length() > 1) {
596                        builder.append(", ");
597                    }
598                    builder.append("FEEDBACK_VISUAL");
599                    break;
600            }
601        }
602        builder.append("]");
603        return builder.toString();
604    }
605
606    /**
607     * Returns the string representation of a flag. For example,
608     * {@link #DEFAULT} is represented by the string DEFAULT.
609     *
610     * @param flag The flag.
611     * @return The string representation.
612     */
613    public static String flagToString(int flag) {
614        switch (flag) {
615            case DEFAULT:
616                return "DEFAULT";
617            case INCLUDE_NOT_IMPORTANT_VIEWS:
618                return "REGARD_VIEWS_NOT_IMPORTANT_FOR_ACCESSIBILITY";
619            default:
620                return null;
621        }
622    }
623
624    /**
625     * @see Parcelable.Creator
626     */
627    public static final Parcelable.Creator<AccessibilityServiceInfo> CREATOR =
628            new Parcelable.Creator<AccessibilityServiceInfo>() {
629        public AccessibilityServiceInfo createFromParcel(Parcel parcel) {
630            AccessibilityServiceInfo info = new AccessibilityServiceInfo();
631            info.initFromParcel(parcel);
632            return info;
633        }
634
635        public AccessibilityServiceInfo[] newArray(int size) {
636            return new AccessibilityServiceInfo[size];
637        }
638    };
639}
640