AccessibilityServiceInfo.java revision 4213804541a8b05cd0587b138a2fd9a3b7fd9350
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     * Resource id of the description of the accessibility service.
228     */
229    private int mDescriptionResId;
230
231    /**
232     * Non localized description of the accessibility service.
233     */
234    private String mNonLocalizedDescription;
235
236    /**
237     * Creates a new instance.
238     */
239    public AccessibilityServiceInfo() {
240        /* do nothing */
241    }
242
243    /**
244     * Creates a new instance.
245     *
246     * @param resolveInfo The service resolve info.
247     * @param context Context for accessing resources.
248     * @throws XmlPullParserException If a XML parsing error occurs.
249     * @throws IOException If a XML parsing error occurs.
250     *
251     * @hide
252     */
253    public AccessibilityServiceInfo(ResolveInfo resolveInfo, Context context)
254            throws XmlPullParserException, IOException {
255        ServiceInfo serviceInfo = resolveInfo.serviceInfo;
256        mId = new ComponentName(serviceInfo.packageName, serviceInfo.name).flattenToShortString();
257        mResolveInfo = resolveInfo;
258
259        XmlResourceParser parser = null;
260
261        try {
262            PackageManager packageManager = context.getPackageManager();
263            parser = serviceInfo.loadXmlMetaData(packageManager,
264                    AccessibilityService.SERVICE_META_DATA);
265            if (parser == null) {
266                return;
267            }
268
269            int type = 0;
270            while (type != XmlPullParser.END_DOCUMENT && type != XmlPullParser.START_TAG) {
271                type = parser.next();
272            }
273
274            String nodeName = parser.getName();
275            if (!TAG_ACCESSIBILITY_SERVICE.equals(nodeName)) {
276                throw new XmlPullParserException( "Meta-data does not start with"
277                        + TAG_ACCESSIBILITY_SERVICE + " tag");
278            }
279
280            AttributeSet allAttributes = Xml.asAttributeSet(parser);
281            Resources resources = packageManager.getResourcesForApplication(
282                    serviceInfo.applicationInfo);
283            TypedArray asAttributes = resources.obtainAttributes(allAttributes,
284                    com.android.internal.R.styleable.AccessibilityService);
285            eventTypes = asAttributes.getInt(
286                    com.android.internal.R.styleable.AccessibilityService_accessibilityEventTypes,
287                    0);
288            String packageNamez = asAttributes.getString(
289                    com.android.internal.R.styleable.AccessibilityService_packageNames);
290            if (packageNamez != null) {
291                packageNames = packageNamez.split("(\\s)*,(\\s)*");
292            }
293            feedbackType = asAttributes.getInt(
294                    com.android.internal.R.styleable.AccessibilityService_accessibilityFeedbackType,
295                    0);
296            notificationTimeout = asAttributes.getInt(
297                    com.android.internal.R.styleable.AccessibilityService_notificationTimeout,
298                    0);
299            flags = asAttributes.getInt(
300                    com.android.internal.R.styleable.AccessibilityService_accessibilityFlags, 0);
301            mSettingsActivityName = asAttributes.getString(
302                    com.android.internal.R.styleable.AccessibilityService_settingsActivity);
303            mCanRetrieveWindowContent = asAttributes.getBoolean(
304                    com.android.internal.R.styleable.AccessibilityService_canRetrieveWindowContent,
305                    false);
306            TypedValue peekedValue = asAttributes.peekValue(
307                    com.android.internal.R.styleable.AccessibilityService_description);
308            if (peekedValue != null) {
309                mDescriptionResId = peekedValue.resourceId;
310                CharSequence nonLocalizedDescription = peekedValue.coerceToString();
311                if (nonLocalizedDescription != null) {
312                    mNonLocalizedDescription = nonLocalizedDescription.toString().trim();
313                }
314            }
315            asAttributes.recycle();
316        } catch (NameNotFoundException e) {
317            throw new XmlPullParserException( "Unable to create context for: "
318                    + serviceInfo.packageName);
319        } finally {
320            if (parser != null) {
321                parser.close();
322            }
323        }
324    }
325
326    /**
327     * Updates the properties that an AccessibilitySerivice can change dynamically.
328     *
329     * @param other The info from which to update the properties.
330     *
331     * @hide
332     */
333    public void updateDynamicallyConfigurableProperties(AccessibilityServiceInfo other) {
334        eventTypes = other.eventTypes;
335        packageNames = other.packageNames;
336        feedbackType = other.feedbackType;
337        notificationTimeout = other.notificationTimeout;
338        flags = other.flags;
339    }
340
341    /**
342     * The accessibility service id.
343     * <p>
344     *   <strong>Generated by the system.</strong>
345     * </p>
346     * @return The id.
347     */
348    public String getId() {
349        return mId;
350    }
351
352    /**
353     * The service {@link ResolveInfo}.
354     * <p>
355     *   <strong>Generated by the system.</strong>
356     * </p>
357     * @return The info.
358     */
359    public ResolveInfo getResolveInfo() {
360        return mResolveInfo;
361    }
362
363    /**
364     * The settings activity name.
365     * <p>
366     *    <strong>Statically set from
367     *    {@link AccessibilityService#SERVICE_META_DATA meta-data}.</strong>
368     * </p>
369     * @return The settings activity name.
370     */
371    public String getSettingsActivityName() {
372        return mSettingsActivityName;
373    }
374
375    /**
376     * Whether this service can retrieve the current window's content.
377     * <p>
378     *    <strong>Statically set from
379     *    {@link AccessibilityService#SERVICE_META_DATA meta-data}.</strong>
380     * </p>
381     * @return True window content can be retrieved.
382     */
383    public boolean getCanRetrieveWindowContent() {
384        return mCanRetrieveWindowContent;
385    }
386
387    /**
388     * Gets the non-localized description of the accessibility service.
389     * <p>
390     *    <strong>Statically set from
391     *    {@link AccessibilityService#SERVICE_META_DATA meta-data}.</strong>
392     * </p>
393     * @return The description.
394     *
395     * @deprecated Use {@link #loadDescription(PackageManager)}.
396     */
397    public String getDescription() {
398        return mNonLocalizedDescription;
399    }
400
401    /**
402     * The localized description of the accessibility service.
403     * <p>
404     *    <strong>Statically set from
405     *    {@link AccessibilityService#SERVICE_META_DATA meta-data}.</strong>
406     * </p>
407     * @return The localized description.
408     */
409    public String loadDescription(PackageManager packageManager) {
410        if (mDescriptionResId == 0) {
411            return mNonLocalizedDescription;
412        }
413        ServiceInfo serviceInfo = mResolveInfo.serviceInfo;
414        CharSequence description = packageManager.getText(serviceInfo.packageName,
415                mDescriptionResId, serviceInfo.applicationInfo);
416        if (description != null) {
417            return description.toString().trim();
418        }
419        return null;
420    }
421
422    /**
423     * {@inheritDoc}
424     */
425    public int describeContents() {
426        return 0;
427    }
428
429    public void writeToParcel(Parcel parcel, int flagz) {
430        parcel.writeInt(eventTypes);
431        parcel.writeStringArray(packageNames);
432        parcel.writeInt(feedbackType);
433        parcel.writeLong(notificationTimeout);
434        parcel.writeInt(flags);
435        parcel.writeString(mId);
436        parcel.writeParcelable(mResolveInfo, 0);
437        parcel.writeString(mSettingsActivityName);
438        parcel.writeInt(mCanRetrieveWindowContent ? 1 : 0);
439        parcel.writeInt(mDescriptionResId);
440        parcel.writeString(mNonLocalizedDescription);
441    }
442
443    private void initFromParcel(Parcel parcel) {
444        eventTypes = parcel.readInt();
445        packageNames = parcel.readStringArray();
446        feedbackType = parcel.readInt();
447        notificationTimeout = parcel.readLong();
448        flags = parcel.readInt();
449        mId = parcel.readString();
450        mResolveInfo = parcel.readParcelable(null);
451        mSettingsActivityName = parcel.readString();
452        mCanRetrieveWindowContent = (parcel.readInt() == 1);
453        mDescriptionResId = parcel.readInt();
454        mNonLocalizedDescription = parcel.readString();
455    }
456
457    @Override
458    public String toString() {
459        StringBuilder stringBuilder = new StringBuilder();
460        appendEventTypes(stringBuilder, eventTypes);
461        stringBuilder.append(", ");
462        appendPackageNames(stringBuilder, packageNames);
463        stringBuilder.append(", ");
464        appendFeedbackTypes(stringBuilder, feedbackType);
465        stringBuilder.append(", ");
466        stringBuilder.append("notificationTimeout: ").append(notificationTimeout);
467        stringBuilder.append(", ");
468        appendFlags(stringBuilder, flags);
469        stringBuilder.append(", ");
470        stringBuilder.append("id: ").append(mId);
471        stringBuilder.append(", ");
472        stringBuilder.append("resolveInfo: ").append(mResolveInfo);
473        stringBuilder.append(", ");
474        stringBuilder.append("settingsActivityName: ").append(mSettingsActivityName);
475        stringBuilder.append(", ");
476        stringBuilder.append("retrieveScreenContent: ").append(mCanRetrieveWindowContent);
477        return stringBuilder.toString();
478    }
479
480    private static void appendFeedbackTypes(StringBuilder stringBuilder, int feedbackTypes) {
481        stringBuilder.append("feedbackTypes:");
482        stringBuilder.append("[");
483        while (feedbackTypes != 0) {
484            final int feedbackTypeBit = (1 << Integer.numberOfTrailingZeros(feedbackTypes));
485            stringBuilder.append(feedbackTypeToString(feedbackTypeBit));
486            feedbackTypes &= ~feedbackTypeBit;
487            if (feedbackTypes != 0) {
488                stringBuilder.append(", ");
489            }
490        }
491        stringBuilder.append("]");
492    }
493
494    private static void appendPackageNames(StringBuilder stringBuilder, String[] packageNames) {
495        stringBuilder.append("packageNames:");
496        stringBuilder.append("[");
497        if (packageNames != null) {
498            final int packageNameCount = packageNames.length;
499            for (int i = 0; i < packageNameCount; i++) {
500                stringBuilder.append(packageNames[i]);
501                if (i < packageNameCount - 1) {
502                    stringBuilder.append(", ");
503                }
504            }
505        }
506        stringBuilder.append("]");
507    }
508
509    private static void appendEventTypes(StringBuilder stringBuilder, int eventTypes) {
510        stringBuilder.append("eventTypes:");
511        stringBuilder.append("[");
512        while (eventTypes != 0) {
513            final int eventTypeBit = (1 << Integer.numberOfTrailingZeros(eventTypes));
514            stringBuilder.append(AccessibilityEvent.eventTypeToString(eventTypeBit));
515            eventTypes &= ~eventTypeBit;
516            if (eventTypes != 0) {
517                stringBuilder.append(", ");
518            }
519        }
520        stringBuilder.append("]");
521    }
522
523    private static void appendFlags(StringBuilder stringBuilder, int flags) {
524        stringBuilder.append("flags:");
525        stringBuilder.append("[");
526        while (flags != 0) {
527            final int flagBit = (1 << Integer.numberOfTrailingZeros(flags));
528            stringBuilder.append(flagToString(flagBit));
529            flags &= ~flagBit;
530            if (flags != 0) {
531                stringBuilder.append(", ");
532            }
533        }
534        stringBuilder.append("]");
535    }
536
537    /**
538     * Returns the string representation of a feedback type. For example,
539     * {@link #FEEDBACK_SPOKEN} is represented by the string FEEDBACK_SPOKEN.
540     *
541     * @param feedbackType The feedback type.
542     * @return The string representation.
543     */
544    public static String feedbackTypeToString(int feedbackType) {
545        StringBuilder builder = new StringBuilder();
546        builder.append("[");
547        while (feedbackType != 0) {
548            final int feedbackTypeFlag = 1 << Integer.numberOfTrailingZeros(feedbackType);
549            feedbackType &= ~feedbackTypeFlag;
550            switch (feedbackTypeFlag) {
551                case FEEDBACK_AUDIBLE:
552                    if (builder.length() > 1) {
553                        builder.append(", ");
554                    }
555                    builder.append("FEEDBACK_AUDIBLE");
556                    break;
557                case FEEDBACK_HAPTIC:
558                    if (builder.length() > 1) {
559                        builder.append(", ");
560                    }
561                    builder.append("FEEDBACK_HAPTIC");
562                    break;
563                case FEEDBACK_GENERIC:
564                    if (builder.length() > 1) {
565                        builder.append(", ");
566                    }
567                    builder.append("FEEDBACK_GENERIC");
568                    break;
569                case FEEDBACK_SPOKEN:
570                    if (builder.length() > 1) {
571                        builder.append(", ");
572                    }
573                    builder.append("FEEDBACK_SPOKEN");
574                    break;
575                case FEEDBACK_VISUAL:
576                    if (builder.length() > 1) {
577                        builder.append(", ");
578                    }
579                    builder.append("FEEDBACK_VISUAL");
580                    break;
581            }
582        }
583        builder.append("]");
584        return builder.toString();
585    }
586
587    /**
588     * Returns the string representation of a flag. For example,
589     * {@link #DEFAULT} is represented by the string DEFAULT.
590     *
591     * @param flag The flag.
592     * @return The string representation.
593     */
594    public static String flagToString(int flag) {
595        switch (flag) {
596            case DEFAULT:
597                return "DEFAULT";
598            case INCLUDE_NOT_IMPORTANT_VIEWS:
599                return "REGARD_VIEWS_NOT_IMPORTANT_FOR_ACCESSIBILITY";
600            default:
601                return null;
602        }
603    }
604
605    /**
606     * @see Parcelable.Creator
607     */
608    public static final Parcelable.Creator<AccessibilityServiceInfo> CREATOR =
609            new Parcelable.Creator<AccessibilityServiceInfo>() {
610        public AccessibilityServiceInfo createFromParcel(Parcel parcel) {
611            AccessibilityServiceInfo info = new AccessibilityServiceInfo();
612            info.initFromParcel(parcel);
613            return info;
614        }
615
616        public AccessibilityServiceInfo[] newArray(int size) {
617            return new AccessibilityServiceInfo[size];
618        }
619    };
620}
621