AccessibilityServiceInfo.java revision cc4053e031371456fe54d51bbad1db721db4ae38
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.Xml;
32import android.view.accessibility.AccessibilityEvent;
33
34import org.xmlpull.v1.XmlPullParser;
35import org.xmlpull.v1.XmlPullParserException;
36
37import java.io.IOException;
38
39/**
40 * This class describes an {@link AccessibilityService}. The system
41 * notifies an {@link AccessibilityService} for
42 * {@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 */
48public class AccessibilityServiceInfo implements Parcelable {
49
50    private static final String TAG_ACCESSIBILITY_SERVICE = "accessibility-service";
51
52    /**
53     * Denotes spoken feedback.
54     */
55    public static final int FEEDBACK_SPOKEN = 0x0000001;
56
57    /**
58     * Denotes haptic feedback.
59     */
60    public static final int FEEDBACK_HAPTIC =  0x0000002;
61
62    /**
63     * Denotes audible (not spoken) feedback.
64     */
65    public static final int FEEDBACK_AUDIBLE = 0x0000004;
66
67    /**
68     * Denotes visual feedback.
69     */
70    public static final int FEEDBACK_VISUAL = 0x0000008;
71
72    /**
73     * Denotes generic feedback.
74     */
75    public static final int FEEDBACK_GENERIC = 0x0000010;
76
77    /**
78     * If an {@link AccessibilityService} is the default for a given type.
79     * Default service is invoked only if no package specific one exists. In case of
80     * more than one package specific service only the earlier registered is notified.
81     */
82    public static final int DEFAULT = 0x0000001;
83
84    /**
85     * The event types an {@link AccessibilityService} is interested in.
86     * <p>
87     *   <strong>Can be dynamically set at runtime.</strong>
88     * </p>
89     * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_CLICKED
90     * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_LONG_CLICKED
91     * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_FOCUSED
92     * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_SELECTED
93     * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_TEXT_CHANGED
94     * @see android.view.accessibility.AccessibilityEvent#TYPE_WINDOW_STATE_CHANGED
95     * @see android.view.accessibility.AccessibilityEvent#TYPE_NOTIFICATION_STATE_CHANGED
96     */
97    public int eventTypes;
98
99    /**
100     * The package names an {@link AccessibilityService} is interested in. Setting
101     * to null is equivalent to all packages.
102     * <p>
103     *   <strong>Can be dynamically set at runtime.</strong>
104     * </p>
105     */
106    public String[] packageNames;
107
108    /**
109     * The feedback type an {@link AccessibilityService} provides.
110     * <p>
111     *   <strong>Can be dynamically set at runtime.</strong>
112     * </p>
113     * @see #FEEDBACK_AUDIBLE
114     * @see #FEEDBACK_GENERIC
115     * @see #FEEDBACK_HAPTIC
116     * @see #FEEDBACK_SPOKEN
117     * @see #FEEDBACK_VISUAL
118     */
119    public int feedbackType;
120
121    /**
122     * The timeout after the most recent event of a given type before an
123     * {@link AccessibilityService} is notified.
124     * <p>
125     *   <strong>Can be dynamically set at runtime.</strong>.
126     * </p>
127     * <p>
128     * Note: The event notification timeout is useful to avoid propagating events to the client
129     *       too frequently since this is accomplished via an expensive interprocess call.
130     *       One can think of the timeout as a criteria to determine when event generation has
131     *       settled down
132     */
133    public long notificationTimeout;
134
135    /**
136     * This field represents a set of flags used for configuring an
137     * {@link AccessibilityService}.
138     * <p>
139     *   <strong>Can be dynamically set at runtime.</strong>
140     * </p>
141     * @see #DEFAULT
142     */
143    public int flags;
144
145    /**
146     * The unique string Id to identify the accessibility service.
147     */
148    private String mId;
149
150    /**
151     * The Service that implements this accessibility service component.
152     */
153    private ResolveInfo mResolveInfo;
154
155    /**
156     * The accessibility service setting activity's name, used by the system
157     * settings to launch the setting activity of this accessibility service.
158     */
159    private String mSettingsActivityName;
160
161    /**
162     * Flag whether this accessibility service can retrieve screen content.
163     */
164    private boolean mCanRetrieveWindowContent;
165
166    /**
167     * Creates a new instance.
168     */
169    public AccessibilityServiceInfo() {
170        /* do nothing */
171    }
172
173    /**
174     * Creates a new instance.
175     *
176     * @param resolveInfo The service resolve info.
177     * @param context Context for accessing resources.
178     * @throws XmlPullParserException If a XML parsing error occurs.
179     * @throws IOException If a XML parsing error occurs.
180     *
181     * @hide
182     */
183    public AccessibilityServiceInfo(ResolveInfo resolveInfo, Context context)
184            throws XmlPullParserException, IOException {
185        ServiceInfo serviceInfo = resolveInfo.serviceInfo;
186        mId = new ComponentName(serviceInfo.packageName, serviceInfo.name).flattenToShortString();
187        mResolveInfo = resolveInfo;
188
189        String settingsActivityName = null;
190        boolean retrieveScreenContent = false;
191        XmlResourceParser parser = null;
192
193        try {
194            PackageManager packageManager = context.getPackageManager();
195            parser = serviceInfo.loadXmlMetaData(packageManager,
196                    AccessibilityService.SERVICE_META_DATA);
197            if (parser == null) {
198                return;
199            }
200
201            int type = 0;
202            while (type != XmlPullParser.END_DOCUMENT && type != XmlPullParser.START_TAG) {
203                type = parser.next();
204            }
205
206            String nodeName = parser.getName();
207            if (!TAG_ACCESSIBILITY_SERVICE.equals(nodeName)) {
208                throw new XmlPullParserException( "Meta-data does not start with"
209                        + TAG_ACCESSIBILITY_SERVICE + " tag");
210            }
211
212            AttributeSet allAttributes = Xml.asAttributeSet(parser);
213            Resources resources = packageManager.getResourcesForApplication(
214                    serviceInfo.applicationInfo);
215            TypedArray asAttributes = resources.obtainAttributes(allAttributes,
216                    com.android.internal.R.styleable.AccessibilityService);
217            eventTypes = asAttributes.getInt(
218                    com.android.internal.R.styleable.AccessibilityService_accessibilityEventTypes,
219                    0);
220            String packageNamez = asAttributes.getString(
221                    com.android.internal.R.styleable.AccessibilityService_packageNames);
222            if (packageNamez != null) {
223                packageNames = packageNamez.split("(\\s)*,(\\s)*");
224            }
225            feedbackType = asAttributes.getInt(
226                    com.android.internal.R.styleable.AccessibilityService_accessibilityFeedbackType,
227                    0);
228            notificationTimeout = asAttributes.getInt(
229                    com.android.internal.R.styleable.AccessibilityService_notificationTimeout,
230                    0);
231            flags = asAttributes.getInt(
232                    com.android.internal.R.styleable.AccessibilityService_accessibilityFlags, 0);
233            mSettingsActivityName = asAttributes.getString(
234                    com.android.internal.R.styleable.AccessibilityService_settingsActivity);
235            mCanRetrieveWindowContent = asAttributes.getBoolean(
236                    com.android.internal.R.styleable.AccessibilityService_canRetrieveWindowContent,
237                    false);
238            asAttributes.recycle();
239        } catch (NameNotFoundException e) {
240            throw new XmlPullParserException( "Unable to create context for: "
241                    + serviceInfo.packageName);
242        } finally {
243            if (parser != null) {
244                parser.close();
245            }
246        }
247    }
248
249    /**
250     * Updates the properties that an AccessibilitySerivice can change dynamically.
251     *
252     * @param other The info from which to update the properties.
253     *
254     * @hide
255     */
256    public void updateDynamicallyConfigurableProperties(AccessibilityServiceInfo other) {
257        eventTypes = other.eventTypes;
258        packageNames = other.packageNames;
259        feedbackType = other.feedbackType;
260        notificationTimeout = other.notificationTimeout;
261        flags = other.flags;
262    }
263
264    /**
265     * The accessibility service id.
266     * <p>
267     *   <strong>Generated by the system.</strong>
268     * </p>
269     * @return The id.
270     */
271    public String getId() {
272        return mId;
273    }
274
275    /**
276     * The service {@link ResolveInfo}.
277     * <p>
278     *   <strong>Generated by the system.</strong>
279     * </p>
280     * @return The info.
281     */
282    public ResolveInfo getResolveInfo() {
283        return mResolveInfo;
284    }
285
286    /**
287     * The settings activity name.
288     * <p>
289     *    <strong>Statically set from
290     *    {@link AccessibilityService#SERVICE_META_DATA meta-data}.</strong>
291     * </p>
292     * @return The settings activity name.
293     */
294    public String getSettingsActivityName() {
295        return mSettingsActivityName;
296    }
297
298    /**
299     * Whether this service can retrieve the currently focused window content.
300     * <p>
301     *    <strong>Statically set from
302     *    {@link AccessibilityService#SERVICE_META_DATA meta-data}.</strong>
303     * </p>
304     * @return True screen content is retrieved.
305     */
306    public boolean getCanRetrieveWindowContent() {
307        return mCanRetrieveWindowContent;
308    }
309
310    /**
311     * {@inheritDoc}
312     */
313    public int describeContents() {
314        return 0;
315    }
316
317    public void writeToParcel(Parcel parcel, int flagz) {
318        parcel.writeInt(eventTypes);
319        parcel.writeStringArray(packageNames);
320        parcel.writeInt(feedbackType);
321        parcel.writeLong(notificationTimeout);
322        parcel.writeInt(flags);
323        parcel.writeString(mId);
324        parcel.writeParcelable(mResolveInfo, 0);
325        parcel.writeString(mSettingsActivityName);
326        parcel.writeInt(mCanRetrieveWindowContent ? 1 : 0);
327    }
328
329    private void initFromParcel(Parcel parcel) {
330        eventTypes = parcel.readInt();
331        packageNames = parcel.readStringArray();
332        feedbackType = parcel.readInt();
333        notificationTimeout = parcel.readLong();
334        flags = parcel.readInt();
335        mId = parcel.readString();
336        mResolveInfo = parcel.readParcelable(null);
337        mSettingsActivityName = parcel.readString();
338        mCanRetrieveWindowContent = (parcel.readInt() == 1);
339    }
340
341    @Override
342    public String toString() {
343        StringBuilder stringBuilder = new StringBuilder();
344        appendEventTypes(stringBuilder, eventTypes);
345        stringBuilder.append(", ");
346        appendPackageNames(stringBuilder, packageNames);
347        stringBuilder.append(", ");
348        appendFeedbackTypes(stringBuilder, feedbackType);
349        stringBuilder.append(", ");
350        stringBuilder.append("notificationTimeout: ").append(notificationTimeout);
351        stringBuilder.append(", ");
352        appendFlags(stringBuilder, flags);
353        stringBuilder.append(", ");
354        stringBuilder.append("id: ").append(mId);
355        stringBuilder.append(", ");
356        stringBuilder.append("resolveInfo: ").append(mResolveInfo);
357        stringBuilder.append(", ");
358        stringBuilder.append("settingsActivityName: ").append(mSettingsActivityName);
359        stringBuilder.append(", ");
360        stringBuilder.append("retrieveScreenContent: ").append(mCanRetrieveWindowContent);
361        return stringBuilder.toString();
362    }
363
364    private static void appendFeedbackTypes(StringBuilder stringBuilder, int feedbackTypes) {
365        stringBuilder.append("feedbackTypes:");
366        stringBuilder.append("[");
367        while (feedbackTypes != 0) {
368            final int feedbackTypeBit = (1 << Integer.numberOfTrailingZeros(feedbackTypes));
369            stringBuilder.append(feedbackTypeToString(feedbackTypeBit));
370            feedbackTypes &= ~feedbackTypeBit;
371            if (feedbackTypes != 0) {
372                stringBuilder.append(", ");
373            }
374        }
375        stringBuilder.append("]");
376    }
377
378    private static void appendPackageNames(StringBuilder stringBuilder, String[] packageNames) {
379        stringBuilder.append("packageNames:");
380        stringBuilder.append("[");
381        if (packageNames != null) {
382            final int packageNameCount = packageNames.length;
383            for (int i = 0; i < packageNameCount; i++) {
384                stringBuilder.append(packageNames[i]);
385                if (i < packageNameCount - 1) {
386                    stringBuilder.append(", ");
387                }
388            }
389        }
390        stringBuilder.append("]");
391    }
392
393    private static void appendEventTypes(StringBuilder stringBuilder, int eventTypes) {
394        stringBuilder.append("eventTypes:");
395        stringBuilder.append("[");
396        while (eventTypes != 0) {
397            final int eventTypeBit = (1 << Integer.numberOfTrailingZeros(eventTypes));
398            stringBuilder.append(AccessibilityEvent.eventTypeToString(eventTypeBit));
399            eventTypes &= ~eventTypeBit;
400            if (eventTypes != 0) {
401                stringBuilder.append(", ");
402            }
403        }
404        stringBuilder.append("]");
405    }
406
407    private static void appendFlags(StringBuilder stringBuilder, int flags) {
408        stringBuilder.append("flags:");
409        stringBuilder.append("[");
410        while (flags != 0) {
411            final int flagBit = (1 << Integer.numberOfTrailingZeros(flags));
412            stringBuilder.append(flagToString(flagBit));
413            flags &= ~flagBit;
414            if (flags != 0) {
415                stringBuilder.append(", ");
416            }
417        }
418        stringBuilder.append("]");
419    }
420
421    /**
422     * Returns the string representation of a feedback type. For example,
423     * {@link #FEEDBACK_SPOKEN} is represented by the string FEEDBACK_SPOKEN.
424     *
425     * @param feedbackType The feedback type.
426     * @return The string representation.
427     */
428    public static String feedbackTypeToString(int feedbackType) {
429        switch (feedbackType) {
430            case FEEDBACK_AUDIBLE:
431                return "FEEDBACK_AUDIBLE";
432            case FEEDBACK_HAPTIC:
433                return "FEEDBACK_HAPTIC";
434            case FEEDBACK_GENERIC:
435                return "FEEDBACK_GENERIC";
436            case FEEDBACK_SPOKEN:
437                return "FEEDBACK_SPOKEN";
438            case FEEDBACK_VISUAL:
439                return "FEEDBACK_VISUAL";
440            default:
441                return null;
442        }
443    }
444
445    /**
446     * Returns the string representation of a flag. For example,
447     * {@link #DEFAULT} is represented by the string DEFAULT.
448     *
449     * @param flag The flag.
450     * @return The string representation.
451     */
452    public static String flagToString(int flag) {
453        switch (flag) {
454            case DEFAULT:
455                return "DEFAULT";
456            default:
457                return null;
458        }
459    }
460
461    /**
462     * @see Parcelable.Creator
463     */
464    public static final Parcelable.Creator<AccessibilityServiceInfo> CREATOR =
465            new Parcelable.Creator<AccessibilityServiceInfo>() {
466        public AccessibilityServiceInfo createFromParcel(Parcel parcel) {
467            AccessibilityServiceInfo info = new AccessibilityServiceInfo();
468            info.initFromParcel(parcel);
469            return info;
470        }
471
472        public AccessibilityServiceInfo[] newArray(int size) {
473            return new AccessibilityServiceInfo[size];
474        }
475    };
476}
477