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