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