/* * Copyright (C) 2009 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.accessibilityservice; import android.content.ComponentName; import android.content.Context; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.content.res.Resources; import android.content.res.TypedArray; import android.content.res.XmlResourceParser; import android.os.Parcel; import android.os.Parcelable; import android.util.AttributeSet; import android.util.Xml; import android.view.accessibility.AccessibilityEvent; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; /** * This class describes an {@link AccessibilityService}. The system notifies an * {@link AccessibilityService} for {@link android.view.accessibility.AccessibilityEvent}s * according to the information encapsulated in this class. * * @see AccessibilityService * @see android.view.accessibility.AccessibilityEvent * @see android.view.accessibility.AccessibilityManager */ public class AccessibilityServiceInfo implements Parcelable { private static final String TAG_ACCESSIBILITY_SERVICE = "accessibility-service"; /** * Denotes spoken feedback. */ public static final int FEEDBACK_SPOKEN = 0x0000001; /** * Denotes haptic feedback. */ public static final int FEEDBACK_HAPTIC = 0x0000002; /** * Denotes audible (not spoken) feedback. */ public static final int FEEDBACK_AUDIBLE = 0x0000004; /** * Denotes visual feedback. */ public static final int FEEDBACK_VISUAL = 0x0000008; /** * Denotes generic feedback. */ public static final int FEEDBACK_GENERIC = 0x0000010; /** * Mask for all feedback types. * * @see #FEEDBACK_SPOKEN * @see #FEEDBACK_HAPTIC * @see #FEEDBACK_AUDIBLE * @see #FEEDBACK_VISUAL * @see #FEEDBACK_GENERIC */ public static final int FEEDBACK_ALL_MASK = 0xFFFFFFFF; /** * If an {@link AccessibilityService} is the default for a given type. * Default service is invoked only if no package specific one exists. In case of * more than one package specific service only the earlier registered is notified. */ public static final int DEFAULT = 0x0000001; /** * The event types an {@link AccessibilityService} is interested in. * <p> * <strong>Can be dynamically set at runtime.</strong> * </p> * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_CLICKED * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_LONG_CLICKED * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_FOCUSED * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_SELECTED * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_TEXT_CHANGED * @see android.view.accessibility.AccessibilityEvent#TYPE_WINDOW_STATE_CHANGED * @see android.view.accessibility.AccessibilityEvent#TYPE_NOTIFICATION_STATE_CHANGED * @see android.view.accessibility.AccessibilityEvent#TYPE_TOUCH_EXPLORATION_GESTURE_START * @see android.view.accessibility.AccessibilityEvent#TYPE_TOUCH_EXPLORATION_GESTURE_END * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_HOVER_ENTER * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_HOVER_EXIT * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_SCROLLED * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_TEXT_SELECTION_CHANGED * @see android.view.accessibility.AccessibilityEvent#TYPE_WINDOW_CONTENT_CHANGED */ public int eventTypes; /** * The package names an {@link AccessibilityService} is interested in. Setting * to <code>null</code> is equivalent to all packages. * <p> * <strong>Can be dynamically set at runtime.</strong> * </p> */ public String[] packageNames; /** * The feedback type an {@link AccessibilityService} provides. * <p> * <strong>Can be dynamically set at runtime.</strong> * </p> * @see #FEEDBACK_AUDIBLE * @see #FEEDBACK_GENERIC * @see #FEEDBACK_HAPTIC * @see #FEEDBACK_SPOKEN * @see #FEEDBACK_VISUAL */ public int feedbackType; /** * The timeout after the most recent event of a given type before an * {@link AccessibilityService} is notified. * <p> * <strong>Can be dynamically set at runtime.</strong>. * </p> * <p> * <strong>Note:</strong> The event notification timeout is useful to avoid propagating * events to the client too frequently since this is accomplished via an expensive * interprocess call. One can think of the timeout as a criteria to determine when * event generation has settled down. */ public long notificationTimeout; /** * This field represents a set of flags used for configuring an * {@link AccessibilityService}. * <p> * <strong>Can be dynamically set at runtime.</strong> * </p> * @see #DEFAULT */ public int flags; /** * The unique string Id to identify the accessibility service. */ private String mId; /** * The Service that implements this accessibility service component. */ private ResolveInfo mResolveInfo; /** * The accessibility service setting activity's name, used by the system * settings to launch the setting activity of this accessibility service. */ private String mSettingsActivityName; /** * Flag whether this accessibility service can retrieve window content. */ private boolean mCanRetrieveWindowContent; /** * Description of the accessibility service. */ private String mDescription; /** * Creates a new instance. */ public AccessibilityServiceInfo() { /* do nothing */ } /** * Creates a new instance. * * @param resolveInfo The service resolve info. * @param context Context for accessing resources. * @throws XmlPullParserException If a XML parsing error occurs. * @throws IOException If a XML parsing error occurs. * * @hide */ public AccessibilityServiceInfo(ResolveInfo resolveInfo, Context context) throws XmlPullParserException, IOException { ServiceInfo serviceInfo = resolveInfo.serviceInfo; mId = new ComponentName(serviceInfo.packageName, serviceInfo.name).flattenToShortString(); mResolveInfo = resolveInfo; XmlResourceParser parser = null; try { PackageManager packageManager = context.getPackageManager(); parser = serviceInfo.loadXmlMetaData(packageManager, AccessibilityService.SERVICE_META_DATA); if (parser == null) { return; } int type = 0; while (type != XmlPullParser.END_DOCUMENT && type != XmlPullParser.START_TAG) { type = parser.next(); } String nodeName = parser.getName(); if (!TAG_ACCESSIBILITY_SERVICE.equals(nodeName)) { throw new XmlPullParserException( "Meta-data does not start with" + TAG_ACCESSIBILITY_SERVICE + " tag"); } AttributeSet allAttributes = Xml.asAttributeSet(parser); Resources resources = packageManager.getResourcesForApplication( serviceInfo.applicationInfo); TypedArray asAttributes = resources.obtainAttributes(allAttributes, com.android.internal.R.styleable.AccessibilityService); eventTypes = asAttributes.getInt( com.android.internal.R.styleable.AccessibilityService_accessibilityEventTypes, 0); String packageNamez = asAttributes.getString( com.android.internal.R.styleable.AccessibilityService_packageNames); if (packageNamez != null) { packageNames = packageNamez.split("(\\s)*,(\\s)*"); } feedbackType = asAttributes.getInt( com.android.internal.R.styleable.AccessibilityService_accessibilityFeedbackType, 0); notificationTimeout = asAttributes.getInt( com.android.internal.R.styleable.AccessibilityService_notificationTimeout, 0); flags = asAttributes.getInt( com.android.internal.R.styleable.AccessibilityService_accessibilityFlags, 0); mSettingsActivityName = asAttributes.getString( com.android.internal.R.styleable.AccessibilityService_settingsActivity); mCanRetrieveWindowContent = asAttributes.getBoolean( com.android.internal.R.styleable.AccessibilityService_canRetrieveWindowContent, false); mDescription = asAttributes.getString( com.android.internal.R.styleable.AccessibilityService_description); asAttributes.recycle(); } catch (NameNotFoundException e) { throw new XmlPullParserException( "Unable to create context for: " + serviceInfo.packageName); } finally { if (parser != null) { parser.close(); } } } /** * Updates the properties that an AccessibilitySerivice can change dynamically. * * @param other The info from which to update the properties. * * @hide */ public void updateDynamicallyConfigurableProperties(AccessibilityServiceInfo other) { eventTypes = other.eventTypes; packageNames = other.packageNames; feedbackType = other.feedbackType; notificationTimeout = other.notificationTimeout; flags = other.flags; } /** * The accessibility service id. * <p> * <strong>Generated by the system.</strong> * </p> * @return The id. */ public String getId() { return mId; } /** * The service {@link ResolveInfo}. * <p> * <strong>Generated by the system.</strong> * </p> * @return The info. */ public ResolveInfo getResolveInfo() { return mResolveInfo; } /** * The settings activity name. * <p> * <strong>Statically set from * {@link AccessibilityService#SERVICE_META_DATA meta-data}.</strong> * </p> * @return The settings activity name. */ public String getSettingsActivityName() { return mSettingsActivityName; } /** * Whether this service can retrieve the current window's content. * <p> * <strong>Statically set from * {@link AccessibilityService#SERVICE_META_DATA meta-data}.</strong> * </p> * @return True window content can be retrieved. */ public boolean getCanRetrieveWindowContent() { return mCanRetrieveWindowContent; } /** * Description of the accessibility service. * <p> * <strong>Statically set from * {@link AccessibilityService#SERVICE_META_DATA meta-data}.</strong> * </p> * @return The description. */ public String getDescription() { return mDescription; } /** * {@inheritDoc} */ public int describeContents() { return 0; } public void writeToParcel(Parcel parcel, int flagz) { parcel.writeInt(eventTypes); parcel.writeStringArray(packageNames); parcel.writeInt(feedbackType); parcel.writeLong(notificationTimeout); parcel.writeInt(flags); parcel.writeString(mId); parcel.writeParcelable(mResolveInfo, 0); parcel.writeString(mSettingsActivityName); parcel.writeInt(mCanRetrieveWindowContent ? 1 : 0); parcel.writeString(mDescription); } private void initFromParcel(Parcel parcel) { eventTypes = parcel.readInt(); packageNames = parcel.readStringArray(); feedbackType = parcel.readInt(); notificationTimeout = parcel.readLong(); flags = parcel.readInt(); mId = parcel.readString(); mResolveInfo = parcel.readParcelable(null); mSettingsActivityName = parcel.readString(); mCanRetrieveWindowContent = (parcel.readInt() == 1); mDescription = parcel.readString(); } @Override public String toString() { StringBuilder stringBuilder = new StringBuilder(); appendEventTypes(stringBuilder, eventTypes); stringBuilder.append(", "); appendPackageNames(stringBuilder, packageNames); stringBuilder.append(", "); appendFeedbackTypes(stringBuilder, feedbackType); stringBuilder.append(", "); stringBuilder.append("notificationTimeout: ").append(notificationTimeout); stringBuilder.append(", "); appendFlags(stringBuilder, flags); stringBuilder.append(", "); stringBuilder.append("id: ").append(mId); stringBuilder.append(", "); stringBuilder.append("resolveInfo: ").append(mResolveInfo); stringBuilder.append(", "); stringBuilder.append("settingsActivityName: ").append(mSettingsActivityName); stringBuilder.append(", "); stringBuilder.append("retrieveScreenContent: ").append(mCanRetrieveWindowContent); return stringBuilder.toString(); } private static void appendFeedbackTypes(StringBuilder stringBuilder, int feedbackTypes) { stringBuilder.append("feedbackTypes:"); stringBuilder.append("["); while (feedbackTypes != 0) { final int feedbackTypeBit = (1 << Integer.numberOfTrailingZeros(feedbackTypes)); stringBuilder.append(feedbackTypeToString(feedbackTypeBit)); feedbackTypes &= ~feedbackTypeBit; if (feedbackTypes != 0) { stringBuilder.append(", "); } } stringBuilder.append("]"); } private static void appendPackageNames(StringBuilder stringBuilder, String[] packageNames) { stringBuilder.append("packageNames:"); stringBuilder.append("["); if (packageNames != null) { final int packageNameCount = packageNames.length; for (int i = 0; i < packageNameCount; i++) { stringBuilder.append(packageNames[i]); if (i < packageNameCount - 1) { stringBuilder.append(", "); } } } stringBuilder.append("]"); } private static void appendEventTypes(StringBuilder stringBuilder, int eventTypes) { stringBuilder.append("eventTypes:"); stringBuilder.append("["); while (eventTypes != 0) { final int eventTypeBit = (1 << Integer.numberOfTrailingZeros(eventTypes)); stringBuilder.append(AccessibilityEvent.eventTypeToString(eventTypeBit)); eventTypes &= ~eventTypeBit; if (eventTypes != 0) { stringBuilder.append(", "); } } stringBuilder.append("]"); } private static void appendFlags(StringBuilder stringBuilder, int flags) { stringBuilder.append("flags:"); stringBuilder.append("["); while (flags != 0) { final int flagBit = (1 << Integer.numberOfTrailingZeros(flags)); stringBuilder.append(flagToString(flagBit)); flags &= ~flagBit; if (flags != 0) { stringBuilder.append(", "); } } stringBuilder.append("]"); } /** * Returns the string representation of a feedback type. For example, * {@link #FEEDBACK_SPOKEN} is represented by the string FEEDBACK_SPOKEN. * * @param feedbackType The feedback type. * @return The string representation. */ public static String feedbackTypeToString(int feedbackType) { StringBuilder builder = new StringBuilder(); builder.append("["); while (feedbackType > 0) { final int feedbackTypeFlag = 1 << Integer.numberOfTrailingZeros(feedbackType); feedbackType &= ~feedbackTypeFlag; if (builder.length() > 1) { builder.append(", "); } switch (feedbackTypeFlag) { case FEEDBACK_AUDIBLE: builder.append("FEEDBACK_AUDIBLE"); break; case FEEDBACK_HAPTIC: builder.append("FEEDBACK_HAPTIC"); break; case FEEDBACK_GENERIC: builder.append("FEEDBACK_GENERIC"); break; case FEEDBACK_SPOKEN: builder.append("FEEDBACK_SPOKEN"); break; case FEEDBACK_VISUAL: builder.append("FEEDBACK_VISUAL"); break; } } builder.append("]"); return builder.toString(); } /** * Returns the string representation of a flag. For example, * {@link #DEFAULT} is represented by the string DEFAULT. * * @param flag The flag. * @return The string representation. */ public static String flagToString(int flag) { switch (flag) { case DEFAULT: return "DEFAULT"; default: return null; } } /** * @see Parcelable.Creator */ public static final Parcelable.Creator<AccessibilityServiceInfo> CREATOR = new Parcelable.Creator<AccessibilityServiceInfo>() { public AccessibilityServiceInfo createFromParcel(Parcel parcel) { AccessibilityServiceInfo info = new AccessibilityServiceInfo(); info.initFromParcel(parcel); return info; } public AccessibilityServiceInfo[] newArray(int size) { return new AccessibilityServiceInfo[size]; } }; }