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.Build; 29 import android.os.Parcel; 30 import android.os.Parcelable; 31 import android.util.AttributeSet; 32 import android.util.TypedValue; 33 import android.util.Xml; 34 import android.view.View; 35 import android.view.accessibility.AccessibilityEvent; 36 37 import org.xmlpull.v1.XmlPullParser; 38 import org.xmlpull.v1.XmlPullParserException; 39 40 import 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 */ 58 public 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 FLAG_INCLUDE_NOT_IMPORTANT_VIEWS = 0x0000002; 135 136 /** 137 * This flag requests that the system gets into touch exploration mode. 138 * In this mode a single finger moving on the screen behaves as a mouse 139 * pointer hovering over the user interface. The system will also detect 140 * certain gestures performed on the touch screen and notify this service. 141 * The system will enable touch exploration mode if there is at least one 142 * accessibility service that has this flag set. Hence, clearing this 143 * flag does not guarantee that the device will not be in touch exploration 144 * mode since there may be another enabled service that requested it. 145 */ 146 public static final int FLAG_REQUEST_TOUCH_EXPLORATION_MODE= 0x0000004; 147 148 /** 149 * The event types an {@link AccessibilityService} is interested in. 150 * <p> 151 * <strong>Can be dynamically set at runtime.</strong> 152 * </p> 153 * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_CLICKED 154 * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_LONG_CLICKED 155 * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_FOCUSED 156 * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_SELECTED 157 * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_TEXT_CHANGED 158 * @see android.view.accessibility.AccessibilityEvent#TYPE_WINDOW_STATE_CHANGED 159 * @see android.view.accessibility.AccessibilityEvent#TYPE_NOTIFICATION_STATE_CHANGED 160 * @see android.view.accessibility.AccessibilityEvent#TYPE_TOUCH_EXPLORATION_GESTURE_START 161 * @see android.view.accessibility.AccessibilityEvent#TYPE_TOUCH_EXPLORATION_GESTURE_END 162 * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_HOVER_ENTER 163 * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_HOVER_EXIT 164 * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_SCROLLED 165 * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_TEXT_SELECTION_CHANGED 166 * @see android.view.accessibility.AccessibilityEvent#TYPE_WINDOW_CONTENT_CHANGED 167 */ 168 public int eventTypes; 169 170 /** 171 * The package names an {@link AccessibilityService} is interested in. Setting 172 * to <code>null</code> is equivalent to all packages. 173 * <p> 174 * <strong>Can be dynamically set at runtime.</strong> 175 * </p> 176 */ 177 public String[] packageNames; 178 179 /** 180 * The feedback type an {@link AccessibilityService} provides. 181 * <p> 182 * <strong>Can be dynamically set at runtime.</strong> 183 * </p> 184 * @see #FEEDBACK_AUDIBLE 185 * @see #FEEDBACK_GENERIC 186 * @see #FEEDBACK_HAPTIC 187 * @see #FEEDBACK_SPOKEN 188 * @see #FEEDBACK_VISUAL 189 */ 190 public int feedbackType; 191 192 /** 193 * The timeout after the most recent event of a given type before an 194 * {@link AccessibilityService} is notified. 195 * <p> 196 * <strong>Can be dynamically set at runtime.</strong>. 197 * </p> 198 * <p> 199 * <strong>Note:</strong> The event notification timeout is useful to avoid propagating 200 * events to the client too frequently since this is accomplished via an expensive 201 * interprocess call. One can think of the timeout as a criteria to determine when 202 * event generation has settled down. 203 */ 204 public long notificationTimeout; 205 206 /** 207 * This field represents a set of flags used for configuring an 208 * {@link AccessibilityService}. 209 * <p> 210 * <strong>Can be dynamically set at runtime.</strong> 211 * </p> 212 * @see #DEFAULT 213 * @see #FLAG_INCLUDE_NOT_IMPORTANT_VIEWS 214 * @see #FLAG_REQUEST_TOUCH_EXPLORATION_MODE 215 */ 216 public int flags; 217 218 /** 219 * The unique string Id to identify the accessibility service. 220 */ 221 private String mId; 222 223 /** 224 * The Service that implements this accessibility service component. 225 */ 226 private ResolveInfo mResolveInfo; 227 228 /** 229 * The accessibility service setting activity's name, used by the system 230 * settings to launch the setting activity of this accessibility service. 231 */ 232 private String mSettingsActivityName; 233 234 /** 235 * Flag whether this accessibility service can retrieve window content. 236 */ 237 private boolean mCanRetrieveWindowContent; 238 239 /** 240 * Resource id of the description of the accessibility service. 241 */ 242 private int mDescriptionResId; 243 244 /** 245 * Non localized description of the accessibility service. 246 */ 247 private String mNonLocalizedDescription; 248 249 /** 250 * Creates a new instance. 251 */ 252 public AccessibilityServiceInfo() { 253 /* do nothing */ 254 } 255 256 /** 257 * Creates a new instance. 258 * 259 * @param resolveInfo The service resolve info. 260 * @param context Context for accessing resources. 261 * @throws XmlPullParserException If a XML parsing error occurs. 262 * @throws IOException If a XML parsing error occurs. 263 * 264 * @hide 265 */ 266 public AccessibilityServiceInfo(ResolveInfo resolveInfo, Context context) 267 throws XmlPullParserException, IOException { 268 ServiceInfo serviceInfo = resolveInfo.serviceInfo; 269 mId = new ComponentName(serviceInfo.packageName, serviceInfo.name).flattenToShortString(); 270 mResolveInfo = resolveInfo; 271 272 XmlResourceParser parser = null; 273 274 try { 275 PackageManager packageManager = context.getPackageManager(); 276 parser = serviceInfo.loadXmlMetaData(packageManager, 277 AccessibilityService.SERVICE_META_DATA); 278 if (parser == null) { 279 return; 280 } 281 282 int type = 0; 283 while (type != XmlPullParser.END_DOCUMENT && type != XmlPullParser.START_TAG) { 284 type = parser.next(); 285 } 286 287 String nodeName = parser.getName(); 288 if (!TAG_ACCESSIBILITY_SERVICE.equals(nodeName)) { 289 throw new XmlPullParserException( "Meta-data does not start with" 290 + TAG_ACCESSIBILITY_SERVICE + " tag"); 291 } 292 293 AttributeSet allAttributes = Xml.asAttributeSet(parser); 294 Resources resources = packageManager.getResourcesForApplication( 295 serviceInfo.applicationInfo); 296 TypedArray asAttributes = resources.obtainAttributes(allAttributes, 297 com.android.internal.R.styleable.AccessibilityService); 298 eventTypes = asAttributes.getInt( 299 com.android.internal.R.styleable.AccessibilityService_accessibilityEventTypes, 300 0); 301 String packageNamez = asAttributes.getString( 302 com.android.internal.R.styleable.AccessibilityService_packageNames); 303 if (packageNamez != null) { 304 packageNames = packageNamez.split("(\\s)*,(\\s)*"); 305 } 306 feedbackType = asAttributes.getInt( 307 com.android.internal.R.styleable.AccessibilityService_accessibilityFeedbackType, 308 0); 309 notificationTimeout = asAttributes.getInt( 310 com.android.internal.R.styleable.AccessibilityService_notificationTimeout, 311 0); 312 flags = asAttributes.getInt( 313 com.android.internal.R.styleable.AccessibilityService_accessibilityFlags, 0); 314 mSettingsActivityName = asAttributes.getString( 315 com.android.internal.R.styleable.AccessibilityService_settingsActivity); 316 mCanRetrieveWindowContent = asAttributes.getBoolean( 317 com.android.internal.R.styleable.AccessibilityService_canRetrieveWindowContent, 318 false); 319 TypedValue peekedValue = asAttributes.peekValue( 320 com.android.internal.R.styleable.AccessibilityService_description); 321 if (peekedValue != null) { 322 mDescriptionResId = peekedValue.resourceId; 323 CharSequence nonLocalizedDescription = peekedValue.coerceToString(); 324 if (nonLocalizedDescription != null) { 325 mNonLocalizedDescription = nonLocalizedDescription.toString().trim(); 326 } 327 } 328 asAttributes.recycle(); 329 } catch (NameNotFoundException e) { 330 throw new XmlPullParserException( "Unable to create context for: " 331 + serviceInfo.packageName); 332 } finally { 333 if (parser != null) { 334 parser.close(); 335 } 336 } 337 } 338 339 /** 340 * Updates the properties that an AccessibilitySerivice can change dynamically. 341 * 342 * @param other The info from which to update the properties. 343 * 344 * @hide 345 */ 346 public void updateDynamicallyConfigurableProperties(AccessibilityServiceInfo other) { 347 eventTypes = other.eventTypes; 348 packageNames = other.packageNames; 349 feedbackType = other.feedbackType; 350 notificationTimeout = other.notificationTimeout; 351 flags = other.flags; 352 } 353 354 /** 355 * The accessibility service id. 356 * <p> 357 * <strong>Generated by the system.</strong> 358 * </p> 359 * @return The id. 360 */ 361 public String getId() { 362 return mId; 363 } 364 365 /** 366 * The service {@link ResolveInfo}. 367 * <p> 368 * <strong>Generated by the system.</strong> 369 * </p> 370 * @return The info. 371 */ 372 public ResolveInfo getResolveInfo() { 373 return mResolveInfo; 374 } 375 376 /** 377 * The settings activity name. 378 * <p> 379 * <strong>Statically set from 380 * {@link AccessibilityService#SERVICE_META_DATA meta-data}.</strong> 381 * </p> 382 * @return The settings activity name. 383 */ 384 public String getSettingsActivityName() { 385 return mSettingsActivityName; 386 } 387 388 /** 389 * Whether this service can retrieve the current window's content. 390 * <p> 391 * <strong>Statically set from 392 * {@link AccessibilityService#SERVICE_META_DATA meta-data}.</strong> 393 * </p> 394 * @return True if window content can be retrieved. 395 */ 396 public boolean getCanRetrieveWindowContent() { 397 return mCanRetrieveWindowContent; 398 } 399 400 /** 401 * Gets the non-localized description of the accessibility service. 402 * <p> 403 * <strong>Statically set from 404 * {@link AccessibilityService#SERVICE_META_DATA meta-data}.</strong> 405 * </p> 406 * @return The description. 407 * 408 * @deprecated Use {@link #loadDescription(PackageManager)}. 409 */ 410 public String getDescription() { 411 return mNonLocalizedDescription; 412 } 413 414 /** 415 * The localized description of the accessibility service. 416 * <p> 417 * <strong>Statically set from 418 * {@link AccessibilityService#SERVICE_META_DATA meta-data}.</strong> 419 * </p> 420 * @return The localized description. 421 */ 422 public String loadDescription(PackageManager packageManager) { 423 if (mDescriptionResId == 0) { 424 return mNonLocalizedDescription; 425 } 426 ServiceInfo serviceInfo = mResolveInfo.serviceInfo; 427 CharSequence description = packageManager.getText(serviceInfo.packageName, 428 mDescriptionResId, serviceInfo.applicationInfo); 429 if (description != null) { 430 return description.toString().trim(); 431 } 432 return null; 433 } 434 435 /** 436 * {@inheritDoc} 437 */ 438 public int describeContents() { 439 return 0; 440 } 441 442 public void writeToParcel(Parcel parcel, int flagz) { 443 parcel.writeInt(eventTypes); 444 parcel.writeStringArray(packageNames); 445 parcel.writeInt(feedbackType); 446 parcel.writeLong(notificationTimeout); 447 parcel.writeInt(flags); 448 parcel.writeString(mId); 449 parcel.writeParcelable(mResolveInfo, 0); 450 parcel.writeString(mSettingsActivityName); 451 parcel.writeInt(mCanRetrieveWindowContent ? 1 : 0); 452 parcel.writeInt(mDescriptionResId); 453 parcel.writeString(mNonLocalizedDescription); 454 } 455 456 private void initFromParcel(Parcel parcel) { 457 eventTypes = parcel.readInt(); 458 packageNames = parcel.readStringArray(); 459 feedbackType = parcel.readInt(); 460 notificationTimeout = parcel.readLong(); 461 flags = parcel.readInt(); 462 mId = parcel.readString(); 463 mResolveInfo = parcel.readParcelable(null); 464 mSettingsActivityName = parcel.readString(); 465 mCanRetrieveWindowContent = (parcel.readInt() == 1); 466 mDescriptionResId = parcel.readInt(); 467 mNonLocalizedDescription = parcel.readString(); 468 } 469 470 @Override 471 public String toString() { 472 StringBuilder stringBuilder = new StringBuilder(); 473 appendEventTypes(stringBuilder, eventTypes); 474 stringBuilder.append(", "); 475 appendPackageNames(stringBuilder, packageNames); 476 stringBuilder.append(", "); 477 appendFeedbackTypes(stringBuilder, feedbackType); 478 stringBuilder.append(", "); 479 stringBuilder.append("notificationTimeout: ").append(notificationTimeout); 480 stringBuilder.append(", "); 481 appendFlags(stringBuilder, flags); 482 stringBuilder.append(", "); 483 stringBuilder.append("id: ").append(mId); 484 stringBuilder.append(", "); 485 stringBuilder.append("resolveInfo: ").append(mResolveInfo); 486 stringBuilder.append(", "); 487 stringBuilder.append("settingsActivityName: ").append(mSettingsActivityName); 488 stringBuilder.append(", "); 489 stringBuilder.append("retrieveScreenContent: ").append(mCanRetrieveWindowContent); 490 return stringBuilder.toString(); 491 } 492 493 private static void appendFeedbackTypes(StringBuilder stringBuilder, int feedbackTypes) { 494 stringBuilder.append("feedbackTypes:"); 495 stringBuilder.append("["); 496 while (feedbackTypes != 0) { 497 final int feedbackTypeBit = (1 << Integer.numberOfTrailingZeros(feedbackTypes)); 498 stringBuilder.append(feedbackTypeToString(feedbackTypeBit)); 499 feedbackTypes &= ~feedbackTypeBit; 500 if (feedbackTypes != 0) { 501 stringBuilder.append(", "); 502 } 503 } 504 stringBuilder.append("]"); 505 } 506 507 private static void appendPackageNames(StringBuilder stringBuilder, String[] packageNames) { 508 stringBuilder.append("packageNames:"); 509 stringBuilder.append("["); 510 if (packageNames != null) { 511 final int packageNameCount = packageNames.length; 512 for (int i = 0; i < packageNameCount; i++) { 513 stringBuilder.append(packageNames[i]); 514 if (i < packageNameCount - 1) { 515 stringBuilder.append(", "); 516 } 517 } 518 } 519 stringBuilder.append("]"); 520 } 521 522 private static void appendEventTypes(StringBuilder stringBuilder, int eventTypes) { 523 stringBuilder.append("eventTypes:"); 524 stringBuilder.append("["); 525 while (eventTypes != 0) { 526 final int eventTypeBit = (1 << Integer.numberOfTrailingZeros(eventTypes)); 527 stringBuilder.append(AccessibilityEvent.eventTypeToString(eventTypeBit)); 528 eventTypes &= ~eventTypeBit; 529 if (eventTypes != 0) { 530 stringBuilder.append(", "); 531 } 532 } 533 stringBuilder.append("]"); 534 } 535 536 private static void appendFlags(StringBuilder stringBuilder, int flags) { 537 stringBuilder.append("flags:"); 538 stringBuilder.append("["); 539 while (flags != 0) { 540 final int flagBit = (1 << Integer.numberOfTrailingZeros(flags)); 541 stringBuilder.append(flagToString(flagBit)); 542 flags &= ~flagBit; 543 if (flags != 0) { 544 stringBuilder.append(", "); 545 } 546 } 547 stringBuilder.append("]"); 548 } 549 550 /** 551 * Returns the string representation of a feedback type. For example, 552 * {@link #FEEDBACK_SPOKEN} is represented by the string FEEDBACK_SPOKEN. 553 * 554 * @param feedbackType The feedback type. 555 * @return The string representation. 556 */ 557 public static String feedbackTypeToString(int feedbackType) { 558 StringBuilder builder = new StringBuilder(); 559 builder.append("["); 560 while (feedbackType != 0) { 561 final int feedbackTypeFlag = 1 << Integer.numberOfTrailingZeros(feedbackType); 562 feedbackType &= ~feedbackTypeFlag; 563 switch (feedbackTypeFlag) { 564 case FEEDBACK_AUDIBLE: 565 if (builder.length() > 1) { 566 builder.append(", "); 567 } 568 builder.append("FEEDBACK_AUDIBLE"); 569 break; 570 case FEEDBACK_HAPTIC: 571 if (builder.length() > 1) { 572 builder.append(", "); 573 } 574 builder.append("FEEDBACK_HAPTIC"); 575 break; 576 case FEEDBACK_GENERIC: 577 if (builder.length() > 1) { 578 builder.append(", "); 579 } 580 builder.append("FEEDBACK_GENERIC"); 581 break; 582 case FEEDBACK_SPOKEN: 583 if (builder.length() > 1) { 584 builder.append(", "); 585 } 586 builder.append("FEEDBACK_SPOKEN"); 587 break; 588 case FEEDBACK_VISUAL: 589 if (builder.length() > 1) { 590 builder.append(", "); 591 } 592 builder.append("FEEDBACK_VISUAL"); 593 break; 594 } 595 } 596 builder.append("]"); 597 return builder.toString(); 598 } 599 600 /** 601 * Returns the string representation of a flag. For example, 602 * {@link #DEFAULT} is represented by the string DEFAULT. 603 * 604 * @param flag The flag. 605 * @return The string representation. 606 */ 607 public static String flagToString(int flag) { 608 switch (flag) { 609 case DEFAULT: 610 return "DEFAULT"; 611 case FLAG_INCLUDE_NOT_IMPORTANT_VIEWS: 612 return "FLAG_INCLUDE_NOT_IMPORTANT_VIEWS"; 613 case FLAG_REQUEST_TOUCH_EXPLORATION_MODE: 614 return "FLAG_REQUEST_TOUCH_EXPLORATION_MODE"; 615 default: 616 return null; 617 } 618 } 619 620 /** 621 * @see Parcelable.Creator 622 */ 623 public static final Parcelable.Creator<AccessibilityServiceInfo> CREATOR = 624 new Parcelable.Creator<AccessibilityServiceInfo>() { 625 public AccessibilityServiceInfo createFromParcel(Parcel parcel) { 626 AccessibilityServiceInfo info = new AccessibilityServiceInfo(); 627 info.initFromParcel(parcel); 628 return info; 629 } 630 631 public AccessibilityServiceInfo[] newArray(int size) { 632 return new AccessibilityServiceInfo[size]; 633 } 634 }; 635 } 636