| /* |
| * 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; |
| |
| /** |
| * 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; |
| |
| /** |
| * 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); |
| 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; |
| } |
| |
| /** |
| * {@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); |
| } |
| |
| 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); |
| } |
| |
| @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) { |
| switch (feedbackType) { |
| case FEEDBACK_AUDIBLE: |
| return "FEEDBACK_AUDIBLE"; |
| case FEEDBACK_HAPTIC: |
| return "FEEDBACK_HAPTIC"; |
| case FEEDBACK_GENERIC: |
| return "FEEDBACK_GENERIC"; |
| case FEEDBACK_SPOKEN: |
| return "FEEDBACK_SPOKEN"; |
| case FEEDBACK_VISUAL: |
| return "FEEDBACK_VISUAL"; |
| default: |
| return null; |
| } |
| } |
| |
| /** |
| * 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]; |
| } |
| }; |
| } |