AccessibilityServiceInfo.java revision e4abc512aa6474f0106f407f7b399832da34483f
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 FLAG_INCLUDE_NOT_IMPORTANT_VIEWS = 0x0000002;
135
136    /**
137     * This flag requests that the system gets into touch exploration mode.
138     * In this mode a single finger moving on the screen behaves as a mouse
139     * pointer hovering over the user interface. The system will also detect
140     * certain gestures performed on the touch screen and notify this service.
141     * The system will enable touch exploration mode if there is at least one
142     * accessibility service that has this flag set. Hence, clearing this
143     * flag does not guarantee that the device will not be in touch exploration
144     * mode since there may be another enabled service that requested it.
145     */
146    public static final int FLAG_REQUEST_TOUCH_EXPLORATION_MODE= 0x0000004;
147
148    /**
149     * The event types an {@link AccessibilityService} is interested in.
150     * <p>
151     *   <strong>Can be dynamically set at runtime.</strong>
152     * </p>
153     * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_CLICKED
154     * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_LONG_CLICKED
155     * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_FOCUSED
156     * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_SELECTED
157     * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_TEXT_CHANGED
158     * @see android.view.accessibility.AccessibilityEvent#TYPE_WINDOW_STATE_CHANGED
159     * @see android.view.accessibility.AccessibilityEvent#TYPE_NOTIFICATION_STATE_CHANGED
160     * @see android.view.accessibility.AccessibilityEvent#TYPE_TOUCH_EXPLORATION_GESTURE_START
161     * @see android.view.accessibility.AccessibilityEvent#TYPE_TOUCH_EXPLORATION_GESTURE_END
162     * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_HOVER_ENTER
163     * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_HOVER_EXIT
164     * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_SCROLLED
165     * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_TEXT_SELECTION_CHANGED
166     * @see android.view.accessibility.AccessibilityEvent#TYPE_WINDOW_CONTENT_CHANGED
167     */
168    public int eventTypes;
169
170    /**
171     * The package names an {@link AccessibilityService} is interested in. Setting
172     * to <code>null</code> is equivalent to all packages.
173     * <p>
174     *   <strong>Can be dynamically set at runtime.</strong>
175     * </p>
176     */
177    public String[] packageNames;
178
179    /**
180     * The feedback type an {@link AccessibilityService} provides.
181     * <p>
182     *   <strong>Can be dynamically set at runtime.</strong>
183     * </p>
184     * @see #FEEDBACK_AUDIBLE
185     * @see #FEEDBACK_GENERIC
186     * @see #FEEDBACK_HAPTIC
187     * @see #FEEDBACK_SPOKEN
188     * @see #FEEDBACK_VISUAL
189     */
190    public int feedbackType;
191
192    /**
193     * The timeout after the most recent event of a given type before an
194     * {@link AccessibilityService} is notified.
195     * <p>
196     *   <strong>Can be dynamically set at runtime.</strong>.
197     * </p>
198     * <p>
199     * <strong>Note:</strong> The event notification timeout is useful to avoid propagating
200     *       events to the client too frequently since this is accomplished via an expensive
201     *       interprocess call. One can think of the timeout as a criteria to determine when
202     *       event generation has settled down.
203     */
204    public long notificationTimeout;
205
206    /**
207     * This field represents a set of flags used for configuring an
208     * {@link AccessibilityService}.
209     * <p>
210     *   <strong>Can be dynamically set at runtime.</strong>
211     * </p>
212     * @see #DEFAULT
213     * @see #FLAG_INCLUDE_NOT_IMPORTANT_VIEWS
214     * @see #FLAG_REQUEST_TOUCH_EXPLORATION_MODE
215     */
216    public int flags;
217
218    /**
219     * The unique string Id to identify the accessibility service.
220     */
221    private String mId;
222
223    /**
224     * The Service that implements this accessibility service component.
225     */
226    private ResolveInfo mResolveInfo;
227
228    /**
229     * The accessibility service setting activity's name, used by the system
230     * settings to launch the setting activity of this accessibility service.
231     */
232    private String mSettingsActivityName;
233
234    /**
235     * Flag whether this accessibility service can retrieve window content.
236     */
237    private boolean mCanRetrieveWindowContent;
238
239    /**
240     * Resource id of the description of the accessibility service.
241     */
242    private int mDescriptionResId;
243
244    /**
245     * Non localized description of the accessibility service.
246     */
247    private String mNonLocalizedDescription;
248
249    /**
250     * Creates a new instance.
251     */
252    public AccessibilityServiceInfo() {
253        /* do nothing */
254    }
255
256    /**
257     * Creates a new instance.
258     *
259     * @param resolveInfo The service resolve info.
260     * @param context Context for accessing resources.
261     * @throws XmlPullParserException If a XML parsing error occurs.
262     * @throws IOException If a XML parsing error occurs.
263     *
264     * @hide
265     */
266    public AccessibilityServiceInfo(ResolveInfo resolveInfo, Context context)
267            throws XmlPullParserException, IOException {
268        ServiceInfo serviceInfo = resolveInfo.serviceInfo;
269        mId = new ComponentName(serviceInfo.packageName, serviceInfo.name).flattenToShortString();
270        mResolveInfo = resolveInfo;
271
272        XmlResourceParser parser = null;
273
274        try {
275            PackageManager packageManager = context.getPackageManager();
276            parser = serviceInfo.loadXmlMetaData(packageManager,
277                    AccessibilityService.SERVICE_META_DATA);
278            if (parser == null) {
279                return;
280            }
281
282            int type = 0;
283            while (type != XmlPullParser.END_DOCUMENT && type != XmlPullParser.START_TAG) {
284                type = parser.next();
285            }
286
287            String nodeName = parser.getName();
288            if (!TAG_ACCESSIBILITY_SERVICE.equals(nodeName)) {
289                throw new XmlPullParserException( "Meta-data does not start with"
290                        + TAG_ACCESSIBILITY_SERVICE + " tag");
291            }
292
293            AttributeSet allAttributes = Xml.asAttributeSet(parser);
294            Resources resources = packageManager.getResourcesForApplication(
295                    serviceInfo.applicationInfo);
296            TypedArray asAttributes = resources.obtainAttributes(allAttributes,
297                    com.android.internal.R.styleable.AccessibilityService);
298            eventTypes = asAttributes.getInt(
299                    com.android.internal.R.styleable.AccessibilityService_accessibilityEventTypes,
300                    0);
301            String packageNamez = asAttributes.getString(
302                    com.android.internal.R.styleable.AccessibilityService_packageNames);
303            if (packageNamez != null) {
304                packageNames = packageNamez.split("(\\s)*,(\\s)*");
305            }
306            feedbackType = asAttributes.getInt(
307                    com.android.internal.R.styleable.AccessibilityService_accessibilityFeedbackType,
308                    0);
309            notificationTimeout = asAttributes.getInt(
310                    com.android.internal.R.styleable.AccessibilityService_notificationTimeout,
311                    0);
312            flags = asAttributes.getInt(
313                    com.android.internal.R.styleable.AccessibilityService_accessibilityFlags, 0);
314            mSettingsActivityName = asAttributes.getString(
315                    com.android.internal.R.styleable.AccessibilityService_settingsActivity);
316            mCanRetrieveWindowContent = asAttributes.getBoolean(
317                    com.android.internal.R.styleable.AccessibilityService_canRetrieveWindowContent,
318                    false);
319            TypedValue peekedValue = asAttributes.peekValue(
320                    com.android.internal.R.styleable.AccessibilityService_description);
321            if (peekedValue != null) {
322                mDescriptionResId = peekedValue.resourceId;
323                CharSequence nonLocalizedDescription = peekedValue.coerceToString();
324                if (nonLocalizedDescription != null) {
325                    mNonLocalizedDescription = nonLocalizedDescription.toString().trim();
326                }
327            }
328            asAttributes.recycle();
329        } catch (NameNotFoundException e) {
330            throw new XmlPullParserException( "Unable to create context for: "
331                    + serviceInfo.packageName);
332        } finally {
333            if (parser != null) {
334                parser.close();
335            }
336        }
337    }
338
339    /**
340     * Updates the properties that an AccessibilitySerivice can change dynamically.
341     *
342     * @param other The info from which to update the properties.
343     *
344     * @hide
345     */
346    public void updateDynamicallyConfigurableProperties(AccessibilityServiceInfo other) {
347        eventTypes = other.eventTypes;
348        packageNames = other.packageNames;
349        feedbackType = other.feedbackType;
350        notificationTimeout = other.notificationTimeout;
351        flags = other.flags;
352    }
353
354    /**
355     * The accessibility service id.
356     * <p>
357     *   <strong>Generated by the system.</strong>
358     * </p>
359     * @return The id.
360     */
361    public String getId() {
362        return mId;
363    }
364
365    /**
366     * The service {@link ResolveInfo}.
367     * <p>
368     *   <strong>Generated by the system.</strong>
369     * </p>
370     * @return The info.
371     */
372    public ResolveInfo getResolveInfo() {
373        return mResolveInfo;
374    }
375
376    /**
377     * The settings activity name.
378     * <p>
379     *    <strong>Statically set from
380     *    {@link AccessibilityService#SERVICE_META_DATA meta-data}.</strong>
381     * </p>
382     * @return The settings activity name.
383     */
384    public String getSettingsActivityName() {
385        return mSettingsActivityName;
386    }
387
388    /**
389     * Whether this service can retrieve the current window's content.
390     * <p>
391     *    <strong>Statically set from
392     *    {@link AccessibilityService#SERVICE_META_DATA meta-data}.</strong>
393     * </p>
394     * @return True if window content can be retrieved.
395     */
396    public boolean getCanRetrieveWindowContent() {
397        return mCanRetrieveWindowContent;
398    }
399
400    /**
401     * Gets the non-localized description of the accessibility service.
402     * <p>
403     *    <strong>Statically set from
404     *    {@link AccessibilityService#SERVICE_META_DATA meta-data}.</strong>
405     * </p>
406     * @return The description.
407     *
408     * @deprecated Use {@link #loadDescription(PackageManager)}.
409     */
410    public String getDescription() {
411        return mNonLocalizedDescription;
412    }
413
414    /**
415     * The localized description of the accessibility service.
416     * <p>
417     *    <strong>Statically set from
418     *    {@link AccessibilityService#SERVICE_META_DATA meta-data}.</strong>
419     * </p>
420     * @return The localized description.
421     */
422    public String loadDescription(PackageManager packageManager) {
423        if (mDescriptionResId == 0) {
424            return mNonLocalizedDescription;
425        }
426        ServiceInfo serviceInfo = mResolveInfo.serviceInfo;
427        CharSequence description = packageManager.getText(serviceInfo.packageName,
428                mDescriptionResId, serviceInfo.applicationInfo);
429        if (description != null) {
430            return description.toString().trim();
431        }
432        return null;
433    }
434
435    /**
436     * {@inheritDoc}
437     */
438    public int describeContents() {
439        return 0;
440    }
441
442    public void writeToParcel(Parcel parcel, int flagz) {
443        parcel.writeInt(eventTypes);
444        parcel.writeStringArray(packageNames);
445        parcel.writeInt(feedbackType);
446        parcel.writeLong(notificationTimeout);
447        parcel.writeInt(flags);
448        parcel.writeString(mId);
449        parcel.writeParcelable(mResolveInfo, 0);
450        parcel.writeString(mSettingsActivityName);
451        parcel.writeInt(mCanRetrieveWindowContent ? 1 : 0);
452        parcel.writeInt(mDescriptionResId);
453        parcel.writeString(mNonLocalizedDescription);
454    }
455
456    private void initFromParcel(Parcel parcel) {
457        eventTypes = parcel.readInt();
458        packageNames = parcel.readStringArray();
459        feedbackType = parcel.readInt();
460        notificationTimeout = parcel.readLong();
461        flags = parcel.readInt();
462        mId = parcel.readString();
463        mResolveInfo = parcel.readParcelable(null);
464        mSettingsActivityName = parcel.readString();
465        mCanRetrieveWindowContent = (parcel.readInt() == 1);
466        mDescriptionResId = parcel.readInt();
467        mNonLocalizedDescription = parcel.readString();
468    }
469
470    @Override
471    public String toString() {
472        StringBuilder stringBuilder = new StringBuilder();
473        appendEventTypes(stringBuilder, eventTypes);
474        stringBuilder.append(", ");
475        appendPackageNames(stringBuilder, packageNames);
476        stringBuilder.append(", ");
477        appendFeedbackTypes(stringBuilder, feedbackType);
478        stringBuilder.append(", ");
479        stringBuilder.append("notificationTimeout: ").append(notificationTimeout);
480        stringBuilder.append(", ");
481        appendFlags(stringBuilder, flags);
482        stringBuilder.append(", ");
483        stringBuilder.append("id: ").append(mId);
484        stringBuilder.append(", ");
485        stringBuilder.append("resolveInfo: ").append(mResolveInfo);
486        stringBuilder.append(", ");
487        stringBuilder.append("settingsActivityName: ").append(mSettingsActivityName);
488        stringBuilder.append(", ");
489        stringBuilder.append("retrieveScreenContent: ").append(mCanRetrieveWindowContent);
490        return stringBuilder.toString();
491    }
492
493    private static void appendFeedbackTypes(StringBuilder stringBuilder, int feedbackTypes) {
494        stringBuilder.append("feedbackTypes:");
495        stringBuilder.append("[");
496        while (feedbackTypes != 0) {
497            final int feedbackTypeBit = (1 << Integer.numberOfTrailingZeros(feedbackTypes));
498            stringBuilder.append(feedbackTypeToString(feedbackTypeBit));
499            feedbackTypes &= ~feedbackTypeBit;
500            if (feedbackTypes != 0) {
501                stringBuilder.append(", ");
502            }
503        }
504        stringBuilder.append("]");
505    }
506
507    private static void appendPackageNames(StringBuilder stringBuilder, String[] packageNames) {
508        stringBuilder.append("packageNames:");
509        stringBuilder.append("[");
510        if (packageNames != null) {
511            final int packageNameCount = packageNames.length;
512            for (int i = 0; i < packageNameCount; i++) {
513                stringBuilder.append(packageNames[i]);
514                if (i < packageNameCount - 1) {
515                    stringBuilder.append(", ");
516                }
517            }
518        }
519        stringBuilder.append("]");
520    }
521
522    private static void appendEventTypes(StringBuilder stringBuilder, int eventTypes) {
523        stringBuilder.append("eventTypes:");
524        stringBuilder.append("[");
525        while (eventTypes != 0) {
526            final int eventTypeBit = (1 << Integer.numberOfTrailingZeros(eventTypes));
527            stringBuilder.append(AccessibilityEvent.eventTypeToString(eventTypeBit));
528            eventTypes &= ~eventTypeBit;
529            if (eventTypes != 0) {
530                stringBuilder.append(", ");
531            }
532        }
533        stringBuilder.append("]");
534    }
535
536    private static void appendFlags(StringBuilder stringBuilder, int flags) {
537        stringBuilder.append("flags:");
538        stringBuilder.append("[");
539        while (flags != 0) {
540            final int flagBit = (1 << Integer.numberOfTrailingZeros(flags));
541            stringBuilder.append(flagToString(flagBit));
542            flags &= ~flagBit;
543            if (flags != 0) {
544                stringBuilder.append(", ");
545            }
546        }
547        stringBuilder.append("]");
548    }
549
550    /**
551     * Returns the string representation of a feedback type. For example,
552     * {@link #FEEDBACK_SPOKEN} is represented by the string FEEDBACK_SPOKEN.
553     *
554     * @param feedbackType The feedback type.
555     * @return The string representation.
556     */
557    public static String feedbackTypeToString(int feedbackType) {
558        StringBuilder builder = new StringBuilder();
559        builder.append("[");
560        while (feedbackType != 0) {
561            final int feedbackTypeFlag = 1 << Integer.numberOfTrailingZeros(feedbackType);
562            feedbackType &= ~feedbackTypeFlag;
563            switch (feedbackTypeFlag) {
564                case FEEDBACK_AUDIBLE:
565                    if (builder.length() > 1) {
566                        builder.append(", ");
567                    }
568                    builder.append("FEEDBACK_AUDIBLE");
569                    break;
570                case FEEDBACK_HAPTIC:
571                    if (builder.length() > 1) {
572                        builder.append(", ");
573                    }
574                    builder.append("FEEDBACK_HAPTIC");
575                    break;
576                case FEEDBACK_GENERIC:
577                    if (builder.length() > 1) {
578                        builder.append(", ");
579                    }
580                    builder.append("FEEDBACK_GENERIC");
581                    break;
582                case FEEDBACK_SPOKEN:
583                    if (builder.length() > 1) {
584                        builder.append(", ");
585                    }
586                    builder.append("FEEDBACK_SPOKEN");
587                    break;
588                case FEEDBACK_VISUAL:
589                    if (builder.length() > 1) {
590                        builder.append(", ");
591                    }
592                    builder.append("FEEDBACK_VISUAL");
593                    break;
594            }
595        }
596        builder.append("]");
597        return builder.toString();
598    }
599
600    /**
601     * Returns the string representation of a flag. For example,
602     * {@link #DEFAULT} is represented by the string DEFAULT.
603     *
604     * @param flag The flag.
605     * @return The string representation.
606     */
607    public static String flagToString(int flag) {
608        switch (flag) {
609            case DEFAULT:
610                return "DEFAULT";
611            case FLAG_INCLUDE_NOT_IMPORTANT_VIEWS:
612                return "FLAG_INCLUDE_NOT_IMPORTANT_VIEWS";
613            case FLAG_REQUEST_TOUCH_EXPLORATION_MODE:
614                return "FLAG_REQUEST_TOUCH_EXPLORATION_MODE";
615            default:
616                return null;
617        }
618    }
619
620    /**
621     * @see Parcelable.Creator
622     */
623    public static final Parcelable.Creator<AccessibilityServiceInfo> CREATOR =
624            new Parcelable.Creator<AccessibilityServiceInfo>() {
625        public AccessibilityServiceInfo createFromParcel(Parcel parcel) {
626            AccessibilityServiceInfo info = new AccessibilityServiceInfo();
627            info.initFromParcel(parcel);
628            return info;
629        }
630
631        public AccessibilityServiceInfo[] newArray(int size) {
632            return new AccessibilityServiceInfo[size];
633        }
634    };
635}
636