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