AccessibilityManagerService.java revision c682fc965df2518a95d23e39ec51d788100d20ec
1/* 2 ** Copyright 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 com.android.server.accessibility; 18 19import static android.accessibilityservice.AccessibilityServiceInfo.DEFAULT; 20import static android.accessibilityservice.AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS; 21 22import android.Manifest; 23import android.accessibilityservice.AccessibilityService; 24import android.accessibilityservice.AccessibilityServiceInfo; 25import android.accessibilityservice.IAccessibilityServiceClient; 26import android.accessibilityservice.IAccessibilityServiceConnection; 27import android.app.AlertDialog; 28import android.app.PendingIntent; 29import android.app.StatusBarManager; 30import android.content.BroadcastReceiver; 31import android.content.ComponentName; 32import android.content.ContentResolver; 33import android.content.Context; 34import android.content.DialogInterface; 35import android.content.DialogInterface.OnClickListener; 36import android.content.Intent; 37import android.content.IntentFilter; 38import android.content.ServiceConnection; 39import android.content.pm.PackageManager; 40import android.content.pm.ResolveInfo; 41import android.content.pm.ServiceInfo; 42import android.database.ContentObserver; 43import android.graphics.Rect; 44import android.hardware.input.InputManager; 45import android.net.Uri; 46import android.os.Binder; 47import android.os.Build; 48import android.os.Bundle; 49import android.os.Handler; 50import android.os.IBinder; 51import android.os.Message; 52import android.os.RemoteException; 53import android.os.ServiceManager; 54import android.os.SystemClock; 55import android.provider.Settings; 56import android.text.TextUtils; 57import android.text.TextUtils.SimpleStringSplitter; 58import android.util.Slog; 59import android.util.SparseArray; 60import android.view.IWindow; 61import android.view.InputDevice; 62import android.view.KeyCharacterMap; 63import android.view.KeyEvent; 64import android.view.WindowManager; 65import android.view.accessibility.AccessibilityEvent; 66import android.view.accessibility.AccessibilityInteractionClient; 67import android.view.accessibility.AccessibilityManager; 68import android.view.accessibility.AccessibilityNodeInfo; 69import android.view.accessibility.IAccessibilityInteractionConnection; 70import android.view.accessibility.IAccessibilityInteractionConnectionCallback; 71import android.view.accessibility.IAccessibilityManager; 72import android.view.accessibility.IAccessibilityManagerClient; 73 74import com.android.internal.R; 75import com.android.internal.content.PackageMonitor; 76import com.android.internal.statusbar.IStatusBarService; 77import com.android.server.wm.WindowManagerService; 78 79import org.xmlpull.v1.XmlPullParserException; 80 81import java.io.IOException; 82import java.util.ArrayList; 83import java.util.Arrays; 84import java.util.HashMap; 85import java.util.HashSet; 86import java.util.Iterator; 87import java.util.List; 88import java.util.Map; 89import java.util.Set; 90 91/** 92 * This class is instantiated by the system as a system level service and can be 93 * accessed only by the system. The task of this service is to be a centralized 94 * event dispatch for {@link AccessibilityEvent}s generated across all processes 95 * on the device. Events are dispatched to {@link AccessibilityService}s. 96 * 97 * @hide 98 */ 99public class AccessibilityManagerService extends IAccessibilityManager.Stub { 100 101 private static final boolean DEBUG = false; 102 103 private static final String LOG_TAG = "AccessibilityManagerService"; 104 105 private static final String FUNCTION_REGISTER_UI_TEST_AUTOMATION_SERVICE = 106 "registerUiTestAutomationService"; 107 108 private static final char COMPONENT_NAME_SEPARATOR = ':'; 109 110 private static final int OWN_PROCESS_ID = android.os.Process.myPid(); 111 112 private static final int MSG_SHOW_ENABLE_TOUCH_EXPLORATION_DIALOG = 1; 113 114 private static final int MSG_TOGGLE_TOUCH_EXPLORATION = 2; 115 116 private static int sIdCounter = 0; 117 118 private static int sNextWindowId; 119 120 final Context mContext; 121 122 final Object mLock = new Object(); 123 124 final List<Service> mServices = new ArrayList<Service>(); 125 126 final List<IAccessibilityManagerClient> mClients = 127 new ArrayList<IAccessibilityManagerClient>(); 128 129 final Map<ComponentName, Service> mComponentNameToServiceMap = new HashMap<ComponentName, Service>(); 130 131 private final List<AccessibilityServiceInfo> mInstalledServices = new ArrayList<AccessibilityServiceInfo>(); 132 133 private final Set<ComponentName> mEnabledServices = new HashSet<ComponentName>(); 134 135 private final Set<ComponentName> mTouchExplorationGrantedServices = new HashSet<ComponentName>(); 136 137 private final SparseArray<AccessibilityConnectionWrapper> mWindowIdToInteractionConnectionWrapperMap = 138 new SparseArray<AccessibilityConnectionWrapper>(); 139 140 private final SparseArray<IBinder> mWindowIdToWindowTokenMap = new SparseArray<IBinder>(); 141 142 private final SimpleStringSplitter mStringColonSplitter = new SimpleStringSplitter(COMPONENT_NAME_SEPARATOR); 143 144 private final Rect mTempRect = new Rect(); 145 146 private PackageManager mPackageManager; 147 148 private int mHandledFeedbackTypes = 0; 149 150 private boolean mIsAccessibilityEnabled; 151 152 private AccessibilityInputFilter mInputFilter; 153 154 private boolean mHasInputFilter; 155 156 private final List<AccessibilityServiceInfo> mEnabledServicesForFeedbackTempList = new ArrayList<AccessibilityServiceInfo>(); 157 158 private boolean mIsTouchExplorationEnabled; 159 160 private final WindowManagerService mWindowManagerService; 161 162 private final SecurityPolicy mSecurityPolicy; 163 164 private final MainHanler mMainHandler; 165 166 private Service mUiAutomationService; 167 168 private Service mQueryBridge; 169 170 private boolean mTouchExplorationGestureEnded; 171 172 private boolean mTouchExplorationGestureStarted; 173 174 private AlertDialog mEnableTouchExplorationDialog; 175 176 /** 177 * Creates a new instance. 178 * 179 * @param context A {@link Context} instance. 180 */ 181 public AccessibilityManagerService(Context context) { 182 mContext = context; 183 mPackageManager = mContext.getPackageManager(); 184 mWindowManagerService = (WindowManagerService) ServiceManager.getService( 185 Context.WINDOW_SERVICE); 186 mSecurityPolicy = new SecurityPolicy(); 187 mMainHandler = new MainHanler(); 188 registerPackageChangeAndBootCompletedBroadcastReceiver(); 189 registerSettingsContentObservers(); 190 } 191 192 /** 193 * Registers a {@link BroadcastReceiver} for the events of 194 * adding/changing/removing/restarting a package and boot completion. 195 */ 196 private void registerPackageChangeAndBootCompletedBroadcastReceiver() { 197 Context context = mContext; 198 199 PackageMonitor monitor = new PackageMonitor() { 200 @Override 201 public void onSomePackagesChanged() { 202 synchronized (mLock) { 203 // We will update when the automation service dies. 204 if (mUiAutomationService == null) { 205 populateAccessibilityServiceListLocked(); 206 manageServicesLocked(); 207 } 208 } 209 } 210 211 @Override 212 public void onPackageRemoved(String packageName, int uid) { 213 synchronized (mLock) { 214 Iterator<ComponentName> it = mEnabledServices.iterator(); 215 while (it.hasNext()) { 216 ComponentName comp = it.next(); 217 String compPkg = comp.getPackageName(); 218 if (compPkg.equals(packageName)) { 219 it.remove(); 220 // Update the enabled services setting. 221 persistComponentNamesToSettingLocked( 222 Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, 223 mEnabledServices); 224 // Update the touch exploration granted services setting. 225 mTouchExplorationGrantedServices.remove(comp); 226 persistComponentNamesToSettingLocked( 227 Settings.Secure. 228 TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES, 229 mEnabledServices); 230 return; 231 } 232 } 233 } 234 } 235 236 @Override 237 public boolean onHandleForceStop(Intent intent, String[] packages, 238 int uid, boolean doit) { 239 synchronized (mLock) { 240 Iterator<ComponentName> it = mEnabledServices.iterator(); 241 while (it.hasNext()) { 242 ComponentName comp = it.next(); 243 String compPkg = comp.getPackageName(); 244 for (String pkg : packages) { 245 if (compPkg.equals(pkg)) { 246 if (!doit) { 247 return true; 248 } 249 it.remove(); 250 persistComponentNamesToSettingLocked( 251 Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, 252 mEnabledServices); 253 } 254 } 255 } 256 return false; 257 } 258 } 259 260 @Override 261 public void onReceive(Context context, Intent intent) { 262 if (intent.getAction() == Intent.ACTION_BOOT_COMPLETED) { 263 synchronized (mLock) { 264 // We will update when the automation service dies. 265 if (mUiAutomationService == null) { 266 populateAccessibilityServiceListLocked(); 267 populateEnabledAccessibilityServicesLocked(); 268 populateTouchExplorationGrantedAccessibilityServicesLocked(); 269 handleAccessibilityEnabledSettingChangedLocked(); 270 handleTouchExplorationEnabledSettingChangedLocked(); 271 updateInputFilterLocked(); 272 sendStateToClientsLocked(); 273 } 274 } 275 return; 276 } 277 278 super.onReceive(context, intent); 279 } 280 }; 281 282 // package changes 283 monitor.register(context, null, true); 284 285 // boot completed 286 IntentFilter bootFiler = new IntentFilter(Intent.ACTION_BOOT_COMPLETED); 287 mContext.registerReceiver(monitor, bootFiler, null, monitor.getRegisteredHandler()); 288 } 289 290 /** 291 * {@link ContentObserver}s for {@link Settings.Secure#ACCESSIBILITY_ENABLED} 292 * and {@link Settings.Secure#ENABLED_ACCESSIBILITY_SERVICES} settings. 293 */ 294 private void registerSettingsContentObservers() { 295 ContentResolver contentResolver = mContext.getContentResolver(); 296 297 Uri accessibilityEnabledUri = Settings.Secure.getUriFor( 298 Settings.Secure.ACCESSIBILITY_ENABLED); 299 contentResolver.registerContentObserver(accessibilityEnabledUri, false, 300 new ContentObserver(new Handler()) { 301 @Override 302 public void onChange(boolean selfChange) { 303 super.onChange(selfChange); 304 synchronized (mLock) { 305 // We will update when the automation service dies. 306 if (mUiAutomationService == null) { 307 handleAccessibilityEnabledSettingChangedLocked(); 308 updateInputFilterLocked(); 309 sendStateToClientsLocked(); 310 } 311 } 312 } 313 }); 314 315 Uri touchExplorationRequestedUri = Settings.Secure.getUriFor( 316 Settings.Secure.TOUCH_EXPLORATION_ENABLED); 317 contentResolver.registerContentObserver(touchExplorationRequestedUri, false, 318 new ContentObserver(new Handler()) { 319 @Override 320 public void onChange(boolean selfChange) { 321 super.onChange(selfChange); 322 synchronized (mLock) { 323 // We will update when the automation service dies. 324 if (mUiAutomationService == null) { 325 handleTouchExplorationEnabledSettingChangedLocked(); 326 updateInputFilterLocked(); 327 sendStateToClientsLocked(); 328 } 329 } 330 } 331 }); 332 333 Uri accessibilityServicesUri = 334 Settings.Secure.getUriFor(Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES); 335 contentResolver.registerContentObserver(accessibilityServicesUri, false, 336 new ContentObserver(new Handler()) { 337 @Override 338 public void onChange(boolean selfChange) { 339 super.onChange(selfChange); 340 synchronized (mLock) { 341 // We will update when the automation service dies. 342 if (mUiAutomationService == null) { 343 populateEnabledAccessibilityServicesLocked(); 344 manageServicesLocked(); 345 } 346 } 347 } 348 }); 349 350 Uri touchExplorationGrantedServicesUri = Settings.Secure.getUriFor( 351 Settings.Secure.TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES); 352 contentResolver.registerContentObserver(touchExplorationGrantedServicesUri, false, 353 new ContentObserver(new Handler()) { 354 @Override 355 public void onChange(boolean selfChange) { 356 super.onChange(selfChange); 357 synchronized (mLock) { 358 // We will update when the automation service dies. 359 if (mUiAutomationService == null) { 360 populateTouchExplorationGrantedAccessibilityServicesLocked(); 361 unbindAllServicesLocked(); 362 manageServicesLocked(); 363 } 364 } 365 } 366 }); 367 } 368 369 public int addClient(IAccessibilityManagerClient client) throws RemoteException { 370 synchronized (mLock) { 371 final IAccessibilityManagerClient addedClient = client; 372 mClients.add(addedClient); 373 // Clients are registered all the time until their process is 374 // killed, therefore we do not have a corresponding unlinkToDeath. 375 client.asBinder().linkToDeath(new DeathRecipient() { 376 public void binderDied() { 377 synchronized (mLock) { 378 addedClient.asBinder().unlinkToDeath(this, 0); 379 mClients.remove(addedClient); 380 } 381 } 382 }, 0); 383 return getState(); 384 } 385 } 386 387 public boolean sendAccessibilityEvent(AccessibilityEvent event) { 388 // The event for gesture start should be strictly before the 389 // first hover enter event for the gesture. 390 if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_HOVER_ENTER 391 && mTouchExplorationGestureStarted) { 392 mTouchExplorationGestureStarted = false; 393 AccessibilityEvent gestureStartEvent = AccessibilityEvent.obtain( 394 AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START); 395 sendAccessibilityEvent(gestureStartEvent); 396 } 397 398 synchronized (mLock) { 399 if (mSecurityPolicy.canDispatchAccessibilityEvent(event)) { 400 mSecurityPolicy.updateActiveWindowAndEventSourceLocked(event); 401 notifyAccessibilityServicesDelayedLocked(event, false); 402 notifyAccessibilityServicesDelayedLocked(event, true); 403 } 404 } 405 406 // The event for gesture end should be strictly after the 407 // last hover exit event for the gesture. 408 if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_HOVER_EXIT 409 && mTouchExplorationGestureEnded) { 410 mTouchExplorationGestureEnded = false; 411 AccessibilityEvent gestureEndEvent = AccessibilityEvent.obtain( 412 AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END); 413 sendAccessibilityEvent(gestureEndEvent); 414 } 415 416 event.recycle(); 417 mHandledFeedbackTypes = 0; 418 419 return (OWN_PROCESS_ID != Binder.getCallingPid()); 420 } 421 422 public List<AccessibilityServiceInfo> getInstalledAccessibilityServiceList() { 423 synchronized (mLock) { 424 return mInstalledServices; 425 } 426 } 427 428 public List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList(int feedbackType) { 429 List<AccessibilityServiceInfo> result = mEnabledServicesForFeedbackTempList; 430 result.clear(); 431 List<Service> services = mServices; 432 synchronized (mLock) { 433 while (feedbackType != 0) { 434 final int feedbackTypeBit = (1 << Integer.numberOfTrailingZeros(feedbackType)); 435 feedbackType &= ~feedbackTypeBit; 436 final int serviceCount = services.size(); 437 for (int i = 0; i < serviceCount; i++) { 438 Service service = services.get(i); 439 if ((service.mFeedbackType & feedbackTypeBit) != 0) { 440 result.add(service.mAccessibilityServiceInfo); 441 } 442 } 443 } 444 } 445 return result; 446 } 447 448 public void interrupt() { 449 synchronized (mLock) { 450 for (int i = 0, count = mServices.size(); i < count; i++) { 451 Service service = mServices.get(i); 452 try { 453 service.mServiceInterface.onInterrupt(); 454 } catch (RemoteException re) { 455 Slog.e(LOG_TAG, "Error during sending interrupt request to " 456 + service.mService, re); 457 } 458 } 459 } 460 } 461 462 public int addAccessibilityInteractionConnection(IWindow windowToken, 463 IAccessibilityInteractionConnection connection) throws RemoteException { 464 synchronized (mLock) { 465 final IWindow addedWindowToken = windowToken; 466 final int windowId = sNextWindowId++; 467 AccessibilityConnectionWrapper wrapper = new AccessibilityConnectionWrapper(windowId, 468 connection); 469 wrapper.linkToDeath(); 470 mWindowIdToWindowTokenMap.put(windowId, addedWindowToken.asBinder()); 471 mWindowIdToInteractionConnectionWrapperMap.put(windowId, wrapper); 472 if (DEBUG) { 473 Slog.i(LOG_TAG, "Adding interaction connection to windowId: " + windowId); 474 } 475 return windowId; 476 } 477 } 478 479 public void removeAccessibilityInteractionConnection(IWindow windowToken) { 480 synchronized (mLock) { 481 final int count = mWindowIdToWindowTokenMap.size(); 482 for (int i = 0; i < count; i++) { 483 if (mWindowIdToWindowTokenMap.valueAt(i) == windowToken.asBinder()) { 484 final int windowId = mWindowIdToWindowTokenMap.keyAt(i); 485 AccessibilityConnectionWrapper wrapper = 486 mWindowIdToInteractionConnectionWrapperMap.get(windowId); 487 wrapper.unlinkToDeath(); 488 removeAccessibilityInteractionConnectionLocked(windowId); 489 return; 490 } 491 } 492 } 493 } 494 495 public void registerUiTestAutomationService(IAccessibilityServiceClient serviceClient, 496 AccessibilityServiceInfo accessibilityServiceInfo) { 497 mSecurityPolicy.enforceCallingPermission(Manifest.permission.RETRIEVE_WINDOW_CONTENT, 498 FUNCTION_REGISTER_UI_TEST_AUTOMATION_SERVICE); 499 ComponentName componentName = new ComponentName("foo.bar", 500 "AutomationAccessibilityService"); 501 synchronized (mLock) { 502 // If an automation services is connected to the system all services are stopped 503 // so the automation one is the only one running. Settings are not changed so when 504 // the automation service goes away the state is restored from the settings. 505 506 // Disable all services. 507 final int runningServiceCount = mServices.size(); 508 for (int i = 0; i < runningServiceCount; i++) { 509 Service runningService = mServices.get(i); 510 runningService.unbind(); 511 } 512 // If necessary enable accessibility and announce that. 513 if (!mIsAccessibilityEnabled) { 514 mIsAccessibilityEnabled = true; 515 sendStateToClientsLocked(); 516 } 517 } 518 // Hook the automation service up. 519 mUiAutomationService = new Service(componentName, accessibilityServiceInfo, true); 520 mUiAutomationService.onServiceConnected(componentName, serviceClient.asBinder()); 521 } 522 523 public void unregisterUiTestAutomationService(IAccessibilityServiceClient serviceClient) { 524 synchronized (mLock) { 525 // Automation service is not bound, so pretend it died to perform clean up. 526 if (mUiAutomationService != null 527 && mUiAutomationService.mServiceInterface == serviceClient) { 528 mUiAutomationService.binderDied(); 529 } 530 } 531 } 532 533 boolean onGesture(int gestureId) { 534 synchronized (mLock) { 535 boolean handled = notifyGestureLocked(gestureId, false); 536 if (!handled) { 537 handled = notifyGestureLocked(gestureId, true); 538 } 539 return handled; 540 } 541 } 542 543 /** 544 * Gets the bounds of the accessibility focus if the provided, 545 * point coordinates are within the currently active window 546 * and accessibility focus is found within the latter. 547 * 548 * @param x X coordinate. 549 * @param y Y coordinate 550 * @param outBounds The output to which to write the focus bounds. 551 * @return The accessibility focus rectangle if the point is in the 552 * window and the window has accessibility focus. 553 */ 554 boolean getAccessibilityFocusBounds(int x, int y, Rect outBounds) { 555 // Instead of keeping track of accessibility focus events per 556 // window to be able to find the focus in the active window, 557 // we take a stateless approach and look it up. This is fine 558 // since we do this only when the user clicks/long presses. 559 Service service = getQueryBridge(); 560 final int connectionId = service.mId; 561 AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance(); 562 client.addConnection(connectionId, service); 563 try { 564 AccessibilityNodeInfo root = AccessibilityInteractionClient.getInstance() 565 .getRootInActiveWindow(connectionId); 566 if (root == null) { 567 return false; 568 } 569 Rect bounds = mTempRect; 570 root.getBoundsInScreen(bounds); 571 if (!bounds.contains(x, y)) { 572 return false; 573 } 574 AccessibilityNodeInfo focus = root.findFocus( 575 AccessibilityNodeInfo.FOCUS_ACCESSIBILITY); 576 if (focus == null) { 577 return false; 578 } 579 focus.getBoundsInScreen(outBounds); 580 return true; 581 } finally { 582 client.removeConnection(connectionId); 583 } 584 } 585 586 private Service getQueryBridge() { 587 if (mQueryBridge == null) { 588 AccessibilityServiceInfo info = new AccessibilityServiceInfo(); 589 mQueryBridge = new Service(null, info, true); 590 } 591 return mQueryBridge; 592 } 593 594 public void touchExplorationGestureEnded() { 595 mTouchExplorationGestureEnded = true; 596 } 597 598 public void touchExplorationGestureStarted() { 599 mTouchExplorationGestureStarted = true; 600 } 601 602 private boolean notifyGestureLocked(int gestureId, boolean isDefault) { 603 // TODO: Now we are giving the gestures to the last enabled 604 // service that can handle them which is the last one 605 // in our list since we write the last enabled as the 606 // last record in the enabled services setting. Ideally, 607 // the user should make the call which service handles 608 // gestures. However, only one service should handle 609 // gestures to avoid user frustration when different 610 // behavior is observed from different combinations of 611 // enabled accessibility services. 612 for (int i = mServices.size() - 1; i >= 0; i--) { 613 Service service = mServices.get(i); 614 if (service.mReqeustTouchExplorationMode && service.mIsDefault == isDefault) { 615 service.notifyGesture(gestureId); 616 return true; 617 } 618 } 619 return false; 620 } 621 622 /** 623 * Removes an AccessibilityInteractionConnection. 624 * 625 * @param windowId The id of the window to which the connection is targeted. 626 */ 627 private void removeAccessibilityInteractionConnectionLocked(int windowId) { 628 mWindowIdToWindowTokenMap.remove(windowId); 629 mWindowIdToInteractionConnectionWrapperMap.remove(windowId); 630 if (DEBUG) { 631 Slog.i(LOG_TAG, "Removing interaction connection to windowId: " + windowId); 632 } 633 } 634 635 /** 636 * Populates the cached list of installed {@link AccessibilityService}s. 637 */ 638 private void populateAccessibilityServiceListLocked() { 639 mInstalledServices.clear(); 640 641 List<ResolveInfo> installedServices = mPackageManager.queryIntentServices( 642 new Intent(AccessibilityService.SERVICE_INTERFACE), 643 PackageManager.GET_SERVICES | PackageManager.GET_META_DATA); 644 645 for (int i = 0, count = installedServices.size(); i < count; i++) { 646 ResolveInfo resolveInfo = installedServices.get(i); 647 ServiceInfo serviceInfo = resolveInfo.serviceInfo; 648 // For now we are enforcing this if the target version is JellyBean or 649 // higher and in a later release we will enforce this for everyone. 650 if (serviceInfo.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.JELLY_BEAN 651 && !android.Manifest.permission.BIND_ACCESSIBILITY_SERVICE.equals( 652 serviceInfo.permission)) { 653 Slog.w(LOG_TAG, "Skipping accessibilty service " + new ComponentName( 654 serviceInfo.packageName, serviceInfo.name).flattenToShortString() 655 + ": it does not require the permission " 656 + android.Manifest.permission.BIND_ACCESSIBILITY_SERVICE); 657 continue; 658 } 659 AccessibilityServiceInfo accessibilityServiceInfo; 660 try { 661 accessibilityServiceInfo = new AccessibilityServiceInfo(resolveInfo, mContext); 662 mInstalledServices.add(accessibilityServiceInfo); 663 } catch (XmlPullParserException xppe) { 664 Slog.e(LOG_TAG, "Error while initializing AccessibilityServiceInfo", xppe); 665 } catch (IOException ioe) { 666 Slog.e(LOG_TAG, "Error while initializing AccessibilityServiceInfo", ioe); 667 } 668 } 669 } 670 671 private void populateEnabledAccessibilityServicesLocked() { 672 populateComponentNamesFromSettingLocked( 673 Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, 674 mEnabledServices); 675 } 676 677 private void populateTouchExplorationGrantedAccessibilityServicesLocked() { 678 populateComponentNamesFromSettingLocked( 679 Settings.Secure.TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES, 680 mTouchExplorationGrantedServices); 681 } 682 683 /** 684 * Performs {@link AccessibilityService}s delayed notification. The delay is configurable 685 * and denotes the period after the last event before notifying the service. 686 * 687 * @param event The event. 688 * @param isDefault True to notify default listeners, not default services. 689 */ 690 private void notifyAccessibilityServicesDelayedLocked(AccessibilityEvent event, 691 boolean isDefault) { 692 try { 693 for (int i = 0, count = mServices.size(); i < count; i++) { 694 Service service = mServices.get(i); 695 696 if (service.mIsDefault == isDefault) { 697 if (canDispathEventLocked(service, event, mHandledFeedbackTypes)) { 698 mHandledFeedbackTypes |= service.mFeedbackType; 699 service.notifyAccessibilityEvent(event); 700 } 701 } 702 } 703 } catch (IndexOutOfBoundsException oobe) { 704 // An out of bounds exception can happen if services are going away 705 // as the for loop is running. If that happens, just bail because 706 // there are no more services to notify. 707 return; 708 } 709 } 710 711 /** 712 * Adds a service. 713 * 714 * @param service The service to add. 715 */ 716 private void tryAddServiceLocked(Service service) { 717 try { 718 if (mServices.contains(service) || !service.isConfigured()) { 719 return; 720 } 721 service.linkToOwnDeath(); 722 mServices.add(service); 723 mComponentNameToServiceMap.put(service.mComponentName, service); 724 updateInputFilterLocked(); 725 tryEnableTouchExplorationLocked(service); 726 } catch (RemoteException e) { 727 /* do nothing */ 728 } 729 } 730 731 /** 732 * Removes a service. 733 * 734 * @param service The service. 735 * @return True if the service was removed, false otherwise. 736 */ 737 private boolean tryRemoveServiceLocked(Service service) { 738 final boolean removed = mServices.remove(service); 739 if (!removed) { 740 return false; 741 } 742 mComponentNameToServiceMap.remove(service.mComponentName); 743 service.unlinkToOwnDeath(); 744 service.dispose(); 745 updateInputFilterLocked(); 746 tryDisableTouchExplorationLocked(service); 747 return removed; 748 } 749 750 /** 751 * Determines if given event can be dispatched to a service based on the package of the 752 * event source and already notified services for that event type. Specifically, a 753 * service is notified if it is interested in events from the package and no other service 754 * providing the same feedback type has been notified. Exception are services the 755 * provide generic feedback (feedback type left as a safety net for unforeseen feedback 756 * types) which are always notified. 757 * 758 * @param service The potential receiver. 759 * @param event The event. 760 * @param handledFeedbackTypes The feedback types for which services have been notified. 761 * @return True if the listener should be notified, false otherwise. 762 */ 763 private boolean canDispathEventLocked(Service service, AccessibilityEvent event, 764 int handledFeedbackTypes) { 765 766 if (!service.isConfigured()) { 767 return false; 768 } 769 770 if (!event.isImportantForAccessibility() 771 && !service.mIncludeNotImportantViews) { 772 return false; 773 } 774 775 int eventType = event.getEventType(); 776 if ((service.mEventTypes & eventType) != eventType) { 777 return false; 778 } 779 780 Set<String> packageNames = service.mPackageNames; 781 CharSequence packageName = event.getPackageName(); 782 783 if (packageNames.isEmpty() || packageNames.contains(packageName)) { 784 int feedbackType = service.mFeedbackType; 785 if ((handledFeedbackTypes & feedbackType) != feedbackType 786 || feedbackType == AccessibilityServiceInfo.FEEDBACK_GENERIC) { 787 return true; 788 } 789 } 790 791 return false; 792 } 793 794 /** 795 * Manages services by starting enabled ones and stopping disabled ones. 796 */ 797 private void manageServicesLocked() { 798 final int enabledInstalledServicesCount = updateServicesStateLocked(mInstalledServices, 799 mEnabledServices); 800 // No enabled installed services => disable accessibility to avoid 801 // sending accessibility events with no recipient across processes. 802 if (mIsAccessibilityEnabled && enabledInstalledServicesCount == 0) { 803 Settings.Secure.putInt(mContext.getContentResolver(), 804 Settings.Secure.ACCESSIBILITY_ENABLED, 0); 805 } 806 } 807 808 /** 809 * Unbinds all bound services. 810 */ 811 private void unbindAllServicesLocked() { 812 List<Service> services = mServices; 813 814 for (int i = 0, count = services.size(); i < count; i++) { 815 Service service = services.get(i); 816 if (service.unbind()) { 817 i--; 818 count--; 819 } 820 } 821 } 822 823 /** 824 * Populates a set with the {@link ComponentName}s stored in a colon 825 * separated value setting. 826 * 827 * @param settingName The setting to parse. 828 * @param outComponentNames The output component names. 829 */ 830 private void populateComponentNamesFromSettingLocked(String settingName, 831 Set<ComponentName> outComponentNames) { 832 outComponentNames.clear(); 833 834 String settingValue = Settings.Secure.getString(mContext.getContentResolver(), settingName); 835 836 if (settingValue != null) { 837 TextUtils.SimpleStringSplitter splitter = mStringColonSplitter; 838 splitter.setString(settingValue); 839 while (splitter.hasNext()) { 840 String str = splitter.next(); 841 if (str == null || str.length() <= 0) { 842 continue; 843 } 844 ComponentName enabledService = ComponentName.unflattenFromString(str); 845 if (enabledService != null) { 846 outComponentNames.add(enabledService); 847 } 848 } 849 } 850 } 851 852 /** 853 * Persists the component names in the specified setting in a 854 * colon separated fashion. 855 * 856 * @param settingName The setting name. 857 * @param componentNames The component names. 858 */ 859 private void persistComponentNamesToSettingLocked(String settingName, 860 Set<ComponentName> componentNames) { 861 StringBuilder builder = new StringBuilder(); 862 for (ComponentName componentName : componentNames) { 863 if (builder.length() > 0) { 864 builder.append(COMPONENT_NAME_SEPARATOR); 865 } 866 builder.append(componentName.flattenToShortString()); 867 } 868 Settings.Secure.putString(mContext.getContentResolver(), settingName, builder.toString()); 869 } 870 871 /** 872 * Updates the state of each service by starting (or keeping running) enabled ones and 873 * stopping the rest. 874 * 875 * @param installedServices All installed {@link AccessibilityService}s. 876 * @param enabledServices The {@link ComponentName}s of the enabled services. 877 * @return The number of enabled installed services. 878 */ 879 private int updateServicesStateLocked(List<AccessibilityServiceInfo> installedServices, 880 Set<ComponentName> enabledServices) { 881 882 Map<ComponentName, Service> componentNameToServiceMap = mComponentNameToServiceMap; 883 boolean isEnabled = mIsAccessibilityEnabled; 884 885 int enabledInstalledServices = 0; 886 for (int i = 0, count = installedServices.size(); i < count; i++) { 887 AccessibilityServiceInfo installedService = installedServices.get(i); 888 ComponentName componentName = ComponentName.unflattenFromString( 889 installedService.getId()); 890 Service service = componentNameToServiceMap.get(componentName); 891 892 if (isEnabled) { 893 if (enabledServices.contains(componentName)) { 894 if (service == null) { 895 service = new Service(componentName, installedService, false); 896 } 897 service.bind(); 898 enabledInstalledServices++; 899 } else { 900 if (service != null) { 901 service.unbind(); 902 } 903 } 904 } else { 905 if (service != null) { 906 service.unbind(); 907 } 908 } 909 } 910 911 return enabledInstalledServices; 912 } 913 914 /** 915 * Sends the state to the clients. 916 */ 917 private void sendStateToClientsLocked() { 918 final int state = getState(); 919 for (int i = 0, count = mClients.size(); i < count; i++) { 920 try { 921 mClients.get(i).setState(state); 922 } catch (RemoteException re) { 923 mClients.remove(i); 924 count--; 925 i--; 926 } 927 } 928 } 929 930 /** 931 * Gets the current state as a set of flags. 932 * 933 * @return The state. 934 */ 935 private int getState() { 936 int state = 0; 937 if (mIsAccessibilityEnabled) { 938 state |= AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED; 939 } 940 // Touch exploration relies on enabled accessibility. 941 if (mIsAccessibilityEnabled && mIsTouchExplorationEnabled) { 942 state |= AccessibilityManager.STATE_FLAG_TOUCH_EXPLORATION_ENABLED; 943 } 944 return state; 945 } 946 947 /** 948 * Updates the touch exploration state. 949 */ 950 private void updateInputFilterLocked() { 951 if (mIsAccessibilityEnabled && mIsTouchExplorationEnabled) { 952 if (!mHasInputFilter) { 953 mHasInputFilter = true; 954 if (mInputFilter == null) { 955 mInputFilter = new AccessibilityInputFilter(mContext, this); 956 } 957 mWindowManagerService.setInputFilter(mInputFilter); 958 } 959 return; 960 } 961 if (mHasInputFilter) { 962 mHasInputFilter = false; 963 mWindowManagerService.setInputFilter(null); 964 } 965 } 966 967 /** 968 * Updated the state based on the accessibility enabled setting. 969 */ 970 private void handleAccessibilityEnabledSettingChangedLocked() { 971 mIsAccessibilityEnabled = Settings.Secure.getInt( 972 mContext.getContentResolver(), 973 Settings.Secure.ACCESSIBILITY_ENABLED, 0) == 1; 974 if (mIsAccessibilityEnabled) { 975 manageServicesLocked(); 976 } else { 977 unbindAllServicesLocked(); 978 } 979 } 980 981 /** 982 * Updates the state based on the touch exploration enabled setting. 983 */ 984 private void handleTouchExplorationEnabledSettingChangedLocked() { 985 mIsTouchExplorationEnabled = Settings.Secure.getInt( 986 mContext.getContentResolver(), 987 Settings.Secure.TOUCH_EXPLORATION_ENABLED, 0) == 1; 988 } 989 990 private void tryEnableTouchExplorationLocked(final Service service) { 991 if (!mIsTouchExplorationEnabled && service.mRequestTouchExplorationMode) { 992 final boolean canToggleTouchExploration = mTouchExplorationGrantedServices.contains( 993 service.mComponentName); 994 if (!service.mIsAutomation && !canToggleTouchExploration) { 995 mMainHandler.obtainMessage(MSG_SHOW_ENABLE_TOUCH_EXPLORATION_DIALOG, 996 service).sendToTarget(); 997 } else { 998 mMainHandler.obtainMessage(MSG_TOGGLE_TOUCH_EXPLORATION, 1, 0).sendToTarget(); 999 } 1000 } 1001 } 1002 1003 private void tryDisableTouchExplorationLocked(Service service) { 1004 if (mIsTouchExplorationEnabled) { 1005 synchronized (mLock) { 1006 final int serviceCount = mServices.size(); 1007 for (int i = 0; i < serviceCount; i++) { 1008 Service other = mServices.get(i); 1009 if (other != service && other.mRequestTouchExplorationMode) { 1010 return; 1011 } 1012 } 1013 mMainHandler.obtainMessage(MSG_TOGGLE_TOUCH_EXPLORATION, 0, 0).sendToTarget(); 1014 } 1015 } 1016 } 1017 1018 private class AccessibilityConnectionWrapper implements DeathRecipient { 1019 private final int mWindowId; 1020 private final IAccessibilityInteractionConnection mConnection; 1021 1022 public AccessibilityConnectionWrapper(int windowId, 1023 IAccessibilityInteractionConnection connection) { 1024 mWindowId = windowId; 1025 mConnection = connection; 1026 } 1027 1028 public void linkToDeath() throws RemoteException { 1029 mConnection.asBinder().linkToDeath(this, 0); 1030 } 1031 1032 public void unlinkToDeath() { 1033 mConnection.asBinder().unlinkToDeath(this, 0); 1034 } 1035 1036 @Override 1037 public void binderDied() { 1038 unlinkToDeath(); 1039 synchronized (mLock) { 1040 removeAccessibilityInteractionConnectionLocked(mWindowId); 1041 } 1042 } 1043 } 1044 1045 private class MainHanler extends Handler { 1046 @Override 1047 public void handleMessage(Message msg) { 1048 final int type = msg.what; 1049 switch (type) { 1050 case MSG_TOGGLE_TOUCH_EXPLORATION: { 1051 final int value = msg.arg1; 1052 Settings.Secure.putInt(mContext.getContentResolver(), 1053 Settings.Secure.TOUCH_EXPLORATION_ENABLED, value); 1054 } break; 1055 case MSG_SHOW_ENABLE_TOUCH_EXPLORATION_DIALOG: { 1056 final Service service = (Service) msg.obj; 1057 String label = service.mResolveInfo.loadLabel( 1058 mContext.getPackageManager()).toString(); 1059 synchronized (mLock) { 1060 if (mIsTouchExplorationEnabled) { 1061 return; 1062 } 1063 if (mEnableTouchExplorationDialog != null 1064 && mEnableTouchExplorationDialog.isShowing()) { 1065 return; 1066 } 1067 mEnableTouchExplorationDialog = new AlertDialog.Builder(mContext) 1068 .setIcon(android.R.drawable.ic_dialog_alert) 1069 .setPositiveButton(android.R.string.ok, new OnClickListener() { 1070 @Override 1071 public void onClick(DialogInterface dialog, int which) { 1072 // The user allowed the service to toggle touch exploration. 1073 mTouchExplorationGrantedServices.add(service.mComponentName); 1074 persistComponentNamesToSettingLocked( 1075 Settings.Secure. 1076 TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES, 1077 mTouchExplorationGrantedServices); 1078 // Enable touch exploration. 1079 Settings.Secure.putInt(mContext.getContentResolver(), 1080 Settings.Secure.TOUCH_EXPLORATION_ENABLED, 1); 1081 } 1082 }) 1083 .setNegativeButton(android.R.string.cancel, new OnClickListener() { 1084 @Override 1085 public void onClick(DialogInterface dialog, int which) { 1086 dialog.dismiss(); 1087 } 1088 }) 1089 .setTitle(R.string.enable_explore_by_touch_warning_title) 1090 .setMessage(mContext.getString( 1091 R.string.enable_explore_by_touch_warning_message, label)) 1092 .create(); 1093 mEnableTouchExplorationDialog.getWindow().setType( 1094 WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG); 1095 mEnableTouchExplorationDialog.setCanceledOnTouchOutside(true); 1096 mEnableTouchExplorationDialog.show(); 1097 } 1098 } 1099 } 1100 } 1101 } 1102 1103 /** 1104 * This class represents an accessibility service. It stores all per service 1105 * data required for the service management, provides API for starting/stopping the 1106 * service and is responsible for adding/removing the service in the data structures 1107 * for service management. The class also exposes configuration interface that is 1108 * passed to the service it represents as soon it is bound. It also serves as the 1109 * connection for the service. 1110 */ 1111 class Service extends IAccessibilityServiceConnection.Stub 1112 implements ServiceConnection, DeathRecipient { 1113 1114 // We pick the MSB to avoid collision since accessibility event types are 1115 // used as message types allowing us to remove messages per event type. 1116 private static final int MSG_ON_GESTURE = 0x80000000; 1117 1118 int mId = 0; 1119 1120 AccessibilityServiceInfo mAccessibilityServiceInfo; 1121 1122 IBinder mService; 1123 1124 IAccessibilityServiceClient mServiceInterface; 1125 1126 int mEventTypes; 1127 1128 int mFeedbackType; 1129 1130 Set<String> mPackageNames = new HashSet<String>(); 1131 1132 boolean mIsDefault; 1133 1134 boolean mRequestTouchExplorationMode; 1135 1136 boolean mIncludeNotImportantViews; 1137 1138 long mNotificationTimeout; 1139 1140 ComponentName mComponentName; 1141 1142 Intent mIntent; 1143 1144 boolean mCanRetrieveScreenContent; 1145 1146 boolean mReqeustTouchExplorationMode; 1147 1148 boolean mIsAutomation; 1149 1150 final Rect mTempBounds = new Rect(); 1151 1152 final ResolveInfo mResolveInfo; 1153 1154 // the events pending events to be dispatched to this service 1155 final SparseArray<AccessibilityEvent> mPendingEvents = 1156 new SparseArray<AccessibilityEvent>(); 1157 1158 /** 1159 * Handler for delayed event dispatch. 1160 */ 1161 public Handler mHandler = new Handler(mMainHandler.getLooper()) { 1162 @Override 1163 public void handleMessage(Message message) { 1164 final int type = message.what; 1165 switch (type) { 1166 case MSG_ON_GESTURE: { 1167 final int gestureId = message.arg1; 1168 notifyGestureInternal(gestureId); 1169 } break; 1170 default: { 1171 final int eventType = type; 1172 notifyAccessibilityEventInternal(eventType); 1173 } break; 1174 } 1175 } 1176 }; 1177 1178 public Service(ComponentName componentName, 1179 AccessibilityServiceInfo accessibilityServiceInfo, boolean isAutomation) { 1180 mResolveInfo = accessibilityServiceInfo.getResolveInfo(); 1181 mId = sIdCounter++; 1182 mComponentName = componentName; 1183 mAccessibilityServiceInfo = accessibilityServiceInfo; 1184 mIsAutomation = isAutomation; 1185 if (!isAutomation) { 1186 mCanRetrieveScreenContent = accessibilityServiceInfo.getCanRetrieveWindowContent(); 1187 mReqeustTouchExplorationMode = 1188 (accessibilityServiceInfo.flags 1189 & AccessibilityServiceInfo.FLAG_REQUEST_TOUCH_EXPLORATION_MODE) != 0; 1190 mIntent = new Intent().setComponent(mComponentName); 1191 mIntent.putExtra(Intent.EXTRA_CLIENT_LABEL, 1192 com.android.internal.R.string.accessibility_binding_label); 1193 mIntent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivity( 1194 mContext, 0, new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS), 0)); 1195 } else { 1196 mCanRetrieveScreenContent = true; 1197 } 1198 setDynamicallyConfigurableProperties(accessibilityServiceInfo); 1199 } 1200 1201 public void setDynamicallyConfigurableProperties(AccessibilityServiceInfo info) { 1202 mEventTypes = info.eventTypes; 1203 mFeedbackType = info.feedbackType; 1204 String[] packageNames = info.packageNames; 1205 if (packageNames != null) { 1206 mPackageNames.addAll(Arrays.asList(packageNames)); 1207 } 1208 mNotificationTimeout = info.notificationTimeout; 1209 mIsDefault = (info.flags & DEFAULT) != 0; 1210 1211 if (mIsAutomation || info.getResolveInfo().serviceInfo.applicationInfo.targetSdkVersion 1212 >= Build.VERSION_CODES.JELLY_BEAN) { 1213 mIncludeNotImportantViews = 1214 (info.flags & FLAG_INCLUDE_NOT_IMPORTANT_VIEWS) != 0; 1215 } 1216 1217 mRequestTouchExplorationMode = (info.flags 1218 & AccessibilityServiceInfo.FLAG_REQUEST_TOUCH_EXPLORATION_MODE) != 0; 1219 1220 // If this service is up and running we may have to enable touch 1221 // exploration, otherwise this will happen when the service connects. 1222 synchronized (mLock) { 1223 if (isConfigured()) { 1224 if (mRequestTouchExplorationMode) { 1225 tryEnableTouchExplorationLocked(this); 1226 } else { 1227 tryDisableTouchExplorationLocked(this); 1228 } 1229 } 1230 } 1231 } 1232 1233 /** 1234 * Binds to the accessibility service. 1235 * 1236 * @return True if binding is successful. 1237 */ 1238 public boolean bind() { 1239 if (!mIsAutomation && mService == null) { 1240 return mContext.bindService(mIntent, this, Context.BIND_AUTO_CREATE); 1241 } 1242 return false; 1243 } 1244 1245 /** 1246 * Unbinds form the accessibility service and removes it from the data 1247 * structures for service management. 1248 * 1249 * @return True if unbinding is successful. 1250 */ 1251 public boolean unbind() { 1252 if (mService != null) { 1253 synchronized (mLock) { 1254 tryRemoveServiceLocked(this); 1255 } 1256 if (!mIsAutomation) { 1257 mContext.unbindService(this); 1258 } 1259 return true; 1260 } 1261 return false; 1262 } 1263 1264 /** 1265 * Returns if the service is configured i.e. at least event types of interest 1266 * and feedback type must be set. 1267 * 1268 * @return True if the service is configured, false otherwise. 1269 */ 1270 public boolean isConfigured() { 1271 return (mEventTypes != 0 && mFeedbackType != 0 && mService != null); 1272 } 1273 1274 @Override 1275 public AccessibilityServiceInfo getServiceInfo() { 1276 synchronized (mLock) { 1277 return mAccessibilityServiceInfo; 1278 } 1279 } 1280 1281 @Override 1282 public void setServiceInfo(AccessibilityServiceInfo info) { 1283 synchronized (mLock) { 1284 // If the XML manifest had data to configure the service its info 1285 // should be already set. In such a case update only the dynamically 1286 // configurable properties. 1287 AccessibilityServiceInfo oldInfo = mAccessibilityServiceInfo; 1288 if (oldInfo != null) { 1289 oldInfo.updateDynamicallyConfigurableProperties(info); 1290 setDynamicallyConfigurableProperties(oldInfo); 1291 } else { 1292 setDynamicallyConfigurableProperties(info); 1293 } 1294 } 1295 } 1296 1297 @Override 1298 public void onServiceConnected(ComponentName componentName, IBinder service) { 1299 mService = service; 1300 mServiceInterface = IAccessibilityServiceClient.Stub.asInterface(service); 1301 try { 1302 mServiceInterface.setConnection(this, mId); 1303 synchronized (mLock) { 1304 tryAddServiceLocked(this); 1305 } 1306 } catch (RemoteException re) { 1307 Slog.w(LOG_TAG, "Error while setting Controller for service: " + service, re); 1308 } 1309 } 1310 1311 @Override 1312 public float findAccessibilityNodeInfoByViewId(int accessibilityWindowId, 1313 long accessibilityNodeId, int viewId, int interactionId, 1314 IAccessibilityInteractionConnectionCallback callback, long interrogatingTid) 1315 throws RemoteException { 1316 final int resolvedWindowId = resolveAccessibilityWindowId(accessibilityWindowId); 1317 IAccessibilityInteractionConnection connection = null; 1318 synchronized (mLock) { 1319 mSecurityPolicy.enforceCanRetrieveWindowContent(this); 1320 final boolean permissionGranted = mSecurityPolicy.canRetrieveWindowContent(this); 1321 if (!permissionGranted) { 1322 return 0; 1323 } else { 1324 connection = getConnectionLocked(resolvedWindowId); 1325 if (connection == null) { 1326 return 0; 1327 } 1328 } 1329 } 1330 final int flags = (mIncludeNotImportantViews) ? 1331 AccessibilityNodeInfo.INCLUDE_NOT_IMPORTANT_VIEWS : 0; 1332 final int interrogatingPid = Binder.getCallingPid(); 1333 final long identityToken = Binder.clearCallingIdentity(); 1334 try { 1335 connection.findAccessibilityNodeInfoByViewId(accessibilityNodeId, viewId, 1336 interactionId, callback, flags, interrogatingPid, interrogatingTid); 1337 } catch (RemoteException re) { 1338 if (DEBUG) { 1339 Slog.e(LOG_TAG, "Error findAccessibilityNodeInfoByViewId()."); 1340 } 1341 } finally { 1342 Binder.restoreCallingIdentity(identityToken); 1343 } 1344 return getCompatibilityScale(resolvedWindowId); 1345 } 1346 1347 @Override 1348 public float findAccessibilityNodeInfosByText(int accessibilityWindowId, 1349 long accessibilityNodeId, String text, int interactionId, 1350 IAccessibilityInteractionConnectionCallback callback, long interrogatingTid) 1351 throws RemoteException { 1352 final int resolvedWindowId = resolveAccessibilityWindowId(accessibilityWindowId); 1353 IAccessibilityInteractionConnection connection = null; 1354 synchronized (mLock) { 1355 mSecurityPolicy.enforceCanRetrieveWindowContent(this); 1356 final boolean permissionGranted = 1357 mSecurityPolicy.canGetAccessibilityNodeInfoLocked(this, resolvedWindowId); 1358 if (!permissionGranted) { 1359 return 0; 1360 } else { 1361 connection = getConnectionLocked(resolvedWindowId); 1362 if (connection == null) { 1363 return 0; 1364 } 1365 } 1366 } 1367 final int flags = (mIncludeNotImportantViews) ? 1368 AccessibilityNodeInfo.INCLUDE_NOT_IMPORTANT_VIEWS : 0; 1369 final int interrogatingPid = Binder.getCallingPid(); 1370 final long identityToken = Binder.clearCallingIdentity(); 1371 try { 1372 connection.findAccessibilityNodeInfosByText(accessibilityNodeId, text, 1373 interactionId, callback, flags, interrogatingPid, interrogatingTid); 1374 } catch (RemoteException re) { 1375 if (DEBUG) { 1376 Slog.e(LOG_TAG, "Error calling findAccessibilityNodeInfosByText()"); 1377 } 1378 } finally { 1379 Binder.restoreCallingIdentity(identityToken); 1380 } 1381 return getCompatibilityScale(resolvedWindowId); 1382 } 1383 1384 @Override 1385 public float findAccessibilityNodeInfoByAccessibilityId(int accessibilityWindowId, 1386 long accessibilityNodeId, int interactionId, 1387 IAccessibilityInteractionConnectionCallback callback, int flags, 1388 long interrogatingTid) throws RemoteException { 1389 final int resolvedWindowId = resolveAccessibilityWindowId(accessibilityWindowId); 1390 IAccessibilityInteractionConnection connection = null; 1391 synchronized (mLock) { 1392 mSecurityPolicy.enforceCanRetrieveWindowContent(this); 1393 final boolean permissionGranted = 1394 mSecurityPolicy.canGetAccessibilityNodeInfoLocked(this, resolvedWindowId); 1395 if (!permissionGranted) { 1396 return 0; 1397 } else { 1398 connection = getConnectionLocked(resolvedWindowId); 1399 if (connection == null) { 1400 return 0; 1401 } 1402 } 1403 } 1404 final int allFlags = flags | ((mIncludeNotImportantViews) ? 1405 AccessibilityNodeInfo.INCLUDE_NOT_IMPORTANT_VIEWS : 0); 1406 final int interrogatingPid = Binder.getCallingPid(); 1407 final long identityToken = Binder.clearCallingIdentity(); 1408 try { 1409 connection.findAccessibilityNodeInfoByAccessibilityId(accessibilityNodeId, 1410 interactionId, callback, allFlags, interrogatingPid, interrogatingTid); 1411 } catch (RemoteException re) { 1412 if (DEBUG) { 1413 Slog.e(LOG_TAG, "Error calling findAccessibilityNodeInfoByAccessibilityId()"); 1414 } 1415 } finally { 1416 Binder.restoreCallingIdentity(identityToken); 1417 } 1418 return getCompatibilityScale(resolvedWindowId); 1419 } 1420 1421 @Override 1422 public float findFocus(int accessibilityWindowId, long accessibilityNodeId, 1423 int focusType, int interactionId, 1424 IAccessibilityInteractionConnectionCallback callback, long interrogatingTid) 1425 throws RemoteException { 1426 final int resolvedWindowId = resolveAccessibilityWindowId(accessibilityWindowId); 1427 IAccessibilityInteractionConnection connection = null; 1428 synchronized (mLock) { 1429 mSecurityPolicy.enforceCanRetrieveWindowContent(this); 1430 final boolean permissionGranted = 1431 mSecurityPolicy.canGetAccessibilityNodeInfoLocked(this, resolvedWindowId); 1432 if (!permissionGranted) { 1433 return 0; 1434 } else { 1435 connection = getConnectionLocked(resolvedWindowId); 1436 if (connection == null) { 1437 return 0; 1438 } 1439 } 1440 } 1441 final int flags = (mIncludeNotImportantViews) ? 1442 AccessibilityNodeInfo.INCLUDE_NOT_IMPORTANT_VIEWS : 0; 1443 final int interrogatingPid = Binder.getCallingPid(); 1444 final long identityToken = Binder.clearCallingIdentity(); 1445 try { 1446 connection.findFocus(accessibilityNodeId, interactionId, focusType, callback, 1447 flags, interrogatingPid, interrogatingTid); 1448 } catch (RemoteException re) { 1449 if (DEBUG) { 1450 Slog.e(LOG_TAG, "Error calling findAccessibilityFocus()"); 1451 } 1452 } finally { 1453 Binder.restoreCallingIdentity(identityToken); 1454 } 1455 return getCompatibilityScale(resolvedWindowId); 1456 } 1457 1458 @Override 1459 public float focusSearch(int accessibilityWindowId, long accessibilityNodeId, 1460 int direction, int interactionId, 1461 IAccessibilityInteractionConnectionCallback callback, long interrogatingTid) 1462 throws RemoteException { 1463 final int resolvedWindowId = resolveAccessibilityWindowId(accessibilityWindowId); 1464 IAccessibilityInteractionConnection connection = null; 1465 synchronized (mLock) { 1466 mSecurityPolicy.enforceCanRetrieveWindowContent(this); 1467 final boolean permissionGranted = 1468 mSecurityPolicy.canGetAccessibilityNodeInfoLocked(this, resolvedWindowId); 1469 if (!permissionGranted) { 1470 return 0; 1471 } else { 1472 connection = getConnectionLocked(resolvedWindowId); 1473 if (connection == null) { 1474 return 0; 1475 } 1476 } 1477 } 1478 final int flags = (mIncludeNotImportantViews) ? 1479 AccessibilityNodeInfo.INCLUDE_NOT_IMPORTANT_VIEWS : 0; 1480 final int interrogatingPid = Binder.getCallingPid(); 1481 final long identityToken = Binder.clearCallingIdentity(); 1482 try { 1483 connection.focusSearch(accessibilityNodeId, interactionId, direction, callback, 1484 flags, interrogatingPid, interrogatingTid); 1485 } catch (RemoteException re) { 1486 if (DEBUG) { 1487 Slog.e(LOG_TAG, "Error calling accessibilityFocusSearch()"); 1488 } 1489 } finally { 1490 Binder.restoreCallingIdentity(identityToken); 1491 } 1492 return getCompatibilityScale(resolvedWindowId); 1493 } 1494 1495 @Override 1496 public boolean performAccessibilityAction(int accessibilityWindowId, 1497 long accessibilityNodeId, int action, Bundle arguments, int interactionId, 1498 IAccessibilityInteractionConnectionCallback callback, long interrogatingTid) { 1499 final int resolvedWindowId = resolveAccessibilityWindowId(accessibilityWindowId); 1500 IAccessibilityInteractionConnection connection = null; 1501 synchronized (mLock) { 1502 final boolean permissionGranted = mSecurityPolicy.canPerformActionLocked(this, 1503 resolvedWindowId, action, arguments); 1504 if (!permissionGranted) { 1505 return false; 1506 } else { 1507 connection = getConnectionLocked(resolvedWindowId); 1508 if (connection == null) { 1509 return false; 1510 } 1511 } 1512 } 1513 final int flags = (mIncludeNotImportantViews) ? 1514 AccessibilityNodeInfo.INCLUDE_NOT_IMPORTANT_VIEWS : 0; 1515 final int interrogatingPid = Binder.getCallingPid(); 1516 final long identityToken = Binder.clearCallingIdentity(); 1517 try { 1518 connection.performAccessibilityAction(accessibilityNodeId, action, arguments, 1519 interactionId, callback, flags, interrogatingPid, interrogatingTid); 1520 } catch (RemoteException re) { 1521 if (DEBUG) { 1522 Slog.e(LOG_TAG, "Error calling performAccessibilityAction()"); 1523 } 1524 } finally { 1525 Binder.restoreCallingIdentity(identityToken); 1526 } 1527 return true; 1528 } 1529 1530 public boolean performGlobalAction(int action) { 1531 switch (action) { 1532 case AccessibilityService.GLOBAL_ACTION_BACK: { 1533 sendDownAndUpKeyEvents(KeyEvent.KEYCODE_BACK); 1534 } return true; 1535 case AccessibilityService.GLOBAL_ACTION_HOME: { 1536 sendDownAndUpKeyEvents(KeyEvent.KEYCODE_HOME); 1537 } return true; 1538 case AccessibilityService.GLOBAL_ACTION_RECENTS: { 1539 openRecents(); 1540 } return true; 1541 case AccessibilityService.GLOBAL_ACTION_NOTIFICATIONS: { 1542 expandStatusBar(); 1543 } return true; 1544 } 1545 return false; 1546 } 1547 1548 public void onServiceDisconnected(ComponentName componentName) { 1549 /* do nothing - #binderDied takes care */ 1550 } 1551 1552 public void linkToOwnDeath() throws RemoteException { 1553 mService.linkToDeath(this, 0); 1554 } 1555 1556 public void unlinkToOwnDeath() { 1557 mService.unlinkToDeath(this, 0); 1558 } 1559 1560 public void dispose() { 1561 try { 1562 // Clear the proxy in the other process so this 1563 // IAccessibilityServiceConnection can be garbage collected. 1564 mServiceInterface.setConnection(null, mId); 1565 } catch (RemoteException re) { 1566 /* ignore */ 1567 } 1568 mService = null; 1569 mServiceInterface = null; 1570 } 1571 1572 public void binderDied() { 1573 synchronized (mLock) { 1574 // The death recipient is unregistered in tryRemoveServiceLocked 1575 tryRemoveServiceLocked(this); 1576 // We no longer have an automation service, so restore 1577 // the state based on values in the settings database. 1578 if (mIsAutomation) { 1579 mUiAutomationService = null; 1580 1581 populateEnabledAccessibilityServicesLocked(); 1582 populateTouchExplorationGrantedAccessibilityServicesLocked(); 1583 1584 handleAccessibilityEnabledSettingChangedLocked(); 1585 sendStateToClientsLocked(); 1586 1587 handleTouchExplorationEnabledSettingChangedLocked(); 1588 updateInputFilterLocked(); 1589 1590 populateAccessibilityServiceListLocked(); 1591 manageServicesLocked(); 1592 } 1593 } 1594 } 1595 1596 /** 1597 * Performs a notification for an {@link AccessibilityEvent}. 1598 * 1599 * @param event The event. 1600 */ 1601 public void notifyAccessibilityEvent(AccessibilityEvent event) { 1602 synchronized (mLock) { 1603 final int eventType = event.getEventType(); 1604 // Make a copy since during dispatch it is possible the event to 1605 // be modified to remove its source if the receiving service does 1606 // not have permission to access the window content. 1607 AccessibilityEvent newEvent = AccessibilityEvent.obtain(event); 1608 AccessibilityEvent oldEvent = mPendingEvents.get(eventType); 1609 mPendingEvents.put(eventType, newEvent); 1610 1611 final int what = eventType; 1612 if (oldEvent != null) { 1613 mHandler.removeMessages(what); 1614 oldEvent.recycle(); 1615 } 1616 1617 Message message = mHandler.obtainMessage(what); 1618 mHandler.sendMessageDelayed(message, mNotificationTimeout); 1619 } 1620 } 1621 1622 /** 1623 * Notifies an accessibility service client for a scheduled event given the event type. 1624 * 1625 * @param eventType The type of the event to dispatch. 1626 */ 1627 private void notifyAccessibilityEventInternal(int eventType) { 1628 IAccessibilityServiceClient listener; 1629 AccessibilityEvent event; 1630 1631 synchronized (mLock) { 1632 listener = mServiceInterface; 1633 1634 // If the service died/was disabled while the message for dispatching 1635 // the accessibility event was propagating the listener may be null. 1636 if (listener == null) { 1637 return; 1638 } 1639 1640 event = mPendingEvents.get(eventType); 1641 1642 // Check for null here because there is a concurrent scenario in which this 1643 // happens: 1) A binder thread calls notifyAccessibilityServiceDelayedLocked 1644 // which posts a message for dispatching an event. 2) The message is pulled 1645 // from the queue by the handler on the service thread and the latter is 1646 // just about to acquire the lock and call this method. 3) Now another binder 1647 // thread acquires the lock calling notifyAccessibilityServiceDelayedLocked 1648 // so the service thread waits for the lock; 4) The binder thread replaces 1649 // the event with a more recent one (assume the same event type) and posts a 1650 // dispatch request releasing the lock. 5) Now the main thread is unblocked and 1651 // dispatches the event which is removed from the pending ones. 6) And ... now 1652 // the service thread handles the last message posted by the last binder call 1653 // but the event is already dispatched and hence looking it up in the pending 1654 // ones yields null. This check is much simpler that keeping count for each 1655 // event type of each service to catch such a scenario since only one message 1656 // is processed at a time. 1657 if (event == null) { 1658 return; 1659 } 1660 1661 mPendingEvents.remove(eventType); 1662 if (mSecurityPolicy.canRetrieveWindowContent(this)) { 1663 event.setConnectionId(mId); 1664 } else { 1665 event.setSource(null); 1666 } 1667 event.setSealed(true); 1668 } 1669 1670 try { 1671 listener.onAccessibilityEvent(event); 1672 if (DEBUG) { 1673 Slog.i(LOG_TAG, "Event " + event + " sent to " + listener); 1674 } 1675 } catch (RemoteException re) { 1676 Slog.e(LOG_TAG, "Error during sending " + event + " to " + listener, re); 1677 } finally { 1678 event.recycle(); 1679 } 1680 } 1681 1682 public void notifyGesture(int gestureId) { 1683 mHandler.obtainMessage(MSG_ON_GESTURE, gestureId, 0).sendToTarget(); 1684 } 1685 1686 private void notifyGestureInternal(int gestureId) { 1687 IAccessibilityServiceClient listener = mServiceInterface; 1688 if (listener != null) { 1689 try { 1690 listener.onGesture(gestureId); 1691 } catch (RemoteException re) { 1692 Slog.e(LOG_TAG, "Error during sending gesture " + gestureId 1693 + " to " + mService, re); 1694 } 1695 } 1696 } 1697 1698 private void sendDownAndUpKeyEvents(int keyCode) { 1699 final long token = Binder.clearCallingIdentity(); 1700 1701 // Inject down. 1702 final long downTime = SystemClock.uptimeMillis(); 1703 KeyEvent down = KeyEvent.obtain(downTime, downTime, KeyEvent.ACTION_DOWN, keyCode, 0, 0, 1704 KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_FROM_SYSTEM, 1705 InputDevice.SOURCE_KEYBOARD, null); 1706 InputManager.getInstance().injectInputEvent(down, 1707 InputManager.INJECT_INPUT_EVENT_MODE_ASYNC); 1708 down.recycle(); 1709 1710 // Inject up. 1711 final long upTime = SystemClock.uptimeMillis(); 1712 KeyEvent up = KeyEvent.obtain(downTime, upTime, KeyEvent.ACTION_UP, keyCode, 0, 0, 1713 KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_FROM_SYSTEM, 1714 InputDevice.SOURCE_KEYBOARD, null); 1715 InputManager.getInstance().injectInputEvent(up, 1716 InputManager.INJECT_INPUT_EVENT_MODE_ASYNC); 1717 up.recycle(); 1718 1719 Binder.restoreCallingIdentity(token); 1720 } 1721 1722 private void expandStatusBar() { 1723 final long token = Binder.clearCallingIdentity(); 1724 1725 StatusBarManager statusBarManager = (StatusBarManager) mContext.getSystemService( 1726 android.app.Service.STATUS_BAR_SERVICE); 1727 statusBarManager.expand(); 1728 1729 Binder.restoreCallingIdentity(token); 1730 } 1731 1732 private void openRecents() { 1733 final long token = Binder.clearCallingIdentity(); 1734 1735 IStatusBarService statusBarService = IStatusBarService.Stub.asInterface( 1736 ServiceManager.getService("statusbar")); 1737 try { 1738 statusBarService.toggleRecentApps(); 1739 } catch (RemoteException e) { 1740 Slog.e(LOG_TAG, "Error toggling recent apps."); 1741 } 1742 1743 Binder.restoreCallingIdentity(token); 1744 } 1745 1746 private IAccessibilityInteractionConnection getConnectionLocked(int windowId) { 1747 if (DEBUG) { 1748 Slog.i(LOG_TAG, "Trying to get interaction connection to windowId: " + windowId); 1749 } 1750 AccessibilityConnectionWrapper wrapper = mWindowIdToInteractionConnectionWrapperMap.get( 1751 windowId); 1752 if (wrapper != null && wrapper.mConnection != null) { 1753 return wrapper.mConnection; 1754 } 1755 if (DEBUG) { 1756 Slog.e(LOG_TAG, "No interaction connection to window: " + windowId); 1757 } 1758 return null; 1759 } 1760 1761 private int resolveAccessibilityWindowId(int accessibilityWindowId) { 1762 if (accessibilityWindowId == AccessibilityNodeInfo.ACTIVE_WINDOW_ID) { 1763 return mSecurityPolicy.mActiveWindowId; 1764 } 1765 return accessibilityWindowId; 1766 } 1767 1768 private float getCompatibilityScale(int windowId) { 1769 IBinder windowToken = mWindowIdToWindowTokenMap.get(windowId); 1770 return mWindowManagerService.getWindowCompatibilityScale(windowToken); 1771 } 1772 } 1773 1774 final class SecurityPolicy { 1775 private static final int VALID_ACTIONS = 1776 AccessibilityNodeInfo.ACTION_CLICK 1777 | AccessibilityNodeInfo.ACTION_LONG_CLICK 1778 | AccessibilityNodeInfo.ACTION_FOCUS 1779 | AccessibilityNodeInfo.ACTION_CLEAR_FOCUS 1780 | AccessibilityNodeInfo.ACTION_SELECT 1781 | AccessibilityNodeInfo.ACTION_CLEAR_SELECTION 1782 | AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS 1783 | AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS 1784 | AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY 1785 | AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY 1786 | AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT 1787 | AccessibilityNodeInfo.ACTION_PREVIOUS_HTML_ELEMENT 1788 | AccessibilityNodeInfo.ACTION_SCROLL_FORWARD 1789 | AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD; 1790 1791 private static final int RETRIEVAL_ALLOWING_EVENT_TYPES = 1792 AccessibilityEvent.TYPE_VIEW_CLICKED 1793 | AccessibilityEvent.TYPE_VIEW_FOCUSED 1794 | AccessibilityEvent.TYPE_VIEW_HOVER_ENTER 1795 | AccessibilityEvent.TYPE_VIEW_HOVER_EXIT 1796 | AccessibilityEvent.TYPE_VIEW_LONG_CLICKED 1797 | AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED 1798 | AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED 1799 | AccessibilityEvent.TYPE_VIEW_SELECTED 1800 | AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED 1801 | AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED 1802 | AccessibilityEvent.TYPE_VIEW_SCROLLED 1803 | AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED 1804 | AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED; 1805 1806 private int mActiveWindowId; 1807 1808 private boolean canDispatchAccessibilityEvent(AccessibilityEvent event) { 1809 // Send window changed event only for the retrieval allowing window. 1810 return (event.getEventType() != AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED 1811 || event.getWindowId() == mActiveWindowId); 1812 } 1813 1814 public void updateActiveWindowAndEventSourceLocked(AccessibilityEvent event) { 1815 // The active window is either the window that has input focus or 1816 // the window that the user is currently touching. If the user is 1817 // touching a window that does not have input focus as soon as the 1818 // the user stops touching that window the focused window becomes 1819 // the active one. 1820 final int windowId = event.getWindowId(); 1821 final int eventType = event.getEventType(); 1822 switch (eventType) { 1823 case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED: { 1824 if (getFocusedWindowId() == windowId) { 1825 mActiveWindowId = windowId; 1826 } 1827 } break; 1828 case AccessibilityEvent.TYPE_VIEW_HOVER_ENTER: 1829 case AccessibilityEvent.TYPE_VIEW_HOVER_EXIT: { 1830 mActiveWindowId = windowId; 1831 } break; 1832 case AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END: { 1833 mActiveWindowId = getFocusedWindowId(); 1834 } break; 1835 } 1836 if ((eventType & RETRIEVAL_ALLOWING_EVENT_TYPES) == 0) { 1837 event.setSource(null); 1838 } 1839 } 1840 1841 public int getRetrievalAllowingWindowLocked() { 1842 return mActiveWindowId; 1843 } 1844 1845 public boolean canGetAccessibilityNodeInfoLocked(Service service, int windowId) { 1846 return canRetrieveWindowContent(service) && isRetrievalAllowingWindow(windowId); 1847 } 1848 1849 public boolean canPerformActionLocked(Service service, int windowId, int action, 1850 Bundle arguments) { 1851 return canRetrieveWindowContent(service) 1852 && isRetrievalAllowingWindow(windowId) 1853 && isActionPermitted(action); 1854 } 1855 1856 public boolean canRetrieveWindowContent(Service service) { 1857 return service.mCanRetrieveScreenContent; 1858 } 1859 1860 public void enforceCanRetrieveWindowContent(Service service) throws RemoteException { 1861 // This happens due to incorrect registration so make it apparent. 1862 if (!canRetrieveWindowContent(service)) { 1863 Slog.e(LOG_TAG, "Accessibility serivce " + service.mComponentName + " does not " + 1864 "declare android:canRetrieveWindowContent."); 1865 throw new RemoteException(); 1866 } 1867 } 1868 1869 private boolean isRetrievalAllowingWindow(int windowId) { 1870 return (mActiveWindowId == windowId); 1871 } 1872 1873 private boolean isActionPermitted(int action) { 1874 return (VALID_ACTIONS & action) != 0; 1875 } 1876 1877 private void enforceCallingPermission(String permission, String function) { 1878 if (OWN_PROCESS_ID == Binder.getCallingPid()) { 1879 return; 1880 } 1881 final int permissionStatus = mContext.checkCallingPermission(permission); 1882 if (permissionStatus != PackageManager.PERMISSION_GRANTED) { 1883 throw new SecurityException("You do not have " + permission 1884 + " required to call " + function); 1885 } 1886 } 1887 1888 private int getFocusedWindowId() { 1889 // We call this only on window focus change or after touch 1890 // exploration gesture end and the shown windows are not that 1891 // many, so the linear look up is just fine. 1892 IBinder token = mWindowManagerService.getFocusedWindowClientToken(); 1893 if (token != null) { 1894 SparseArray<IBinder> windows = mWindowIdToWindowTokenMap; 1895 final int windowCount = windows.size(); 1896 for (int i = 0; i < windowCount; i++) { 1897 if (windows.valueAt(i) == token) { 1898 return windows.keyAt(i); 1899 } 1900 } 1901 } 1902 return -1; 1903 } 1904 } 1905} 1906