content: /*
   * Copyright (C) 2009 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package android.accessibilityservice;
  
  import android.accessibilityservice.GestureDescription.MotionEventGenerator;
  import android.annotation.IntDef;
  import android.annotation.NonNull;
  import android.annotation.Nullable;
  import android.annotation.RequiresPermission;
  import android.annotation.UnsupportedAppUsage;
  import android.app.Service;
  import android.content.Context;
  import android.content.Intent;
  import android.content.pm.ParceledListSlice;
  import android.graphics.Region;
  import android.os.Build;
  import android.os.Handler;
  import android.os.IBinder;
  import android.os.Looper;
  import android.os.Message;
  import android.os.RemoteException;
  import android.util.ArrayMap;
  import android.util.Log;
  import android.util.Slog;
  import android.util.SparseArray;
  import android.view.Display;
  import android.view.KeyEvent;
  import android.view.WindowManager;
  import android.view.WindowManagerImpl;
  import android.view.accessibility.AccessibilityEvent;
  import android.view.accessibility.AccessibilityInteractionClient;
  import android.view.accessibility.AccessibilityNodeInfo;
  import android.view.accessibility.AccessibilityWindowInfo;
  
  import com.android.internal.os.HandlerCaller;
  import com.android.internal.os.SomeArgs;
  
  import java.lang.annotation.Retention;
  import java.lang.annotation.RetentionPolicy;
  import java.util.List;
  
  /**
   * Accessibility services should only be used to assist users with disabilities in using
   * Android devices and apps. They run in the background and receive callbacks by the system
   * when {@link AccessibilityEvent}s are fired. Such events denote some state transition
   * in the user interface, for example, the focus has changed, a button has been clicked,
   * etc. Such a service can optionally request the capability for querying the content
   * of the active window. Development of an accessibility service requires extending this
   * class and implementing its abstract methods.
   *
   * <div class="special reference">
   * <h3>Developer Guides</h3>
   * <p>For more information about creating AccessibilityServices, read the
   * <a href="{@docRoot}guide/topics/ui/accessibility/index.html">Accessibility</a>
   * developer guide.</p>
   * </div>
   *
   * <h3>Lifecycle</h3>
   * <p>
   * The lifecycle of an accessibility service is managed exclusively by the system and
   * follows the established service life cycle. Starting an accessibility service is triggered
   * exclusively by the user explicitly turning the service on in device settings. After the system
   * binds to a service, it calls {@link AccessibilityService#onServiceConnected()}. This method can
   * be overridden by clients that want to perform post binding setup.
   * </p>
   * <p>
   * An accessibility service stops either when the user turns it off in device settings or when
   * it calls {@link AccessibilityService#disableSelf()}.
   * </p>
   * <h3>Declaration</h3>
   * <p>
   * An accessibility is declared as any other service in an AndroidManifest.xml, but it
   * must do two things:
   * <ul>
   *     <ol>
   *         Specify that it handles the "android.accessibilityservice.AccessibilityService"
   *         {@link android.content.Intent}.
   *     </ol>
   *     <ol>
   *         Request the {@link android.Manifest.permission#BIND_ACCESSIBILITY_SERVICE} permission to
   *         ensure that only the system can bind to it.
   *     </ol>
   * </ul>
   * If either of these items is missing, the system will ignore the accessibility service.
   * Following is an example declaration:
   * </p>
   * <pre> &lt;service android:name=".MyAccessibilityService"
   *         android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"&gt;
   *     &lt;intent-filter&gt;
   *         &lt;action android:name="android.accessibilityservice.AccessibilityService" /&gt;
   *     &lt;/intent-filter&gt;
   *     . . .
   * &lt;/service&gt;</pre>
   * <h3>Configuration</h3>
   * <p>
   * An accessibility service can be configured to receive specific types of accessibility events,
   * listen only to specific packages, get events from each type only once in a given time frame,
   * retrieve window content, specify a settings activity, etc.
   * </p>
   * <p>
   * There are two approaches for configuring an accessibility service:
   * </p>
   * <ul>
   * <li>
   * Providing a {@link #SERVICE_META_DATA meta-data} entry in the manifest when declaring
   * the service. A service declaration with a meta-data tag is presented below:
   * <pre> &lt;service android:name=".MyAccessibilityService"&gt;
   *     &lt;intent-filter&gt;
   *         &lt;action android:name="android.accessibilityservice.AccessibilityService" /&gt;
   *     &lt;/intent-filter&gt;
   *     &lt;meta-data android:name="android.accessibilityservice" android:resource="@xml/accessibilityservice" /&gt;
   * &lt;/service&gt;</pre>
   * <p class="note">
   * <strong>Note:</strong> This approach enables setting all properties.
   * </p>
   * <p>
   * For more details refer to {@link #SERVICE_META_DATA} and
   * <code>&lt;{@link android.R.styleable#AccessibilityService accessibility-service}&gt;</code>.
   * </p>
   * </li>
   * <li>
   * Calling {@link AccessibilityService#setServiceInfo(AccessibilityServiceInfo)}. Note
   * that this method can be called any time to dynamically change the service configuration.
   * <p class="note">
   * <strong>Note:</strong> This approach enables setting only dynamically configurable properties:
   * {@link AccessibilityServiceInfo#eventTypes},
   * {@link AccessibilityServiceInfo#feedbackType},
   * {@link AccessibilityServiceInfo#flags},
   * {@link AccessibilityServiceInfo#notificationTimeout},
   * {@link AccessibilityServiceInfo#packageNames}
   * </p>
   * <p>
   * For more details refer to {@link AccessibilityServiceInfo}.
   * </p>
   * </li>
   * </ul>
   * <h3>Retrieving window content</h3>
   * <p>
   * A service can specify in its declaration that it can retrieve window
   * content which is represented as a tree of {@link AccessibilityWindowInfo} and
   * {@link AccessibilityNodeInfo} objects. Note that
   * declaring this capability requires that the service declares its configuration via
   * an XML resource referenced by {@link #SERVICE_META_DATA}.
   * </p>
   * <p>
   * Window content may be retrieved with
   * {@link AccessibilityEvent#getSource() AccessibilityEvent.getSource()},
   * {@link AccessibilityService#findFocus(int)},
   * {@link AccessibilityService#getWindows()}, or
   * {@link AccessibilityService#getRootInActiveWindow()}.
   * </p>
   * <p class="note">
   * <strong>Note</strong> An accessibility service may have requested to be notified for
   * a subset of the event types, and thus be unaware when the node hierarchy has changed. It is also
   * possible for a node to contain outdated information because the window content may change at any
   * time.
   * </p>
   * <h3>Notification strategy</h3>
   * <p>
   * All accessibility services are notified of all events they have requested, regardless of their
   * feedback type.
   * </p>
   * <p class="note">
   * <strong>Note:</strong> The event notification timeout is useful to avoid propagating
   * events to the client too frequently since this is accomplished via an expensive
   * interprocess call. One can think of the timeout as a criteria to determine when
   * event generation has settled down.</p>
   * <h3>Event types</h3>
   * <ul>
   * <li>{@link AccessibilityEvent#TYPE_VIEW_CLICKED}</li>
   * <li>{@link AccessibilityEvent#TYPE_VIEW_LONG_CLICKED}</li>
   * <li>{@link AccessibilityEvent#TYPE_VIEW_FOCUSED}</li>
   * <li>{@link AccessibilityEvent#TYPE_VIEW_SELECTED}</li>
   * <li>{@link AccessibilityEvent#TYPE_VIEW_TEXT_CHANGED}</li>
   * <li>{@link AccessibilityEvent#TYPE_WINDOW_STATE_CHANGED}</li>
   * <li>{@link AccessibilityEvent#TYPE_NOTIFICATION_STATE_CHANGED}</li>
   * <li>{@link AccessibilityEvent#TYPE_TOUCH_EXPLORATION_GESTURE_START}</li>
   * <li>{@link AccessibilityEvent#TYPE_TOUCH_EXPLORATION_GESTURE_END}</li>
   * <li>{@link AccessibilityEvent#TYPE_VIEW_HOVER_ENTER}</li>
   * <li>{@link AccessibilityEvent#TYPE_VIEW_HOVER_EXIT}</li>
   * <li>{@link AccessibilityEvent#TYPE_VIEW_SCROLLED}</li>
   * <li>{@link AccessibilityEvent#TYPE_VIEW_TEXT_SELECTION_CHANGED}</li>
   * <li>{@link AccessibilityEvent#TYPE_WINDOW_CONTENT_CHANGED}</li>
   * <li>{@link AccessibilityEvent#TYPE_ANNOUNCEMENT}</li>
   * <li>{@link AccessibilityEvent#TYPE_GESTURE_DETECTION_START}</li>
   * <li>{@link AccessibilityEvent#TYPE_GESTURE_DETECTION_END}</li>
   * <li>{@link AccessibilityEvent#TYPE_TOUCH_INTERACTION_START}</li>
   * <li>{@link AccessibilityEvent#TYPE_TOUCH_INTERACTION_END}</li>
   * <li>{@link AccessibilityEvent#TYPE_VIEW_ACCESSIBILITY_FOCUSED}</li>
   * <li>{@link AccessibilityEvent#TYPE_WINDOWS_CHANGED}</li>
   * <li>{@link AccessibilityEvent#TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED}</li>
   * </ul>
   * <h3>Feedback types</h3>
   * <ul>
   * <li>{@link AccessibilityServiceInfo#FEEDBACK_AUDIBLE}</li>
   * <li>{@link AccessibilityServiceInfo#FEEDBACK_HAPTIC}</li>
   * <li>{@link AccessibilityServiceInfo#FEEDBACK_AUDIBLE}</li>
   * <li>{@link AccessibilityServiceInfo#FEEDBACK_VISUAL}</li>
   * <li>{@link AccessibilityServiceInfo#FEEDBACK_GENERIC}</li>
   * <li>{@link AccessibilityServiceInfo#FEEDBACK_BRAILLE}</li>
   * </ul>
   * @see AccessibilityEvent
   * @see AccessibilityServiceInfo
   * @see android.view.accessibility.AccessibilityManager
   */
  public abstract class AccessibilityService extends Service {
  
      /**
       * The user has performed a swipe up gesture on the touch screen.
       */
      public static final int GESTURE_SWIPE_UP = 1;
  
      /**
       * The user has performed a swipe down gesture on the touch screen.
       */
      public static final int GESTURE_SWIPE_DOWN = 2;
  
      /**
       * The user has performed a swipe left gesture on the touch screen.
       */
      public static final int GESTURE_SWIPE_LEFT = 3;
  
      /**
       * The user has performed a swipe right gesture on the touch screen.
       */
      public static final int GESTURE_SWIPE_RIGHT = 4;
  
      /**
       * The user has performed a swipe left and right gesture on the touch screen.
       */
      public static final int GESTURE_SWIPE_LEFT_AND_RIGHT = 5;
  
      /**
       * The user has performed a swipe right and left gesture on the touch screen.
       */
      public static final int GESTURE_SWIPE_RIGHT_AND_LEFT = 6;
  
      /**
       * The user has performed a swipe up and down gesture on the touch screen.
       */
      public static final int GESTURE_SWIPE_UP_AND_DOWN = 7;
  
      /**
       * The user has performed a swipe down and up gesture on the touch screen.
       */
      public static final int GESTURE_SWIPE_DOWN_AND_UP = 8;
  
      /**
       * The user has performed a left and up gesture on the touch screen.
       */
      public static final int GESTURE_SWIPE_LEFT_AND_UP = 9;
  
      /**
       * The user has performed a left and down gesture on the touch screen.
       */
      public static final int GESTURE_SWIPE_LEFT_AND_DOWN = 10;
  
      /**
       * The user has performed a right and up gesture on the touch screen.
       */
      public static final int GESTURE_SWIPE_RIGHT_AND_UP = 11;
  
      /**
       * The user has performed a right and down gesture on the touch screen.
       */
      public static final int GESTURE_SWIPE_RIGHT_AND_DOWN = 12;
  
      /**
       * The user has performed an up and left gesture on the touch screen.
       */
      public static final int GESTURE_SWIPE_UP_AND_LEFT = 13;
  
      /**
       * The user has performed an up and right gesture on the touch screen.
       */
      public static final int GESTURE_SWIPE_UP_AND_RIGHT = 14;
  
      /**
       * The user has performed an down and left gesture on the touch screen.
       */
      public static final int GESTURE_SWIPE_DOWN_AND_LEFT = 15;
  
      /**
       * The user has performed an down and right gesture on the touch screen.
       */
      public static final int GESTURE_SWIPE_DOWN_AND_RIGHT = 16;
  
      /**
       * The {@link Intent} that must be declared as handled by the service.
       */
      public static final String SERVICE_INTERFACE =
          "android.accessibilityservice.AccessibilityService";
  
      /**
       * Name under which an AccessibilityService component publishes information
       * about itself. This meta-data must reference an XML resource containing an
       * <code>&lt;{@link android.R.styleable#AccessibilityService accessibility-service}&gt;</code>
       * tag. This is a sample XML file configuring an accessibility service:
       * <pre> &lt;accessibility-service
       *     android:accessibilityEventTypes="typeViewClicked|typeViewFocused"
       *     android:packageNames="foo.bar, foo.baz"
       *     android:accessibilityFeedbackType="feedbackSpoken"
       *     android:notificationTimeout="100"
       *     android:accessibilityFlags="flagDefault"
       *     android:settingsActivity="foo.bar.TestBackActivity"
       *     android:canRetrieveWindowContent="true"
       *     android:canRequestTouchExplorationMode="true"
       *     . . .
       * /&gt;</pre>
       */
      public static final String SERVICE_META_DATA = "android.accessibilityservice";
  
      /**
       * Action to go back.
       */
      public static final int GLOBAL_ACTION_BACK = 1;
  
      /**
       * Action to go home.
       */
      public static final int GLOBAL_ACTION_HOME = 2;
  
      /**
       * Action to toggle showing the overview of recent apps. Will fail on platforms that don't
       * show recent apps.
       */
      public static final int GLOBAL_ACTION_RECENTS = 3;
  
      /**
       * Action to open the notifications.
       */
      public static final int GLOBAL_ACTION_NOTIFICATIONS = 4;
  
      /**
       * Action to open the quick settings.
       */
      public static final int GLOBAL_ACTION_QUICK_SETTINGS = 5;
  
      /**
       * Action to open the power long-press dialog.
       */
      public static final int GLOBAL_ACTION_POWER_DIALOG = 6;
  
      /**
       * Action to toggle docking the current app's window
       */
      public static final int GLOBAL_ACTION_TOGGLE_SPLIT_SCREEN = 7;
  
      /**
       * Action to lock the screen
       */
      public static final int GLOBAL_ACTION_LOCK_SCREEN = 8;
  
      /**
       * Action to take a screenshot
       */
      public static final int GLOBAL_ACTION_TAKE_SCREENSHOT = 9;
  
      private static final String LOG_TAG = "AccessibilityService";
  
      /**
       * Interface used by IAccessibilityServiceWrapper to call the service from its main thread.
       * @hide
       */
      public interface Callbacks {
          void onAccessibilityEvent(AccessibilityEvent event);
          void onInterrupt();
          void onServiceConnected();
          void init(int connectionId, IBinder windowToken);
          boolean onGesture(int gestureId);
          boolean onKeyEvent(KeyEvent event);
          /** Magnification changed callbacks for different displays */
          void onMagnificationChanged(int displayId, @NonNull Region region,
                  float scale, float centerX, float centerY);
          void onSoftKeyboardShowModeChanged(int showMode);
          void onPerformGestureResult(int sequence, boolean completedSuccessfully);
          void onFingerprintCapturingGesturesChanged(boolean active);
          void onFingerprintGesture(int gesture);
          void onAccessibilityButtonClicked();
          void onAccessibilityButtonAvailabilityChanged(boolean available);
      }
  
      /**
       * Annotations for Soft Keyboard show modes so tools can catch invalid show modes.
       * @hide
       */
      @Retention(RetentionPolicy.SOURCE)
      @IntDef(prefix = { "SHOW_MODE_" }, value = {
              SHOW_MODE_AUTO,
              SHOW_MODE_HIDDEN,
              SHOW_MODE_IGNORE_HARD_KEYBOARD
      })
      public @interface SoftKeyboardShowMode {}
  
      /**
       * Allow the system to control when the soft keyboard is shown.
       * @see SoftKeyboardController
       */
      public static final int SHOW_MODE_AUTO = 0;
  
      /**
       * Never show the soft keyboard.
       * @see SoftKeyboardController
       */
      public static final int SHOW_MODE_HIDDEN = 1;
  
      /**
       * Allow the soft keyboard to be shown, even if a hard keyboard is connected
       * @see SoftKeyboardController
       */
      public static final int SHOW_MODE_IGNORE_HARD_KEYBOARD = 2;
  
      /**
       * Mask used to cover the show modes supported in public API
       * @hide
       */
      public static final int SHOW_MODE_MASK = 0x03;
  
      /**
       * Bit used to hold the old value of the hard IME setting to restore when a service is shut
       * down.
       * @hide
       */
      public static final int SHOW_MODE_HARD_KEYBOARD_ORIGINAL_VALUE = 0x20000000;
  
      /**
       * Bit for show mode setting to indicate that the user has overridden the hard keyboard
       * behavior.
       * @hide
       */
      public static final int SHOW_MODE_HARD_KEYBOARD_OVERRIDDEN = 0x40000000;
  
      private int mConnectionId = AccessibilityInteractionClient.NO_ID;
  
      @UnsupportedAppUsage
      private AccessibilityServiceInfo mInfo;
  
      @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
      private IBinder mWindowToken;
  
      private WindowManager mWindowManager;
  
      /** List of magnification controllers, mapping from displayId -> MagnificationController. */
      private final SparseArray<MagnificationController> mMagnificationControllers =
              new SparseArray<>(0);
      private SoftKeyboardController mSoftKeyboardController;
      private AccessibilityButtonController mAccessibilityButtonController;
  
      private int mGestureStatusCallbackSequence;
  
      private SparseArray<GestureResultCallbackInfo> mGestureStatusCallbackInfos;
  
      private final Object mLock = new Object();
  
      private FingerprintGestureController mFingerprintGestureController;
  
      /**
       * Callback for {@link android.view.accessibility.AccessibilityEvent}s.
       *
       * @param event The new event. This event is owned by the caller and cannot be used after
       * this method returns. Services wishing to use the event after this method returns should
       * make a copy.
       */
      public abstract void onAccessibilityEvent(AccessibilityEvent event);
  
      /**
       * Callback for interrupting the accessibility feedback.
       */
      public abstract void onInterrupt();
  
      /**
       * Dispatches service connection to internal components first, then the
       * client code.
       */
      private void dispatchServiceConnected() {
          synchronized (mLock) {
              for (int i = 0; i < mMagnificationControllers.size(); i++) {
                  mMagnificationControllers.valueAt(i).onServiceConnectedLocked();
              }
          }
          if (mSoftKeyboardController != null) {
              mSoftKeyboardController.onServiceConnected();
          }
  
          // The client gets to handle service connection last, after we've set
          // up any state upon which their code may rely.
          onServiceConnected();
      }
  
      /**
       * This method is a part of the {@link AccessibilityService} lifecycle and is
       * called after the system has successfully bound to the service. If is
       * convenient to use this method for setting the {@link AccessibilityServiceInfo}.
       *
       * @see AccessibilityServiceInfo
       * @see #setServiceInfo(AccessibilityServiceInfo)
       */
      protected void onServiceConnected() {
  
      }
  
      /**
       * Called by the system when the user performs a specific gesture on the
       * touch screen.
       *
       * <strong>Note:</strong> To receive gestures an accessibility service must
       * request that the device is in touch exploration mode by setting the
       * {@link android.accessibilityservice.AccessibilityServiceInfo#FLAG_REQUEST_TOUCH_EXPLORATION_MODE}
       * flag.
       *
       * @param gestureId The unique id of the performed gesture.
       *
       * @return Whether the gesture was handled.
       *
       * @see #GESTURE_SWIPE_UP
       * @see #GESTURE_SWIPE_UP_AND_LEFT
       * @see #GESTURE_SWIPE_UP_AND_DOWN
       * @see #GESTURE_SWIPE_UP_AND_RIGHT
       * @see #GESTURE_SWIPE_DOWN
       * @see #GESTURE_SWIPE_DOWN_AND_LEFT
       * @see #GESTURE_SWIPE_DOWN_AND_UP
       * @see #GESTURE_SWIPE_DOWN_AND_RIGHT
       * @see #GESTURE_SWIPE_LEFT
       * @see #GESTURE_SWIPE_LEFT_AND_UP
       * @see #GESTURE_SWIPE_LEFT_AND_RIGHT
       * @see #GESTURE_SWIPE_LEFT_AND_DOWN
       * @see #GESTURE_SWIPE_RIGHT
       * @see #GESTURE_SWIPE_RIGHT_AND_UP
       * @see #GESTURE_SWIPE_RIGHT_AND_LEFT
       * @see #GESTURE_SWIPE_RIGHT_AND_DOWN
       */
      protected boolean onGesture(int gestureId) {
          return false;
      }
  
      /**
       * Callback that allows an accessibility service to observe the key events
       * before they are passed to the rest of the system. This means that the events
       * are first delivered here before they are passed to the device policy, the
       * input method, or applications.
       * <p>
       * <strong>Note:</strong> It is important that key events are handled in such
       * a way that the event stream that would be passed to the rest of the system
       * is well-formed. For example, handling the down event but not the up event
       * and vice versa would generate an inconsistent event stream.
       * </p>
       * <p>
       * <strong>Note:</strong> The key events delivered in this method are copies
       * and modifying them will have no effect on the events that will be passed
       * to the system. This method is intended to perform purely filtering
       * functionality.
       * <p>
       *
       * @param event The event to be processed. This event is owned by the caller and cannot be used
       * after this method returns. Services wishing to use the event after this method returns should
       * make a copy.
       * @return If true then the event will be consumed and not delivered to
       *         applications, otherwise it will be delivered as usual.
       */
      protected boolean onKeyEvent(KeyEvent event) {
          return false;
      }
  
      /**
       * Gets the windows on the screen. This method returns only the windows
       * that a sighted user can interact with, as opposed to all windows.
       * For example, if there is a modal dialog shown and the user cannot touch
       * anything behind it, then only the modal window will be reported
       * (assuming it is the top one). For convenience the returned windows
       * are ordered in a descending layer order, which is the windows that
       * are on top are reported first. Since the user can always
       * interact with the window that has input focus by typing, the focused
       * window is always returned (even if covered by a modal window).
       * <p>
       * <strong>Note:</strong> In order to access the windows your service has
       * to declare the capability to retrieve window content by setting the
       * {@link android.R.styleable#AccessibilityService_canRetrieveWindowContent}
       * property in its meta-data. For details refer to {@link #SERVICE_META_DATA}.
       * Also the service has to opt-in to retrieve the interactive windows by
       * setting the {@link AccessibilityServiceInfo#FLAG_RETRIEVE_INTERACTIVE_WINDOWS}
       * flag.
       * </p>
       *
       * @return The windows if there are windows and the service is can retrieve
       *         them, otherwise an empty list.
       */
      public List<AccessibilityWindowInfo> getWindows() {
          return AccessibilityInteractionClient.getInstance().getWindows(mConnectionId);
      }
  
      /**
       * Gets the root node in the currently active window if this service
       * can retrieve window content. The active window is the one that the user
       * is currently touching or the window with input focus, if the user is not
       * touching any window.
       * <p>
       * The currently active window is defined as the window that most recently fired one
       * of the following events:
       * {@link AccessibilityEvent#TYPE_WINDOW_STATE_CHANGED},
       * {@link AccessibilityEvent#TYPE_VIEW_HOVER_ENTER},
       * {@link AccessibilityEvent#TYPE_VIEW_HOVER_EXIT}.
       * In other words, the last window shown that also has input focus.
       * </p>
       * <p>
       * <strong>Note:</strong> In order to access the root node your service has
       * to declare the capability to retrieve window content by setting the
       * {@link android.R.styleable#AccessibilityService_canRetrieveWindowContent}
       * property in its meta-data. For details refer to {@link #SERVICE_META_DATA}.
       * </p>
       *
       * @return The root node if this service can retrieve window content.
       */
      public AccessibilityNodeInfo getRootInActiveWindow() {
          return AccessibilityInteractionClient.getInstance().getRootInActiveWindow(mConnectionId);
      }
  
      /**
       * Disables the service. After calling this method, the service will be disabled and settings
       * will show that it is turned off.
       */
      public final void disableSelf() {
          final IAccessibilityServiceConnection connection =
                  AccessibilityInteractionClient.getInstance().getConnection(mConnectionId);
          if (connection != null) {
              try {
                  connection.disableSelf();
              } catch (RemoteException re) {
                  throw new RuntimeException(re);
              }
          }
      }
  
      /**
       * Returns the magnification controller, which may be used to query and
       * modify the state of display magnification.
       * <p>
       * <strong>Note:</strong> In order to control magnification, your service
       * must declare the capability by setting the
       * {@link android.R.styleable#AccessibilityService_canControlMagnification}
       * property in its meta-data. For more information, see
       * {@link #SERVICE_META_DATA}.
       *
       * @return the magnification controller
       */
      @NonNull
      public final MagnificationController getMagnificationController() {
          return getMagnificationController(Display.DEFAULT_DISPLAY);
      }
  
      /**
       * Returns the magnification controller of specified logical display, which may be used to
       * query and modify the state of display magnification.
       * <p>
       * <strong>Note:</strong> In order to control magnification, your service
       * must declare the capability by setting the
       * {@link android.R.styleable#AccessibilityService_canControlMagnification}
       * property in its meta-data. For more information, see
       * {@link #SERVICE_META_DATA}.
       *
       * @param displayId The logic display id, use {@link Display#DEFAULT_DISPLAY} for
       *                  default display.
       * @return the magnification controller
       *
       * @hide
       */
      @NonNull
      public final MagnificationController getMagnificationController(int displayId) {
          synchronized (mLock) {
              MagnificationController controller = mMagnificationControllers.get(displayId);
              if (controller == null) {
                  controller = new MagnificationController(this, mLock, displayId);
                  mMagnificationControllers.put(displayId, controller);
              }
              return controller;
          }
      }
  
      /**
       * Get the controller for fingerprint gestures. This feature requires {@link
       * AccessibilityServiceInfo#CAPABILITY_CAN_REQUEST_FINGERPRINT_GESTURES}.
       *
       *<strong>Note: </strong> The service must be connected before this method is called.
       *
       * @return The controller for fingerprint gestures, or {@code null} if gestures are unavailable.
       */
      @RequiresPermission(android.Manifest.permission.USE_FINGERPRINT)
      public final @NonNull FingerprintGestureController getFingerprintGestureController() {
          if (mFingerprintGestureController == null) {
              mFingerprintGestureController = new FingerprintGestureController(
                      AccessibilityInteractionClient.getInstance().getConnection(mConnectionId));
          }
          return mFingerprintGestureController;
      }
  
      /**
       * Dispatch a gesture to the touch screen. Any gestures currently in progress, whether from
       * the user, this service, or another service, will be cancelled.
       * <p>
       * The gesture will be dispatched as if it were performed directly on the screen by a user, so
       * the events may be affected by features such as magnification and explore by touch.
       * </p>
       * <p>
       * <strong>Note:</strong> In order to dispatch gestures, your service
       * must declare the capability by setting the
       * {@link android.R.styleable#AccessibilityService_canPerformGestures}
       * property in its meta-data. For more information, see
       * {@link #SERVICE_META_DATA}.
       * </p>
       *
       * @param gesture The gesture to dispatch
       * @param callback The object to call back when the status of the gesture is known. If
       * {@code null}, no status is reported.
       * @param handler The handler on which to call back the {@code callback} object. If
       * {@code null}, the object is called back on the service's main thread.
       *
       * @return {@code true} if the gesture is dispatched, {@code false} if not.
       */
      public final boolean dispatchGesture(@NonNull GestureDescription gesture,
              @Nullable GestureResultCallback callback,
              @Nullable Handler handler) {
          final IAccessibilityServiceConnection connection =
                  AccessibilityInteractionClient.getInstance().getConnection(
                          mConnectionId);
          if (connection == null) {
              return false;
          }
          List<GestureDescription.GestureStep> steps =
                  MotionEventGenerator.getGestureStepsFromGestureDescription(gesture, 100);
          try {
              synchronized (mLock) {
                  mGestureStatusCallbackSequence++;
                  if (callback != null) {
                      if (mGestureStatusCallbackInfos == null) {
                          mGestureStatusCallbackInfos = new SparseArray<>();
                      }
                      GestureResultCallbackInfo callbackInfo = new GestureResultCallbackInfo(gesture,
                              callback, handler);
                      mGestureStatusCallbackInfos.put(mGestureStatusCallbackSequence, callbackInfo);
                  }
                  connection.sendGesture(mGestureStatusCallbackSequence,
                          new ParceledListSlice<>(steps));
              }
          } catch (RemoteException re) {
              throw new RuntimeException(re);
          }
          return true;
      }
  
      void onPerformGestureResult(int sequence, final boolean completedSuccessfully) {
          if (mGestureStatusCallbackInfos == null) {
              return;
          }
          GestureResultCallbackInfo callbackInfo;
          synchronized (mLock) {
              callbackInfo = mGestureStatusCallbackInfos.get(sequence);
          }
          final GestureResultCallbackInfo finalCallbackInfo = callbackInfo;
          if ((callbackInfo != null) && (callbackInfo.gestureDescription != null)
                  && (callbackInfo.callback != null)) {
              if (callbackInfo.handler != null) {
                  callbackInfo.handler.post(new Runnable() {
                      @Override
                      public void run() {
                          if (completedSuccessfully) {
                              finalCallbackInfo.callback
                                      .onCompleted(finalCallbackInfo.gestureDescription);
                          } else {
                              finalCallbackInfo.callback
                                      .onCancelled(finalCallbackInfo.gestureDescription);
                          }
                      }
                  });
                  return;
              }
              if (completedSuccessfully) {
                  callbackInfo.callback.onCompleted(callbackInfo.gestureDescription);
              } else {
                  callbackInfo.callback.onCancelled(callbackInfo.gestureDescription);
              }
          }
      }
  
      private void onMagnificationChanged(int displayId, @NonNull Region region, float scale,
              float centerX, float centerY) {
          MagnificationController controller;
          synchronized (mLock) {
              controller = mMagnificationControllers.get(displayId);
          }
          if (controller != null) {
              controller.dispatchMagnificationChanged(region, scale, centerX, centerY);
          }
      }
  
      /**
       * Callback for fingerprint gesture handling
       * @param active If gesture detection is active
       */
      private void onFingerprintCapturingGesturesChanged(boolean active) {
          getFingerprintGestureController().onGestureDetectionActiveChanged(active);
      }
  
      /**
       * Callback for fingerprint gesture handling
       * @param gesture The identifier for the gesture performed
       */
      private void onFingerprintGesture(int gesture) {
          getFingerprintGestureController().onGesture(gesture);
      }
  
      /**
       * Used to control and query the state of display magnification.
       */
      public static final class MagnificationController {
          private final AccessibilityService mService;
          private final int mDisplayId;
  
          /**
           * Map of listeners to their handlers. Lazily created when adding the
           * first magnification listener.
           */
          private ArrayMap<OnMagnificationChangedListener, Handler> mListeners;
          private final Object mLock;
  
          MagnificationController(@NonNull AccessibilityService service, @NonNull Object lock,
                  int displayId) {
              mService = service;
              mLock = lock;
              mDisplayId = displayId;
          }
  
          /**
           * Called when the service is connected.
           */
          void onServiceConnectedLocked() {
              if (mListeners != null && !mListeners.isEmpty()) {
                  setMagnificationCallbackEnabled(true);
              }
          }
  
          /**
           * Adds the specified change listener to the list of magnification
           * change listeners. The callback will occur on the service's main
           * thread.
           *
           * @param listener the listener to add, must be non-{@code null}
           */
          public void addListener(@NonNull OnMagnificationChangedListener listener) {
              addListener(listener, null);
          }
  
          /**
           * Adds the specified change listener to the list of magnification
           * change listeners. The callback will occur on the specified
           * {@link Handler}'s thread, or on the service's main thread if the
           * handler is {@code null}.
           *
           * @param listener the listener to add, must be non-null
           * @param handler the handler on which the callback should execute, or
           *        {@code null} to execute on the service's main thread
           */
          public void addListener(@NonNull OnMagnificationChangedListener listener,
                  @Nullable Handler handler) {
              synchronized (mLock) {
                  if (mListeners == null) {
                      mListeners = new ArrayMap<>();
                  }
  
                  final boolean shouldEnableCallback = mListeners.isEmpty();
                  mListeners.put(listener, handler);
  
                  if (shouldEnableCallback) {
                      // This may fail if the service is not connected yet, but if we
                      // still have listeners when it connects then we can try again.
                      setMagnificationCallbackEnabled(true);
                  }
              }
          }
  
          /**
           * Removes the specified change listener from the list of magnification change listeners.
           *
           * @param listener the listener to remove, must be non-null
           * @return {@code true} if the listener was removed, {@code false} otherwise
           */
          public boolean removeListener(@NonNull OnMagnificationChangedListener listener) {
              if (mListeners == null) {
                  return false;
              }
  
              synchronized (mLock) {
                  final int keyIndex = mListeners.indexOfKey(listener);
                  final boolean hasKey = keyIndex >= 0;
                  if (hasKey) {
                      mListeners.removeAt(keyIndex);
                  }
  
                  if (hasKey && mListeners.isEmpty()) {
                      // We just removed the last listener, so we don't need
                      // callbacks from the service anymore.
                      setMagnificationCallbackEnabled(false);
                  }
  
                  return hasKey;
              }
          }
  
          private void setMagnificationCallbackEnabled(boolean enabled) {
              final IAccessibilityServiceConnection connection =
                      AccessibilityInteractionClient.getInstance().getConnection(
                              mService.mConnectionId);
              if (connection != null) {
                  try {
                      connection.setMagnificationCallbackEnabled(mDisplayId, enabled);
                  } catch (RemoteException re) {
                      throw new RuntimeException(re);
                  }
              }
          }
  
          /**
           * Dispatches magnification changes to any registered listeners. This
           * should be called on the service's main thread.
           */
          void dispatchMagnificationChanged(final @NonNull Region region, final float scale,
                  final float centerX, final float centerY) {
              final ArrayMap<OnMagnificationChangedListener, Handler> entries;
              synchronized (mLock) {
                  if (mListeners == null || mListeners.isEmpty()) {
                      Slog.d(LOG_TAG, "Received magnification changed "
                              + "callback with no listeners registered!");
                      setMagnificationCallbackEnabled(false);
                      return;
                  }
  
                  // Listeners may remove themselves. Perform a shallow copy to avoid concurrent
                  // modification.
                  entries = new ArrayMap<>(mListeners);
              }
  
              for (int i = 0, count = entries.size(); i < count; i++) {
                  final OnMagnificationChangedListener listener = entries.keyAt(i);
                  final Handler handler = entries.valueAt(i);
                  if (handler != null) {
                      handler.post(new Runnable() {
                          @Override
                          public void run() {
                              listener.onMagnificationChanged(MagnificationController.this,
                                      region, scale, centerX, centerY);
                          }
                      });
                  } else {
                      // We're already on the main thread, just run the listener.
                      listener.onMagnificationChanged(this, region, scale, centerX, centerY);
                  }
              }
          }
  
          /**
           * Returns the current magnification scale.
           * <p>
           * <strong>Note:</strong> If the service is not yet connected (e.g.
           * {@link AccessibilityService#onServiceConnected()} has not yet been
           * called) or the service has been disconnected, this method will
           * return a default value of {@code 1.0f}.
           *
           * @return the current magnification scale
           */
          public float getScale() {
              final IAccessibilityServiceConnection connection =
                      AccessibilityInteractionClient.getInstance().getConnection(
                              mService.mConnectionId);
              if (connection != null) {
                  try {
                      return connection.getMagnificationScale(mDisplayId);
                  } catch (RemoteException re) {
                      Log.w(LOG_TAG, "Failed to obtain scale", re);
                      re.rethrowFromSystemServer();
                  }
              }
              return 1.0f;
          }
  
          /**
           * Returns the unscaled screen-relative X coordinate of the focal
           * center of the magnified region. This is the point around which
           * zooming occurs and is guaranteed to lie within the magnified
           * region.
           * <p>
           * <strong>Note:</strong> If the service is not yet connected (e.g.
           * {@link AccessibilityService#onServiceConnected()} has not yet been
           * called) or the service has been disconnected, this method will
           * return a default value of {@code 0.0f}.
           *
           * @return the unscaled screen-relative X coordinate of the center of
           *         the magnified region
           */
          public float getCenterX() {
              final IAccessibilityServiceConnection connection =
                      AccessibilityInteractionClient.getInstance().getConnection(
                              mService.mConnectionId);
              if (connection != null) {
                  try {
                      return connection.getMagnificationCenterX(mDisplayId);
                  } catch (RemoteException re) {
                      Log.w(LOG_TAG, "Failed to obtain center X", re);
                      re.rethrowFromSystemServer();
                  }
              }
              return 0.0f;
          }
  
          /**
           * Returns the unscaled screen-relative Y coordinate of the focal
           * center of the magnified region. This is the point around which
           * zooming occurs and is guaranteed to lie within the magnified
           * region.
           * <p>
           * <strong>Note:</strong> If the service is not yet connected (e.g.
           * {@link AccessibilityService#onServiceConnected()} has not yet been
           * called) or the service has been disconnected, this method will
           * return a default value of {@code 0.0f}.
           *
           * @return the unscaled screen-relative Y coordinate of the center of
           *         the magnified region
           */
          public float getCenterY() {
              final IAccessibilityServiceConnection connection =
                      AccessibilityInteractionClient.getInstance().getConnection(
                              mService.mConnectionId);
              if (connection != null) {
                  try {
                      return connection.getMagnificationCenterY(mDisplayId);
                  } catch (RemoteException re) {
                      Log.w(LOG_TAG, "Failed to obtain center Y", re);
                      re.rethrowFromSystemServer();
                  }
              }
              return 0.0f;
          }
  
          /**
           * Returns the region of the screen currently active for magnification. Changes to
           * magnification scale and center only affect this portion of the screen. The rest of the
           * screen, for example input methods, cannot be magnified. This region is relative to the
           * unscaled screen and is independent of the scale and center point.
           * <p>
           * The returned region will be empty if magnification is not active. Magnification is active
           * if magnification gestures are enabled or if a service is running that can control
           * magnification.
           * <p>
           * <strong>Note:</strong> If the service is not yet connected (e.g.
           * {@link AccessibilityService#onServiceConnected()} has not yet been
           * called) or the service has been disconnected, this method will
           * return an empty region.
           *
           * @return the region of the screen currently active for magnification, or an empty region
           * if magnification is not active.
           */
          @NonNull
          public Region getMagnificationRegion() {
              final IAccessibilityServiceConnection connection =
                      AccessibilityInteractionClient.getInstance().getConnection(
                              mService.mConnectionId);
              if (connection != null) {
                  try {
                      return connection.getMagnificationRegion(mDisplayId);
                  } catch (RemoteException re) {
                      Log.w(LOG_TAG, "Failed to obtain magnified region", re);
                      re.rethrowFromSystemServer();
                  }
              }
              return Region.obtain();
          }
  
          /**
           * Resets magnification scale and center to their default (e.g. no
           * magnification) values.
           * <p>
           * <strong>Note:</strong> If the service is not yet connected (e.g.
           * {@link AccessibilityService#onServiceConnected()} has not yet been
           * called) or the service has been disconnected, this method will have
           * no effect and return {@code false}.
           *
           * @param animate {@code true} to animate from the current scale and
           *                center or {@code false} to reset the scale and center
           *                immediately
           * @return {@code true} on success, {@code false} on failure
           */
          public boolean reset(boolean animate) {
              final IAccessibilityServiceConnection connection =
                      AccessibilityInteractionClient.getInstance().getConnection(
                              mService.mConnectionId);
              if (connection != null) {
                  try {
                      return connection.resetMagnification(mDisplayId, animate);
                  } catch (RemoteException re) {
                      Log.w(LOG_TAG, "Failed to reset", re);
                      re.rethrowFromSystemServer();
                  }
              }
              return false;
          }
  
          /**
           * Sets the magnification scale.
           * <p>
           * <strong>Note:</strong> If the service is not yet connected (e.g.
           * {@link AccessibilityService#onServiceConnected()} has not yet been
           * called) or the service has been disconnected, this method will have
           * no effect and return {@code false}.
           *
           * @param scale the magnification scale to set, must be >= 1 and <= 8
           * @param animate {@code true} to animate from the current scale or
           *                {@code false} to set the scale immediately
           * @return {@code true} on success, {@code false} on failure
           */
          public boolean setScale(float scale, boolean animate) {
              final IAccessibilityServiceConnection connection =
                      AccessibilityInteractionClient.getInstance().getConnection(
                              mService.mConnectionId);
              if (connection != null) {
                  try {
                      return connection.setMagnificationScaleAndCenter(mDisplayId,
                              scale, Float.NaN, Float.NaN, animate);
                  } catch (RemoteException re) {
                      Log.w(LOG_TAG, "Failed to set scale", re);
                      re.rethrowFromSystemServer();
                  }
              }
              return false;
          }
  
          /**
           * Sets the center of the magnified viewport.
           * <p>
           * <strong>Note:</strong> If the service is not yet connected (e.g.
           * {@link AccessibilityService#onServiceConnected()} has not yet been
           * called) or the service has been disconnected, this method will have
           * no effect and return {@code false}.
           *
           * @param centerX the unscaled screen-relative X coordinate on which to
           *                center the viewport
           * @param centerY the unscaled screen-relative Y coordinate on which to
           *                center the viewport
           * @param animate {@code true} to animate from the current viewport
           *                center or {@code false} to set the center immediately
           * @return {@code true} on success, {@code false} on failure
           */
          public boolean setCenter(float centerX, float centerY, boolean animate) {
              final IAccessibilityServiceConnection connection =
                      AccessibilityInteractionClient.getInstance().getConnection(
                              mService.mConnectionId);
              if (connection != null) {
                  try {
                      return connection.setMagnificationScaleAndCenter(mDisplayId,
                              Float.NaN, centerX, centerY, animate);
                  } catch (RemoteException re) {
                      Log.w(LOG_TAG, "Failed to set center", re);
                      re.rethrowFromSystemServer();
                  }
              }
              return false;
          }
  
          /**
           * Listener for changes in the state of magnification.
           */
          public interface OnMagnificationChangedListener {
              /**
               * Called when the magnified region, scale, or center changes.
               *
               * @param controller the magnification controller
               * @param region the magnification region
               * @param scale the new scale
               * @param centerX the new X coordinate, in unscaled coordinates, around which
               * magnification is focused
               * @param centerY the new Y coordinate, in unscaled coordinates, around which
               * magnification is focused
               */
              void onMagnificationChanged(@NonNull MagnificationController controller,
                      @NonNull Region region, float scale, float centerX, float centerY);
          }
      }
  
      /**
       * Returns the soft keyboard controller, which may be used to query and modify the soft keyboard
       * show mode.
       *
       * @return the soft keyboard controller
       */
      @NonNull
      public final SoftKeyboardController getSoftKeyboardController() {
          synchronized (mLock) {
              if (mSoftKeyboardController == null) {
                  mSoftKeyboardController = new SoftKeyboardController(this, mLock);
              }
              return mSoftKeyboardController;
          }
      }
  
      private void onSoftKeyboardShowModeChanged(int showMode) {
          if (mSoftKeyboardController != null) {
              mSoftKeyboardController.dispatchSoftKeyboardShowModeChanged(showMode);
          }
      }
  
      /**
       * Used to control, query, and listen for changes to the soft keyboard show mode.
       * <p>
       * Accessibility services may request to override the decisions normally made about whether or
       * not the soft keyboard is shown.
       * <p>
       * If multiple services make conflicting requests, the last request is honored. A service may
       * register a listener to find out if the mode has changed under it.
       * <p>
       * If the user takes action to override the behavior behavior requested by an accessibility
       * service, the user's request takes precendence, the show mode will be reset to
       * {@link AccessibilityService#SHOW_MODE_AUTO}, and services will no longer be able to control
       * that aspect of the soft keyboard's behavior.
       * <p>
       * Note: Because soft keyboards are independent apps, the framework does not have total control
       * over their behavior. They may choose to show themselves, or not, without regard to requests
       * made here. So the framework will make a best effort to deliver the behavior requested, but
       * cannot guarantee success.
       *
       * @see AccessibilityService#SHOW_MODE_AUTO
       * @see AccessibilityService#SHOW_MODE_HIDDEN
       * @see AccessibilityService#SHOW_MODE_IGNORE_HARD_KEYBOARD
       */
      public static final class SoftKeyboardController {
          private final AccessibilityService mService;
  
          /**
           * Map of listeners to their handlers. Lazily created when adding the first
           * soft keyboard change listener.
           */
          private ArrayMap<OnShowModeChangedListener, Handler> mListeners;
          private final Object mLock;
  
          SoftKeyboardController(@NonNull AccessibilityService service, @NonNull Object lock) {
              mService = service;
              mLock = lock;
          }
  
          /**
           * Called when the service is connected.
           */
          void onServiceConnected() {
              synchronized(mLock) {
                  if (mListeners != null && !mListeners.isEmpty()) {
                      setSoftKeyboardCallbackEnabled(true);
                  }
              }
          }
  
          /**
           * Adds the specified change listener to the list of show mode change listeners. The
           * callback will occur on the service's main thread. Listener is not called on registration.
           */
          public void addOnShowModeChangedListener(@NonNull OnShowModeChangedListener listener) {
              addOnShowModeChangedListener(listener, null);
          }
  
          /**
           * Adds the specified change listener to the list of soft keyboard show mode change
           * listeners. The callback will occur on the specified {@link Handler}'s thread, or on the
           * services's main thread if the handler is {@code null}.
           *
           * @param listener the listener to add, must be non-null
           * @param handler the handler on which to callback should execute, or {@code null} to
           *        execute on the service's main thread
           */
          public void addOnShowModeChangedListener(@NonNull OnShowModeChangedListener listener,
                  @Nullable Handler handler) {
              synchronized (mLock) {
                  if (mListeners == null) {
                      mListeners = new ArrayMap<>();
                  }
  
                  final boolean shouldEnableCallback = mListeners.isEmpty();
                  mListeners.put(listener, handler);
  
                  if (shouldEnableCallback) {
                      // This may fail if the service is not connected yet, but if we still have
                      // listeners when it connects, we can try again.
                      setSoftKeyboardCallbackEnabled(true);
                  }
              }
          }
  
          /**
           * Removes the specified change listener from the list of keyboard show mode change
           * listeners.
           *
           * @param listener the listener to remove, must be non-null
           * @return {@code true} if the listener was removed, {@code false} otherwise
           */
          public boolean removeOnShowModeChangedListener(
                  @NonNull OnShowModeChangedListener listener) {
              if (mListeners == null) {
                  return false;
              }
  
              synchronized (mLock) {
                  final int keyIndex = mListeners.indexOfKey(listener);
                  final boolean hasKey = keyIndex >= 0;
                  if (hasKey) {
                      mListeners.removeAt(keyIndex);
                  }
  
                  if (hasKey && mListeners.isEmpty()) {
                      // We just removed the last listener, so we don't need callbacks from the
                      // service anymore.
                      setSoftKeyboardCallbackEnabled(false);
                  }
  
                  return hasKey;
              }
          }
  
          private void setSoftKeyboardCallbackEnabled(boolean enabled) {
              final IAccessibilityServiceConnection connection =
                      AccessibilityInteractionClient.getInstance().getConnection(
                              mService.mConnectionId);
              if (connection != null) {
                  try {
                      connection.setSoftKeyboardCallbackEnabled(enabled);
                  } catch (RemoteException re) {
                      throw new RuntimeException(re);
                  }
              }
          }
  
          /**
           * Dispatches the soft keyboard show mode change to any registered listeners. This should
           * be called on the service's main thread.
           */
          void dispatchSoftKeyboardShowModeChanged(final int showMode) {
              final ArrayMap<OnShowModeChangedListener, Handler> entries;
              synchronized (mLock) {
                  if (mListeners == null || mListeners.isEmpty()) {
                      Slog.w(LOG_TAG, "Received soft keyboard show mode changed callback"
                              + " with no listeners registered!");
                      setSoftKeyboardCallbackEnabled(false);
                      return;
                  }
  
                  // Listeners may remove themselves. Perform a shallow copy to avoid concurrent
                  // modification.
                  entries = new ArrayMap<>(mListeners);
              }
  
              for (int i = 0, count = entries.size(); i < count; i++) {
                  final OnShowModeChangedListener listener = entries.keyAt(i);
                  final Handler handler = entries.valueAt(i);
                  if (handler != null) {
                      handler.post(new Runnable() {
                          @Override
                          public void run() {
                              listener.onShowModeChanged(SoftKeyboardController.this, showMode);
                          }
                      });
                  } else {
                      // We're already on the main thread, just run the listener.
                      listener.onShowModeChanged(this, showMode);
                  }
              }
          }
  
          /**
           * Returns the show mode of the soft keyboard.
           *
           * @return the current soft keyboard show mode
           *
           * @see AccessibilityService#SHOW_MODE_AUTO
           * @see AccessibilityService#SHOW_MODE_HIDDEN
           * @see AccessibilityService#SHOW_MODE_IGNORE_HARD_KEYBOARD
           */
          @SoftKeyboardShowMode
          public int getShowMode() {
              final IAccessibilityServiceConnection connection =
                      AccessibilityInteractionClient.getInstance().getConnection(
                              mService.mConnectionId);
              if (connection != null) {
                  try {
                      return connection.getSoftKeyboardShowMode();
                  } catch (RemoteException re) {
                      Log.w(LOG_TAG, "Failed to set soft keyboard behavior", re);
                      re.rethrowFromSystemServer();
                  }
              }
              return SHOW_MODE_AUTO;
          }
  
          /**
           * Sets the soft keyboard show mode.
           * <p>
           * <strong>Note:</strong> If the service is not yet connected (e.g.
           * {@link AccessibilityService#onServiceConnected()} has not yet been called) or the
           * service has been disconnected, this method will have no effect and return {@code false}.
           *
           * @param showMode the new show mode for the soft keyboard
           * @return {@code true} on success
           *
           * @see AccessibilityService#SHOW_MODE_AUTO
           * @see AccessibilityService#SHOW_MODE_HIDDEN
           * @see AccessibilityService#SHOW_MODE_IGNORE_HARD_KEYBOARD
           */
          public boolean setShowMode(@SoftKeyboardShowMode int showMode) {
             final IAccessibilityServiceConnection connection =
                     AccessibilityInteractionClient.getInstance().getConnection(
                             mService.mConnectionId);
             if (connection != null) {
                 try {
                     return connection.setSoftKeyboardShowMode(showMode);
                 } catch (RemoteException re) {
                     Log.w(LOG_TAG, "Failed to set soft keyboard behavior", re);
                     re.rethrowFromSystemServer();
                 }
             }
             return false;
          }
  
          /**
           * Listener for changes in the soft keyboard show mode.
           */
          public interface OnShowModeChangedListener {
             /**
              * Called when the soft keyboard behavior changes. The default show mode is
              * {@code SHOW_MODE_AUTO}, where the soft keyboard is shown when a text input field is
              * focused. An AccessibilityService can also request the show mode
              * {@code SHOW_MODE_HIDDEN}, where the soft keyboard is never shown.
              *
              * @param controller the soft keyboard controller
              * @param showMode the current soft keyboard show mode
              */
              void onShowModeChanged(@NonNull SoftKeyboardController controller,
                      @SoftKeyboardShowMode int showMode);
          }
      }
  
      /**
       * Returns the controller for the accessibility button within the system's navigation area.
       * This instance may be used to query the accessibility button's state and register listeners
       * for interactions with and state changes for the accessibility button when
       * {@link AccessibilityServiceInfo#FLAG_REQUEST_ACCESSIBILITY_BUTTON} is set.
       * <p>
       * <strong>Note:</strong> Not all devices are capable of displaying the accessibility button
       * within a navigation area, and as such, use of this class should be considered only as an
       * optional feature or shortcut on supported device implementations.
       * </p>
       *
       * @return the accessibility button controller for this {@link AccessibilityService}
       */
      @NonNull
      public final AccessibilityButtonController getAccessibilityButtonController() {
          synchronized (mLock) {
              if (mAccessibilityButtonController == null) {
                  mAccessibilityButtonController = new AccessibilityButtonController(
                          AccessibilityInteractionClient.getInstance().getConnection(mConnectionId));
              }
              return mAccessibilityButtonController;
          }
      }
  
      private void onAccessibilityButtonClicked() {
          getAccessibilityButtonController().dispatchAccessibilityButtonClicked();
      }
  
      private void onAccessibilityButtonAvailabilityChanged(boolean available) {
          getAccessibilityButtonController().dispatchAccessibilityButtonAvailabilityChanged(
                  available);
      }
  
      /**
       * Performs a global action. Such an action can be performed
       * at any moment regardless of the current application or user
       * location in that application. For example going back, going
       * home, opening recents, etc.
       *
       * @param action The action to perform.
       * @return Whether the action was successfully performed.
       *
       * @see #GLOBAL_ACTION_BACK
       * @see #GLOBAL_ACTION_HOME
       * @see #GLOBAL_ACTION_NOTIFICATIONS
       * @see #GLOBAL_ACTION_RECENTS
       */
      public final boolean performGlobalAction(int action) {
          IAccessibilityServiceConnection connection =
              AccessibilityInteractionClient.getInstance().getConnection(mConnectionId);
          if (connection != null) {
              try {
                  return connection.performGlobalAction(action);
              } catch (RemoteException re) {
                  Log.w(LOG_TAG, "Error while calling performGlobalAction", re);
                  re.rethrowFromSystemServer();
              }
          }
          return false;
      }
  
      /**
       * Find the view that has the specified focus type. The search is performed
       * across all windows.
       * <p>
       * <strong>Note:</strong> In order to access the windows your service has
       * to declare the capability to retrieve window content by setting the
       * {@link android.R.styleable#AccessibilityService_canRetrieveWindowContent}
       * property in its meta-data. For details refer to {@link #SERVICE_META_DATA}.
       * Also the service has to opt-in to retrieve the interactive windows by
       * setting the {@link AccessibilityServiceInfo#FLAG_RETRIEVE_INTERACTIVE_WINDOWS}
       * flag. Otherwise, the search will be performed only in the active window.
       * </p>
       *
       * @param focus The focus to find. One of {@link AccessibilityNodeInfo#FOCUS_INPUT} or
       *         {@link AccessibilityNodeInfo#FOCUS_ACCESSIBILITY}.
       * @return The node info of the focused view or null.
       *
       * @see AccessibilityNodeInfo#FOCUS_INPUT
       * @see AccessibilityNodeInfo#FOCUS_ACCESSIBILITY
       */
      public AccessibilityNodeInfo findFocus(int focus) {
          return AccessibilityInteractionClient.getInstance().findFocus(mConnectionId,
                  AccessibilityWindowInfo.ANY_WINDOW_ID, AccessibilityNodeInfo.ROOT_NODE_ID, focus);
      }
  
      /**
       * Gets the an {@link AccessibilityServiceInfo} describing this
       * {@link AccessibilityService}. This method is useful if one wants
       * to change some of the dynamically configurable properties at
       * runtime.
       *
       * @return The accessibility service info.
       *
       * @see AccessibilityServiceInfo
       */
      public final AccessibilityServiceInfo getServiceInfo() {
          IAccessibilityServiceConnection connection =
              AccessibilityInteractionClient.getInstance().getConnection(mConnectionId);
          if (connection != null) {
              try {
                  return connection.getServiceInfo();
              } catch (RemoteException re) {
                  Log.w(LOG_TAG, "Error while getting AccessibilityServiceInfo", re);
                  re.rethrowFromSystemServer();
              }
          }
          return null;
      }
  
      /**
       * Sets the {@link AccessibilityServiceInfo} that describes this service.
       * <p>
       * Note: You can call this method any time but the info will be picked up after
       *       the system has bound to this service and when this method is called thereafter.
       *
       * @param info The info.
       */
      public final void setServiceInfo(AccessibilityServiceInfo info) {
          mInfo = info;
          sendServiceInfo();
      }
  
      /**
       * Sets the {@link AccessibilityServiceInfo} for this service if the latter is
       * properly set and there is an {@link IAccessibilityServiceConnection} to the
       * AccessibilityManagerService.
       */
      private void sendServiceInfo() {
          IAccessibilityServiceConnection connection =
              AccessibilityInteractionClient.getInstance().getConnection(mConnectionId);
          if (mInfo != null && connection != null) {
              try {
                  connection.setServiceInfo(mInfo);
                  mInfo = null;
                  AccessibilityInteractionClient.getInstance().clearCache();
              } catch (RemoteException re) {
                  Log.w(LOG_TAG, "Error while setting AccessibilityServiceInfo", re);
                  re.rethrowFromSystemServer();
              }
          }
      }
  
      @Override
      public Object getSystemService(@ServiceName @NonNull String name) {
          if (getBaseContext() == null) {
              throw new IllegalStateException(
                      "System services not available to Activities before onCreate()");
          }
  
          // Guarantee that we always return the same window manager instance.
          if (WINDOW_SERVICE.equals(name)) {
              if (mWindowManager == null) {
                  mWindowManager = (WindowManager) getBaseContext().getSystemService(name);
              }
              return mWindowManager;
          }
          return super.getSystemService(name);
      }
  
      /**
       * Implement to return the implementation of the internal accessibility
       * service interface.
       */
      @Override
      public final IBinder onBind(Intent intent) {
          return new IAccessibilityServiceClientWrapper(this, getMainLooper(), new Callbacks() {
              @Override
              public void onServiceConnected() {
                  AccessibilityService.this.dispatchServiceConnected();
              }
  
              @Override
              public void onInterrupt() {
                  AccessibilityService.this.onInterrupt();
              }
  
              @Override
              public void onAccessibilityEvent(AccessibilityEvent event) {
                  AccessibilityService.this.onAccessibilityEvent(event);
              }
  
              @Override
              public void init(int connectionId, IBinder windowToken) {
                  mConnectionId = connectionId;
                  mWindowToken = windowToken;
  
                  // The client may have already obtained the window manager, so
                  // update the default token on whatever manager we gave them.
                  final WindowManagerImpl wm = (WindowManagerImpl) getSystemService(WINDOW_SERVICE);
                  wm.setDefaultToken(windowToken);
              }
  
              @Override
              public boolean onGesture(int gestureId) {
                  return AccessibilityService.this.onGesture(gestureId);
              }
  
              @Override
              public boolean onKeyEvent(KeyEvent event) {
                  return AccessibilityService.this.onKeyEvent(event);
              }
  
              @Override
              public void onMagnificationChanged(int displayId, @NonNull Region region,
                      float scale, float centerX, float centerY) {
                  AccessibilityService.this.onMagnificationChanged(displayId, region, scale,
                          centerX, centerY);
              }
  
              @Override
              public void onSoftKeyboardShowModeChanged(int showMode) {
                  AccessibilityService.this.onSoftKeyboardShowModeChanged(showMode);
              }
  
              @Override
              public void onPerformGestureResult(int sequence, boolean completedSuccessfully) {
                  AccessibilityService.this.onPerformGestureResult(sequence, completedSuccessfully);
              }
  
              @Override
              public void onFingerprintCapturingGesturesChanged(boolean active) {
                  AccessibilityService.this.onFingerprintCapturingGesturesChanged(active);
              }
  
              @Override
              public void onFingerprintGesture(int gesture) {
                  AccessibilityService.this.onFingerprintGesture(gesture);
              }
  
              @Override
              public void onAccessibilityButtonClicked() {
                  AccessibilityService.this.onAccessibilityButtonClicked();
              }
  
              @Override
              public void onAccessibilityButtonAvailabilityChanged(boolean available) {
                  AccessibilityService.this.onAccessibilityButtonAvailabilityChanged(available);
              }
          });
      }
  
      /**
       * Implements the internal {@link IAccessibilityServiceClient} interface to convert
       * incoming calls to it back to calls on an {@link AccessibilityService}.
       *
       * @hide
       */
      public static class IAccessibilityServiceClientWrapper extends IAccessibilityServiceClient.Stub
              implements HandlerCaller.Callback {
          private static final int DO_INIT = 1;
          private static final int DO_ON_INTERRUPT = 2;
          private static final int DO_ON_ACCESSIBILITY_EVENT = 3;
          private static final int DO_ON_GESTURE = 4;
          private static final int DO_CLEAR_ACCESSIBILITY_CACHE = 5;
          private static final int DO_ON_KEY_EVENT = 6;
          private static final int DO_ON_MAGNIFICATION_CHANGED = 7;
          private static final int DO_ON_SOFT_KEYBOARD_SHOW_MODE_CHANGED = 8;
          private static final int DO_GESTURE_COMPLETE = 9;
          private static final int DO_ON_FINGERPRINT_ACTIVE_CHANGED = 10;
          private static final int DO_ON_FINGERPRINT_GESTURE = 11;
          private static final int DO_ACCESSIBILITY_BUTTON_CLICKED = 12;
          private static final int DO_ACCESSIBILITY_BUTTON_AVAILABILITY_CHANGED = 13;
  
          private final HandlerCaller mCaller;
  
          private final Callbacks mCallback;
  
          private int mConnectionId = AccessibilityInteractionClient.NO_ID;
  
          public IAccessibilityServiceClientWrapper(Context context, Looper looper,
                  Callbacks callback) {
              mCallback = callback;
              mCaller = new HandlerCaller(context, looper, this, true /*asyncHandler*/);
          }
  
          public void init(IAccessibilityServiceConnection connection, int connectionId,
                  IBinder windowToken) {
              Message message = mCaller.obtainMessageIOO(DO_INIT, connectionId,
                      connection, windowToken);
              mCaller.sendMessage(message);
          }
  
          public void onInterrupt() {
              Message message = mCaller.obtainMessage(DO_ON_INTERRUPT);
              mCaller.sendMessage(message);
          }
  
          public void onAccessibilityEvent(AccessibilityEvent event, boolean serviceWantsEvent) {
              Message message = mCaller.obtainMessageBO(
                      DO_ON_ACCESSIBILITY_EVENT, serviceWantsEvent, event);
              mCaller.sendMessage(message);
          }
  
          public void onGesture(int gestureId) {
              Message message = mCaller.obtainMessageI(DO_ON_GESTURE, gestureId);
              mCaller.sendMessage(message);
          }
  
          public void clearAccessibilityCache() {
              Message message = mCaller.obtainMessage(DO_CLEAR_ACCESSIBILITY_CACHE);
              mCaller.sendMessage(message);
          }
  
          @Override
          public void onKeyEvent(KeyEvent event, int sequence) {
              Message message = mCaller.obtainMessageIO(DO_ON_KEY_EVENT, sequence, event);
              mCaller.sendMessage(message);
          }
  
          /** Magnification changed callbacks for different displays */
          public void onMagnificationChanged(int displayId, @NonNull Region region,
                  float scale, float centerX, float centerY) {
              final SomeArgs args = SomeArgs.obtain();
              args.arg1 = region;
              args.arg2 = scale;
              args.arg3 = centerX;
              args.arg4 = centerY;
              args.argi1 = displayId;
  
              final Message message = mCaller.obtainMessageO(DO_ON_MAGNIFICATION_CHANGED, args);
              mCaller.sendMessage(message);
          }
  
          public void onSoftKeyboardShowModeChanged(int showMode) {
            final Message message =
                    mCaller.obtainMessageI(DO_ON_SOFT_KEYBOARD_SHOW_MODE_CHANGED, showMode);
            mCaller.sendMessage(message);
          }
  
          public void onPerformGestureResult(int sequence, boolean successfully) {
              Message message = mCaller.obtainMessageII(DO_GESTURE_COMPLETE, sequence,
                      successfully ? 1 : 0);
              mCaller.sendMessage(message);
          }
  
          public void onFingerprintCapturingGesturesChanged(boolean active) {
              mCaller.sendMessage(mCaller.obtainMessageI(
                      DO_ON_FINGERPRINT_ACTIVE_CHANGED, active ? 1 : 0));
          }
  
          public void onFingerprintGesture(int gesture) {
              mCaller.sendMessage(mCaller.obtainMessageI(DO_ON_FINGERPRINT_GESTURE, gesture));
          }
  
          public void onAccessibilityButtonClicked() {
              final Message message = mCaller.obtainMessage(DO_ACCESSIBILITY_BUTTON_CLICKED);
              mCaller.sendMessage(message);
          }
  
          public void onAccessibilityButtonAvailabilityChanged(boolean available) {
              final Message message = mCaller.obtainMessageI(
                      DO_ACCESSIBILITY_BUTTON_AVAILABILITY_CHANGED, (available ? 1 : 0));
              mCaller.sendMessage(message);
          }
  
          @Override
          public void executeMessage(Message message) {
              switch (message.what) {
                  case DO_ON_ACCESSIBILITY_EVENT: {
                      AccessibilityEvent event = (AccessibilityEvent) message.obj;
                      boolean serviceWantsEvent = message.arg1 != 0;
                      if (event != null) {
                          // Send the event to AccessibilityCache via AccessibilityInteractionClient
                          AccessibilityInteractionClient.getInstance().onAccessibilityEvent(event);
                          if (serviceWantsEvent
                                  && (mConnectionId != AccessibilityInteractionClient.NO_ID)) {
                              // Send the event to AccessibilityService
                              mCallback.onAccessibilityEvent(event);
                          }
                          // Make sure the event is recycled.
                          try {
                              event.recycle();
                          } catch (IllegalStateException ise) {
                              /* ignore - best effort */
                          }
                      }
                  } return;
  
                  case DO_ON_INTERRUPT: {
                      if (mConnectionId != AccessibilityInteractionClient.NO_ID) {
                          mCallback.onInterrupt();
                      }
                  } return;
  
                  case DO_INIT: {
                      mConnectionId = message.arg1;
                      SomeArgs args = (SomeArgs) message.obj;
                      IAccessibilityServiceConnection connection =
                              (IAccessibilityServiceConnection) args.arg1;
                      IBinder windowToken = (IBinder) args.arg2;
                      args.recycle();
                      if (connection != null) {
                          AccessibilityInteractionClient.getInstance().addConnection(mConnectionId,
                                  connection);
                          mCallback.init(mConnectionId, windowToken);
                          mCallback.onServiceConnected();
                      } else {
                          AccessibilityInteractionClient.getInstance().removeConnection(
                                  mConnectionId);
                          mConnectionId = AccessibilityInteractionClient.NO_ID;
                          AccessibilityInteractionClient.getInstance().clearCache();
                          mCallback.init(AccessibilityInteractionClient.NO_ID, null);
                      }
                  } return;
  
                  case DO_ON_GESTURE: {
                      if (mConnectionId != AccessibilityInteractionClient.NO_ID) {
                          final int gestureId = message.arg1;
                          mCallback.onGesture(gestureId);
                      }
                  } return;
  
                  case DO_CLEAR_ACCESSIBILITY_CACHE: {
                      AccessibilityInteractionClient.getInstance().clearCache();
                  } return;
  
                  case DO_ON_KEY_EVENT: {
                      KeyEvent event = (KeyEvent) message.obj;
                      try {
                          IAccessibilityServiceConnection connection = AccessibilityInteractionClient
                                  .getInstance().getConnection(mConnectionId);
                          if (connection != null) {
                              final boolean result = mCallback.onKeyEvent(event);
                              final int sequence = message.arg1;
                              try {
                                  connection.setOnKeyEventResult(result, sequence);
                              } catch (RemoteException re) {
                                  /* ignore */
                              }
                          }
                      } finally {
                          // Make sure the event is recycled.
                          try {
                              event.recycle();
                          } catch (IllegalStateException ise) {
                              /* ignore - best effort */
                          }
                      }
                  } return;
  
                  case DO_ON_MAGNIFICATION_CHANGED: {
                      if (mConnectionId != AccessibilityInteractionClient.NO_ID) {
                          final SomeArgs args = (SomeArgs) message.obj;
                          final Region region = (Region) args.arg1;
                          final float scale = (float) args.arg2;
                          final float centerX = (float) args.arg3;
                          final float centerY = (float) args.arg4;
                          final int displayId = args.argi1;
                          args.recycle();
                          mCallback.onMagnificationChanged(displayId, region, scale,
                                  centerX, centerY);
                      }
                  } return;
  
                  case DO_ON_SOFT_KEYBOARD_SHOW_MODE_CHANGED: {
                      if (mConnectionId != AccessibilityInteractionClient.NO_ID) {
                          final int showMode = (int) message.arg1;
                          mCallback.onSoftKeyboardShowModeChanged(showMode);
                      }
                  } return;
  
                  case DO_GESTURE_COMPLETE: {
                      if (mConnectionId != AccessibilityInteractionClient.NO_ID) {
                          final boolean successfully = message.arg2 == 1;
                          mCallback.onPerformGestureResult(message.arg1, successfully);
                      }
                  } return;
                  case DO_ON_FINGERPRINT_ACTIVE_CHANGED: {
                      if (mConnectionId != AccessibilityInteractionClient.NO_ID) {
                          mCallback.onFingerprintCapturingGesturesChanged(message.arg1 == 1);
                      }
                  } return;
                  case DO_ON_FINGERPRINT_GESTURE: {
                      if (mConnectionId != AccessibilityInteractionClient.NO_ID) {
                          mCallback.onFingerprintGesture(message.arg1);
                      }
                  } return;
  
                  case (DO_ACCESSIBILITY_BUTTON_CLICKED): {
                      if (mConnectionId != AccessibilityInteractionClient.NO_ID) {
                          mCallback.onAccessibilityButtonClicked();
                      }
                  } return;
  
                  case (DO_ACCESSIBILITY_BUTTON_AVAILABILITY_CHANGED): {
                      if (mConnectionId != AccessibilityInteractionClient.NO_ID) {
                          final boolean available = (message.arg1 != 0);
                          mCallback.onAccessibilityButtonAvailabilityChanged(available);
                      }
                  } return;
  
                  default :
                      Log.w(LOG_TAG, "Unknown message type " + message.what);
              }
          }
      }
  
      /**
       * Class used to report status of dispatched gestures
       */
      public static abstract class GestureResultCallback {
          /** Called when the gesture has completed successfully
           *
           * @param gestureDescription The description of the gesture that completed.
           */
          public void onCompleted(GestureDescription gestureDescription) {
          }
  
          /** Called when the gesture was cancelled
           *
           * @param gestureDescription The description of the gesture that was cancelled.
           */
          public void onCancelled(GestureDescription gestureDescription) {
          }
      }
  
      /* Object to keep track of gesture result callbacks */
      private static class GestureResultCallbackInfo {
          GestureDescription gestureDescription;
          GestureResultCallback callback;
          Handler handler;
  
          GestureResultCallbackInfo(GestureDescription gestureDescription,
                  GestureResultCallback callback, Handler handler) {
              this.gestureDescription = gestureDescription;
              this.callback = callback;
              this.handler = handler;
          }
      }
  }
  