blob: b9878cd9c97a0c9f06c34137a1174d0008a1ebd9 [file] [log] [blame]
/*
* 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
*/
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
*/
public int eventTypes;
/**
* The package names an {@link AccessibilityService} is interested in. Setting
* to null 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>
* Note: 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 screen 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;
String settingsActivityName = null;
boolean retrieveScreenContent = false;
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 currently focused window content.
* <p>
* <strong>Statically set from
* {@link AccessibilityService#SERVICE_META_DATA meta-data}.</strong>
* </p>
* @return True screen content is 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];
}
};
}