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 17 package android.accessibilityservice; 18 19 import android.content.ComponentName; 20 import android.content.Context; 21 import android.content.pm.PackageManager; 22 import android.content.pm.PackageManager.NameNotFoundException; 23 import android.content.pm.ResolveInfo; 24 import android.content.pm.ServiceInfo; 25 import android.content.res.Resources; 26 import android.content.res.TypedArray; 27 import android.content.res.XmlResourceParser; 28 import android.os.Parcel; 29 import android.os.Parcelable; 30 import android.util.AttributeSet; 31 import android.util.Xml; 32 import android.view.accessibility.AccessibilityEvent; 33 34 import org.xmlpull.v1.XmlPullParser; 35 import org.xmlpull.v1.XmlPullParserException; 36 37 import java.io.IOException; 38 39 /** 40 * This class describes an {@link AccessibilityService}. The system notifies an 41 * {@link AccessibilityService} for {@link android.view.accessibility.AccessibilityEvent}s 42 * according to the information encapsulated in this class. 43 * 44 * @see AccessibilityService 45 * @see android.view.accessibility.AccessibilityEvent 46 * @see android.view.accessibility.AccessibilityManager 47 */ 48 public class AccessibilityServiceInfo implements Parcelable { 49 50 private static final String TAG_ACCESSIBILITY_SERVICE = "accessibility-service"; 51 52 /** 53 * Denotes spoken feedback. 54 */ 55 public static final int FEEDBACK_SPOKEN = 0x0000001; 56 57 /** 58 * Denotes haptic feedback. 59 */ 60 public static final int FEEDBACK_HAPTIC = 0x0000002; 61 62 /** 63 * Denotes audible (not spoken) feedback. 64 */ 65 public static final int FEEDBACK_AUDIBLE = 0x0000004; 66 67 /** 68 * Denotes visual feedback. 69 */ 70 public static final int FEEDBACK_VISUAL = 0x0000008; 71 72 /** 73 * Denotes generic feedback. 74 */ 75 public static final int FEEDBACK_GENERIC = 0x0000010; 76 77 /** 78 * Mask for all feedback types. 79 * 80 * @see #FEEDBACK_SPOKEN 81 * @see #FEEDBACK_HAPTIC 82 * @see #FEEDBACK_AUDIBLE 83 * @see #FEEDBACK_VISUAL 84 * @see #FEEDBACK_GENERIC 85 */ 86 public static final int FEEDBACK_ALL_MASK = 0xFFFFFFFF; 87 88 /** 89 * If an {@link AccessibilityService} is the default for a given type. 90 * Default service is invoked only if no package specific one exists. In case of 91 * more than one package specific service only the earlier registered is notified. 92 */ 93 public static final int DEFAULT = 0x0000001; 94 95 /** 96 * The event types an {@link AccessibilityService} is interested in. 97 * <p> 98 * <strong>Can be dynamically set at runtime.</strong> 99 * </p> 100 * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_CLICKED 101 * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_LONG_CLICKED 102 * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_FOCUSED 103 * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_SELECTED 104 * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_TEXT_CHANGED 105 * @see android.view.accessibility.AccessibilityEvent#TYPE_WINDOW_STATE_CHANGED 106 * @see android.view.accessibility.AccessibilityEvent#TYPE_NOTIFICATION_STATE_CHANGED 107 * @see android.view.accessibility.AccessibilityEvent#TYPE_TOUCH_EXPLORATION_GESTURE_START 108 * @see android.view.accessibility.AccessibilityEvent#TYPE_TOUCH_EXPLORATION_GESTURE_END 109 * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_HOVER_ENTER 110 * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_HOVER_EXIT 111 * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_SCROLLED 112 * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_TEXT_SELECTION_CHANGED 113 * @see android.view.accessibility.AccessibilityEvent#TYPE_WINDOW_CONTENT_CHANGED 114 */ 115 public int eventTypes; 116 117 /** 118 * The package names an {@link AccessibilityService} is interested in. Setting 119 * to <code>null</code> is equivalent to all packages. 120 * <p> 121 * <strong>Can be dynamically set at runtime.</strong> 122 * </p> 123 */ 124 public String[] packageNames; 125 126 /** 127 * The feedback type an {@link AccessibilityService} provides. 128 * <p> 129 * <strong>Can be dynamically set at runtime.</strong> 130 * </p> 131 * @see #FEEDBACK_AUDIBLE 132 * @see #FEEDBACK_GENERIC 133 * @see #FEEDBACK_HAPTIC 134 * @see #FEEDBACK_SPOKEN 135 * @see #FEEDBACK_VISUAL 136 */ 137 public int feedbackType; 138 139 /** 140 * The timeout after the most recent event of a given type before an 141 * {@link AccessibilityService} is notified. 142 * <p> 143 * <strong>Can be dynamically set at runtime.</strong>. 144 * </p> 145 * <p> 146 * <strong>Note:</strong> The event notification timeout is useful to avoid propagating 147 * events to the client too frequently since this is accomplished via an expensive 148 * interprocess call. One can think of the timeout as a criteria to determine when 149 * event generation has settled down. 150 */ 151 public long notificationTimeout; 152 153 /** 154 * This field represents a set of flags used for configuring an 155 * {@link AccessibilityService}. 156 * <p> 157 * <strong>Can be dynamically set at runtime.</strong> 158 * </p> 159 * @see #DEFAULT 160 */ 161 public int flags; 162 163 /** 164 * The unique string Id to identify the accessibility service. 165 */ 166 private String mId; 167 168 /** 169 * The Service that implements this accessibility service component. 170 */ 171 private ResolveInfo mResolveInfo; 172 173 /** 174 * The accessibility service setting activity's name, used by the system 175 * settings to launch the setting activity of this accessibility service. 176 */ 177 private String mSettingsActivityName; 178 179 /** 180 * Flag whether this accessibility service can retrieve window content. 181 */ 182 private boolean mCanRetrieveWindowContent; 183 184 /** 185 * Description of the accessibility service. 186 */ 187 private String mDescription; 188 189 /** 190 * Creates a new instance. 191 */ 192 public AccessibilityServiceInfo() { 193 /* do nothing */ 194 } 195 196 /** 197 * Creates a new instance. 198 * 199 * @param resolveInfo The service resolve info. 200 * @param context Context for accessing resources. 201 * @throws XmlPullParserException If a XML parsing error occurs. 202 * @throws IOException If a XML parsing error occurs. 203 * 204 * @hide 205 */ 206 public AccessibilityServiceInfo(ResolveInfo resolveInfo, Context context) 207 throws XmlPullParserException, IOException { 208 ServiceInfo serviceInfo = resolveInfo.serviceInfo; 209 mId = new ComponentName(serviceInfo.packageName, serviceInfo.name).flattenToShortString(); 210 mResolveInfo = resolveInfo; 211 212 XmlResourceParser parser = null; 213 214 try { 215 PackageManager packageManager = context.getPackageManager(); 216 parser = serviceInfo.loadXmlMetaData(packageManager, 217 AccessibilityService.SERVICE_META_DATA); 218 if (parser == null) { 219 return; 220 } 221 222 int type = 0; 223 while (type != XmlPullParser.END_DOCUMENT && type != XmlPullParser.START_TAG) { 224 type = parser.next(); 225 } 226 227 String nodeName = parser.getName(); 228 if (!TAG_ACCESSIBILITY_SERVICE.equals(nodeName)) { 229 throw new XmlPullParserException( "Meta-data does not start with" 230 + TAG_ACCESSIBILITY_SERVICE + " tag"); 231 } 232 233 AttributeSet allAttributes = Xml.asAttributeSet(parser); 234 Resources resources = packageManager.getResourcesForApplication( 235 serviceInfo.applicationInfo); 236 TypedArray asAttributes = resources.obtainAttributes(allAttributes, 237 com.android.internal.R.styleable.AccessibilityService); 238 eventTypes = asAttributes.getInt( 239 com.android.internal.R.styleable.AccessibilityService_accessibilityEventTypes, 240 0); 241 String packageNamez = asAttributes.getString( 242 com.android.internal.R.styleable.AccessibilityService_packageNames); 243 if (packageNamez != null) { 244 packageNames = packageNamez.split("(\\s)*,(\\s)*"); 245 } 246 feedbackType = asAttributes.getInt( 247 com.android.internal.R.styleable.AccessibilityService_accessibilityFeedbackType, 248 0); 249 notificationTimeout = asAttributes.getInt( 250 com.android.internal.R.styleable.AccessibilityService_notificationTimeout, 251 0); 252 flags = asAttributes.getInt( 253 com.android.internal.R.styleable.AccessibilityService_accessibilityFlags, 0); 254 mSettingsActivityName = asAttributes.getString( 255 com.android.internal.R.styleable.AccessibilityService_settingsActivity); 256 mCanRetrieveWindowContent = asAttributes.getBoolean( 257 com.android.internal.R.styleable.AccessibilityService_canRetrieveWindowContent, 258 false); 259 mDescription = asAttributes.getString( 260 com.android.internal.R.styleable.AccessibilityService_description); 261 asAttributes.recycle(); 262 } catch (NameNotFoundException e) { 263 throw new XmlPullParserException( "Unable to create context for: " 264 + serviceInfo.packageName); 265 } finally { 266 if (parser != null) { 267 parser.close(); 268 } 269 } 270 } 271 272 /** 273 * Updates the properties that an AccessibilitySerivice can change dynamically. 274 * 275 * @param other The info from which to update the properties. 276 * 277 * @hide 278 */ 279 public void updateDynamicallyConfigurableProperties(AccessibilityServiceInfo other) { 280 eventTypes = other.eventTypes; 281 packageNames = other.packageNames; 282 feedbackType = other.feedbackType; 283 notificationTimeout = other.notificationTimeout; 284 flags = other.flags; 285 } 286 287 /** 288 * The accessibility service id. 289 * <p> 290 * <strong>Generated by the system.</strong> 291 * </p> 292 * @return The id. 293 */ 294 public String getId() { 295 return mId; 296 } 297 298 /** 299 * The service {@link ResolveInfo}. 300 * <p> 301 * <strong>Generated by the system.</strong> 302 * </p> 303 * @return The info. 304 */ 305 public ResolveInfo getResolveInfo() { 306 return mResolveInfo; 307 } 308 309 /** 310 * The settings activity name. 311 * <p> 312 * <strong>Statically set from 313 * {@link AccessibilityService#SERVICE_META_DATA meta-data}.</strong> 314 * </p> 315 * @return The settings activity name. 316 */ 317 public String getSettingsActivityName() { 318 return mSettingsActivityName; 319 } 320 321 /** 322 * Whether this service can retrieve the current window's content. 323 * <p> 324 * <strong>Statically set from 325 * {@link AccessibilityService#SERVICE_META_DATA meta-data}.</strong> 326 * </p> 327 * @return True window content can be retrieved. 328 */ 329 public boolean getCanRetrieveWindowContent() { 330 return mCanRetrieveWindowContent; 331 } 332 333 /** 334 * Description of the accessibility service. 335 * <p> 336 * <strong>Statically set from 337 * {@link AccessibilityService#SERVICE_META_DATA meta-data}.</strong> 338 * </p> 339 * @return The description. 340 */ 341 public String getDescription() { 342 return mDescription; 343 } 344 345 /** 346 * {@inheritDoc} 347 */ 348 public int describeContents() { 349 return 0; 350 } 351 352 public void writeToParcel(Parcel parcel, int flagz) { 353 parcel.writeInt(eventTypes); 354 parcel.writeStringArray(packageNames); 355 parcel.writeInt(feedbackType); 356 parcel.writeLong(notificationTimeout); 357 parcel.writeInt(flags); 358 parcel.writeString(mId); 359 parcel.writeParcelable(mResolveInfo, 0); 360 parcel.writeString(mSettingsActivityName); 361 parcel.writeInt(mCanRetrieveWindowContent ? 1 : 0); 362 parcel.writeString(mDescription); 363 } 364 365 private void initFromParcel(Parcel parcel) { 366 eventTypes = parcel.readInt(); 367 packageNames = parcel.readStringArray(); 368 feedbackType = parcel.readInt(); 369 notificationTimeout = parcel.readLong(); 370 flags = parcel.readInt(); 371 mId = parcel.readString(); 372 mResolveInfo = parcel.readParcelable(null); 373 mSettingsActivityName = parcel.readString(); 374 mCanRetrieveWindowContent = (parcel.readInt() == 1); 375 mDescription = parcel.readString(); 376 } 377 378 @Override 379 public String toString() { 380 StringBuilder stringBuilder = new StringBuilder(); 381 appendEventTypes(stringBuilder, eventTypes); 382 stringBuilder.append(", "); 383 appendPackageNames(stringBuilder, packageNames); 384 stringBuilder.append(", "); 385 appendFeedbackTypes(stringBuilder, feedbackType); 386 stringBuilder.append(", "); 387 stringBuilder.append("notificationTimeout: ").append(notificationTimeout); 388 stringBuilder.append(", "); 389 appendFlags(stringBuilder, flags); 390 stringBuilder.append(", "); 391 stringBuilder.append("id: ").append(mId); 392 stringBuilder.append(", "); 393 stringBuilder.append("resolveInfo: ").append(mResolveInfo); 394 stringBuilder.append(", "); 395 stringBuilder.append("settingsActivityName: ").append(mSettingsActivityName); 396 stringBuilder.append(", "); 397 stringBuilder.append("retrieveScreenContent: ").append(mCanRetrieveWindowContent); 398 return stringBuilder.toString(); 399 } 400 401 private static void appendFeedbackTypes(StringBuilder stringBuilder, int feedbackTypes) { 402 stringBuilder.append("feedbackTypes:"); 403 stringBuilder.append("["); 404 while (feedbackTypes != 0) { 405 final int feedbackTypeBit = (1 << Integer.numberOfTrailingZeros(feedbackTypes)); 406 stringBuilder.append(feedbackTypeToString(feedbackTypeBit)); 407 feedbackTypes &= ~feedbackTypeBit; 408 if (feedbackTypes != 0) { 409 stringBuilder.append(", "); 410 } 411 } 412 stringBuilder.append("]"); 413 } 414 415 private static void appendPackageNames(StringBuilder stringBuilder, String[] packageNames) { 416 stringBuilder.append("packageNames:"); 417 stringBuilder.append("["); 418 if (packageNames != null) { 419 final int packageNameCount = packageNames.length; 420 for (int i = 0; i < packageNameCount; i++) { 421 stringBuilder.append(packageNames[i]); 422 if (i < packageNameCount - 1) { 423 stringBuilder.append(", "); 424 } 425 } 426 } 427 stringBuilder.append("]"); 428 } 429 430 private static void appendEventTypes(StringBuilder stringBuilder, int eventTypes) { 431 stringBuilder.append("eventTypes:"); 432 stringBuilder.append("["); 433 while (eventTypes != 0) { 434 final int eventTypeBit = (1 << Integer.numberOfTrailingZeros(eventTypes)); 435 stringBuilder.append(AccessibilityEvent.eventTypeToString(eventTypeBit)); 436 eventTypes &= ~eventTypeBit; 437 if (eventTypes != 0) { 438 stringBuilder.append(", "); 439 } 440 } 441 stringBuilder.append("]"); 442 } 443 444 private static void appendFlags(StringBuilder stringBuilder, int flags) { 445 stringBuilder.append("flags:"); 446 stringBuilder.append("["); 447 while (flags != 0) { 448 final int flagBit = (1 << Integer.numberOfTrailingZeros(flags)); 449 stringBuilder.append(flagToString(flagBit)); 450 flags &= ~flagBit; 451 if (flags != 0) { 452 stringBuilder.append(", "); 453 } 454 } 455 stringBuilder.append("]"); 456 } 457 458 /** 459 * Returns the string representation of a feedback type. For example, 460 * {@link #FEEDBACK_SPOKEN} is represented by the string FEEDBACK_SPOKEN. 461 * 462 * @param feedbackType The feedback type. 463 * @return The string representation. 464 */ 465 public static String feedbackTypeToString(int feedbackType) { 466 StringBuilder builder = new StringBuilder(); 467 builder.append("["); 468 while (feedbackType > 0) { 469 final int feedbackTypeFlag = 1 << Integer.numberOfTrailingZeros(feedbackType); 470 feedbackType &= ~feedbackTypeFlag; 471 if (builder.length() > 1) { 472 builder.append(", "); 473 } 474 switch (feedbackTypeFlag) { 475 case FEEDBACK_AUDIBLE: 476 builder.append("FEEDBACK_AUDIBLE"); 477 break; 478 case FEEDBACK_HAPTIC: 479 builder.append("FEEDBACK_HAPTIC"); 480 break; 481 case FEEDBACK_GENERIC: 482 builder.append("FEEDBACK_GENERIC"); 483 break; 484 case FEEDBACK_SPOKEN: 485 builder.append("FEEDBACK_SPOKEN"); 486 break; 487 case FEEDBACK_VISUAL: 488 builder.append("FEEDBACK_VISUAL"); 489 break; 490 } 491 } 492 builder.append("]"); 493 return builder.toString(); 494 } 495 496 /** 497 * Returns the string representation of a flag. For example, 498 * {@link #DEFAULT} is represented by the string DEFAULT. 499 * 500 * @param flag The flag. 501 * @return The string representation. 502 */ 503 public static String flagToString(int flag) { 504 switch (flag) { 505 case DEFAULT: 506 return "DEFAULT"; 507 default: 508 return null; 509 } 510 } 511 512 /** 513 * @see Parcelable.Creator 514 */ 515 public static final Parcelable.Creator<AccessibilityServiceInfo> CREATOR = 516 new Parcelable.Creator<AccessibilityServiceInfo>() { 517 public AccessibilityServiceInfo createFromParcel(Parcel parcel) { 518 AccessibilityServiceInfo info = new AccessibilityServiceInfo(); 519 info.initFromParcel(parcel); 520 return info; 521 } 522 523 public AccessibilityServiceInfo[] newArray(int size) { 524 return new AccessibilityServiceInfo[size]; 525 } 526 }; 527 } 528