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