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