summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Phil Weaver <pweaver@google.com> 2017-08-25 17:37:04 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2017-08-25 17:37:04 +0000
commit1f3f7a858c4bb079d52cb6628335ee12c950a46c (patch)
tree18b8fc0d31840e252124a706d88a7ef62a284cf7
parent0c8030dffe675dfcf8b77e86ca83b73d840db882 (diff)
parent015847aa4f5ba31d6a4fd6695202ae60aaec926a (diff)
Merge "Refactoring A11yService and UiAutomation handling"
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityClientConnection.java1325
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java2224
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java364
-rw-r--r--services/accessibility/java/com/android/server/accessibility/GlobalActionPerformer.java156
-rw-r--r--services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java244
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java116
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/GlobalActionPerformerTest.java72
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java160
8 files changed, 2737 insertions, 1924 deletions
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityClientConnection.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityClientConnection.java
new file mode 100644
index 000000000000..df4c8ed5e400
--- /dev/null
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityClientConnection.java
@@ -0,0 +1,1325 @@
+/*
+ ** Copyright 2017, 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 com.android.server.accessibility;
+
+import static android.accessibilityservice.AccessibilityServiceInfo.DEFAULT;
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY;
+
+import android.accessibilityservice.AccessibilityService;
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.accessibilityservice.GestureDescription;
+import android.accessibilityservice.IAccessibilityServiceClient;
+import android.accessibilityservice.IAccessibilityServiceConnection;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.PendingIntent;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.ParceledListSlice;
+import android.graphics.Region;
+import android.os.Binder;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.view.KeyEvent;
+import android.view.MagnificationSpec;
+import android.view.View;
+import android.view.WindowManagerInternal;
+import android.view.accessibility.AccessibilityCache;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityInteractionClient;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityWindowInfo;
+import android.view.accessibility.IAccessibilityInteractionConnection;
+import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
+
+
+import com.android.internal.os.SomeArgs;
+import com.android.internal.util.DumpUtils;
+import com.android.server.accessibility.AccessibilityManagerService.SecurityPolicy;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * This class represents an accessibility client - either an AccessibilityService or a UiAutomation.
+ * It is responsible for behavior common to both types of clients.
+ */
+abstract class AccessibilityClientConnection extends IAccessibilityServiceConnection.Stub
+ implements ServiceConnection, IBinder.DeathRecipient, KeyEventDispatcher.KeyEventFilter,
+ FingerprintGestureDispatcher.FingerprintGestureClient {
+ private static final boolean DEBUG = false;
+ private static final String LOG_TAG = "AccessibilityClientConnection";
+
+ protected final Context mContext;
+ protected final SystemSupport mSystemSupport;
+ private final WindowManagerInternal mWindowManagerService;
+ private final GlobalActionPerformer mGlobalActionPerformer;
+
+ // Handler for scheduling method invocations on the main thread.
+ public final InvocationHandler mInvocationHandler;
+
+ final int mId;
+
+ final AccessibilityServiceInfo mAccessibilityServiceInfo;
+
+ // Lock must match the one used by AccessibilityManagerService
+ protected final Object mLock;
+
+ protected final SecurityPolicy mSecurityPolicy;
+
+ // The service that's bound to this instance. Whenever this value is non-null, this
+ // object is registered as a death recipient
+ IBinder mService;
+
+ IAccessibilityServiceClient mServiceInterface;
+
+ int mEventTypes;
+
+ int mFeedbackType;
+
+ Set<String> mPackageNames = new HashSet<>();
+
+ boolean mIsDefault;
+
+ boolean mRequestTouchExplorationMode;
+
+ boolean mRequestFilterKeyEvents;
+
+ boolean mRetrieveInteractiveWindows;
+
+ boolean mCaptureFingerprintGestures;
+
+ boolean mRequestAccessibilityButton;
+
+ boolean mReceivedAccessibilityButtonCallbackSinceBind;
+
+ boolean mLastAccessibilityButtonCallbackState;
+
+ int mFetchFlags;
+
+ long mNotificationTimeout;
+
+ final ComponentName mComponentName;
+
+ // the events pending events to be dispatched to this service
+ final SparseArray<AccessibilityEvent> mPendingEvents = new SparseArray<>();
+
+ /** Whether this service relies on its {@link AccessibilityCache} being up to date */
+ boolean mUsesAccessibilityCache = false;
+
+ // Handler only for dispatching accessibility events since we use event
+ // types as message types allowing us to remove messages per event type.
+ public Handler mEventDispatchHandler;
+
+ final IBinder mOverlayWindowToken = new Binder();
+
+
+ public interface SystemSupport {
+ /**
+ * @return The current dispatcher for key events
+ */
+ @NonNull KeyEventDispatcher getKeyEventDispatcher();
+
+ /**
+ * @param windowId The id of the window of interest
+ * @return The magnification spec for the window, or {@code null} if none is available
+ */
+ @Nullable MagnificationSpec getCompatibleMagnificationSpecLocked(int windowId);
+
+ /**
+ * @return The current injector of motion events, if one exists
+ */
+ @Nullable MotionEventInjector getMotionEventInjectorLocked();
+
+ /**
+ * @return The current dispatcher for fingerprint gestures, if one exists
+ */
+ @Nullable FingerprintGestureDispatcher getFingerprintGestureDispatcher();
+
+ /**
+ * @return The magnification controller
+ */
+ @NonNull MagnificationController getMagnificationController();
+
+ /**
+ * Resolve a connection for a window id
+ *
+ * @param windowId The id of the window of interest
+ *
+ * @return a connection to the window
+ */
+ IAccessibilityInteractionConnection getConnectionLocked(int windowId);
+
+ /**
+ * Perform the specified accessibility action
+ *
+ * @param resolvedWindowId The window ID
+ * [Other parameters match the method on IAccessibilityServiceConnection]
+ *
+ * @return Whether or not the action could be sent to the app process
+ */
+ boolean performAccessibilityAction(int resolvedWindowId,
+ long accessibilityNodeId, int action, Bundle arguments, int interactionId,
+ IAccessibilityInteractionConnectionCallback callback, int fetchFlags,
+ long interrogatingTid);
+
+ /**
+ * Replace the interaction callback if needed, for example if the window is in picture-
+ * in-picture mode and needs its nodes replaced.
+ *
+ * @param originalCallback The callback we were planning to use
+ * @param resolvedWindowId The ID of the window we're calling
+ * @param interactionId The id for the original callback
+ * @param interrogatingPid Process ID of requester
+ * @param interrogatingTid Thread ID of requester
+ *
+ * @return The callback to use, which may be the original one.
+ */
+ @NonNull IAccessibilityInteractionConnectionCallback replaceCallbackIfNeeded(
+ IAccessibilityInteractionConnectionCallback originalCallback,
+ int resolvedWindowId, int interactionId, int interrogatingPid,
+ long interrogatingTid);
+
+ /**
+ * Request that the system make sure windows are available to interrogate
+ */
+ void ensureWindowsAvailableTimed();
+
+ /**
+ * Called back to notify system that the client has changed
+ * @param serviceInfoChanged True if the service's AccessibilityServiceInfo changed.
+ */
+ void onClientChange(boolean serviceInfoChanged);
+
+ int getCurrentUserIdLocked();
+
+ boolean isAccessibilityButtonShown();
+
+ /**
+ * Persists the component names in the specified setting in a
+ * colon separated fashion.
+ *
+ * @param settingName The setting name.
+ * @param componentNames The component names.
+ * @param userId The user id to persist the setting for.
+ */
+ void persistComponentNamesToSettingLocked(String settingName,
+ Set<ComponentName> componentNames, int userId);
+
+ /* This is exactly PendingIntent.getActivity, separated out for testability */
+ PendingIntent getPendingIntentActivity(Context context, int requestCode, Intent intent,
+ int flags);
+ }
+
+ public AccessibilityClientConnection(Context context, ComponentName componentName,
+ AccessibilityServiceInfo accessibilityServiceInfo, int id, Handler mainHandler,
+ Object lock, SecurityPolicy securityPolicy, SystemSupport systemSupport,
+ WindowManagerInternal windowManagerInternal,
+ GlobalActionPerformer globalActionPerfomer) {
+ mContext = context;
+ mWindowManagerService = windowManagerInternal;
+ mId = id;
+ mComponentName = componentName;
+ mAccessibilityServiceInfo = accessibilityServiceInfo;
+ mLock = lock;
+ mSecurityPolicy = securityPolicy;
+ mGlobalActionPerformer = globalActionPerfomer;
+ mSystemSupport = systemSupport;
+ mInvocationHandler = new InvocationHandler(mainHandler.getLooper());
+ mEventDispatchHandler = new Handler(mainHandler.getLooper()) {
+ @Override
+ public void handleMessage(Message message) {
+ final int eventType = message.what;
+ AccessibilityEvent event = (AccessibilityEvent) message.obj;
+ boolean serviceWantsEvent = message.arg1 != 0;
+ notifyAccessibilityEventInternal(eventType, event, serviceWantsEvent);
+ }
+ };
+ setDynamicallyConfigurableProperties(accessibilityServiceInfo);
+ }
+
+ @Override
+ public boolean onKeyEvent(KeyEvent keyEvent, int sequenceNumber) {
+ if (!mRequestFilterKeyEvents || (mServiceInterface == null)) {
+ return false;
+ }
+ if((mAccessibilityServiceInfo.getCapabilities()
+ & AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS) == 0) {
+ return false;
+ }
+ try {
+ mServiceInterface.onKeyEvent(keyEvent, sequenceNumber);
+ } catch (RemoteException e) {
+ return false;
+ }
+ return true;
+ }
+
+ public void setDynamicallyConfigurableProperties(AccessibilityServiceInfo info) {
+ mEventTypes = info.eventTypes;
+ mFeedbackType = info.feedbackType;
+ String[] packageNames = info.packageNames;
+ if (packageNames != null) {
+ mPackageNames.addAll(Arrays.asList(packageNames));
+ }
+ mNotificationTimeout = info.notificationTimeout;
+ mIsDefault = (info.flags & DEFAULT) != 0;
+
+ if (supportsFlagForNotImportantViews(info)) {
+ if ((info.flags & AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS) != 0) {
+ mFetchFlags |= AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;
+ } else {
+ mFetchFlags &= ~AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;
+ }
+ }
+
+ if ((info.flags & AccessibilityServiceInfo.FLAG_REPORT_VIEW_IDS) != 0) {
+ mFetchFlags |= AccessibilityNodeInfo.FLAG_REPORT_VIEW_IDS;
+ } else {
+ mFetchFlags &= ~AccessibilityNodeInfo.FLAG_REPORT_VIEW_IDS;
+ }
+
+ mRequestTouchExplorationMode = (info.flags
+ & AccessibilityServiceInfo.FLAG_REQUEST_TOUCH_EXPLORATION_MODE) != 0;
+ mRequestFilterKeyEvents = (info.flags
+ & AccessibilityServiceInfo.FLAG_REQUEST_FILTER_KEY_EVENTS) != 0;
+ mRetrieveInteractiveWindows = (info.flags
+ & AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS) != 0;
+ mCaptureFingerprintGestures = (info.flags
+ & AccessibilityServiceInfo.FLAG_REQUEST_FINGERPRINT_GESTURES) != 0;
+ mRequestAccessibilityButton = (info.flags
+ & AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0;
+ }
+
+ protected boolean supportsFlagForNotImportantViews(AccessibilityServiceInfo info) {
+ return info.getResolveInfo().serviceInfo.applicationInfo.targetSdkVersion
+ >= Build.VERSION_CODES.JELLY_BEAN;
+ }
+
+ public boolean canReceiveEventsLocked() {
+ return (mEventTypes != 0 && mFeedbackType != 0 && mService != null);
+ }
+
+ @Override
+ public void setOnKeyEventResult(boolean handled, int sequence) {
+ mSystemSupport.getKeyEventDispatcher().setOnKeyEventResult(this, handled, sequence);
+ }
+
+ @Override
+ public AccessibilityServiceInfo getServiceInfo() {
+ synchronized (mLock) {
+ return mAccessibilityServiceInfo;
+ }
+ }
+
+ @Override
+ public void setServiceInfo(AccessibilityServiceInfo info) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ // If the XML manifest had data to configure the service its info
+ // should be already set. In such a case update only the dynamically
+ // configurable properties.
+ AccessibilityServiceInfo oldInfo = mAccessibilityServiceInfo;
+ if (oldInfo != null) {
+ oldInfo.updateDynamicallyConfigurableProperties(info);
+ setDynamicallyConfigurableProperties(oldInfo);
+ } else {
+ setDynamicallyConfigurableProperties(info);
+ }
+ mSystemSupport.onClientChange(true);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ protected abstract boolean isCalledForCurrentUserLocked();
+
+ @Override
+ public List<AccessibilityWindowInfo> getWindows() {
+ mSystemSupport.ensureWindowsAvailableTimed();
+ synchronized (mLock) {
+ if (!isCalledForCurrentUserLocked()) {
+ return null;
+ }
+ final boolean permissionGranted =
+ mSecurityPolicy.canRetrieveWindowsLocked(this);
+ if (!permissionGranted) {
+ return null;
+ }
+ if (mSecurityPolicy.mWindows == null) {
+ return null;
+ }
+ List<AccessibilityWindowInfo> windows = new ArrayList<>();
+ final int windowCount = mSecurityPolicy.mWindows.size();
+ for (int i = 0; i < windowCount; i++) {
+ AccessibilityWindowInfo window = mSecurityPolicy.mWindows.get(i);
+ AccessibilityWindowInfo windowClone =
+ AccessibilityWindowInfo.obtain(window);
+ windowClone.setConnectionId(mId);
+ windows.add(windowClone);
+ }
+ return windows;
+ }
+ }
+
+ @Override
+ public AccessibilityWindowInfo getWindow(int windowId) {
+ mSystemSupport.ensureWindowsAvailableTimed();
+ synchronized (mLock) {
+ if (!isCalledForCurrentUserLocked()) {
+ return null;
+ }
+ final boolean permissionGranted =
+ mSecurityPolicy.canRetrieveWindowsLocked(this);
+ if (!permissionGranted) {
+ return null;
+ }
+ AccessibilityWindowInfo window = mSecurityPolicy.findA11yWindowInfoById(windowId);
+ if (window != null) {
+ AccessibilityWindowInfo windowClone = AccessibilityWindowInfo.obtain(window);
+ windowClone.setConnectionId(mId);
+ return windowClone;
+ }
+ return null;
+ }
+ }
+
+ @Override
+ public boolean findAccessibilityNodeInfosByViewId(int accessibilityWindowId,
+ long accessibilityNodeId, String viewIdResName, int interactionId,
+ IAccessibilityInteractionConnectionCallback callback, long interrogatingTid)
+ throws RemoteException {
+ final int resolvedWindowId;
+ IAccessibilityInteractionConnection connection = null;
+ Region partialInteractiveRegion = Region.obtain();
+ MagnificationSpec spec;
+ synchronized (mLock) {
+ mUsesAccessibilityCache = true;
+ if (!isCalledForCurrentUserLocked()) {
+ return false;
+ }
+ resolvedWindowId = resolveAccessibilityWindowIdLocked(accessibilityWindowId);
+ final boolean permissionGranted =
+ mSecurityPolicy.canGetAccessibilityNodeInfoLocked(this, resolvedWindowId);
+ if (!permissionGranted) {
+ return false;
+ } else {
+ connection = mSystemSupport.getConnectionLocked(resolvedWindowId);
+ if (connection == null) {
+ return false;
+ }
+ }
+ if (!mSecurityPolicy.computePartialInteractiveRegionForWindowLocked(
+ resolvedWindowId, partialInteractiveRegion)) {
+ partialInteractiveRegion.recycle();
+ partialInteractiveRegion = null;
+ }
+ spec = mSystemSupport.getCompatibleMagnificationSpecLocked(resolvedWindowId);
+ }
+ final int interrogatingPid = Binder.getCallingPid();
+ callback = mSystemSupport.replaceCallbackIfNeeded(callback, resolvedWindowId, interactionId,
+ interrogatingPid, interrogatingTid);
+ final long identityToken = Binder.clearCallingIdentity();
+ try {
+ connection.findAccessibilityNodeInfosByViewId(accessibilityNodeId, viewIdResName,
+ partialInteractiveRegion, interactionId, callback, mFetchFlags,
+ interrogatingPid, interrogatingTid, spec);
+ return true;
+ } catch (RemoteException re) {
+ if (DEBUG) {
+ Slog.e(LOG_TAG, "Error findAccessibilityNodeInfoByViewId().");
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identityToken);
+ // Recycle if passed to another process.
+ if (partialInteractiveRegion != null && Binder.isProxy(connection)) {
+ partialInteractiveRegion.recycle();
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean findAccessibilityNodeInfosByText(int accessibilityWindowId,
+ long accessibilityNodeId, String text, int interactionId,
+ IAccessibilityInteractionConnectionCallback callback, long interrogatingTid)
+ throws RemoteException {
+ final int resolvedWindowId;
+ IAccessibilityInteractionConnection connection = null;
+ Region partialInteractiveRegion = Region.obtain();
+ MagnificationSpec spec;
+ synchronized (mLock) {
+ mUsesAccessibilityCache = true;
+ if (!isCalledForCurrentUserLocked()) {
+ return false;
+ }
+ resolvedWindowId = resolveAccessibilityWindowIdLocked(accessibilityWindowId);
+ final boolean permissionGranted =
+ mSecurityPolicy.canGetAccessibilityNodeInfoLocked(this, resolvedWindowId);
+ if (!permissionGranted) {
+ return false;
+ } else {
+ connection = mSystemSupport.getConnectionLocked(resolvedWindowId);
+ if (connection == null) {
+ return false;
+ }
+ }
+ if (!mSecurityPolicy.computePartialInteractiveRegionForWindowLocked(
+ resolvedWindowId, partialInteractiveRegion)) {
+ partialInteractiveRegion.recycle();
+ partialInteractiveRegion = null;
+ }
+ spec = mSystemSupport.getCompatibleMagnificationSpecLocked(resolvedWindowId);
+ }
+ final int interrogatingPid = Binder.getCallingPid();
+ callback = mSystemSupport.replaceCallbackIfNeeded(callback, resolvedWindowId, interactionId,
+ interrogatingPid, interrogatingTid);
+ final long identityToken = Binder.clearCallingIdentity();
+ try {
+ connection.findAccessibilityNodeInfosByText(accessibilityNodeId, text,
+ partialInteractiveRegion, interactionId, callback, mFetchFlags,
+ interrogatingPid, interrogatingTid, spec);
+ return true;
+ } catch (RemoteException re) {
+ if (DEBUG) {
+ Slog.e(LOG_TAG, "Error calling findAccessibilityNodeInfosByText()");
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identityToken);
+ // Recycle if passed to another process.
+ if (partialInteractiveRegion != null && Binder.isProxy(connection)) {
+ partialInteractiveRegion.recycle();
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean findAccessibilityNodeInfoByAccessibilityId(
+ int accessibilityWindowId, long accessibilityNodeId, int interactionId,
+ IAccessibilityInteractionConnectionCallback callback, int flags,
+ long interrogatingTid, Bundle arguments) throws RemoteException {
+ final int resolvedWindowId;
+ IAccessibilityInteractionConnection connection = null;
+ Region partialInteractiveRegion = Region.obtain();
+ MagnificationSpec spec;
+ synchronized (mLock) {
+ mUsesAccessibilityCache = true;
+ if (!isCalledForCurrentUserLocked()) {
+ return false;
+ }
+ resolvedWindowId = resolveAccessibilityWindowIdLocked(accessibilityWindowId);
+ final boolean permissionGranted =
+ mSecurityPolicy.canGetAccessibilityNodeInfoLocked(this, resolvedWindowId);
+ if (!permissionGranted) {
+ return false;
+ } else {
+ connection = mSystemSupport.getConnectionLocked(resolvedWindowId);
+ if (connection == null) {
+ return false;
+ }
+ }
+ if (!mSecurityPolicy.computePartialInteractiveRegionForWindowLocked(
+ resolvedWindowId, partialInteractiveRegion)) {
+ partialInteractiveRegion.recycle();
+ partialInteractiveRegion = null;
+ }
+ spec = mSystemSupport.getCompatibleMagnificationSpecLocked(resolvedWindowId);
+ }
+ final int interrogatingPid = Binder.getCallingPid();
+ callback = mSystemSupport.replaceCallbackIfNeeded(callback, resolvedWindowId, interactionId,
+ interrogatingPid, interrogatingTid);
+ final long identityToken = Binder.clearCallingIdentity();
+ try {
+ connection.findAccessibilityNodeInfoByAccessibilityId(accessibilityNodeId,
+ partialInteractiveRegion, interactionId, callback, mFetchFlags | flags,
+ interrogatingPid, interrogatingTid, spec, arguments);
+ return true;
+ } catch (RemoteException re) {
+ if (DEBUG) {
+ Slog.e(LOG_TAG, "Error calling findAccessibilityNodeInfoByAccessibilityId()");
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identityToken);
+ // Recycle if passed to another process.
+ if (partialInteractiveRegion != null && Binder.isProxy(connection)) {
+ partialInteractiveRegion.recycle();
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean findFocus(int accessibilityWindowId, long accessibilityNodeId,
+ int focusType, int interactionId,
+ IAccessibilityInteractionConnectionCallback callback, long interrogatingTid)
+ throws RemoteException {
+ final int resolvedWindowId;
+ IAccessibilityInteractionConnection connection = null;
+ Region partialInteractiveRegion = Region.obtain();
+ MagnificationSpec spec;
+ synchronized (mLock) {
+ if (!isCalledForCurrentUserLocked()) {
+ return false;
+ }
+ resolvedWindowId = resolveAccessibilityWindowIdForFindFocusLocked(
+ accessibilityWindowId, focusType);
+ final boolean permissionGranted =
+ mSecurityPolicy.canGetAccessibilityNodeInfoLocked(this, resolvedWindowId);
+ if (!permissionGranted) {
+ return false;
+ } else {
+ connection = mSystemSupport.getConnectionLocked(resolvedWindowId);
+ if (connection == null) {
+ return false;
+ }
+ }
+ if (!mSecurityPolicy.computePartialInteractiveRegionForWindowLocked(
+ resolvedWindowId, partialInteractiveRegion)) {
+ partialInteractiveRegion.recycle();
+ partialInteractiveRegion = null;
+ }
+ spec = mSystemSupport.getCompatibleMagnificationSpecLocked(resolvedWindowId);
+ }
+ final int interrogatingPid = Binder.getCallingPid();
+ callback = mSystemSupport.replaceCallbackIfNeeded(callback, resolvedWindowId, interactionId,
+ interrogatingPid, interrogatingTid);
+ final long identityToken = Binder.clearCallingIdentity();
+ try {
+ connection.findFocus(accessibilityNodeId, focusType, partialInteractiveRegion,
+ interactionId, callback, mFetchFlags, interrogatingPid, interrogatingTid,
+ spec);
+ return true;
+ } catch (RemoteException re) {
+ if (DEBUG) {
+ Slog.e(LOG_TAG, "Error calling findFocus()");
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identityToken);
+ // Recycle if passed to another process.
+ if (partialInteractiveRegion != null && Binder.isProxy(connection)) {
+ partialInteractiveRegion.recycle();
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean focusSearch(int accessibilityWindowId, long accessibilityNodeId,
+ int direction, int interactionId,
+ IAccessibilityInteractionConnectionCallback callback, long interrogatingTid)
+ throws RemoteException {
+ final int resolvedWindowId;
+ IAccessibilityInteractionConnection connection = null;
+ Region partialInteractiveRegion = Region.obtain();
+ MagnificationSpec spec;
+ synchronized (mLock) {
+ if (!isCalledForCurrentUserLocked()) {
+ return false;
+ }
+ resolvedWindowId = resolveAccessibilityWindowIdLocked(accessibilityWindowId);
+ final boolean permissionGranted =
+ mSecurityPolicy.canGetAccessibilityNodeInfoLocked(this, resolvedWindowId);
+ if (!permissionGranted) {
+ return false;
+ } else {
+ connection = mSystemSupport.getConnectionLocked(resolvedWindowId);
+ if (connection == null) {
+ return false;
+ }
+ }
+ if (!mSecurityPolicy.computePartialInteractiveRegionForWindowLocked(
+ resolvedWindowId, partialInteractiveRegion)) {
+ partialInteractiveRegion.recycle();
+ partialInteractiveRegion = null;
+ }
+ spec = mSystemSupport.getCompatibleMagnificationSpecLocked(resolvedWindowId);
+ }
+ final int interrogatingPid = Binder.getCallingPid();
+ callback = mSystemSupport.replaceCallbackIfNeeded(callback, resolvedWindowId, interactionId,
+ interrogatingPid, interrogatingTid);
+ final long identityToken = Binder.clearCallingIdentity();
+ try {
+ connection.focusSearch(accessibilityNodeId, direction, partialInteractiveRegion,
+ interactionId, callback, mFetchFlags, interrogatingPid, interrogatingTid,
+ spec);
+ return true;
+ } catch (RemoteException re) {
+ if (DEBUG) {
+ Slog.e(LOG_TAG, "Error calling accessibilityFocusSearch()");
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identityToken);
+ // Recycle if passed to another process.
+ if (partialInteractiveRegion != null && Binder.isProxy(connection)) {
+ partialInteractiveRegion.recycle();
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public void sendGesture(int sequence, ParceledListSlice gestureSteps) {
+ }
+
+ @Override
+ public boolean performAccessibilityAction(int accessibilityWindowId,
+ long accessibilityNodeId, int action, Bundle arguments, int interactionId,
+ IAccessibilityInteractionConnectionCallback callback, long interrogatingTid)
+ throws RemoteException {
+ final int resolvedWindowId;
+ IAccessibilityInteractionConnection connection = null;
+ synchronized (mLock) {
+ if (!isCalledForCurrentUserLocked()) {
+ return false;
+ }
+ resolvedWindowId = resolveAccessibilityWindowIdLocked(accessibilityWindowId);
+ if (!mSecurityPolicy.canGetAccessibilityNodeInfoLocked(this, resolvedWindowId)) {
+ return false;
+ }
+ }
+ boolean returnValue =
+ mSystemSupport.performAccessibilityAction(resolvedWindowId, accessibilityNodeId,
+ action, arguments, interactionId, callback, mFetchFlags, interrogatingTid);
+ return returnValue;
+ }
+
+ @Override
+ public boolean performGlobalAction(int action) {
+ synchronized (mLock) {
+ if (!isCalledForCurrentUserLocked()) {
+ return false;
+ }
+ }
+ return mGlobalActionPerformer.performGlobalAction(action);
+ }
+
+ @Override
+ public boolean isFingerprintGestureDetectionAvailable() {
+ if (isCapturingFingerprintGestures()) {
+ FingerprintGestureDispatcher dispatcher =
+ mSystemSupport.getFingerprintGestureDispatcher();
+ return (dispatcher != null) && dispatcher.isFingerprintGestureDetectionAvailable();
+ }
+ return false;
+ }
+
+ @Override
+ public float getMagnificationScale() {
+ synchronized (mLock) {
+ if (!isCalledForCurrentUserLocked()) {
+ return 1.0f;
+ }
+ }
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ return mSystemSupport.getMagnificationController().getScale();
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public Region getMagnificationRegion() {
+ synchronized (mLock) {
+ final Region region = Region.obtain();
+ if (!isCalledForCurrentUserLocked()) {
+ return region;
+ }
+ MagnificationController magnificationController =
+ mSystemSupport.getMagnificationController();
+ boolean registeredJustForThisCall =
+ registerMagnificationIfNeeded(magnificationController);
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ magnificationController.getMagnificationRegion(region);
+ return region;
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ if (registeredJustForThisCall) {
+ magnificationController.unregister();
+ }
+ }
+ }
+ }
+
+ @Override
+ public float getMagnificationCenterX() {
+ synchronized (mLock) {
+ if (!isCalledForCurrentUserLocked()) {
+ return 0.0f;
+ }
+ MagnificationController magnificationController =
+ mSystemSupport.getMagnificationController();
+ boolean registeredJustForThisCall =
+ registerMagnificationIfNeeded(magnificationController);
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ return magnificationController.getCenterX();
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ if (registeredJustForThisCall) {
+ magnificationController.unregister();
+ }
+ }
+ }
+ }
+
+ @Override
+ public float getMagnificationCenterY() {
+ synchronized (mLock) {
+ if (!isCalledForCurrentUserLocked()) {
+ return 0.0f;
+ }
+ MagnificationController magnificationController =
+ mSystemSupport.getMagnificationController();
+ boolean registeredJustForThisCall =
+ registerMagnificationIfNeeded(magnificationController);
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ return magnificationController.getCenterY();
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ if (registeredJustForThisCall) {
+ magnificationController.unregister();
+ }
+ }
+ }
+ }
+
+ private boolean registerMagnificationIfNeeded(
+ MagnificationController magnificationController) {
+ if (!magnificationController.isRegisteredLocked()
+ && mSecurityPolicy.canControlMagnification(this)) {
+ magnificationController.register();
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean resetMagnification(boolean animate) {
+ synchronized (mLock) {
+ if (!isCalledForCurrentUserLocked()) {
+ return false;
+ }
+ if (!mSecurityPolicy.canControlMagnification(this)) {
+ return false;
+ }
+ }
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ return mSystemSupport.getMagnificationController().reset(animate);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public boolean setMagnificationScaleAndCenter(float scale, float centerX, float centerY,
+ boolean animate) {
+ synchronized (mLock) {
+ if (!isCalledForCurrentUserLocked()) {
+ return false;
+ }
+ if (!mSecurityPolicy.canControlMagnification(this)) {
+ return false;
+ }
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ MagnificationController magnificationController =
+ mSystemSupport.getMagnificationController();
+ if (!magnificationController.isRegisteredLocked()) {
+ magnificationController.register();
+ }
+ return magnificationController
+ .setScaleAndCenter(scale, centerX, centerY, animate, mId);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ }
+
+ @Override
+ public void setMagnificationCallbackEnabled(boolean enabled) {
+ mInvocationHandler.setMagnificationCallbackEnabled(enabled);
+ }
+
+ public boolean isMagnificationCallbackEnabled() {
+ return mInvocationHandler.mIsMagnificationCallbackEnabled;
+ }
+
+ @Override
+ public void setSoftKeyboardCallbackEnabled(boolean enabled) {
+ mInvocationHandler.setSoftKeyboardCallbackEnabled(enabled);
+ }
+
+ @Override
+ public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
+ if (!DumpUtils.checkDumpPermission(mContext, LOG_TAG, pw)) return;
+ synchronized (mLock) {
+ pw.append("Service[label=" + mAccessibilityServiceInfo.getResolveInfo()
+ .loadLabel(mContext.getPackageManager()));
+ pw.append(", feedbackType"
+ + AccessibilityServiceInfo.feedbackTypeToString(mFeedbackType));
+ pw.append(", capabilities=" + mAccessibilityServiceInfo.getCapabilities());
+ pw.append(", eventTypes="
+ + AccessibilityEvent.eventTypeToString(mEventTypes));
+ pw.append(", notificationTimeout=" + mNotificationTimeout);
+ pw.append("]");
+ }
+ }
+
+ public void onAdded() {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ mWindowManagerService.addWindowToken(mOverlayWindowToken,
+ TYPE_ACCESSIBILITY_OVERLAY, DEFAULT_DISPLAY);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ public void onRemoved() {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ mWindowManagerService.removeWindowToken(mOverlayWindowToken, true, DEFAULT_DISPLAY);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ public void resetLocked() {
+ mSystemSupport.getKeyEventDispatcher().flush(this);
+ try {
+ // Clear the proxy in the other process so this
+ // IAccessibilityServiceConnection can be garbage collected.
+ if (mServiceInterface != null) {
+ mServiceInterface.init(null, mId, null);
+ }
+ } catch (RemoteException re) {
+ /* ignore */
+ }
+ if (mService != null) {
+ mService.unlinkToDeath(this, 0);
+ mService = null;
+ }
+
+ mServiceInterface = null;
+ mReceivedAccessibilityButtonCallbackSinceBind = false;
+ }
+
+ public boolean isConnectedLocked() {
+ return (mService != null);
+ }
+
+ public void notifyAccessibilityEvent(AccessibilityEvent event) {
+ synchronized (mLock) {
+ final boolean serviceWantsEvent = wantsEventLocked(event);
+ if (!serviceWantsEvent && !mUsesAccessibilityCache &&
+ ((AccessibilityCache.CACHE_CRITICAL_EVENTS_MASK & event.getEventType()) == 0)) {
+ return;
+ }
+
+ final int eventType = event.getEventType();
+ // Make a copy since during dispatch it is possible the event to
+ // be modified to remove its source if the receiving service does
+ // not have permission to access the window content.
+ AccessibilityEvent newEvent = AccessibilityEvent.obtain(event);
+ Message message;
+ if ((mNotificationTimeout > 0)
+ && (eventType != AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED)) {
+ // Allow at most one pending event
+ final AccessibilityEvent oldEvent = mPendingEvents.get(eventType);
+ mPendingEvents.put(eventType, newEvent);
+ if (oldEvent != null) {
+ mEventDispatchHandler.removeMessages(eventType);
+ oldEvent.recycle();
+ }
+ message = mEventDispatchHandler.obtainMessage(eventType);
+ } else {
+ // Send all messages, bypassing mPendingEvents
+ message = mEventDispatchHandler.obtainMessage(eventType, newEvent);
+ }
+ message.arg1 = serviceWantsEvent ? 1 : 0;
+
+ mEventDispatchHandler.sendMessageDelayed(message, mNotificationTimeout);
+ }
+ }
+
+ /**
+ * Determines if given event can be dispatched to a service based on the package of the
+ * event source. Specifically, a service is notified if it is interested in events from the
+ * package.
+ *
+ * @param event The event.
+ * @return True if the listener should be notified, false otherwise.
+ */
+ private boolean wantsEventLocked(AccessibilityEvent event) {
+
+ if (!canReceiveEventsLocked()) {
+ return false;
+ }
+
+ if ((event.getWindowId() != AccessibilityWindowInfo.UNDEFINED_WINDOW_ID)
+ && !event.isImportantForAccessibility()
+ && (mFetchFlags & AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS) == 0) {
+ return false;
+ }
+
+ int eventType = event.getEventType();
+ if ((mEventTypes & eventType) != eventType) {
+ return false;
+ }
+
+ Set<String> packageNames = mPackageNames;
+ String packageName = (event.getPackageName() != null)
+ ? event.getPackageName().toString() : null;
+
+ return (packageNames.isEmpty() || packageNames.contains(packageName));
+ }
+
+ /**
+ * Notifies an accessibility service client for a scheduled event given the event type.
+ *
+ * @param eventType The type of the event to dispatch.
+ */
+ private void notifyAccessibilityEventInternal(
+ int eventType,
+ AccessibilityEvent event,
+ boolean serviceWantsEvent) {
+ IAccessibilityServiceClient listener;
+
+ synchronized (mLock) {
+ listener = mServiceInterface;
+
+ // If the service died/was disabled while the message for dispatching
+ // the accessibility event was propagating the listener may be null.
+ if (listener == null) {
+ return;
+ }
+
+ // There are two ways we notify for events, throttled AND non-throttled. If we
+ // are not throttling, then messages come with events, which we handle with
+ // minimal fuss.
+ if (event == null) {
+ // We are throttling events, so we'll send the event for this type in
+ // mPendingEvents as long as it it's null. It can only null due to a race
+ // condition:
+ //
+ // 1) A binder thread calls notifyAccessibilityServiceDelayedLocked
+ // which posts a message for dispatching an event and stores the event
+ // in mPendingEvents.
+ // 2) The message is pulled from the queue by the handler on the service
+ // thread and this method is just about to acquire the lock.
+ // 3) Another binder thread acquires the lock in notifyAccessibilityEvent
+ // 4) notifyAccessibilityEvent recycles the event that this method was about
+ // to process, replaces it with a new one, and posts a second message
+ // 5) This method grabs the new event, processes it, and removes it from
+ // mPendingEvents
+ // 6) The second message dispatched in (4) arrives, but the event has been
+ // remvoved in (5).
+ event = mPendingEvents.get(eventType);
+ if (event == null) {
+ return;
+ }
+ mPendingEvents.remove(eventType);
+ }
+ if (mSecurityPolicy.canRetrieveWindowContentLocked(this)) {
+ event.setConnectionId(mId);
+ } else {
+ event.setSource((View) null);
+ }
+ event.setSealed(true);
+ }
+
+ try {
+ listener.onAccessibilityEvent(event, serviceWantsEvent);
+ if (DEBUG) {
+ Slog.i(LOG_TAG, "Event " + event + " sent to " + listener);
+ }
+ } catch (RemoteException re) {
+ Slog.e(LOG_TAG, "Error during sending " + event + " to " + listener, re);
+ } finally {
+ event.recycle();
+ }
+ }
+
+ public void notifyGesture(int gestureId) {
+ mInvocationHandler.obtainMessage(InvocationHandler.MSG_ON_GESTURE,
+ gestureId, 0).sendToTarget();
+ }
+
+ public void notifyClearAccessibilityNodeInfoCache() {
+ mInvocationHandler.sendEmptyMessage(
+ InvocationHandler.MSG_CLEAR_ACCESSIBILITY_CACHE);
+ }
+
+ public void notifyMagnificationChangedLocked(@NonNull Region region,
+ float scale, float centerX, float centerY) {
+ mInvocationHandler
+ .notifyMagnificationChangedLocked(region, scale, centerX, centerY);
+ }
+
+ public void notifySoftKeyboardShowModeChangedLocked(int showState) {
+ mInvocationHandler.notifySoftKeyboardShowModeChangedLocked(showState);
+ }
+
+ public void notifyAccessibilityButtonClickedLocked() {
+ mInvocationHandler.notifyAccessibilityButtonClickedLocked();
+ }
+
+ public void notifyAccessibilityButtonAvailabilityChangedLocked(boolean available) {
+ mInvocationHandler.notifyAccessibilityButtonAvailabilityChangedLocked(available);
+ }
+
+ /**
+ * Called by the invocation handler to notify the service that the
+ * state of magnification has changed.
+ */
+ private void notifyMagnificationChangedInternal(@NonNull Region region,
+ float scale, float centerX, float centerY) {
+ final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
+ if (listener != null) {
+ try {
+ listener.onMagnificationChanged(region, scale, centerX, centerY);
+ } catch (RemoteException re) {
+ Slog.e(LOG_TAG, "Error sending magnification changes to " + mService, re);
+ }
+ }
+ }
+
+ /**
+ * Called by the invocation handler to notify the service that the state of the soft
+ * keyboard show mode has changed.
+ */
+ private void notifySoftKeyboardShowModeChangedInternal(int showState) {
+ final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
+ if (listener != null) {
+ try {
+ listener.onSoftKeyboardShowModeChanged(showState);
+ } catch (RemoteException re) {
+ Slog.e(LOG_TAG, "Error sending soft keyboard show mode changes to " + mService,
+ re);
+ }
+ }
+ }
+
+ private void notifyAccessibilityButtonClickedInternal() {
+ final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
+ if (listener != null) {
+ try {
+ listener.onAccessibilityButtonClicked();
+ } catch (RemoteException re) {
+ Slog.e(LOG_TAG, "Error sending accessibility button click to " + mService, re);
+ }
+ }
+ }
+
+ private void notifyAccessibilityButtonAvailabilityChangedInternal(boolean available) {
+ // Only notify the service if it's not been notified or the state has changed
+ if (mReceivedAccessibilityButtonCallbackSinceBind
+ && (mLastAccessibilityButtonCallbackState == available)) {
+ return;
+ }
+ mReceivedAccessibilityButtonCallbackSinceBind = true;
+ mLastAccessibilityButtonCallbackState = available;
+ final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
+ if (listener != null) {
+ try {
+ listener.onAccessibilityButtonAvailabilityChanged(available);
+ } catch (RemoteException re) {
+ Slog.e(LOG_TAG,
+ "Error sending accessibility button availability change to " + mService,
+ re);
+ }
+ }
+ }
+
+ private void notifyGestureInternal(int gestureId) {
+ final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
+ if (listener != null) {
+ try {
+ listener.onGesture(gestureId);
+ } catch (RemoteException re) {
+ Slog.e(LOG_TAG, "Error during sending gesture " + gestureId
+ + " to " + mService, re);
+ }
+ }
+ }
+
+ private void notifyClearAccessibilityCacheInternal() {
+ final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
+ if (listener != null) {
+ try {
+ listener.clearAccessibilityCache();
+ } catch (RemoteException re) {
+ Slog.e(LOG_TAG, "Error during requesting accessibility info cache"
+ + " to be cleared.", re);
+ }
+ }
+ }
+
+ private IAccessibilityServiceClient getServiceInterfaceSafely() {
+ synchronized (mLock) {
+ return mServiceInterface;
+ }
+ }
+
+ private int resolveAccessibilityWindowIdLocked(int accessibilityWindowId) {
+ if (accessibilityWindowId == AccessibilityWindowInfo.ACTIVE_WINDOW_ID) {
+ return mSecurityPolicy.getActiveWindowId();
+ }
+ return accessibilityWindowId;
+ }
+
+ private int resolveAccessibilityWindowIdForFindFocusLocked(int windowId, int focusType) {
+ if (windowId == AccessibilityWindowInfo.ACTIVE_WINDOW_ID) {
+ return mSecurityPolicy.mActiveWindowId;
+ }
+ if (windowId == AccessibilityWindowInfo.ANY_WINDOW_ID) {
+ if (focusType == AccessibilityNodeInfo.FOCUS_INPUT) {
+ return mSecurityPolicy.mFocusedWindowId;
+ } else if (focusType == AccessibilityNodeInfo.FOCUS_ACCESSIBILITY) {
+ return mSecurityPolicy.mAccessibilityFocusedWindowId;
+ }
+ }
+ return windowId;
+ }
+
+ private final class InvocationHandler extends Handler {
+ public static final int MSG_ON_GESTURE = 1;
+ public static final int MSG_CLEAR_ACCESSIBILITY_CACHE = 2;
+
+ private static final int MSG_ON_MAGNIFICATION_CHANGED = 5;
+ private static final int MSG_ON_SOFT_KEYBOARD_STATE_CHANGED = 6;
+ private static final int MSG_ON_ACCESSIBILITY_BUTTON_CLICKED = 7;
+ private static final int MSG_ON_ACCESSIBILITY_BUTTON_AVAILABILITY_CHANGED = 8;
+
+ private boolean mIsMagnificationCallbackEnabled = false;
+ private boolean mIsSoftKeyboardCallbackEnabled = false;
+
+ public InvocationHandler(Looper looper) {
+ super(looper, null, true);
+ }
+
+ @Override
+ public void handleMessage(Message message) {
+ final int type = message.what;
+ switch (type) {
+ case MSG_ON_GESTURE: {
+ final int gestureId = message.arg1;
+ notifyGestureInternal(gestureId);
+ } break;
+
+ case MSG_CLEAR_ACCESSIBILITY_CACHE: {
+ notifyClearAccessibilityCacheInternal();
+ } break;
+
+ case MSG_ON_MAGNIFICATION_CHANGED: {
+ 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;
+ notifyMagnificationChangedInternal(region, scale, centerX, centerY);
+ args.recycle();
+ } break;
+
+ case MSG_ON_SOFT_KEYBOARD_STATE_CHANGED: {
+ final int showState = (int) message.arg1;
+ notifySoftKeyboardShowModeChangedInternal(showState);
+ } break;
+
+ case MSG_ON_ACCESSIBILITY_BUTTON_CLICKED: {
+ notifyAccessibilityButtonClickedInternal();
+ } break;
+
+ case MSG_ON_ACCESSIBILITY_BUTTON_AVAILABILITY_CHANGED: {
+ final boolean available = (message.arg1 != 0);
+ notifyAccessibilityButtonAvailabilityChangedInternal(available);
+ } break;
+
+ default: {
+ throw new IllegalArgumentException("Unknown message: " + type);
+ }
+ }
+ }
+
+ public void notifyMagnificationChangedLocked(@NonNull Region region, float scale,
+ float centerX, float centerY) {
+ if (!mIsMagnificationCallbackEnabled) {
+ // Callback is disabled, don't bother packing args.
+ return;
+ }
+
+ final SomeArgs args = SomeArgs.obtain();
+ args.arg1 = region;
+ args.arg2 = scale;
+ args.arg3 = centerX;
+ args.arg4 = centerY;
+
+ final Message msg = obtainMessage(MSG_ON_MAGNIFICATION_CHANGED, args);
+ msg.sendToTarget();
+ }
+
+ public void setMagnificationCallbackEnabled(boolean enabled) {
+ mIsMagnificationCallbackEnabled = enabled;
+ }
+
+ public void notifySoftKeyboardShowModeChangedLocked(int showState) {
+ if (!mIsSoftKeyboardCallbackEnabled) {
+ return;
+ }
+
+ final Message msg = obtainMessage(MSG_ON_SOFT_KEYBOARD_STATE_CHANGED, showState, 0);
+ msg.sendToTarget();
+ }
+
+ public void setSoftKeyboardCallbackEnabled(boolean enabled) {
+ mIsSoftKeyboardCallbackEnabled = enabled;
+ }
+
+ public void notifyAccessibilityButtonClickedLocked() {
+ final Message msg = obtainMessage(MSG_ON_ACCESSIBILITY_BUTTON_CLICKED);
+ msg.sendToTarget();
+ }
+
+ public void notifyAccessibilityButtonAvailabilityChangedLocked(boolean available) {
+ final Message msg = obtainMessage(MSG_ON_ACCESSIBILITY_BUTTON_AVAILABILITY_CHANGED,
+ (available ? 1 : 0), 0);
+ msg.sendToTarget();
+ }
+ }
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 83dfccb57ebf..06c110de6db2 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -16,8 +16,6 @@
package com.android.server.accessibility;
-import static android.accessibilityservice.AccessibilityServiceInfo.DEFAULT;
-import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY;
import static android.view.accessibility.AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS;
import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS;
@@ -25,15 +23,13 @@ import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLEAR_ACCE
import android.Manifest;
import android.accessibilityservice.AccessibilityService;
import android.accessibilityservice.AccessibilityServiceInfo;
-import android.accessibilityservice.GestureDescription;
import android.accessibilityservice.IAccessibilityServiceClient;
import android.accessibilityservice.IAccessibilityServiceConnection;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.ActivityManagerInternal;
import android.app.AlertDialog;
import android.app.PendingIntent;
-import android.app.StatusBarManager;
-import android.app.UiAutomation;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentResolver;
@@ -42,9 +38,7 @@ import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.content.IntentFilter;
-import android.content.ServiceConnection;
import android.content.pm.PackageManager;
-import android.content.pm.ParceledListSlice;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.pm.UserInfo;
@@ -54,7 +48,6 @@ import android.graphics.Rect;
import android.graphics.Region;
import android.hardware.display.DisplayManager;
import android.hardware.fingerprint.IFingerprintService;
-import android.hardware.input.InputManager;
import android.media.AudioManagerInternal;
import android.net.Uri;
import android.os.Binder;
@@ -84,8 +77,6 @@ import android.util.Slog;
import android.util.SparseArray;
import android.view.Display;
import android.view.IWindow;
-import android.view.InputDevice;
-import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.MagnificationSpec;
import android.view.View;
@@ -107,12 +98,10 @@ import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.content.PackageMonitor;
-import com.android.internal.os.SomeArgs;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.IntPair;
import com.android.server.LocalServices;
import com.android.server.policy.AccessibilityShortcutController;
-import com.android.server.statusbar.StatusBarManagerInternal;
import org.xmlpull.v1.XmlPullParserException;
@@ -120,7 +109,6 @@ import java.io.FileDescriptor;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
@@ -137,7 +125,8 @@ import java.util.function.Consumer;
* event dispatch for {@link AccessibilityEvent}s generated across all processes
* on the device. Events are dispatched to {@link AccessibilityService}s.
*/
-public class AccessibilityManagerService extends IAccessibilityManager.Stub {
+public class AccessibilityManagerService extends IAccessibilityManager.Stub
+ implements AccessibilityClientConnection.SystemSupport {
private static final boolean DEBUG = false;
@@ -164,9 +153,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
private static final String SET_PIP_ACTION_REPLACEMENT =
"setPictureInPictureActionReplacingConnection";
- private static final ComponentName sFakeAccessibilityServiceComponentName =
- new ComponentName("foo.bar", "FakeService");
-
private static final String FUNCTION_DUMP = "dump";
private static final char COMPONENT_NAME_SEPARATOR = ':';
@@ -203,6 +189,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
private final MainHandler mMainHandler;
+ private final GlobalActionPerformer mGlobalActionPerformer;
+
private MagnificationController mMagnificationController;
private InteractionBridge mInteractionBridge;
@@ -240,6 +228,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
private final UserManager mUserManager;
+ private final UiAutomationManager mUiAutomationManager = new UiAutomationManager();
+
private int mCurrentUserId = UserHandle.USER_SYSTEM;
//TODO: Remove this hack
@@ -266,11 +256,28 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
mSecurityPolicy = new SecurityPolicy();
mMainHandler = new MainHandler(mContext.getMainLooper());
+ mGlobalActionPerformer = new GlobalActionPerformer(mContext, mWindowManagerService);
+
registerBroadcastReceivers();
new AccessibilityContentObserver(mMainHandler).register(
context.getContentResolver());
}
+ @Override
+ public int getCurrentUserIdLocked() {
+ return mCurrentUserId;
+ }
+
+ @Override
+ public boolean isAccessibilityButtonShown() {
+ return mIsAccessibilityButtonShown;
+ }
+
+ @Nullable
+ public FingerprintGestureDispatcher getFingerprintGestureDispatcher() {
+ return mFingerprintGestureDispatcher;
+ }
+
private UserState getUserStateLocked(int userId) {
UserState state = mUserStates.get(userId);
if (state == null) {
@@ -296,10 +303,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
// have different attributes, resolve info (does not support equals),
// etc. Remove them then to force reload.
userState.mInstalledServices.clear();
- if (!userState.isUiAutomationSuppressingOtherServices()) {
- if (readConfigurationForUserStateLocked(userState)) {
- onUserStateChangedLocked(userState);
- }
+ if (readConfigurationForUserStateLocked(userState)) {
+ onUserStateChangedLocked(userState);
}
}
}
@@ -316,7 +321,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
UserState userState = getUserStateLocked(userId);
boolean unboundAService = false;
for (int i = userState.mBoundServices.size() - 1; i >= 0; i--) {
- Service boundService = userState.mBoundServices.get(i);
+ AccessibilityServiceConnection boundService =
+ userState.mBoundServices.get(i);
String servicePkg = boundService.mComponentName.getPackageName();
if (servicePkg.equals(packageName)) {
boundService.unbindLocked();
@@ -355,10 +361,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
Settings.Secure.
TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES,
userState.mTouchExplorationGrantedServices, userId);
- // We will update when the automation service dies.
- if (!userState.isUiAutomationSuppressingOtherServices()) {
- onUserStateChangedLocked(userState);
- }
+ onUserStateChangedLocked(userState);
return;
}
}
@@ -389,10 +392,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
persistComponentNamesToSettingLocked(
Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
userState.mEnabledServices, userId);
- // We will update when the automation service dies.
- if (!userState.isUiAutomationSuppressingOtherServices()) {
- onUserStateChangedLocked(userState);
- }
+ onUserStateChangedLocked(userState);
}
}
}
@@ -426,10 +426,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
// We will update when the automation service dies.
synchronized (mLock) {
UserState userState = getCurrentUserStateLocked();
- if (!userState.isUiAutomationSuppressingOtherServices()) {
- if (readConfigurationForUserStateLocked(userState)) {
- onUserStateChangedLocked(userState);
- }
+ if (readConfigurationForUserStateLocked(userState)) {
+ onUserStateChangedLocked(userState);
}
}
} else if (Intent.ACTION_SETTING_RESTORED.equals(action)) {
@@ -530,6 +528,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
synchronized (mLock) {
notifyAccessibilityServicesDelayedLocked(event, false);
notifyAccessibilityServicesDelayedLocked(event, true);
+ mUiAutomationManager.sendAccessibilityEventLocked(event);
}
}
@@ -546,16 +545,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
// performs the current profile parent resolution.
final int resolvedUserId = mSecurityPolicy
.resolveCallingUserIdEnforcingPermissionsLocked(userId);
- // The automation service is a fake one and should not be reported
- // to clients as being installed - it really is not.
- UserState userState = getUserStateLocked(resolvedUserId);
- if (userState.mUiAutomationService != null) {
- List<AccessibilityServiceInfo> installedServices = new ArrayList<>();
- installedServices.addAll(userState.mInstalledServices);
- installedServices.remove(userState.mUiAutomationService.mAccessibilityServiceInfo);
- return installedServices;
- }
- return userState.mInstalledServices;
+ return getUserStateLocked(resolvedUserId).mInstalledServices;
}
}
@@ -571,18 +561,16 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
// The automation service can suppress other services.
final UserState userState = getUserStateLocked(resolvedUserId);
- if (userState.isUiAutomationSuppressingOtherServices()) {
+ if (mUiAutomationManager.suppressingAccessibilityServicesLocked()) {
return Collections.emptyList();
}
- final List<Service> services = userState.mBoundServices;
+ final List<AccessibilityServiceConnection> services = userState.mBoundServices;
final int serviceCount = services.size();
final List<AccessibilityServiceInfo> result = new ArrayList<>(serviceCount);
for (int i = 0; i < serviceCount; ++i) {
- final Service service = services.get(i);
- // Don't report the UIAutomation (fake service)
- if (!sFakeAccessibilityServiceComponentName.equals(service.mComponentName)
- && (service.mFeedbackType & feedbackType) != 0) {
+ final AccessibilityServiceConnection service = services.get(i);
+ if ((service.mFeedbackType & feedbackType) != 0) {
result.add(service.mAccessibilityServiceInfo);
}
}
@@ -603,11 +591,12 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
if (resolvedUserId != mCurrentUserId) {
return;
}
- List<Service> services = getUserStateLocked(resolvedUserId).mBoundServices;
+ List<AccessibilityServiceConnection> services =
+ getUserStateLocked(resolvedUserId).mBoundServices;
int numServices = services.size();
interfacesToInterrupt = new ArrayList<>(numServices);
for (int i = 0; i < numServices; i++) {
- Service service = services.get(i);
+ AccessibilityServiceConnection service = services.get(i);
IBinder a11yServiceBinder = service.mService;
IAccessibilityServiceClient a11yServiceInterface = service.mServiceInterface;
if ((a11yServiceBinder != null) && (a11yServiceInterface != null)) {
@@ -747,59 +736,19 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
mSecurityPolicy.enforceCallingPermission(Manifest.permission.RETRIEVE_WINDOW_CONTENT,
FUNCTION_REGISTER_UI_TEST_AUTOMATION_SERVICE);
- accessibilityServiceInfo.setComponentName(sFakeAccessibilityServiceComponentName);
-
synchronized (mLock) {
- UserState userState = getCurrentUserStateLocked();
-
- if (userState.mUiAutomationService != null) {
- throw new IllegalStateException("UiAutomationService " + serviceClient
- + "already registered!");
- }
-
- try {
- owner.linkToDeath(userState.mUiAutomationSerivceOnwerDeathRecipient, 0);
- } catch (RemoteException re) {
- Slog.e(LOG_TAG, "Couldn't register for the death of a"
- + " UiTestAutomationService!", re);
- return;
- }
-
- userState.mUiAutomationServiceOwner = owner;
- userState.mUiAutomationServiceClient = serviceClient;
- userState.mUiAutomationFlags = flags;
- userState.mInstalledServices.add(accessibilityServiceInfo);
- if ((flags & UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES) == 0) {
- // Set the temporary state, and use it instead of settings
- userState.mIsTouchExplorationEnabled = false;
- userState.mIsDisplayMagnificationEnabled = false;
- userState.mIsNavBarMagnificationEnabled = false;
- userState.mIsAutoclickEnabled = false;
- userState.mEnabledServices.clear();
- }
- userState.mEnabledServices.add(sFakeAccessibilityServiceComponentName);
- userState.mTouchExplorationGrantedServices.add(sFakeAccessibilityServiceComponentName);
-
- // Use the new state instead of settings.
- onUserStateChangedLocked(userState);
+ mUiAutomationManager.registerUiTestAutomationServiceLocked(owner, serviceClient,
+ mContext, accessibilityServiceInfo, sIdCounter++, mMainHandler, mLock,
+ mSecurityPolicy, this, mWindowManagerService, mGlobalActionPerformer, flags);
+ onUserStateChangedLocked(getCurrentUserStateLocked());
}
}
@Override
public void unregisterUiTestAutomationService(IAccessibilityServiceClient serviceClient) {
synchronized (mLock) {
- UserState userState = getCurrentUserStateLocked();
- // Automation service is not bound, so pretend it died to perform clean up.
- if (userState.mUiAutomationService != null
- && serviceClient != null
- && userState.mUiAutomationService.mServiceInterface != null
- && userState.mUiAutomationService.mServiceInterface.asBinder()
- == serviceClient.asBinder()) {
- userState.mUiAutomationService.binderDied();
- } else {
- throw new IllegalStateException("UiAutomationService " + serviceClient
- + " not registered!");
- }
+ mUiAutomationManager.unregisterUiTestAutomationServiceLocked(serviceClient);
+ onUserStateChangedLocked(getCurrentUserStateLocked());
}
}
@@ -816,11 +765,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
// Set the temporary state.
UserState userState = getCurrentUserStateLocked();
- // This is a nop if UI automation is enabled.
- if (userState.isUiAutomationSuppressingOtherServices()) {
- return;
- }
-
userState.mIsTouchExplorationEnabled = touchExplorationEnabled;
userState.mIsDisplayMagnificationEnabled = false;
userState.mIsNavBarMagnificationEnabled = false;
@@ -910,7 +854,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
@VisibleForTesting
public boolean notifyKeyEvent(KeyEvent event, int policyFlags) {
synchronized (mLock) {
- List<Service> boundServices = getCurrentUserStateLocked().mBoundServices;
+ List<AccessibilityServiceConnection> boundServices =
+ getCurrentUserStateLocked().mBoundServices;
if (boundServices.isEmpty()) {
return false;
}
@@ -950,6 +895,22 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
}
}
+ @Override
+ public MotionEventInjector getMotionEventInjectorLocked() {
+ final long endMillis = SystemClock.uptimeMillis() + WAIT_MOTION_INJECTOR_TIMEOUT_MILLIS;
+ while ((mMotionEventInjector == null) && (SystemClock.uptimeMillis() < endMillis)) {
+ try {
+ mLock.wait(endMillis - SystemClock.uptimeMillis());
+ } catch (InterruptedException ie) {
+ /* ignore */
+ }
+ }
+ if (mMotionEventInjector == null) {
+ Slog.e(LOG_TAG, "MotionEventInjector installation timed out");
+ }
+ return mMotionEventInjector;
+ }
+
/**
* Gets a point within the accessibility focused node where we can send down
* and up events to perform a click.
@@ -1038,10 +999,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
mCurrentUserId = userId;
UserState userState = getCurrentUserStateLocked();
- if (userState.mUiAutomationService != null) {
- // Switching users disables the UI automation service.
- userState.mUiAutomationService.binderDied();
- }
readConfigurationForUserStateLocked(userState);
// Even if reading did not yield change, we have to update
@@ -1110,7 +1067,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
// enabled accessibility services.
UserState state = getCurrentUserStateLocked();
for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
- Service service = state.mBoundServices.get(i);
+ AccessibilityServiceConnection service = state.mBoundServices.get(i);
if (service.mRequestTouchExplorationMode && service.mIsDefault == isDefault) {
service.notifyGesture(gestureId);
return true;
@@ -1122,7 +1079,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
private void notifyClearAccessibilityCacheLocked() {
UserState state = getCurrentUserStateLocked();
for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
- Service service = state.mBoundServices.get(i);
+ AccessibilityServiceConnection service = state.mBoundServices.get(i);
service.notifyClearAccessibilityNodeInfoCache();
}
}
@@ -1131,7 +1088,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
float scale, float centerX, float centerY) {
final UserState state = getCurrentUserStateLocked();
for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
- final Service service = state.mBoundServices.get(i);
+ final AccessibilityServiceConnection service = state.mBoundServices.get(i);
service.notifyMagnificationChangedLocked(region, scale, centerX, centerY);
}
}
@@ -1139,7 +1096,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
private void notifySoftKeyboardShowModeChangedLocked(int showMode) {
final UserState state = getCurrentUserStateLocked();
for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
- final Service service = state.mBoundServices.get(i);
+ final AccessibilityServiceConnection service = state.mBoundServices.get(i);
service.notifySoftKeyboardShowModeChangedLocked(showMode);
}
}
@@ -1149,7 +1106,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
int potentialTargets = state.mIsNavBarMagnificationEnabled ? 1 : 0;
for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
- final Service service = state.mBoundServices.get(i);
+ final AccessibilityServiceConnection service = state.mBoundServices.get(i);
if (service.mRequestAccessibilityButton) {
potentialTargets++;
}
@@ -1165,7 +1122,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
return;
} else {
for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
- final Service service = state.mBoundServices.get(i);
+ final AccessibilityServiceConnection service = state.mBoundServices.get(i);
if (service.mRequestAccessibilityButton) {
service.notifyAccessibilityButtonClickedLocked();
return;
@@ -1184,7 +1141,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
return;
} else {
for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
- final Service service = state.mBoundServices.get(i);
+ final AccessibilityServiceConnection service = state.mBoundServices.get(i);
if (service.mRequestAccessibilityButton && (service.mComponentName.equals(
state.mServiceAssignedToAccessibilityButton))) {
service.notifyAccessibilityButtonClickedLocked();
@@ -1202,10 +1159,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
final UserState state = getCurrentUserStateLocked();
mIsAccessibilityButtonShown = available;
for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
- final Service service = state.mBoundServices.get(i);
- if (service.mRequestAccessibilityButton) {
- service.notifyAccessibilityButtonAvailabilityChangedLocked(
- service.isAccessibilityButtonAvailableLocked(state));
+ final AccessibilityServiceConnection clientConnection = state.mBoundServices.get(i);
+ if (clientConnection.mRequestAccessibilityButton) {
+ clientConnection.notifyAccessibilityButtonAvailabilityChangedLocked(
+ clientConnection.isAccessibilityButtonAvailableLocked(state));
}
}
}
@@ -1281,9 +1238,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
if (!mTempComponentNameSet.equals(userState.mEnabledServices)) {
userState.mEnabledServices.clear();
userState.mEnabledServices.addAll(mTempComponentNameSet);
- if (userState.mUiAutomationService != null) {
- userState.mEnabledServices.add(sFakeAccessibilityServiceComponentName);
- }
mTempComponentNameSet.clear();
return true;
}
@@ -1319,16 +1273,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
try {
UserState state = getCurrentUserStateLocked();
for (int i = 0, count = state.mBoundServices.size(); i < count; i++) {
- Service service = state.mBoundServices.get(i);
+ AccessibilityServiceConnection service = state.mBoundServices.get(i);
if (service.mIsDefault == isDefault) {
- if (doesServiceWantEventLocked(service, event)) {
- service.notifyAccessibilityEvent(event, true);
- } else if (service.mUsesAccessibilityCache
- && (AccessibilityCache.CACHE_CRITICAL_EVENTS_MASK
- & event.getEventType()) != 0) {
- service.notifyAccessibilityEvent(event, false);
- }
+ service.notifyAccessibilityEvent(event);
}
}
} catch (IndexOutOfBoundsException oobe) {
@@ -1338,42 +1286,12 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
}
}
- private void addServiceLocked(Service service, UserState userState) {
- try {
- if (!userState.mBoundServices.contains(service)) {
- service.onAdded();
- userState.mBoundServices.add(service);
- userState.mComponentNameToServiceMap.put(service.mComponentName, service);
- scheduleNotifyClientsOfServicesStateChange(userState);
- }
- } catch (RemoteException re) {
- /* do nothing */
- }
- }
-
- /**
- * Removes a service.
- *
- * @param service The service.
- */
- private void removeServiceLocked(Service service, UserState userState) {
- userState.mBoundServices.remove(service);
- service.onRemoved();
- // It may be possible to bind a service twice, which confuses the map. Rebuild the map
- // to make sure we can still reach a service
- userState.mComponentNameToServiceMap.clear();
- for (int i = 0; i < userState.mBoundServices.size(); i++) {
- Service boundService = userState.mBoundServices.get(i);
- userState.mComponentNameToServiceMap.put(boundService.mComponentName, boundService);
- }
- scheduleNotifyClientsOfServicesStateChange(userState);
- }
-
private void updateRelevantEventsLocked(UserState userState) {
int relevantEventTypes = AccessibilityCache.CACHE_CRITICAL_EVENTS_MASK;
- for (Service service : userState.mBoundServices) {
+ for (AccessibilityServiceConnection service : userState.mBoundServices) {
relevantEventTypes |= service.mEventTypes;
}
+ relevantEventTypes |= mUiAutomationManager.getRequestedEventMaskLocked();
int finalRelevantEventTypes = relevantEventTypes;
if (userState.mLastSentRelevantEventTypes != finalRelevantEventTypes) {
@@ -1398,48 +1316,12 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
userState.mUserClients.broadcast(clientAction);
}
- /**
- * Determines if given event can be dispatched to a service based on the package of the
- * event source. Specifically, a service is notified if it is interested in events from the
- * package.
- *
- * @param service The potential receiver.
- * @param event The event.
- * @return True if the listener should be notified, false otherwise.
- */
- private boolean doesServiceWantEventLocked(Service service, AccessibilityEvent event) {
-
- if (!service.canReceiveEventsLocked()) {
- return false;
- }
-
- if ((event.getWindowId() != AccessibilityWindowInfo.UNDEFINED_WINDOW_ID)
- && !event.isImportantForAccessibility()
- && (service.mFetchFlags & AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS)
- == 0) {
- return false;
- }
-
- int eventType = event.getEventType();
- if ((service.mEventTypes & eventType) != eventType) {
- return false;
- }
-
- Set<String> packageNames = service.mPackageNames;
- String packageName = (event.getPackageName() != null)
- ? event.getPackageName().toString() : null;
-
- return (packageNames.isEmpty() || packageNames.contains(packageName));
- }
-
private void unbindAllServicesLocked(UserState userState) {
- List<Service> services = userState.mBoundServices;
- for (int i = 0, count = services.size(); i < count; i++) {
- Service service = services.get(i);
- if (service.unbindLocked()) {
- i--;
- count--;
- }
+ List<AccessibilityServiceConnection> services = userState.mBoundServices;
+ for (int count = services.size(); count > 1; count--) {
+ // When the service is unbound, it disappears from the list, so there's no need to
+ // keep track of the index
+ services.get(0).unbindLocked();
}
}
@@ -1489,14 +1371,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
}
}
- /**
- * Persists the component names in the specified setting in a
- * colon separated fashion.
- *
- * @param settingName The setting name.
- * @param componentNames The component names.
- */
- private void persistComponentNamesToSettingLocked(String settingName,
+ @Override
+ public void persistComponentNamesToSettingLocked(String settingName,
Set<ComponentName> componentNames, int userId) {
StringBuilder builder = new StringBuilder();
for (ComponentName componentName : componentNames) {
@@ -1515,7 +1391,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
}
private void updateServicesLocked(UserState userState) {
- Map<ComponentName, Service> componentNameToServiceMap =
+ Map<ComponentName, AccessibilityServiceConnection> componentNameToServiceMap =
userState.mComponentNameToServiceMap;
boolean isUnlockingOrUnlocked = LocalServices.getService(UserManagerInternal.class)
.isUserUnlockingOrUnlocked(userState.mUserId);
@@ -1525,7 +1401,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
ComponentName componentName = ComponentName.unflattenFromString(
installedService.getId());
- Service service = componentNameToServiceMap.get(componentName);
+ AccessibilityServiceConnection service = componentNameToServiceMap.get(componentName);
// Ignore non-encryption-aware services until user is unlocked
if (!isUnlockingOrUnlocked && !installedService.isDirectBootAware()) {
@@ -1537,9 +1413,12 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
if (userState.mBindingServices.contains(componentName)) {
continue;
}
- if (userState.mEnabledServices.contains(componentName)) {
+ if (userState.mEnabledServices.contains(componentName)
+ && !mUiAutomationManager.suppressingAccessibilityServicesLocked()) {
if (service == null) {
- service = new Service(userState.mUserId, componentName, installedService);
+ service = new AccessibilityServiceConnection(userState, mContext, componentName,
+ installedService, sIdCounter++, mMainHandler, mLock, mSecurityPolicy,
+ this, mWindowManagerService, mGlobalActionPerformer);
} else if (userState.mBoundServices.contains(service)) {
continue;
}
@@ -1600,6 +1479,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
}
private void updateInputFilter(UserState userState) {
+ if (mUiAutomationManager.suppressingAccessibilityServicesLocked()) return;
+
boolean setInputFilter = false;
AccessibilityInputFilter inputFilter = null;
synchronized (mLock) {
@@ -1614,8 +1495,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
flags |= AccessibilityInputFilter.FLAG_FEATURE_CONTROL_SCREEN_MAGNIFIER;
}
// Touch exploration without accessibility makes no sense.
- if (userState.isHandlingAccessibilityEvents()
- && userState.mIsTouchExplorationEnabled) {
+ if (userState.isHandlingAccessibilityEvents() && userState.mIsTouchExplorationEnabled) {
flags |= AccessibilityInputFilter.FLAG_FEATURE_TOUCH_EXPLORATION;
}
if (userState.mIsFilterKeyEventsEnabled) {
@@ -1652,13 +1532,13 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
}
}
- private void showEnableTouchExplorationDialog(final Service service) {
+ private void showEnableTouchExplorationDialog(final AccessibilityServiceConnection service) {
synchronized (mLock) {
- String label = service.mResolveInfo.loadLabel(
- mContext.getPackageManager()).toString();
+ String label = service.getServiceInfo().getResolveInfo()
+ .loadLabel(mContext.getPackageManager()).toString();
- final UserState state = getCurrentUserStateLocked();
- if (state.mIsTouchExplorationEnabled) {
+ final UserState userState = getCurrentUserStateLocked();
+ if (userState.mIsTouchExplorationEnabled) {
return;
}
if (mEnableTouchExplorationDialog != null
@@ -1671,18 +1551,17 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
@Override
public void onClick(DialogInterface dialog, int which) {
// The user allowed the service to toggle touch exploration.
- state.mTouchExplorationGrantedServices.add(service.mComponentName);
+ userState.mTouchExplorationGrantedServices.add(service.mComponentName);
persistComponentNamesToSettingLocked(
Settings.Secure.TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES,
- state.mTouchExplorationGrantedServices, state.mUserId);
+ userState.mTouchExplorationGrantedServices, userState.mUserId);
// Enable touch exploration.
- UserState userState = getUserStateLocked(service.mUserId);
userState.mIsTouchExplorationEnabled = true;
final long identity = Binder.clearCallingIdentity();
try {
Settings.Secure.putIntForUser(mContext.getContentResolver(),
Settings.Secure.TOUCH_EXPLORATION_ENABLED, 1,
- service.mUserId);
+ userState.mUserId);
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -1745,10 +1624,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
// is typically the one that operates with interactive windows, So,
// this is fine. Note that to allow a service to work across windows
// we have to allow accessibility focus stay in any of them. Sigh...
- List<Service> boundServices = userState.mBoundServices;
+ List<AccessibilityServiceConnection> boundServices = userState.mBoundServices;
final int boundServiceCount = boundServices.size();
for (int i = 0; i < boundServiceCount; i++) {
- Service boundService = boundServices.get(i);
+ AccessibilityServiceConnection boundService = boundServices.get(i);
if (boundService.canRetrieveInteractiveWindowsLocked()) {
userState.mAccessibilityFocusOnlyInActiveWindow = false;
return;
@@ -1764,20 +1643,25 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
// binding we do an update pass after each bind event, so we run this
// code and register the callback if needed.
- List<Service> boundServices = userState.mBoundServices;
+ boolean observingWindows = mUiAutomationManager.canRetrieveInteractiveWindowsLocked();
+ List<AccessibilityServiceConnection> boundServices = userState.mBoundServices;
final int boundServiceCount = boundServices.size();
- for (int i = 0; i < boundServiceCount; i++) {
- Service boundService = boundServices.get(i);
+ for (int i = 0; !observingWindows && (i < boundServiceCount); i++) {
+ AccessibilityServiceConnection boundService = boundServices.get(i);
if (boundService.canRetrieveInteractiveWindowsLocked()) {
- if (mWindowsForAccessibilityCallback == null) {
- mWindowsForAccessibilityCallback = new WindowsForAccessibilityCallback();
- mWindowManagerService.setWindowsForAccessibilityCallback(
- mWindowsForAccessibilityCallback);
- }
- return;
+ observingWindows = true;
}
}
+ if (observingWindows) {
+ if (mWindowsForAccessibilityCallback == null) {
+ mWindowsForAccessibilityCallback = new WindowsForAccessibilityCallback();
+ mWindowManagerService.setWindowsForAccessibilityCallback(
+ mWindowsForAccessibilityCallback);
+ }
+ return;
+ }
+
if (mWindowsForAccessibilityCallback != null) {
mWindowsForAccessibilityCallback = null;
mWindowManagerService.setWindowsForAccessibilityCallback(null);
@@ -1811,7 +1695,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
private void updatePerformGesturesLocked(UserState userState) {
final int serviceCount = userState.mBoundServices.size();
for (int i = 0; i < serviceCount; i++) {
- Service service = userState.mBoundServices.get(i);
+ AccessibilityServiceConnection service = userState.mBoundServices.get(i);
if ((service.mAccessibilityServiceInfo.getCapabilities()
& AccessibilityServiceInfo.CAPABILITY_CAN_PERFORM_GESTURES) != 0) {
userState.mIsPerformGesturesEnabled = true;
@@ -1824,7 +1708,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
private void updateFilterKeyEventsLocked(UserState userState) {
final int serviceCount = userState.mBoundServices.size();
for (int i = 0; i < serviceCount; i++) {
- Service service = userState.mBoundServices.get(i);
+ AccessibilityServiceConnection service = userState.mBoundServices.get(i);
if (service.mRequestFilterKeyEvents
&& (service.mAccessibilityServiceInfo.getCapabilities()
& AccessibilityServiceInfo
@@ -1851,10 +1735,12 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
private void updateAccessibilityEnabledSetting(UserState userState) {
final long identity = Binder.clearCallingIdentity();
+ final boolean isA11yEnabled = mUiAutomationManager.isUiAutomationRunningLocked()
+ || userState.isHandlingAccessibilityEvents();
try {
Settings.Secure.putIntForUser(mContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_ENABLED,
- userState.isHandlingAccessibilityEvents() ? 1 : 0,
+ (isA11yEnabled) ? 1 : 0,
userState.mUserId);
} finally {
Binder.restoreCallingIdentity(identity);
@@ -1927,11 +1813,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
}
private void updateTouchExplorationLocked(UserState userState) {
- boolean enabled = false;
+ boolean enabled = mUiAutomationManager.isTouchExplorationEnabledLocked();
final int serviceCount = userState.mBoundServices.size();
for (int i = 0; i < serviceCount; i++) {
- Service service = userState.mBoundServices.get(i);
- if (canRequestAndRequestsTouchExplorationLocked(service)) {
+ AccessibilityServiceConnection service = userState.mBoundServices.get(i);
+ if (canRequestAndRequestsTouchExplorationLocked(service, userState)) {
enabled = true;
break;
}
@@ -2033,21 +1919,17 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
}
}
- private boolean canRequestAndRequestsTouchExplorationLocked(Service service) {
+ private boolean canRequestAndRequestsTouchExplorationLocked(
+ AccessibilityServiceConnection service, UserState userState) {
// Service not ready or cannot request the feature - well nothing to do.
if (!service.canReceiveEventsLocked() || !service.mRequestTouchExplorationMode) {
return false;
}
- // UI test automation service can always enable it.
- if (service.mIsAutomation) {
- return true;
- }
- if (service.mResolveInfo.serviceInfo.applicationInfo.targetSdkVersion
+ if (service.getServiceInfo().getResolveInfo().serviceInfo.applicationInfo.targetSdkVersion
<= Build.VERSION_CODES.JELLY_BEAN_MR1) {
// Up to JB-MR1 we had a white list with services that can enable touch
// exploration. When a service is first started we show a dialog to the
// use to get a permission to white list the service.
- UserState userState = getUserStateLocked(service.mUserId);
if (userState.mTouchExplorationGrantedServices.contains(service.mComponentName)) {
return true;
} else if (mEnableTouchExplorationDialog == null
@@ -2081,8 +1963,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
return;
}
- if (userState.mIsDisplayMagnificationEnabled || userState.mIsNavBarMagnificationEnabled
- || userHasListeningMagnificationServicesLocked(userState)) {
+ if (!mUiAutomationManager.suppressingAccessibilityServicesLocked()
+ && (userState.mIsDisplayMagnificationEnabled
+ || userState.mIsNavBarMagnificationEnabled
+ || userHasListeningMagnificationServicesLocked(userState))) {
// Initialize the magnification controller if necessary
getMagnificationController();
mMagnificationController.register();
@@ -2096,9 +1980,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
* controlling magnification.
*/
private boolean userHasMagnificationServicesLocked(UserState userState) {
- final List<Service> services = userState.mBoundServices;
+ final List<AccessibilityServiceConnection> services = userState.mBoundServices;
for (int i = 0, count = services.size(); i < count; i++) {
- final Service service = services.get(i);
+ final AccessibilityServiceConnection service = services.get(i);
if (mSecurityPolicy.canControlMagnification(service)) {
return true;
}
@@ -2111,11 +1995,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
* controlling magnification and are actively listening for magnification updates.
*/
private boolean userHasListeningMagnificationServicesLocked(UserState userState) {
- final List<Service> services = userState.mBoundServices;
+ final List<AccessibilityServiceConnection> services = userState.mBoundServices;
for (int i = 0, count = services.size(); i < count; i++) {
- final Service service = services.get(i);
+ final AccessibilityServiceConnection service = services.get(i);
if (mSecurityPolicy.canControlMagnification(service)
- && service.mInvocationHandler.mIsMagnificationCallbackEnabled) {
+ && service.isMagnificationCallbackEnabled()) {
return true;
}
}
@@ -2127,7 +2011,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
// Only check whether we need to reset the soft keyboard mode if it is not set to the
// default.
if ((userId == mCurrentUserId) && (userState.mSoftKeyboardShowMode != 0)) {
- // Check whether the last Accessibility Service that changed the soft keyboard mode to
+ // Check whether the last AccessibilityService that changed the soft keyboard mode to
// something other than the default is still enabled and, if not, remove flag and
// reset to the default soft keyboard behavior.
boolean serviceChangingSoftKeyboardModeIsEnabled =
@@ -2151,7 +2035,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
}
private void updateFingerprintGestureHandling(UserState userState) {
- final List<Service> services;
+ final List<AccessibilityServiceConnection> services;
synchronized (mLock) {
services = userState.mBoundServices;
if ((mFingerprintGestureDispatcher == null)
@@ -2184,7 +2068,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
private void updateAccessibilityButtonTargetsLocked(UserState userState) {
for (int i = userState.mBoundServices.size() - 1; i >= 0; i--) {
- final Service service = userState.mBoundServices.get(i);
+ final AccessibilityServiceConnection service = userState.mBoundServices.get(i);
if (service.mRequestAccessibilityButton) {
service.notifyAccessibilityButtonAvailabilityChangedLocked(
service.isAccessibilityButtonAvailableLocked(userState));
@@ -2193,7 +2077,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
}
@GuardedBy("mLock")
- private MagnificationSpec getCompatibleMagnificationSpecLocked(int windowId) {
+ @Override
+ public MagnificationSpec getCompatibleMagnificationSpecLocked(int windowId) {
IBinder windowToken = mGlobalWindowTokens.get(windowId);
if (windowToken == null) {
windowToken = getCurrentUserStateLocked().mWindowTokens.get(windowId);
@@ -2205,7 +2090,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
return null;
}
- private KeyEventDispatcher getKeyEventDispatcher() {
+ @Override
+ public KeyEventDispatcher getKeyEventDispatcher() {
if (mKeyEventDispatcher == null) {
mKeyEventDispatcher = new KeyEventDispatcher(
mMainHandler, MainHandler.MSG_SEND_KEY_EVENT_TO_INPUT_FILTER, mLock,
@@ -2214,6 +2100,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
return mKeyEventDispatcher;
}
+ @Override
+ public PendingIntent getPendingIntentActivity(Context context, int requestCode, Intent intent,
+ int flags) {
+ return PendingIntent.getActivity(context, requestCode, intent, flags);
+ }
/**
* AIDL-exposed method to be called when the accessibility shortcut is enabled. Requires
* permission to write secure settings, since someone with that permission can enable
@@ -2317,9 +2208,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
pw.append(", navBarMagnificationEnabled="
+ userState.mIsNavBarMagnificationEnabled);
pw.append(", autoclickEnabled=" + userState.mIsAutoclickEnabled);
- if (userState.mUiAutomationService != null) {
+ if (mUiAutomationManager.isUiAutomationRunningLocked()) {
pw.append(", ");
- userState.mUiAutomationService.dump(fd, pw, args);
+ mUiAutomationManager.dumpUiAutomationService(fd, pw, args);
pw.println();
}
pw.append("}");
@@ -2332,7 +2223,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
pw.println();
pw.append(" ");
}
- Service service = userState.mBoundServices.get(j);
+ AccessibilityServiceConnection service = userState.mBoundServices.get(j);
service.dump(fd, pw, args);
}
pw.println("}]");
@@ -2383,7 +2274,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
}
}
- private final class MainHandler extends Handler {
+ final class MainHandler extends Handler {
public static final int MSG_SEND_ACCESSIBILITY_EVENT_TO_INPUT_FILTER = 1;
public static final int MSG_SEND_STATE_TO_CLIENTS = 2;
public static final int MSG_SEND_CLEARED_STATE_TO_CLIENTS_FOR_USER = 3;
@@ -2450,7 +2341,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
} break;
case MSG_SHOW_ENABLED_TOUCH_EXPLORATION_DIALOG: {
- Service service = (Service) msg.obj;
+ AccessibilityServiceConnection service =
+ (AccessibilityServiceConnection) msg.obj;
showEnableTouchExplorationDialog(service);
} break;
@@ -2498,7 +2390,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
} break;
case MSG_INIT_SERVICE: {
- final Service service = (Service) msg.obj;
+ final AccessibilityServiceConnection service =
+ (AccessibilityServiceConnection) msg.obj;
service.initializeService();
} break;
}
@@ -2570,7 +2463,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
return -1;
}
- private void ensureWindowsAvailableTimed() {
+ @Override
+ public void ensureWindowsAvailableTimed() {
synchronized (mLock) {
if (mSecurityPolicy.mWindows != null) {
return;
@@ -2603,7 +2497,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
}
}
- MagnificationController getMagnificationController() {
+ @Override
+ public MagnificationController getMagnificationController() {
synchronized (mLock) {
if (mMagnificationController == null) {
mMagnificationController = new MagnificationController(mContext, this, mLock);
@@ -2613,1620 +2508,96 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
}
}
- /**
- * This class represents an accessibility service. It stores all per service
- * data required for the service management, provides API for starting/stopping the
- * service and is responsible for adding/removing the service in the data structures
- * for service management. The class also exposes configuration interface that is
- * passed to the service it represents as soon it is bound. It also serves as the
- * connection for the service.
- */
- class Service extends IAccessibilityServiceConnection.Stub
- implements ServiceConnection, DeathRecipient, KeyEventDispatcher.KeyEventFilter,
- FingerprintGestureDispatcher.FingerprintGestureClient {
-
- final int mUserId;
-
- int mId = 0;
-
- AccessibilityServiceInfo mAccessibilityServiceInfo;
-
- // The service that's bound to this instance. Whenever this value is non-null, this
- // object is registered as a death recipient
- IBinder mService;
-
- IAccessibilityServiceClient mServiceInterface;
-
- int mEventTypes;
-
- int mFeedbackType;
-
- Set<String> mPackageNames = new HashSet<>();
-
- boolean mIsDefault;
-
- boolean mRequestTouchExplorationMode;
-
- boolean mRequestFilterKeyEvents;
-
- boolean mRetrieveInteractiveWindows;
-
- boolean mCaptureFingerprintGestures;
-
- boolean mRequestAccessibilityButton;
-
- boolean mReceivedAccessibilityButtonCallbackSinceBind;
-
- boolean mLastAccessibilityButtonCallbackState;
-
- int mFetchFlags;
-
- long mNotificationTimeout;
-
- ComponentName mComponentName;
-
- Intent mIntent;
-
- boolean mIsAutomation;
-
- final ResolveInfo mResolveInfo;
-
- final IBinder mOverlayWindowToken = new Binder();
-
- // the events pending events to be dispatched to this service
- final SparseArray<AccessibilityEvent> mPendingEvents =
- new SparseArray<>();
-
- boolean mWasConnectedAndDied;
-
- /** Whether this service relies on its {@link AccessibilityCache} being up to date */
- boolean mUsesAccessibilityCache = false;
-
- // Handler only for dispatching accessibility events since we use event
- // types as message types allowing us to remove messages per event type.
- public Handler mEventDispatchHandler = new Handler(mMainHandler.getLooper()) {
- @Override
- public void handleMessage(Message message) {
- final int eventType = message.what;
- AccessibilityEvent event = (AccessibilityEvent) message.obj;
- boolean serviceWantsEvent = message.arg1 != 0;
- notifyAccessibilityEventInternal(eventType, event, serviceWantsEvent);
- }
- };
-
- // Handler for scheduling method invocations on the main thread.
- public final InvocationHandler mInvocationHandler = new InvocationHandler(
- mMainHandler.getLooper());
-
- public Service(int userId, ComponentName componentName,
- AccessibilityServiceInfo accessibilityServiceInfo) {
- mUserId = userId;
- mResolveInfo = accessibilityServiceInfo.getResolveInfo();
- mId = sIdCounter++;
- mComponentName = componentName;
- mAccessibilityServiceInfo = accessibilityServiceInfo;
- mIsAutomation = (sFakeAccessibilityServiceComponentName.equals(componentName));
- if (!mIsAutomation) {
- mIntent = new Intent().setComponent(mComponentName);
- mIntent.putExtra(Intent.EXTRA_CLIENT_LABEL,
- com.android.internal.R.string.accessibility_binding_label);
- final long idendtity = Binder.clearCallingIdentity();
- try {
- mIntent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivity(
- mContext, 0, new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS), 0));
- } finally {
- Binder.restoreCallingIdentity(idendtity);
- }
- }
- setDynamicallyConfigurableProperties(accessibilityServiceInfo);
- }
-
- @Override
- public boolean onKeyEvent(KeyEvent keyEvent, int sequenceNumber) {
- if (!mRequestFilterKeyEvents || (mServiceInterface == null)) {
- return false;
- }
- if((mAccessibilityServiceInfo.getCapabilities()
- & AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS) == 0) {
- return false;
- }
- try {
- mServiceInterface.onKeyEvent(keyEvent, sequenceNumber);
- } catch (RemoteException e) {
- return false;
- }
- return true;
- }
-
- @Override
- public boolean isCapturingFingerprintGestures() {
- return (mServiceInterface != null)
- && mSecurityPolicy.canCaptureFingerprintGestures(this)
- && mCaptureFingerprintGestures;
- }
-
- @Override
- public void onFingerprintGestureDetectionActiveChanged(boolean active) {
- if (!isCapturingFingerprintGestures()) {
- return;
- }
- IAccessibilityServiceClient serviceInterface;
- synchronized (mLock) {
- serviceInterface = mServiceInterface;
- }
- if (serviceInterface != null) {
- try {
- mServiceInterface.onFingerprintCapturingGesturesChanged(active);
- } catch (RemoteException e) {
- }
- }
- }
-
- @Override
- public void onFingerprintGesture(int gesture) {
- if (!isCapturingFingerprintGestures()) {
- return;
- }
- IAccessibilityServiceClient serviceInterface;
- synchronized (mLock) {
- serviceInterface = mServiceInterface;
- }
- if (serviceInterface != null) {
- try {
- mServiceInterface.onFingerprintGesture(gesture);
- } catch (RemoteException e) {
- }
- }
- }
-
- public void setDynamicallyConfigurableProperties(AccessibilityServiceInfo info) {
- mEventTypes = info.eventTypes;
- mFeedbackType = info.feedbackType;
- String[] packageNames = info.packageNames;
- if (packageNames != null) {
- mPackageNames.addAll(Arrays.asList(packageNames));
- }
- mNotificationTimeout = info.notificationTimeout;
- mIsDefault = (info.flags & DEFAULT) != 0;
-
- if (mIsAutomation || info.getResolveInfo().serviceInfo.applicationInfo.targetSdkVersion
- >= Build.VERSION_CODES.JELLY_BEAN) {
- if ((info.flags & AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS) != 0) {
- mFetchFlags |= AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;
- } else {
- mFetchFlags &= ~AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;
- }
- }
-
- if ((info.flags & AccessibilityServiceInfo.FLAG_REPORT_VIEW_IDS) != 0) {
- mFetchFlags |= AccessibilityNodeInfo.FLAG_REPORT_VIEW_IDS;
- } else {
- mFetchFlags &= ~AccessibilityNodeInfo.FLAG_REPORT_VIEW_IDS;
- }
-
- mRequestTouchExplorationMode = (info.flags
- & AccessibilityServiceInfo.FLAG_REQUEST_TOUCH_EXPLORATION_MODE) != 0;
- mRequestFilterKeyEvents = (info.flags
- & AccessibilityServiceInfo.FLAG_REQUEST_FILTER_KEY_EVENTS) != 0;
- mRetrieveInteractiveWindows = (info.flags
- & AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS) != 0;
- mCaptureFingerprintGestures = (info.flags
- & AccessibilityServiceInfo.FLAG_REQUEST_FINGERPRINT_GESTURES) != 0;
- mRequestAccessibilityButton = (info.flags
- & AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0;
- }
-
- /**
- * Binds to the accessibility service.
- *
- * @return True if binding is successful.
- */
- public boolean bindLocked() {
- UserState userState = getUserStateLocked(mUserId);
- if (!mIsAutomation) {
- final long identity = Binder.clearCallingIdentity();
- try {
- if (mService == null && mContext.bindServiceAsUser(
- mIntent, this,
- Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE,
- new UserHandle(mUserId))) {
- userState.mBindingServices.add(mComponentName);
- }
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- } else {
- userState.mBindingServices.add(mComponentName);
- mMainHandler.post(new Runnable() {
- @Override
- public void run() {
- // Simulate asynchronous connection since in onServiceConnected
- // we may modify the state data in case of an error but bind is
- // called while iterating over the data and bad things can happen.
- onServiceConnected(mComponentName,
- userState.mUiAutomationServiceClient.asBinder());
- }
- });
- userState.mUiAutomationService = this;
- }
- return false;
- }
-
- /**
- * Unbinds from the accessibility service and removes it from the data
- * structures for service management.
- *
- * @return True if unbinding is successful.
- */
- public boolean unbindLocked() {
- UserState userState = getUserStateLocked(mUserId);
- getKeyEventDispatcher().flush(this);
- if (!mIsAutomation) {
- mContext.unbindService(this);
- } else {
- userState.destroyUiAutomationService();
- }
- removeServiceLocked(this, userState);
- resetLocked();
- return true;
- }
-
- @Override
- public void disableSelf() {
- synchronized(mLock) {
- UserState userState = getUserStateLocked(mUserId);
- if (userState.mEnabledServices.remove(mComponentName)) {
- final long identity = Binder.clearCallingIdentity();
- try {
- persistComponentNamesToSettingLocked(
- Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
- userState.mEnabledServices, mUserId);
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- onUserStateChangedLocked(userState);
- }
- }
- }
-
- public boolean canReceiveEventsLocked() {
- return (mEventTypes != 0 && mFeedbackType != 0 && mService != null);
- }
-
- @Override
- public void setOnKeyEventResult(boolean handled, int sequence) {
- getKeyEventDispatcher().setOnKeyEventResult(this, handled, sequence);
- }
-
- @Override
- public AccessibilityServiceInfo getServiceInfo() {
- synchronized (mLock) {
- return mAccessibilityServiceInfo;
- }
- }
-
- public boolean canRetrieveInteractiveWindowsLocked() {
- return mSecurityPolicy.canRetrieveWindowContentLocked(this)
- && mRetrieveInteractiveWindows;
- }
-
- @Override
- public void setServiceInfo(AccessibilityServiceInfo info) {
- final long identity = Binder.clearCallingIdentity();
- try {
- synchronized (mLock) {
- // If the XML manifest had data to configure the service its info
- // should be already set. In such a case update only the dynamically
- // configurable properties.
- AccessibilityServiceInfo oldInfo = mAccessibilityServiceInfo;
- if (oldInfo != null) {
- oldInfo.updateDynamicallyConfigurableProperties(info);
- setDynamicallyConfigurableProperties(oldInfo);
- } else {
- setDynamicallyConfigurableProperties(info);
- }
- UserState userState = getUserStateLocked(mUserId);
- onUserStateChangedLocked(userState);
- scheduleNotifyClientsOfServicesStateChange(userState);
- }
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
-
- @Override
- public void onServiceConnected(ComponentName componentName, IBinder service) {
- synchronized (mLock) {
- if (mService != service) {
- if (mService != null) {
- mService.unlinkToDeath(this, 0);
- }
- mService = service;
- try {
- mService.linkToDeath(this, 0);
- } catch (RemoteException re) {
- Slog.e(LOG_TAG, "Failed registering death link");
- binderDied();
- return;
- }
- }
- mServiceInterface = IAccessibilityServiceClient.Stub.asInterface(service);
- UserState userState = getUserStateLocked(mUserId);
- addServiceLocked(this, userState);
- if (userState.mBindingServices.contains(mComponentName) || mWasConnectedAndDied) {
- userState.mBindingServices.remove(mComponentName);
- mWasConnectedAndDied = false;
- onUserStateChangedLocked(userState);
- // Initialize the service on the main handler after we're done setting up for
- // the new configuration (for example, initializing the input filter).
- mMainHandler.obtainMessage(MainHandler.MSG_INIT_SERVICE, this).sendToTarget();
- } else {
- binderDied();
- }
- }
- }
-
- private void initializeService() {
- final IAccessibilityServiceClient serviceInterface;
- synchronized (mLock) {
- serviceInterface = mServiceInterface;
- }
- if (serviceInterface == null) return;
- try {
- serviceInterface.init(this, mId, mOverlayWindowToken);
- } catch (RemoteException re) {
- Slog.w(LOG_TAG, "Error while setting connection for service: "
- + serviceInterface, re);
- binderDied();
- }
- }
-
- private boolean isCalledForCurrentUserLocked() {
- // We treat calls from a profile as if made by its parent as profiles
- // share the accessibility state of the parent. The call below
- // performs the current profile parent resolution.
- final int resolvedUserId = mSecurityPolicy
- .resolveCallingUserIdEnforcingPermissionsLocked(UserHandle.USER_CURRENT);
- return resolvedUserId == mCurrentUserId;
- }
-
- @Override
- public List<AccessibilityWindowInfo> getWindows() {
- ensureWindowsAvailableTimed();
- synchronized (mLock) {
- if (!isCalledForCurrentUserLocked()) {
- return null;
- }
- final boolean permissionGranted =
- mSecurityPolicy.canRetrieveWindowsLocked(this);
- if (!permissionGranted) {
- return null;
- }
- if (mSecurityPolicy.mWindows == null) {
- return null;
- }
- List<AccessibilityWindowInfo> windows = new ArrayList<>();
- final int windowCount = mSecurityPolicy.mWindows.size();
- for (int i = 0; i < windowCount; i++) {
- AccessibilityWindowInfo window = mSecurityPolicy.mWindows.get(i);
- AccessibilityWindowInfo windowClone =
- AccessibilityWindowInfo.obtain(window);
- windowClone.setConnectionId(mId);
- windows.add(windowClone);
- }
- return windows;
- }
- }
-
- @Override
- public AccessibilityWindowInfo getWindow(int windowId) {
- ensureWindowsAvailableTimed();
- synchronized (mLock) {
- if (!isCalledForCurrentUserLocked()) {
- return null;
- }
- final boolean permissionGranted =
- mSecurityPolicy.canRetrieveWindowsLocked(this);
- if (!permissionGranted) {
- return null;
- }
- AccessibilityWindowInfo window = mSecurityPolicy.findA11yWindowInfoById(windowId);
- if (window != null) {
- AccessibilityWindowInfo windowClone = AccessibilityWindowInfo.obtain(window);
- windowClone.setConnectionId(mId);
- return windowClone;
- }
- return null;
- }
- }
-
- @Override
- public boolean findAccessibilityNodeInfosByViewId(int accessibilityWindowId,
- long accessibilityNodeId, String viewIdResName, int interactionId,
- IAccessibilityInteractionConnectionCallback callback, long interrogatingTid)
- throws RemoteException {
- final int resolvedWindowId;
- IAccessibilityInteractionConnection connection = null;
- Region partialInteractiveRegion = Region.obtain();
- MagnificationSpec spec;
- synchronized (mLock) {
- mUsesAccessibilityCache = true;
- if (!isCalledForCurrentUserLocked()) {
- return false;
- }
- resolvedWindowId = resolveAccessibilityWindowIdLocked(accessibilityWindowId);
- final boolean permissionGranted =
- mSecurityPolicy.canGetAccessibilityNodeInfoLocked(this, resolvedWindowId);
- if (!permissionGranted) {
- return false;
- } else {
- connection = getConnectionLocked(resolvedWindowId);
- if (connection == null) {
- return false;
- }
- }
- if (!mSecurityPolicy.computePartialInteractiveRegionForWindowLocked(
- resolvedWindowId, partialInteractiveRegion)) {
- partialInteractiveRegion.recycle();
- partialInteractiveRegion = null;
- }
- spec = getCompatibleMagnificationSpecLocked(resolvedWindowId);
- }
- final int interrogatingPid = Binder.getCallingPid();
- callback = replaceCallbackIfNeeded(callback, resolvedWindowId, interactionId,
- interrogatingPid, interrogatingTid);
- final long identityToken = Binder.clearCallingIdentity();
- try {
- connection.findAccessibilityNodeInfosByViewId(accessibilityNodeId, viewIdResName,
- partialInteractiveRegion, interactionId, callback, mFetchFlags,
- interrogatingPid, interrogatingTid, spec);
- return true;
- } catch (RemoteException re) {
- if (DEBUG) {
- Slog.e(LOG_TAG, "Error findAccessibilityNodeInfoByViewId().");
- }
- } finally {
- Binder.restoreCallingIdentity(identityToken);
- // Recycle if passed to another process.
- if (partialInteractiveRegion != null && Binder.isProxy(connection)) {
- partialInteractiveRegion.recycle();
- }
- }
- return false;
- }
-
- @Override
- public boolean findAccessibilityNodeInfosByText(int accessibilityWindowId,
- long accessibilityNodeId, String text, int interactionId,
- IAccessibilityInteractionConnectionCallback callback, long interrogatingTid)
- throws RemoteException {
- final int resolvedWindowId;
- IAccessibilityInteractionConnection connection = null;
- Region partialInteractiveRegion = Region.obtain();
- MagnificationSpec spec;
- synchronized (mLock) {
- mUsesAccessibilityCache = true;
- if (!isCalledForCurrentUserLocked()) {
- return false;
- }
- resolvedWindowId = resolveAccessibilityWindowIdLocked(accessibilityWindowId);
- final boolean permissionGranted =
- mSecurityPolicy.canGetAccessibilityNodeInfoLocked(this, resolvedWindowId);
- if (!permissionGranted) {
- return false;
- } else {
- connection = getConnectionLocked(resolvedWindowId);
- if (connection == null) {
- return false;
- }
- }
- if (!mSecurityPolicy.computePartialInteractiveRegionForWindowLocked(
- resolvedWindowId, partialInteractiveRegion)) {
- partialInteractiveRegion.recycle();
- partialInteractiveRegion = null;
- }
- spec = getCompatibleMagnificationSpecLocked(resolvedWindowId);
- }
- final int interrogatingPid = Binder.getCallingPid();
- callback = replaceCallbackIfNeeded(callback, resolvedWindowId, interactionId,
- interrogatingPid, interrogatingTid);
- final long identityToken = Binder.clearCallingIdentity();
- try {
- connection.findAccessibilityNodeInfosByText(accessibilityNodeId, text,
- partialInteractiveRegion, interactionId, callback, mFetchFlags,
- interrogatingPid, interrogatingTid, spec);
- return true;
- } catch (RemoteException re) {
- if (DEBUG) {
- Slog.e(LOG_TAG, "Error calling findAccessibilityNodeInfosByText()");
- }
- } finally {
- Binder.restoreCallingIdentity(identityToken);
- // Recycle if passed to another process.
- if (partialInteractiveRegion != null && Binder.isProxy(connection)) {
- partialInteractiveRegion.recycle();
- }
- }
- return false;
- }
-
- @Override
- public boolean findAccessibilityNodeInfoByAccessibilityId(
- int accessibilityWindowId, long accessibilityNodeId, int interactionId,
- IAccessibilityInteractionConnectionCallback callback, int flags,
- long interrogatingTid, Bundle arguments) throws RemoteException {
- final int resolvedWindowId;
- IAccessibilityInteractionConnection connection = null;
- Region partialInteractiveRegion = Region.obtain();
- MagnificationSpec spec;
- synchronized (mLock) {
- mUsesAccessibilityCache = true;
- if (!isCalledForCurrentUserLocked()) {
- return false;
- }
- resolvedWindowId = resolveAccessibilityWindowIdLocked(accessibilityWindowId);
- final boolean permissionGranted =
- mSecurityPolicy.canGetAccessibilityNodeInfoLocked(this, resolvedWindowId);
- if (!permissionGranted) {
- return false;
- } else {
- connection = getConnectionLocked(resolvedWindowId);
- if (connection == null) {
- return false;
- }
- }
- if (!mSecurityPolicy.computePartialInteractiveRegionForWindowLocked(
- resolvedWindowId, partialInteractiveRegion)) {
- partialInteractiveRegion.recycle();
- partialInteractiveRegion = null;
- }
- spec = getCompatibleMagnificationSpecLocked(resolvedWindowId);
- }
- final int interrogatingPid = Binder.getCallingPid();
- callback = replaceCallbackIfNeeded(callback, resolvedWindowId, interactionId,
- interrogatingPid, interrogatingTid);
- final long identityToken = Binder.clearCallingIdentity();
- try {
- connection.findAccessibilityNodeInfoByAccessibilityId(accessibilityNodeId,
- partialInteractiveRegion, interactionId, callback, mFetchFlags | flags,
- interrogatingPid, interrogatingTid, spec, arguments);
- return true;
- } catch (RemoteException re) {
- if (DEBUG) {
- Slog.e(LOG_TAG, "Error calling findAccessibilityNodeInfoByAccessibilityId()");
- }
- } finally {
- Binder.restoreCallingIdentity(identityToken);
- // Recycle if passed to another process.
- if (partialInteractiveRegion != null && Binder.isProxy(connection)) {
- partialInteractiveRegion.recycle();
- }
- }
- return false;
- }
-
- @Override
- public boolean findFocus(int accessibilityWindowId, long accessibilityNodeId,
- int focusType, int interactionId,
- IAccessibilityInteractionConnectionCallback callback, long interrogatingTid)
- throws RemoteException {
- final int resolvedWindowId;
- IAccessibilityInteractionConnection connection = null;
- Region partialInteractiveRegion = Region.obtain();
- MagnificationSpec spec;
- synchronized (mLock) {
- if (!isCalledForCurrentUserLocked()) {
- return false;
- }
- resolvedWindowId = resolveAccessibilityWindowIdForFindFocusLocked(
- accessibilityWindowId, focusType);
- final boolean permissionGranted =
- mSecurityPolicy.canGetAccessibilityNodeInfoLocked(this, resolvedWindowId);
- if (!permissionGranted) {
- return false;
- } else {
- connection = getConnectionLocked(resolvedWindowId);
- if (connection == null) {
- return false;
- }
- }
- if (!mSecurityPolicy.computePartialInteractiveRegionForWindowLocked(
- resolvedWindowId, partialInteractiveRegion)) {
- partialInteractiveRegion.recycle();
- partialInteractiveRegion = null;
- }
- spec = getCompatibleMagnificationSpecLocked(resolvedWindowId);
- }
- final int interrogatingPid = Binder.getCallingPid();
- callback = replaceCallbackIfNeeded(callback, resolvedWindowId, interactionId,
- interrogatingPid, interrogatingTid);
- final long identityToken = Binder.clearCallingIdentity();
- try {
- connection.findFocus(accessibilityNodeId, focusType, partialInteractiveRegion,
- interactionId, callback, mFetchFlags, interrogatingPid, interrogatingTid,
- spec);
- return true;
- } catch (RemoteException re) {
- if (DEBUG) {
- Slog.e(LOG_TAG, "Error calling findFocus()");
- }
- } finally {
- Binder.restoreCallingIdentity(identityToken);
- // Recycle if passed to another process.
- if (partialInteractiveRegion != null && Binder.isProxy(connection)) {
- partialInteractiveRegion.recycle();
- }
- }
- return false;
- }
-
- @Override
- public boolean focusSearch(int accessibilityWindowId, long accessibilityNodeId,
- int direction, int interactionId,
- IAccessibilityInteractionConnectionCallback callback, long interrogatingTid)
- throws RemoteException {
- final int resolvedWindowId;
- IAccessibilityInteractionConnection connection = null;
- Region partialInteractiveRegion = Region.obtain();
- MagnificationSpec spec;
- synchronized (mLock) {
- if (!isCalledForCurrentUserLocked()) {
- return false;
- }
- resolvedWindowId = resolveAccessibilityWindowIdLocked(accessibilityWindowId);
- final boolean permissionGranted =
- mSecurityPolicy.canGetAccessibilityNodeInfoLocked(this, resolvedWindowId);
- if (!permissionGranted) {
- return false;
- } else {
- connection = getConnectionLocked(resolvedWindowId);
- if (connection == null) {
- return false;
- }
- }
- if (!mSecurityPolicy.computePartialInteractiveRegionForWindowLocked(
- resolvedWindowId, partialInteractiveRegion)) {
- partialInteractiveRegion.recycle();
- partialInteractiveRegion = null;
- }
- spec = getCompatibleMagnificationSpecLocked(resolvedWindowId);
- }
- final int interrogatingPid = Binder.getCallingPid();
- callback = replaceCallbackIfNeeded(callback, resolvedWindowId, interactionId,
- interrogatingPid, interrogatingTid);
- final long identityToken = Binder.clearCallingIdentity();
- try {
- connection.focusSearch(accessibilityNodeId, direction, partialInteractiveRegion,
- interactionId, callback, mFetchFlags, interrogatingPid, interrogatingTid,
- spec);
- return true;
- } catch (RemoteException re) {
- if (DEBUG) {
- Slog.e(LOG_TAG, "Error calling accessibilityFocusSearch()");
- }
- } finally {
- Binder.restoreCallingIdentity(identityToken);
- // Recycle if passed to another process.
- if (partialInteractiveRegion != null && Binder.isProxy(connection)) {
- partialInteractiveRegion.recycle();
- }
- }
- return false;
- }
-
- @Override
- public void sendGesture(int sequence, ParceledListSlice gestureSteps) {
- synchronized (mLock) {
- if (mSecurityPolicy.canPerformGestures(this)) {
- final long endMillis =
- SystemClock.uptimeMillis() + WAIT_MOTION_INJECTOR_TIMEOUT_MILLIS;
- while ((mMotionEventInjector == null)
- && (SystemClock.uptimeMillis() < endMillis)) {
- try {
- mLock.wait(endMillis - SystemClock.uptimeMillis());
- } catch (InterruptedException ie) {
- /* ignore */
- }
- }
- if (mMotionEventInjector != null) {
- List<GestureDescription.GestureStep> steps = gestureSteps.getList();
- mMotionEventInjector.injectEvents(steps, mServiceInterface, sequence);
- return;
- } else {
- Slog.e(LOG_TAG, "MotionEventInjector installation timed out");
- }
- }
- }
- try {
- mServiceInterface.onPerformGestureResult(sequence, false);
- } catch (RemoteException re) {
- Slog.e(LOG_TAG, "Error sending motion event injection failure to "
- + mServiceInterface, re);
- }
- }
-
- @Override
- public boolean performAccessibilityAction(int accessibilityWindowId,
- long accessibilityNodeId, int action, Bundle arguments, int interactionId,
- IAccessibilityInteractionConnectionCallback callback, long interrogatingTid)
- throws RemoteException {
- final int resolvedWindowId;
- IAccessibilityInteractionConnection connection = null;
- IBinder activityToken = null;
- synchronized (mLock) {
- if (!isCalledForCurrentUserLocked()) {
- return false;
- }
- resolvedWindowId = resolveAccessibilityWindowIdLocked(accessibilityWindowId);
- if (!mSecurityPolicy.canGetAccessibilityNodeInfoLocked(this, resolvedWindowId)) {
- return false;
- }
- connection = getConnectionLocked(resolvedWindowId);
- if (connection == null) return false;
- final boolean isA11yFocusAction = (action == ACTION_ACCESSIBILITY_FOCUS)
- || (action == ACTION_CLEAR_ACCESSIBILITY_FOCUS);
- final AccessibilityWindowInfo a11yWindowInfo =
- mSecurityPolicy.findA11yWindowInfoById(resolvedWindowId);
- if (!isA11yFocusAction) {
- final WindowInfo windowInfo =
- mSecurityPolicy.findWindowInfoById(resolvedWindowId);
- if (windowInfo != null) activityToken = windowInfo.activityToken;
- }
- if ((a11yWindowInfo != null) && a11yWindowInfo.inPictureInPicture()) {
- if ((mPictureInPictureActionReplacingConnection != null)
- && !isA11yFocusAction) {
- connection = mPictureInPictureActionReplacingConnection.mConnection;
- }
- }
- }
- final int interrogatingPid = Binder.getCallingPid();
- final long identityToken = Binder.clearCallingIdentity();
- try {
- // Regardless of whether or not the action succeeds, it was generated by an
- // accessibility service that is driven by user actions, so note user activity.
- mPowerManager.userActivity(SystemClock.uptimeMillis(),
- PowerManager.USER_ACTIVITY_EVENT_ACCESSIBILITY, 0);
-
- if (activityToken != null) {
- LocalServices.getService(ActivityManagerInternal.class)
- .setFocusedActivity(activityToken);
- }
- connection.performAccessibilityAction(accessibilityNodeId, action, arguments,
- interactionId, callback, mFetchFlags, interrogatingPid, interrogatingTid);
- } catch (RemoteException re) {
- if (DEBUG) {
- Slog.e(LOG_TAG, "Error calling performAccessibilityAction()");
- }
- } finally {
- Binder.restoreCallingIdentity(identityToken);
- }
- return true;
- }
-
- @Override
- public boolean performGlobalAction(int action) {
- synchronized (mLock) {
- if (!isCalledForCurrentUserLocked()) {
- return false;
- }
- }
- final long identity = Binder.clearCallingIdentity();
- try {
- mPowerManager.userActivity(SystemClock.uptimeMillis(),
- PowerManager.USER_ACTIVITY_EVENT_ACCESSIBILITY, 0);
- switch (action) {
- case AccessibilityService.GLOBAL_ACTION_BACK: {
- sendDownAndUpKeyEvents(KeyEvent.KEYCODE_BACK);
- } return true;
- case AccessibilityService.GLOBAL_ACTION_HOME: {
- sendDownAndUpKeyEvents(KeyEvent.KEYCODE_HOME);
- } return true;
- case AccessibilityService.GLOBAL_ACTION_RECENTS: {
- return openRecents();
- }
- case AccessibilityService.GLOBAL_ACTION_NOTIFICATIONS: {
- expandNotifications();
- } return true;
- case AccessibilityService.GLOBAL_ACTION_QUICK_SETTINGS: {
- expandQuickSettings();
- } return true;
- case AccessibilityService.GLOBAL_ACTION_POWER_DIALOG: {
- showGlobalActions();
- } return true;
- case AccessibilityService.GLOBAL_ACTION_TOGGLE_SPLIT_SCREEN: {
- toggleSplitScreen();
- } return true;
- }
- return false;
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
-
- @Override
- public boolean isFingerprintGestureDetectionAvailable() {
- return isCapturingFingerprintGestures()
- && (mFingerprintGestureDispatcher != null)
- && mFingerprintGestureDispatcher.isFingerprintGestureDetectionAvailable();
- }
-
- @Override
- public float getMagnificationScale() {
- synchronized (mLock) {
- if (!isCalledForCurrentUserLocked()) {
- return 1.0f;
- }
- }
- final long identity = Binder.clearCallingIdentity();
- try {
- return getMagnificationController().getScale();
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
-
- @Override
- public Region getMagnificationRegion() {
- synchronized (mLock) {
- final Region region = Region.obtain();
- if (!isCalledForCurrentUserLocked()) {
- return region;
- }
- MagnificationController magnificationController = getMagnificationController();
- boolean registeredJustForThisCall =
- registerMagnificationIfNeeded(magnificationController);
- final long identity = Binder.clearCallingIdentity();
- try {
- magnificationController.getMagnificationRegion(region);
- return region;
- } finally {
- Binder.restoreCallingIdentity(identity);
- if (registeredJustForThisCall) {
- magnificationController.unregister();
- }
- }
- }
- }
-
- @Override
- public float getMagnificationCenterX() {
- synchronized (mLock) {
- if (!isCalledForCurrentUserLocked()) {
- return 0.0f;
- }
+ @Override
+ public boolean performAccessibilityAction(int resolvedWindowId,
+ long accessibilityNodeId, int action, Bundle arguments, int interactionId,
+ IAccessibilityInteractionConnectionCallback callback, int fetchFlags,
+ long interrogatingTid) {
+ IAccessibilityInteractionConnection connection = null;
+ IBinder activityToken = null;
+ synchronized (mLock) {
+ connection = getConnectionLocked(resolvedWindowId);
+ if (connection == null) return false;
+ final boolean isA11yFocusAction = (action == ACTION_ACCESSIBILITY_FOCUS)
+ || (action == ACTION_CLEAR_ACCESSIBILITY_FOCUS);
+ final AccessibilityWindowInfo a11yWindowInfo =
+ mSecurityPolicy.findA11yWindowInfoById(resolvedWindowId);
+ if (!isA11yFocusAction) {
+ final WindowInfo windowInfo =
+ mSecurityPolicy.findWindowInfoById(resolvedWindowId);
+ if (windowInfo != null) activityToken = windowInfo.activityToken;
}
- MagnificationController magnificationController = getMagnificationController();
- boolean registeredJustForThisCall =
- registerMagnificationIfNeeded(magnificationController);
- final long identity = Binder.clearCallingIdentity();
- try {
- return magnificationController.getCenterX();
- } finally {
- Binder.restoreCallingIdentity(identity);
- if (registeredJustForThisCall) {
- magnificationController.unregister();
- }
+ if ((a11yWindowInfo != null) && a11yWindowInfo.isInPictureInPictureMode()
+ && (mPictureInPictureActionReplacingConnection != null) && !isA11yFocusAction) {
+ connection = mPictureInPictureActionReplacingConnection.mConnection;
}
}
+ final int interrogatingPid = Binder.getCallingPid();
+ final long identityToken = Binder.clearCallingIdentity();
+ try {
+ // Regardless of whether or not the action succeeds, it was generated by an
+ // accessibility service that is driven by user actions, so note user activity.
+ mPowerManager.userActivity(SystemClock.uptimeMillis(),
+ PowerManager.USER_ACTIVITY_EVENT_ACCESSIBILITY, 0);
- @Override
- public float getMagnificationCenterY() {
- synchronized (mLock) {
- if (!isCalledForCurrentUserLocked()) {
- return 0.0f;
- }
+ if (activityToken != null) {
+ LocalServices.getService(ActivityManagerInternal.class)
+ .setFocusedActivity(activityToken);
}
- MagnificationController magnificationController = getMagnificationController();
- boolean registeredJustForThisCall =
- registerMagnificationIfNeeded(magnificationController);
- final long identity = Binder.clearCallingIdentity();
- try {
- return magnificationController.getCenterY();
- } finally {
- Binder.restoreCallingIdentity(identity);
- if (registeredJustForThisCall) {
- magnificationController.unregister();
- }
- }
- }
-
- private boolean registerMagnificationIfNeeded(
- MagnificationController magnificationController) {
- if (!magnificationController.isRegisteredLocked()
- && mSecurityPolicy.canControlMagnification(this)) {
- magnificationController.register();
- return true;
+ connection.performAccessibilityAction(accessibilityNodeId, action, arguments,
+ interactionId, callback, fetchFlags, interrogatingPid, interrogatingTid);
+ } catch (RemoteException re) {
+ if (DEBUG) {
+ Slog.e(LOG_TAG, "Error calling performAccessibilityAction: " + re);
}
return false;
+ } finally {
+ Binder.restoreCallingIdentity(identityToken);
}
+ return true;
+ }
- @Override
- public boolean resetMagnification(boolean animate) {
- synchronized (mLock) {
- if (!isCalledForCurrentUserLocked()) {
- return false;
- }
- final boolean permissionGranted = mSecurityPolicy.canControlMagnification(this);
- if (!permissionGranted) {
- return false;
- }
- }
- final long identity = Binder.clearCallingIdentity();
- try {
- return getMagnificationController().reset(animate);
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
-
- @Override
- public boolean setMagnificationScaleAndCenter(float scale, float centerX, float centerY,
- boolean animate) {
- synchronized (mLock) {
- if (!isCalledForCurrentUserLocked()) {
- return false;
- }
- final boolean permissionGranted = mSecurityPolicy.canControlMagnification(this);
- if (!permissionGranted) {
- return false;
- }
- final long identity = Binder.clearCallingIdentity();
- try {
- MagnificationController magnificationController = getMagnificationController();
- if (!magnificationController.isRegisteredLocked()) {
- magnificationController.register();
- }
- return magnificationController
- .setScaleAndCenter(scale, centerX, centerY, animate, mId);
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
- }
-
- @Override
- public void setMagnificationCallbackEnabled(boolean enabled) {
- mInvocationHandler.setMagnificationCallbackEnabled(enabled);
- }
-
- @Override
- public boolean setSoftKeyboardShowMode(int showMode) {
- final UserState userState;
- synchronized (mLock) {
- if (!isCalledForCurrentUserLocked()) {
- return false;
- }
-
- userState = getCurrentUserStateLocked();
- }
-
- final long identity = Binder.clearCallingIdentity();
- try {
- // Keep track of the last service to request a non-default show mode. The show mode
- // should be restored to default should this service be disabled.
- if (showMode == Settings.Secure.SHOW_MODE_AUTO) {
- userState.mServiceChangingSoftKeyboardMode = null;
- } else {
- userState.mServiceChangingSoftKeyboardMode = mComponentName;
- }
-
- Settings.Secure.putIntForUser(mContext.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE, showMode,
- userState.mUserId);
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- return true;
- }
-
- @Override
- public void setSoftKeyboardCallbackEnabled(boolean enabled) {
- mInvocationHandler.setSoftKeyboardCallbackEnabled(enabled);
- }
-
- @Override
- public boolean isAccessibilityButtonAvailable() {
- final UserState userState;
- synchronized (mLock) {
- if (!isCalledForCurrentUserLocked()) {
- return false;
- }
- userState = getCurrentUserStateLocked();
- return isAccessibilityButtonAvailableLocked(userState);
- }
- }
-
- @Override
- public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
- if (!DumpUtils.checkDumpPermission(mContext, LOG_TAG, pw)) return;
- synchronized (mLock) {
- pw.append("Service[label=" + mAccessibilityServiceInfo.getResolveInfo()
- .loadLabel(mContext.getPackageManager()));
- pw.append(", feedbackType"
- + AccessibilityServiceInfo.feedbackTypeToString(mFeedbackType));
- pw.append(", capabilities=" + mAccessibilityServiceInfo.getCapabilities());
- pw.append(", eventTypes="
- + AccessibilityEvent.eventTypeToString(mEventTypes));
- pw.append(", notificationTimeout=" + mNotificationTimeout);
- pw.append("]");
- }
- }
-
- @Override
- public void onServiceDisconnected(ComponentName componentName) {
- binderDied();
- }
-
- public void onAdded() throws RemoteException {
- final long identity = Binder.clearCallingIdentity();
- try {
- mWindowManagerService.addWindowToken(mOverlayWindowToken,
- TYPE_ACCESSIBILITY_OVERLAY, DEFAULT_DISPLAY);
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
-
- public void onRemoved() {
- final long identity = Binder.clearCallingIdentity();
- try {
- mWindowManagerService.removeWindowToken(mOverlayWindowToken, true, DEFAULT_DISPLAY);
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
-
- public void resetLocked() {
- try {
- // Clear the proxy in the other process so this
- // IAccessibilityServiceConnection can be garbage collected.
- if (mServiceInterface != null) {
- mServiceInterface.init(null, mId, null);
- }
- } catch (RemoteException re) {
- /* ignore */
- }
- if (mService != null) {
- mService.unlinkToDeath(this, 0);
- mService = null;
- }
- mServiceInterface = null;
- mReceivedAccessibilityButtonCallbackSinceBind = false;
- }
-
- public boolean isConnectedLocked() {
- return (mService != null);
- }
-
- public void binderDied() {
- synchronized (mLock) {
- // It is possible that this service's package was force stopped during
- // whose handling the death recipient is unlinked and still get a call
- // on binderDied since the call was made before we unlink but was
- // waiting on the lock we held during the force stop handling.
- if (!isConnectedLocked()) {
- return;
- }
- mWasConnectedAndDied = true;
- getKeyEventDispatcher().flush(this);
- UserState userState = getUserStateLocked(mUserId);
- resetLocked();
- if (mIsAutomation) {
- // This is typically done when unbinding, but UiAutomation isn't bound.
- removeServiceLocked(this, userState);
- // We no longer have an automation service, so restore
- // the state based on values in the settings database.
- userState.mInstalledServices.remove(mAccessibilityServiceInfo);
- userState.mEnabledServices.remove(mComponentName);
- userState.destroyUiAutomationService();
- readConfigurationForUserStateLocked(userState);
- }
- if (mId == getMagnificationController().getIdOfLastServiceToMagnify()) {
- getMagnificationController().resetIfNeeded(true);
- }
- onUserStateChangedLocked(userState);
- }
- }
-
- /**
- * Performs a notification for an {@link AccessibilityEvent}.
- *
- * @param event The event.
- * @param serviceWantsEvent whether the event should be received by
- * {@link AccessibilityService#onAccessibilityEvent} (true),
- * as opposed to just {@link AccessibilityInteractionClient#onAccessibilityEvent} (false)
- */
- public void notifyAccessibilityEvent(AccessibilityEvent event, boolean serviceWantsEvent) {
- synchronized (mLock) {
- final int eventType = event.getEventType();
- // Make a copy since during dispatch it is possible the event to
- // be modified to remove its source if the receiving service does
- // not have permission to access the window content.
- AccessibilityEvent newEvent = AccessibilityEvent.obtain(event);
- Message message;
- if ((mNotificationTimeout > 0)
- && (eventType != AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED)) {
- // Allow at most one pending event
- final AccessibilityEvent oldEvent = mPendingEvents.get(eventType);
- mPendingEvents.put(eventType, newEvent);
- if (oldEvent != null) {
- mEventDispatchHandler.removeMessages(eventType);
- oldEvent.recycle();
- }
- message = mEventDispatchHandler.obtainMessage(eventType);
- } else {
- // Send all messages, bypassing mPendingEvents
- message = mEventDispatchHandler.obtainMessage(eventType, newEvent);
- }
- message.arg1 = serviceWantsEvent ? 1 : 0;
-
- mEventDispatchHandler.sendMessageDelayed(message, mNotificationTimeout);
- }
- }
-
- private boolean isAccessibilityButtonAvailableLocked(UserState userState) {
- // If the service does not request the accessibility button, it isn't available
- if (!mRequestAccessibilityButton) {
- return false;
- }
-
- // If the accessibility button isn't currently shown, it cannot be available to services
- if (!mIsAccessibilityButtonShown) {
- return false;
- }
-
- // If magnification is on and assigned to the accessibility button, services cannot be
- if (userState.mIsNavBarMagnificationEnabled
- && userState.mIsNavBarMagnificationAssignedToAccessibilityButton) {
- return false;
- }
-
- int requestingServices = 0;
- for (int i = userState.mBoundServices.size() - 1; i >= 0; i--) {
- final Service service = userState.mBoundServices.get(i);
- if (service.mRequestAccessibilityButton) {
- requestingServices++;
- }
- }
-
- if (requestingServices == 1) {
- // If only a single service is requesting, it must be this service, and the
- // accessibility button is available to it
- return true;
- } else {
- // With more than one active service, we derive the target from the user's settings
- if (userState.mServiceAssignedToAccessibilityButton == null) {
- // If the user has not made an assignment, we treat the button as available to
- // all services until the user interacts with the button to make an assignment
- return true;
- } else {
- // If an assignment was made, it defines availability
- return mComponentName.equals(userState.mServiceAssignedToAccessibilityButton);
- }
- }
- }
-
- /**
- * Notifies an accessibility service client for a scheduled event given the event type.
- *
- * @param eventType The type of the event to dispatch.
- */
- private void notifyAccessibilityEventInternal(
- int eventType,
- AccessibilityEvent event,
- boolean serviceWantsEvent) {
- IAccessibilityServiceClient listener;
-
- synchronized (mLock) {
- listener = mServiceInterface;
-
- // If the service died/was disabled while the message for dispatching
- // the accessibility event was propagating the listener may be null.
- if (listener == null) {
- return;
- }
-
- // There are two ways we notify for events, throttled and non-throttled. If we
- // are not throttling, then messages come with events, which we handle with
- // minimal fuss.
- if (event == null) {
- // We are throttling events, so we'll send the event for this type in
- // mPendingEvents as long as it it's null. It can only null due to a race
- // condition:
- //
- // 1) A binder thread calls notifyAccessibilityServiceDelayedLocked
- // which posts a message for dispatching an event and stores the event
- // in mPendingEvents.
- // 2) The message is pulled from the queue by the handler on the service
- // thread and this method is just about to acquire the lock.
- // 3) Another binder thread acquires the lock in notifyAccessibilityEvent
- // 4) notifyAccessibilityEvent recycles the event that this method was about
- // to process, replaces it with a new one, and posts a second message
- // 5) This method grabs the new event, processes it, and removes it from
- // mPendingEvents
- // 6) The second message dispatched in (4) arrives, but the event has been
- // remvoved in (5).
- event = mPendingEvents.get(eventType);
- if (event == null) {
- return;
- }
- mPendingEvents.remove(eventType);
- }
- if (mSecurityPolicy.canRetrieveWindowContentLocked(this)) {
- event.setConnectionId(mId);
- } else {
- event.setSource((View) null);
- }
- event.setSealed(true);
- }
-
- try {
- listener.onAccessibilityEvent(event, serviceWantsEvent);
- if (DEBUG) {
- Slog.i(LOG_TAG, "Event " + event + " sent to " + listener);
- }
- } catch (RemoteException re) {
- Slog.e(LOG_TAG, "Error during sending " + event + " to " + listener, re);
- } finally {
- event.recycle();
- }
- }
-
- public void notifyGesture(int gestureId) {
- mInvocationHandler.obtainMessage(InvocationHandler.MSG_ON_GESTURE,
- gestureId, 0).sendToTarget();
- }
-
- public void notifyClearAccessibilityNodeInfoCache() {
- mInvocationHandler.sendEmptyMessage(
- InvocationHandler.MSG_CLEAR_ACCESSIBILITY_CACHE);
- }
-
- public void notifyMagnificationChangedLocked(@NonNull Region region,
- float scale, float centerX, float centerY) {
- mInvocationHandler
- .notifyMagnificationChangedLocked(region, scale, centerX, centerY);
- }
-
- public void notifySoftKeyboardShowModeChangedLocked(int showState) {
- mInvocationHandler.notifySoftKeyboardShowModeChangedLocked(showState);
- }
-
- public void notifyAccessibilityButtonClickedLocked() {
- mInvocationHandler.notifyAccessibilityButtonClickedLocked();
- }
-
- public void notifyAccessibilityButtonAvailabilityChangedLocked(boolean available) {
- mInvocationHandler.notifyAccessibilityButtonAvailabilityChangedLocked(available);
- }
-
- /**
- * Called by the invocation handler to notify the service that the
- * state of magnification has changed.
- */
- private void notifyMagnificationChangedInternal(@NonNull Region region,
- float scale, float centerX, float centerY) {
- final IAccessibilityServiceClient listener;
- synchronized (mLock) {
- listener = mServiceInterface;
- }
- if (listener != null) {
- try {
- listener.onMagnificationChanged(region, scale, centerX, centerY);
- } catch (RemoteException re) {
- Slog.e(LOG_TAG, "Error sending magnification changes to " + mService, re);
- }
- }
- }
-
- /**
- * Called by the invocation handler to notify the service that the state of the soft
- * keyboard show mode has changed.
- */
- private void notifySoftKeyboardShowModeChangedInternal(int showState) {
- final IAccessibilityServiceClient listener;
- synchronized (mLock) {
- listener = mServiceInterface;
- }
- if (listener != null) {
- try {
- listener.onSoftKeyboardShowModeChanged(showState);
- } catch (RemoteException re) {
- Slog.e(LOG_TAG, "Error sending soft keyboard show mode changes to " + mService,
- re);
- }
- }
- }
-
- private void notifyAccessibilityButtonClickedInternal() {
- final IAccessibilityServiceClient listener;
- synchronized (mLock) {
- listener = mServiceInterface;
- }
- if (listener != null) {
- try {
- listener.onAccessibilityButtonClicked();
- } catch (RemoteException re) {
- Slog.e(LOG_TAG, "Error sending accessibility button click to " + mService, re);
- }
- }
- }
-
- private void notifyAccessibilityButtonAvailabilityChangedInternal(boolean available) {
- // Only notify the service if it's not been notified or the state has changed
- if (mReceivedAccessibilityButtonCallbackSinceBind
- && (mLastAccessibilityButtonCallbackState == available)) {
- return;
- }
- mReceivedAccessibilityButtonCallbackSinceBind = true;
- mLastAccessibilityButtonCallbackState = available;
- final IAccessibilityServiceClient listener;
- synchronized (mLock) {
- listener = mServiceInterface;
- }
- if (listener != null) {
- try {
- listener.onAccessibilityButtonAvailabilityChanged(available);
- } catch (RemoteException re) {
- Slog.e(LOG_TAG,
- "Error sending accessibility button availability change to " + mService,
- re);
- }
- }
- }
-
- private void notifyGestureInternal(int gestureId) {
- final IAccessibilityServiceClient listener;
- synchronized (mLock) {
- listener = mServiceInterface;
- }
- if (listener != null) {
- try {
- listener.onGesture(gestureId);
- } catch (RemoteException re) {
- Slog.e(LOG_TAG, "Error during sending gesture " + gestureId
- + " to " + mService, re);
- }
- }
- }
-
- private void notifyClearAccessibilityCacheInternal() {
- final IAccessibilityServiceClient listener;
- synchronized (mLock) {
- listener = mServiceInterface;
- }
- if (listener != null) {
- try {
- listener.clearAccessibilityCache();
- } catch (RemoteException re) {
- Slog.e(LOG_TAG, "Error during requesting accessibility info cache"
- + " to be cleared.", re);
- }
- }
- }
-
- private void sendDownAndUpKeyEvents(int keyCode) {
- final long token = Binder.clearCallingIdentity();
-
- // Inject down.
- final long downTime = SystemClock.uptimeMillis();
- KeyEvent down = KeyEvent.obtain(downTime, downTime, KeyEvent.ACTION_DOWN, keyCode, 0, 0,
- KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_FROM_SYSTEM,
- InputDevice.SOURCE_KEYBOARD, null);
- InputManager.getInstance().injectInputEvent(down,
- InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
- down.recycle();
-
- // Inject up.
- final long upTime = SystemClock.uptimeMillis();
- KeyEvent up = KeyEvent.obtain(downTime, upTime, KeyEvent.ACTION_UP, keyCode, 0, 0,
- KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_FROM_SYSTEM,
- InputDevice.SOURCE_KEYBOARD, null);
- InputManager.getInstance().injectInputEvent(up,
- InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
- up.recycle();
-
- Binder.restoreCallingIdentity(token);
- }
-
- private void expandNotifications() {
- final long token = Binder.clearCallingIdentity();
-
- StatusBarManager statusBarManager = (StatusBarManager) mContext.getSystemService(
- android.app.Service.STATUS_BAR_SERVICE);
- statusBarManager.expandNotificationsPanel();
-
- Binder.restoreCallingIdentity(token);
- }
-
- private void expandQuickSettings() {
- final long token = Binder.clearCallingIdentity();
-
- StatusBarManager statusBarManager = (StatusBarManager) mContext.getSystemService(
- android.app.Service.STATUS_BAR_SERVICE);
- statusBarManager.expandSettingsPanel();
-
- Binder.restoreCallingIdentity(token);
- }
-
- private boolean openRecents() {
- final long token = Binder.clearCallingIdentity();
- try {
- StatusBarManagerInternal statusBarService = LocalServices.getService(
- StatusBarManagerInternal.class);
- if (statusBarService == null) {
- return false;
- }
- statusBarService.toggleRecentApps();
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- return true;
- }
-
- private void showGlobalActions() {
- mWindowManagerService.showGlobalActions();
- }
-
- private void toggleSplitScreen() {
- LocalServices.getService(StatusBarManagerInternal.class).toggleSplitScreen();
+ @Override
+ public IAccessibilityInteractionConnection getConnectionLocked(int windowId) {
+ if (DEBUG) {
+ Slog.i(LOG_TAG, "Trying to get interaction connection to windowId: " + windowId);
}
-
- private IAccessibilityInteractionConnection getConnectionLocked(int windowId) {
- if (DEBUG) {
- Slog.i(LOG_TAG, "Trying to get interaction connection to windowId: " + windowId);
- }
- AccessibilityConnectionWrapper wrapper = mGlobalInteractionConnections.get(windowId);
- if (wrapper == null) {
- wrapper = getCurrentUserStateLocked().mInteractionConnections.get(windowId);
- }
- if (wrapper != null && wrapper.mConnection != null) {
- return wrapper.mConnection;
- }
- if (DEBUG) {
- Slog.e(LOG_TAG, "No interaction connection to window: " + windowId);
- }
- return null;
+ AccessibilityManagerService.AccessibilityConnectionWrapper wrapper =
+ mGlobalInteractionConnections.get(windowId);
+ if (wrapper == null) {
+ wrapper = getCurrentUserStateLocked().mInteractionConnections.get(windowId);
}
-
- private int resolveAccessibilityWindowIdLocked(int accessibilityWindowId) {
- if (accessibilityWindowId == AccessibilityWindowInfo.ACTIVE_WINDOW_ID) {
- return mSecurityPolicy.getActiveWindowId();
- }
- return accessibilityWindowId;
+ if (wrapper != null && wrapper.mConnection != null) {
+ return wrapper.mConnection;
}
-
- private int resolveAccessibilityWindowIdForFindFocusLocked(int windowId, int focusType) {
- if (windowId == AccessibilityWindowInfo.ACTIVE_WINDOW_ID) {
- return mSecurityPolicy.mActiveWindowId;
- }
- if (windowId == AccessibilityWindowInfo.ANY_WINDOW_ID) {
- if (focusType == AccessibilityNodeInfo.FOCUS_INPUT) {
- return mSecurityPolicy.mFocusedWindowId;
- } else if (focusType == AccessibilityNodeInfo.FOCUS_ACCESSIBILITY) {
- return mSecurityPolicy.mAccessibilityFocusedWindowId;
- }
- }
- return windowId;
+ if (DEBUG) {
+ Slog.e(LOG_TAG, "No interaction connection to window: " + windowId);
}
+ return null;
+ }
- private IAccessibilityInteractionConnectionCallback replaceCallbackIfNeeded(
- IAccessibilityInteractionConnectionCallback originalCallback,
- int resolvedWindowId, int interactionId, int interrogatingPid,
- long interrogatingTid) {
- AccessibilityWindowInfo windowInfo =
- mSecurityPolicy.findA11yWindowInfoById(resolvedWindowId);
- if ((windowInfo == null) || !windowInfo.inPictureInPicture()
- || (mPictureInPictureActionReplacingConnection == null)) {
- return originalCallback;
- }
- return new ActionReplacingCallback(originalCallback,
- mPictureInPictureActionReplacingConnection.mConnection, interactionId,
- interrogatingPid, interrogatingTid);
+ @Override
+ public IAccessibilityInteractionConnectionCallback replaceCallbackIfNeeded(
+ IAccessibilityInteractionConnectionCallback originalCallback,
+ int resolvedWindowId, int interactionId, int interrogatingPid,
+ long interrogatingTid) {
+ AccessibilityWindowInfo windowInfo =
+ mSecurityPolicy.findA11yWindowInfoById(resolvedWindowId);
+ if ((windowInfo == null) || !windowInfo.isInPictureInPictureMode()
+ || (mPictureInPictureActionReplacingConnection == null)) {
+ return originalCallback;
}
+ return new ActionReplacingCallback(originalCallback,
+ mPictureInPictureActionReplacingConnection.mConnection, interactionId,
+ interrogatingPid, interrogatingTid);
+ }
- private final class InvocationHandler extends Handler {
- public static final int MSG_ON_GESTURE = 1;
- public static final int MSG_CLEAR_ACCESSIBILITY_CACHE = 2;
-
- private static final int MSG_ON_MAGNIFICATION_CHANGED = 5;
- private static final int MSG_ON_SOFT_KEYBOARD_STATE_CHANGED = 6;
- private static final int MSG_ON_ACCESSIBILITY_BUTTON_CLICKED = 7;
- private static final int MSG_ON_ACCESSIBILITY_BUTTON_AVAILABILITY_CHANGED = 8;
-
- private boolean mIsMagnificationCallbackEnabled = false;
- private boolean mIsSoftKeyboardCallbackEnabled = false;
-
- public InvocationHandler(Looper looper) {
- super(looper, null, true);
- }
-
- @Override
- public void handleMessage(Message message) {
- final int type = message.what;
- switch (type) {
- case MSG_ON_GESTURE: {
- final int gestureId = message.arg1;
- notifyGestureInternal(gestureId);
- } break;
-
- case MSG_CLEAR_ACCESSIBILITY_CACHE: {
- notifyClearAccessibilityCacheInternal();
- } break;
-
- case MSG_ON_MAGNIFICATION_CHANGED: {
- 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;
- notifyMagnificationChangedInternal(region, scale, centerX, centerY);
- } break;
-
- case MSG_ON_SOFT_KEYBOARD_STATE_CHANGED: {
- final int showState = (int) message.arg1;
- notifySoftKeyboardShowModeChangedInternal(showState);
- } break;
-
- case MSG_ON_ACCESSIBILITY_BUTTON_CLICKED: {
- notifyAccessibilityButtonClickedInternal();
- } break;
-
- case MSG_ON_ACCESSIBILITY_BUTTON_AVAILABILITY_CHANGED: {
- final boolean available = (message.arg1 != 0);
- notifyAccessibilityButtonAvailabilityChangedInternal(available);
- } break;
-
- default: {
- throw new IllegalArgumentException("Unknown message: " + type);
- }
- }
- }
-
- public void notifyMagnificationChangedLocked(@NonNull Region region, float scale,
- float centerX, float centerY) {
- if (!mIsMagnificationCallbackEnabled) {
- // Callback is disabled, don't bother packing args.
- return;
- }
-
- final SomeArgs args = SomeArgs.obtain();
- args.arg1 = region;
- args.arg2 = scale;
- args.arg3 = centerX;
- args.arg4 = centerY;
-
- final Message msg = obtainMessage(MSG_ON_MAGNIFICATION_CHANGED, args);
- msg.sendToTarget();
- }
-
- public void setMagnificationCallbackEnabled(boolean enabled) {
- mIsMagnificationCallbackEnabled = enabled;
- }
-
- public void notifySoftKeyboardShowModeChangedLocked(int showState) {
- if (!mIsSoftKeyboardCallbackEnabled) {
- return;
- }
-
- final Message msg = obtainMessage(MSG_ON_SOFT_KEYBOARD_STATE_CHANGED, showState, 0);
- msg.sendToTarget();
- }
-
- public void setSoftKeyboardCallbackEnabled(boolean enabled) {
- mIsSoftKeyboardCallbackEnabled = enabled;
- }
-
- public void notifyAccessibilityButtonClickedLocked() {
- final Message msg = obtainMessage(MSG_ON_ACCESSIBILITY_BUTTON_CLICKED);
- msg.sendToTarget();
- }
-
- public void notifyAccessibilityButtonAvailabilityChangedLocked(boolean available) {
- final Message msg = obtainMessage(MSG_ON_ACCESSIBILITY_BUTTON_AVAILABILITY_CHANGED,
- (available ? 1 : 0), 0);
- msg.sendToTarget();
- }
+ @Override
+ public void onClientChange(boolean serviceInfoChanged) {
+ AccessibilityManagerService.UserState userState = getUserStateLocked(mCurrentUserId);
+ onUserStateChangedLocked(userState);
+ if (serviceInfoChanged) {
+ scheduleNotifyClientsOfServicesStateChange(userState);
}
}
@@ -4339,17 +2710,32 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
}
private final class InteractionBridge {
+ private final ComponentName COMPONENT_NAME =
+ new ComponentName("com.android.server.accessibility", "InteractionBridge");
+
private final Display mDefaultDisplay;
private final int mConnectionId;
private final AccessibilityInteractionClient mClient;
public InteractionBridge() {
- AccessibilityServiceInfo info = new AccessibilityServiceInfo();
+ final AccessibilityServiceInfo info = new AccessibilityServiceInfo();
info.setCapabilities(AccessibilityServiceInfo.CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT);
info.flags |= AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS;
info.flags |= AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;
- Service service = new Service(UserHandle.USER_NULL,
- sFakeAccessibilityServiceComponentName, info);
+ final UserState userState;
+ synchronized (mLock) {
+ userState = getCurrentUserStateLocked();
+ }
+ AccessibilityServiceConnection service = new AccessibilityServiceConnection(
+ userState, mContext,
+ COMPONENT_NAME, info, sIdCounter++, mMainHandler, mLock, mSecurityPolicy,
+ AccessibilityManagerService.this, mWindowManagerService,
+ mGlobalActionPerformer) {
+ @Override
+ public boolean supportsFlagForNotImportantViews(AccessibilityServiceInfo info) {
+ return true;
+ }
+ };
mConnectionId = service.mId;
@@ -4442,7 +2828,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
}
}
- final class SecurityPolicy {
+ public class SecurityPolicy {
public static final int INVALID_WINDOW_ID = -1;
private static final int RETRIEVAL_ALLOWING_EVENT_TYPES =
@@ -4783,30 +3169,31 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
}
}
- public boolean canGetAccessibilityNodeInfoLocked(Service service, int windowId) {
+ public boolean canGetAccessibilityNodeInfoLocked(
+ AccessibilityClientConnection service, int windowId) {
return canRetrieveWindowContentLocked(service) && isRetrievalAllowingWindow(windowId);
}
- public boolean canRetrieveWindowsLocked(Service service) {
+ public boolean canRetrieveWindowsLocked(AccessibilityClientConnection service) {
return canRetrieveWindowContentLocked(service) && service.mRetrieveInteractiveWindows;
}
- public boolean canRetrieveWindowContentLocked(Service service) {
+ public boolean canRetrieveWindowContentLocked(AccessibilityClientConnection service) {
return (service.mAccessibilityServiceInfo.getCapabilities()
& AccessibilityServiceInfo.CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT) != 0;
}
- public boolean canControlMagnification(Service service) {
+ public boolean canControlMagnification(AccessibilityClientConnection service) {
return (service.mAccessibilityServiceInfo.getCapabilities()
& AccessibilityServiceInfo.CAPABILITY_CAN_CONTROL_MAGNIFICATION) != 0;
}
- public boolean canPerformGestures(Service service) {
+ public boolean canPerformGestures(AccessibilityServiceConnection service) {
return (service.mAccessibilityServiceInfo.getCapabilities()
& AccessibilityServiceInfo.CAPABILITY_CAN_PERFORM_GESTURES) != 0;
}
- public boolean canCaptureFingerprintGestures(Service service) {
+ public boolean canCaptureFingerprintGestures(AccessibilityServiceConnection service) {
return (service.mAccessibilityServiceInfo.getCapabilities()
& AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_FINGERPRINT_GESTURES) != 0;
}
@@ -4880,7 +3267,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
return findA11yWindowInfoById(windowId) != null;
}
- private AccessibilityWindowInfo findA11yWindowInfoById(int windowId) {
+ public AccessibilityWindowInfo findA11yWindowInfoById(int windowId) {
return mA11yWindowInfoById.get(windowId);
}
@@ -4924,7 +3311,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
}
}
- private class UserState {
+ public class UserState {
public final int mUserId;
// Non-transient state.
@@ -4939,18 +3326,18 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
// Transient state.
- public final CopyOnWriteArrayList<Service> mBoundServices =
+ public final CopyOnWriteArrayList<AccessibilityServiceConnection> mBoundServices =
new CopyOnWriteArrayList<>();
public int mLastSentRelevantEventTypes = AccessibilityEvent.TYPES_ALL_MASK;
- public final Map<ComponentName, Service> mComponentNameToServiceMap =
+ public final Map<ComponentName, AccessibilityServiceConnection> mComponentNameToServiceMap =
new HashMap<>();
public final List<AccessibilityServiceInfo> mInstalledServices =
new ArrayList<>();
- public final Set<ComponentName> mBindingServices = new HashSet<>();
+ private final Set<ComponentName> mBindingServices = new HashSet<>();
public final Set<ComponentName> mEnabledServices = new HashSet<>();
@@ -4977,35 +3364,19 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
public boolean mIsFilterKeyEventsEnabled;
public boolean mAccessibilityFocusOnlyInActiveWindow;
- private Service mUiAutomationService;
- private int mUiAutomationFlags;
- private IAccessibilityServiceClient mUiAutomationServiceClient;
-
- private IBinder mUiAutomationServiceOwner;
- private final DeathRecipient mUiAutomationSerivceOnwerDeathRecipient =
- new DeathRecipient() {
- @Override
- public void binderDied() {
- mUiAutomationServiceOwner.unlinkToDeath(
- mUiAutomationSerivceOnwerDeathRecipient, 0);
- mUiAutomationServiceOwner = null;
- if (mUiAutomationService != null) {
- mUiAutomationService.binderDied();
- }
- }
- };
-
public UserState(int userId) {
mUserId = userId;
}
public int getClientState() {
int clientState = 0;
- if (isHandlingAccessibilityEvents()) {
+ final boolean a11yEnabled = (mUiAutomationManager.isUiAutomationRunningLocked()
+ || isHandlingAccessibilityEvents());
+ if (a11yEnabled) {
clientState |= AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED;
}
// Touch exploration relies on enabled accessibility.
- if (isHandlingAccessibilityEvents() && mIsTouchExplorationEnabled) {
+ if (a11yEnabled && mIsTouchExplorationEnabled) {
clientState |= AccessibilityManager.STATE_FLAG_TOUCH_EXPLORATION_ENABLED;
}
if (mIsTextHighContrastEnabled) {
@@ -5019,11 +3390,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
}
public void onSwitchToAnotherUser() {
- // Clear UI test automation state.
- if (mUiAutomationService != null) {
- mUiAutomationService.binderDied();
- }
-
// Unbind all services.
unbindAllServicesLocked(this);
@@ -5046,20 +3412,35 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
mSoftKeyboardShowMode = 0;
}
- public void destroyUiAutomationService() {
- mUiAutomationService = null;
- mUiAutomationFlags = 0;
- mUiAutomationServiceClient = null;
- if (mUiAutomationServiceOwner != null) {
- mUiAutomationServiceOwner.unlinkToDeath(
- mUiAutomationSerivceOnwerDeathRecipient, 0);
- mUiAutomationServiceOwner = null;
+ public void addServiceLocked(AccessibilityServiceConnection serviceConnection) {
+ if (!mBoundServices.contains(serviceConnection)) {
+ serviceConnection.onAdded();
+ mBoundServices.add(serviceConnection);
+ mComponentNameToServiceMap.put(serviceConnection.mComponentName, serviceConnection);
+ scheduleNotifyClientsOfServicesStateChange(this);
}
}
- boolean isUiAutomationSuppressingOtherServices() {
- return ((mUiAutomationService != null) && (mUiAutomationFlags
- & UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES) == 0);
+ /**
+ * Removes a service.
+ *
+ * @param serviceConnection The service.
+ */
+ public void removeServiceLocked(AccessibilityServiceConnection serviceConnection) {
+ mBoundServices.remove(serviceConnection);
+ serviceConnection.onRemoved();
+ // It may be possible to bind a service twice, which confuses the map. Rebuild the map
+ // to make sure we can still reach a service
+ mComponentNameToServiceMap.clear();
+ for (int i = 0; i < mBoundServices.size(); i++) {
+ AccessibilityServiceConnection boundClient = mBoundServices.get(i);
+ mComponentNameToServiceMap.put(boundClient.mComponentName, boundClient);
+ }
+ scheduleNotifyClientsOfServicesStateChange(this);
+ }
+
+ public Set<ComponentName> getBindingServicesLocked() {
+ return mBindingServices;
}
}
@@ -5145,11 +3526,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
// we are checking for changes only the parent settings.
UserState userState = getCurrentUserStateLocked();
- // If the automation service is suppressing, we will update when it dies.
- if (userState.isUiAutomationSuppressingOtherServices()) {
- return;
- }
-
if (mTouchExplorationEnabledUri.equals(uri)) {
if (readTouchExplorationEnabledSettingLocked(userState)) {
onUserStateChangedLocked(userState);
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
new file mode 100644
index 000000000000..eb26752988aa
--- /dev/null
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
@@ -0,0 +1,364 @@
+/*
+ ** Copyright 2017, 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 com.android.server.accessibility;
+
+import static android.provider.Settings.Secure.SHOW_MODE_AUTO;
+
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.accessibilityservice.GestureDescription;
+import android.accessibilityservice.IAccessibilityServiceClient;
+import android.accessibilityservice.IAccessibilityServiceConnection;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ParceledListSlice;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.util.Slog;
+import android.view.WindowManagerInternal;
+
+import com.android.server.accessibility.AccessibilityManagerService.SecurityPolicy;
+import com.android.server.accessibility.AccessibilityManagerService.UserState;
+
+import java.lang.ref.WeakReference;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * This class represents an accessibility service. It stores all per service
+ * data required for the service management, provides API for starting/stopping the
+ * service and is responsible for adding/removing the service in the data structures
+ * for service management. The class also exposes configuration interface that is
+ * passed to the service it represents as soon it is bound. It also serves as the
+ * connection for the service.
+ */
+class AccessibilityServiceConnection extends AccessibilityClientConnection {
+ private static final String LOG_TAG = "AccessibilityServiceConnection";
+ /*
+ Holding a weak reference so there isn't a loop of references. UserState keeps lists of bound
+ and binding services. These are freed on user changes, but just in case it somehow gets lost
+ the weak reference will let the memory get GCed.
+
+ Having the reference be null when being called is a very bad sign, but we check the condition.
+ */
+ final WeakReference<UserState> mUserStateWeakReference;
+ final Intent mIntent;
+
+ private final Handler mMainHandler;
+
+ private boolean mWasConnectedAndDied;
+
+
+ public AccessibilityServiceConnection(UserState userState, Context context,
+ ComponentName componentName,
+ AccessibilityServiceInfo accessibilityServiceInfo, int id, Handler mainHandler,
+ Object lock, SecurityPolicy securityPolicy, SystemSupport systemSupport,
+ WindowManagerInternal windowManagerInternal,
+ GlobalActionPerformer globalActionPerfomer) {
+ super(context, componentName, accessibilityServiceInfo, id, mainHandler, lock,
+ securityPolicy, systemSupport, windowManagerInternal, globalActionPerfomer);
+ mUserStateWeakReference = new WeakReference<UserState>(userState);
+ mIntent = new Intent().setComponent(mComponentName);
+ mMainHandler = mainHandler;
+ mIntent.putExtra(Intent.EXTRA_CLIENT_LABEL,
+ com.android.internal.R.string.accessibility_binding_label);
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ mIntent.putExtra(Intent.EXTRA_CLIENT_INTENT, mSystemSupport.getPendingIntentActivity(
+ mContext, 0, new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS), 0));
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ public void bindLocked() {
+ UserState userState = mUserStateWeakReference.get();
+ if (userState == null) return;
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ if (mService == null && mContext.bindServiceAsUser(
+ mIntent, this,
+ Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE,
+ new UserHandle(userState.mUserId))) {
+ userState.getBindingServicesLocked().add(mComponentName);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ public void unbindLocked() {
+ mContext.unbindService(this);
+ UserState userState = mUserStateWeakReference.get();
+ if (userState == null) return;
+ userState.removeServiceLocked(this);
+ resetLocked();
+ }
+
+ public boolean canRetrieveInteractiveWindowsLocked() {
+ return mSecurityPolicy.canRetrieveWindowContentLocked(this) && mRetrieveInteractiveWindows;
+ }
+
+ @Override
+ public void disableSelf() {
+ synchronized (mLock) {
+ UserState userState = mUserStateWeakReference.get();
+ if (userState == null) return;
+ if (userState.mEnabledServices.remove(mComponentName)) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ mSystemSupport.persistComponentNamesToSettingLocked(
+ Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
+ userState.mEnabledServices, userState.mUserId);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ mSystemSupport.onClientChange(false);
+ }
+ }
+ }
+
+ @Override
+ public void onServiceConnected(ComponentName componentName, IBinder service) {
+ synchronized (mLock) {
+ if (mService != service) {
+ if (mService != null) {
+ mService.unlinkToDeath(this, 0);
+ }
+ mService = service;
+ try {
+ mService.linkToDeath(this, 0);
+ } catch (RemoteException re) {
+ Slog.e(LOG_TAG, "Failed registering death link");
+ binderDied();
+ return;
+ }
+ }
+ mServiceInterface = IAccessibilityServiceClient.Stub.asInterface(service);
+ UserState userState = mUserStateWeakReference.get();
+ if (userState == null) return;
+ userState.addServiceLocked(this);
+ mSystemSupport.onClientChange(false);
+ // Initialize the service on the main handler after we're done setting up for
+ // the new configuration (for example, initializing the input filter).
+ mMainHandler.obtainMessage(
+ AccessibilityManagerService.MainHandler.MSG_INIT_SERVICE, this).sendToTarget();
+ }
+ }
+
+ public void initializeService() {
+ IAccessibilityServiceClient serviceInterface = null;
+ synchronized (mLock) {
+ UserState userState = mUserStateWeakReference.get();
+ if (userState == null) return;
+ Set<ComponentName> bindingServices = userState.getBindingServicesLocked();
+ if (bindingServices.contains(mComponentName) || mWasConnectedAndDied) {
+ bindingServices.remove(mComponentName);
+ mWasConnectedAndDied = false;
+ serviceInterface = mServiceInterface;
+ }
+ }
+ if (serviceInterface == null) {
+ binderDied();
+ return;
+ }
+ try {
+ serviceInterface.init(this, mId, mOverlayWindowToken);
+ } catch (RemoteException re) {
+ Slog.w(LOG_TAG, "Error while setting connection for service: "
+ + serviceInterface, re);
+ binderDied();
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName componentName) {
+ binderDied();
+ }
+
+ @Override
+ protected boolean isCalledForCurrentUserLocked() {
+ // We treat calls from a profile as if made by its parent as profiles
+ // share the accessibility state of the parent. The call below
+ // performs the current profile parent resolution.
+ final int resolvedUserId = mSecurityPolicy
+ .resolveCallingUserIdEnforcingPermissionsLocked(UserHandle.USER_CURRENT);
+ return resolvedUserId == mSystemSupport.getCurrentUserIdLocked();
+ }
+
+ @Override
+ public boolean setSoftKeyboardShowMode(int showMode) {
+ synchronized (mLock) {
+ if (!isCalledForCurrentUserLocked()) {
+ return false;
+ }
+ }
+ UserState userState = mUserStateWeakReference.get();
+ if (userState == null) return false;
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ // Keep track of the last service to request a non-default show mode. The show mode
+ // should be restored to default should this service be disabled.
+ userState.mServiceChangingSoftKeyboardMode = (showMode == SHOW_MODE_AUTO)
+ ? null : mComponentName;
+
+ Settings.Secure.putIntForUser(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE, showMode,
+ userState.mUserId);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ return true;
+ }
+
+ @Override
+ public boolean isAccessibilityButtonAvailable() {
+ synchronized (mLock) {
+ if (!isCalledForCurrentUserLocked()) {
+ return false;
+ }
+ UserState userState = mUserStateWeakReference.get();
+ return (userState != null) && isAccessibilityButtonAvailableLocked(userState);
+ }
+ }
+
+ public void binderDied() {
+ synchronized (mLock) {
+ // It is possible that this service's package was force stopped during
+ // whose handling the death recipient is unlinked and still get a call
+ // on binderDied since the call was made before we unlink but was
+ // waiting on the lock we held during the force stop handling.
+ if (!isConnectedLocked()) {
+ return;
+ }
+ mWasConnectedAndDied = true;
+ resetLocked();
+ if (mId == mSystemSupport.getMagnificationController().getIdOfLastServiceToMagnify()) {
+ mSystemSupport.getMagnificationController().resetIfNeeded(true);
+ }
+ mSystemSupport.onClientChange(false);
+ }
+ }
+
+ public boolean isAccessibilityButtonAvailableLocked(UserState userState) {
+ // If the service does not request the accessibility button, it isn't available
+ if (!mRequestAccessibilityButton) {
+ return false;
+ }
+
+ // If the accessibility button isn't currently shown, it cannot be available to services
+ if (!mSystemSupport.isAccessibilityButtonShown()) {
+ return false;
+ }
+
+ // If magnification is on and assigned to the accessibility button, services cannot be
+ if (userState.mIsNavBarMagnificationEnabled
+ && userState.mIsNavBarMagnificationAssignedToAccessibilityButton) {
+ return false;
+ }
+
+ int requestingServices = 0;
+ for (int i = userState.mBoundServices.size() - 1; i >= 0; i--) {
+ final AccessibilityServiceConnection service = userState.mBoundServices.get(i);
+ if (service.mRequestAccessibilityButton) {
+ requestingServices++;
+ }
+ }
+
+ if (requestingServices == 1) {
+ // If only a single service is requesting, it must be this service, and the
+ // accessibility button is available to it
+ return true;
+ } else {
+ // With more than one active service, we derive the target from the user's settings
+ if (userState.mServiceAssignedToAccessibilityButton == null) {
+ // If the user has not made an assignment, we treat the button as available to
+ // all services until the user interacts with the button to make an assignment
+ return true;
+ } else {
+ // If an assignment was made, it defines availability
+ return mComponentName.equals(userState.mServiceAssignedToAccessibilityButton);
+ }
+ }
+ }
+
+ @Override
+ public boolean isCapturingFingerprintGestures() {
+ return (mServiceInterface != null)
+ && mSecurityPolicy.canCaptureFingerprintGestures(this)
+ && mCaptureFingerprintGestures;
+ }
+
+ @Override
+ public void onFingerprintGestureDetectionActiveChanged(boolean active) {
+ if (!isCapturingFingerprintGestures()) {
+ return;
+ }
+ IAccessibilityServiceClient serviceInterface;
+ synchronized (mLock) {
+ serviceInterface = mServiceInterface;
+ }
+ if (serviceInterface != null) {
+ try {
+ mServiceInterface.onFingerprintCapturingGesturesChanged(active);
+ } catch (RemoteException e) {
+ }
+ }
+ }
+
+ @Override
+ public void onFingerprintGesture(int gesture) {
+ if (!isCapturingFingerprintGestures()) {
+ return;
+ }
+ IAccessibilityServiceClient serviceInterface;
+ synchronized (mLock) {
+ serviceInterface = mServiceInterface;
+ }
+ if (serviceInterface != null) {
+ try {
+ mServiceInterface.onFingerprintGesture(gesture);
+ } catch (RemoteException e) {
+ }
+ }
+ }
+
+ @Override
+ public void sendGesture(int sequence, ParceledListSlice gestureSteps) {
+ synchronized (mLock) {
+ if (mSecurityPolicy.canPerformGestures(this)) {
+ MotionEventInjector motionEventInjector =
+ mSystemSupport.getMotionEventInjectorLocked();
+ if (motionEventInjector != null) {
+ motionEventInjector.injectEvents(
+ gestureSteps.getList(), mServiceInterface, sequence);
+ } else {
+ try {
+ mServiceInterface.onPerformGestureResult(sequence, false);
+ } catch (RemoteException re) {
+ Slog.e(LOG_TAG, "Error sending motion event injection failure to "
+ + mServiceInterface, re);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/GlobalActionPerformer.java b/services/accessibility/java/com/android/server/accessibility/GlobalActionPerformer.java
new file mode 100644
index 000000000000..5db6f7da8102
--- /dev/null
+++ b/services/accessibility/java/com/android/server/accessibility/GlobalActionPerformer.java
@@ -0,0 +1,156 @@
+/*
+ ** Copyright 2017, 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 com.android.server.accessibility;
+
+import android.accessibilityservice.AccessibilityService;
+import android.app.StatusBarManager;
+import android.content.Context;
+import android.hardware.input.InputManager;
+import android.os.Binder;
+import android.os.SystemClock;
+import android.view.InputDevice;
+import android.view.KeyCharacterMap;
+import android.view.KeyEvent;
+import android.view.WindowManagerInternal;
+
+import com.android.server.LocalServices;
+import com.android.server.statusbar.StatusBarManagerInternal;
+
+/**
+ * Handle the back-end of AccessibilityService#performGlobalAction
+ */
+public class GlobalActionPerformer {
+ private final WindowManagerInternal mWindowManagerService;
+ private final Context mContext;
+
+ public GlobalActionPerformer(Context context, WindowManagerInternal windowManagerInternal) {
+ mContext = context;
+ mWindowManagerService = windowManagerInternal;
+ }
+
+ public boolean performGlobalAction(int action) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ switch (action) {
+ case AccessibilityService.GLOBAL_ACTION_BACK: {
+ sendDownAndUpKeyEvents(KeyEvent.KEYCODE_BACK);
+ }
+ return true;
+ case AccessibilityService.GLOBAL_ACTION_HOME: {
+ sendDownAndUpKeyEvents(KeyEvent.KEYCODE_HOME);
+ }
+ return true;
+ case AccessibilityService.GLOBAL_ACTION_RECENTS: {
+ return openRecents();
+ }
+ case AccessibilityService.GLOBAL_ACTION_NOTIFICATIONS: {
+ expandNotifications();
+ }
+ return true;
+ case AccessibilityService.GLOBAL_ACTION_QUICK_SETTINGS: {
+ expandQuickSettings();
+ }
+ return true;
+ case AccessibilityService.GLOBAL_ACTION_POWER_DIALOG: {
+ showGlobalActions();
+ }
+ return true;
+ case AccessibilityService.GLOBAL_ACTION_TOGGLE_SPLIT_SCREEN: {
+ return toggleSplitScreen();
+ }
+ }
+ return false;
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ private void sendDownAndUpKeyEvents(int keyCode) {
+ final long token = Binder.clearCallingIdentity();
+
+ // Inject down.
+ final long downTime = SystemClock.uptimeMillis();
+ sendKeyEventIdentityCleared(keyCode, KeyEvent.ACTION_DOWN, downTime, downTime);
+ sendKeyEventIdentityCleared(
+ keyCode, KeyEvent.ACTION_UP, downTime, SystemClock.uptimeMillis());
+
+ Binder.restoreCallingIdentity(token);
+ }
+
+ private void sendKeyEventIdentityCleared(int keyCode, int action, long downTime, long time) {
+ KeyEvent event = KeyEvent.obtain(downTime, time, action, keyCode, 0, 0,
+ KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_FROM_SYSTEM,
+ InputDevice.SOURCE_KEYBOARD, null);
+ InputManager.getInstance()
+ .injectInputEvent(event, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
+ event.recycle();
+ }
+
+ private void expandNotifications() {
+ final long token = Binder.clearCallingIdentity();
+
+ StatusBarManager statusBarManager = (StatusBarManager) mContext.getSystemService(
+ android.app.Service.STATUS_BAR_SERVICE);
+ statusBarManager.expandNotificationsPanel();
+
+ Binder.restoreCallingIdentity(token);
+ }
+
+ private void expandQuickSettings() {
+ final long token = Binder.clearCallingIdentity();
+
+ StatusBarManager statusBarManager = (StatusBarManager) mContext.getSystemService(
+ android.app.Service.STATUS_BAR_SERVICE);
+ statusBarManager.expandSettingsPanel();
+
+ Binder.restoreCallingIdentity(token);
+ }
+
+ private boolean openRecents() {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ StatusBarManagerInternal statusBarService = LocalServices.getService(
+ StatusBarManagerInternal.class);
+ if (statusBarService == null) {
+ return false;
+ }
+ statusBarService.toggleRecentApps();
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ return true;
+ }
+
+ private void showGlobalActions() {
+ mWindowManagerService.showGlobalActions();
+ }
+
+ private boolean toggleSplitScreen() {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ StatusBarManagerInternal statusBarService = LocalServices.getService(
+ StatusBarManagerInternal.class);
+ if (statusBarService == null) {
+ return false;
+ }
+ statusBarService.toggleSplitScreen();
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ return true;
+ }
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java
new file mode 100644
index 000000000000..3a1de98e739a
--- /dev/null
+++ b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java
@@ -0,0 +1,244 @@
+/*
+ ** Copyright 2017, 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 com.android.server.accessibility;
+
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.accessibilityservice.IAccessibilityServiceClient;
+import android.app.UiAutomation;
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.IBinder.DeathRecipient;
+import android.os.RemoteException;
+import android.util.Slog;
+import android.view.WindowManagerInternal;
+import android.view.accessibility.AccessibilityEvent;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+/**
+ * Class to manage UiAutomation.
+ */
+class UiAutomationManager {
+ private static final ComponentName COMPONENT_NAME =
+ new ComponentName("com.android.server.accessibility", "UiAutomation");
+ private static final String LOG_TAG = "UiAutomationManager";
+
+ private UiAutomationService mUiAutomationService;
+
+ private AccessibilityServiceInfo mUiAutomationServiceInfo;
+
+ private int mUiAutomationFlags;
+
+ private IBinder mUiAutomationServiceOwner;
+ private final DeathRecipient mUiAutomationSerivceOwnerDeathRecipient =
+ new DeathRecipient() {
+ @Override
+ public void binderDied() {
+ mUiAutomationServiceOwner.unlinkToDeath(this, 0);
+ mUiAutomationServiceOwner = null;
+ if (mUiAutomationService != null) {
+ destroyUiAutomationService();
+ }
+ }
+ };
+
+ /**
+ * Register a UiAutomation. Only one may be registered at a time.
+ *
+ * @param owner A binder object owned by the process that owns the UiAutomation to be
+ * registered.
+ * @param serviceClient The UiAutomation's service interface.
+ * @param accessibilityServiceInfo The UiAutomation's service info
+ * @param flags The UiAutomation's flags
+ * @param id The id for the service connection
+ */
+ void registerUiTestAutomationServiceLocked(IBinder owner,
+ IAccessibilityServiceClient serviceClient,
+ Context context, AccessibilityServiceInfo accessibilityServiceInfo,
+ int id, Handler mainHandler, Object lock,
+ AccessibilityManagerService.SecurityPolicy securityPolicy,
+ AccessibilityClientConnection.SystemSupport systemSupport,
+ WindowManagerInternal windowManagerInternal,
+ GlobalActionPerformer globalActionPerfomer, int flags) {
+ accessibilityServiceInfo.setComponentName(COMPONENT_NAME);
+
+ if (mUiAutomationService != null) {
+ throw new IllegalStateException("UiAutomationService " + serviceClient
+ + "already registered!");
+ }
+
+ try {
+ owner.linkToDeath(mUiAutomationSerivceOwnerDeathRecipient, 0);
+ } catch (RemoteException re) {
+ Slog.e(LOG_TAG, "Couldn't register for the death of a UiTestAutomationService!", re);
+ return;
+ }
+
+ mUiAutomationService = new UiAutomationService(context, accessibilityServiceInfo, id,
+ mainHandler, lock, securityPolicy, systemSupport, windowManagerInternal,
+ globalActionPerfomer);
+ mUiAutomationServiceOwner = owner;
+ mUiAutomationFlags = flags;
+ mUiAutomationServiceInfo = accessibilityServiceInfo;
+ mUiAutomationService.mServiceInterface = serviceClient;
+ mUiAutomationService.onAdded();
+ try {
+ mUiAutomationService.mServiceInterface.asBinder().linkToDeath(mUiAutomationService, 0);
+ } catch (RemoteException re) {
+ Slog.e(LOG_TAG, "Failed registering death link: " + re);
+ destroyUiAutomationService();
+ return;
+ }
+
+ mUiAutomationService.connectServiceUnknownThread();
+ }
+
+ void unregisterUiTestAutomationServiceLocked(IAccessibilityServiceClient serviceClient) {
+ if ((mUiAutomationService == null)
+ || (serviceClient == null)
+ || (mUiAutomationService.mServiceInterface == null)
+ || (serviceClient.asBinder()
+ != mUiAutomationService.mServiceInterface.asBinder())) {
+ throw new IllegalStateException("UiAutomationService " + serviceClient
+ + " not registered!");
+ }
+
+ destroyUiAutomationService();
+ }
+
+ void sendAccessibilityEventLocked(AccessibilityEvent event) {
+ if (mUiAutomationService != null) {
+ mUiAutomationService.notifyAccessibilityEvent(event);
+ }
+ }
+
+ boolean isUiAutomationRunningLocked() {
+ return (mUiAutomationService != null);
+ }
+
+ boolean suppressingAccessibilityServicesLocked() {
+ return (mUiAutomationService != null) && ((mUiAutomationFlags
+ & UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES) == 0);
+ }
+
+ boolean isTouchExplorationEnabledLocked() {
+ return (mUiAutomationService != null)
+ && mUiAutomationService.mRequestTouchExplorationMode;
+ }
+
+ boolean canRetrieveInteractiveWindowsLocked() {
+ return (mUiAutomationService != null) && mUiAutomationService.mRetrieveInteractiveWindows;
+ }
+
+ int getRequestedEventMaskLocked() {
+ if (mUiAutomationService == null) return 0;
+ return mUiAutomationService.mEventTypes;
+ }
+
+ void dumpUiAutomationService(FileDescriptor fd, final PrintWriter pw, String[] args) {
+ if (mUiAutomationService != null) {
+ mUiAutomationService.dump(fd, pw, args);
+ }
+ }
+
+ private void destroyUiAutomationService() {
+ mUiAutomationService.mServiceInterface.asBinder().unlinkToDeath(mUiAutomationService, 0);
+ mUiAutomationService.onRemoved();
+ mUiAutomationService.resetLocked();
+ mUiAutomationService = null;
+ mUiAutomationFlags = 0;
+ if (mUiAutomationServiceOwner != null) {
+ mUiAutomationServiceOwner.unlinkToDeath(mUiAutomationSerivceOwnerDeathRecipient, 0);
+ mUiAutomationServiceOwner = null;
+ }
+ }
+
+ private class UiAutomationService extends AccessibilityClientConnection {
+ UiAutomationService(Context context, AccessibilityServiceInfo accessibilityServiceInfo,
+ int id, Handler mainHandler, Object lock,
+ AccessibilityManagerService.SecurityPolicy securityPolicy,
+ SystemSupport systemSupport, WindowManagerInternal windowManagerInternal,
+ GlobalActionPerformer globalActionPerfomer) {
+ super(context, COMPONENT_NAME, accessibilityServiceInfo, id, mainHandler, lock,
+ securityPolicy, systemSupport, windowManagerInternal, globalActionPerfomer);
+ }
+
+ void connectServiceUnknownThread() {
+ // This needs to be done on the main thread
+ mEventDispatchHandler.post(() -> {
+ try {
+ mService = mServiceInterface.asBinder();
+ mService.linkToDeath(this, 0);
+ mServiceInterface.init(this, mId, mOverlayWindowToken);
+ } catch (RemoteException re) {
+ Slog.w(LOG_TAG, "Error initialized connection", re);
+ destroyUiAutomationService();
+ }
+ });
+ }
+
+ @Override
+ public void binderDied() {
+ destroyUiAutomationService();
+ }
+
+ @Override
+ protected boolean isCalledForCurrentUserLocked() {
+ // Allow UiAutomation to work for any user
+ return true;
+ }
+
+ @Override
+ protected boolean supportsFlagForNotImportantViews(AccessibilityServiceInfo info) {
+ return true;
+ }
+
+ // Since this isn't really an accessibility service, several methods are just stubbed here.
+ @Override
+ public boolean setSoftKeyboardShowMode(int mode) {
+ return false;
+ }
+
+ @Override
+ public boolean isAccessibilityButtonAvailable() {
+ return false;
+ }
+
+ @Override
+ public void disableSelf() {}
+
+ @Override
+ public void onServiceConnected(ComponentName componentName, IBinder service) {}
+
+ @Override
+ public void onServiceDisconnected(ComponentName componentName) {}
+
+ @Override
+ public boolean isCapturingFingerprintGestures() {
+ return false;
+ }
+
+ @Override
+ public void onFingerprintGestureDetectionActiveChanged(boolean active) {}
+
+ @Override
+ public void onFingerprintGesture(int gesture) {}
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java
new file mode 100644
index 000000000000..d3a8deee799e
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java
@@ -0,0 +1,116 @@
+/*
+ ** Copyright 2017, 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 com.android.server.accessibility;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.view.WindowManagerInternal;
+
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Arrays;
+import java.util.HashSet;
+
+
+/**
+ * Tests for AccessibilityServiceConnection
+ */
+public class AccessibilityServiceConnectionTest {
+ static final ComponentName COMPONENT_NAME = new ComponentName(
+ "com.android.server.accessibility", "AccessibilityServiceConnectionTest");
+ static final int SERVICE_ID = 42;
+
+ AccessibilityServiceConnection mConnection;
+ @Mock AccessibilityManagerService.UserState mMockUserState;
+ @Mock Context mMockContext;
+ @Mock AccessibilityServiceInfo mMockServiceInfo;
+ @Mock ResolveInfo mMockResolveInfo;
+ @Mock AccessibilityManagerService.SecurityPolicy mMockSecurityPolicy;
+ @Mock AccessibilityClientConnection.SystemSupport mMockSystemSupport;
+ @Mock WindowManagerInternal mMockWindowManagerInternal;
+ @Mock GlobalActionPerformer mMockGlobalActionPerformer;
+ @Mock KeyEventDispatcher mMockKeyEventDispatcher;
+
+
+ @BeforeClass
+ public static void oneTimeInitialization() {
+ if (Looper.myLooper() == null) {
+ Looper.prepare();
+ }
+ }
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ when(mMockSystemSupport.getKeyEventDispatcher()).thenReturn(mMockKeyEventDispatcher);
+ when(mMockServiceInfo.getResolveInfo()).thenReturn(mMockResolveInfo);
+ mMockResolveInfo.serviceInfo = mock(ServiceInfo.class);
+ mMockResolveInfo.serviceInfo.applicationInfo = mock(ApplicationInfo.class);
+
+ mConnection = new AccessibilityServiceConnection(mMockUserState, mMockContext,
+ COMPONENT_NAME, mMockServiceInfo, SERVICE_ID, new Handler(), new Object(),
+ mMockSecurityPolicy, mMockSystemSupport, mMockWindowManagerInternal,
+ mMockGlobalActionPerformer);
+ }
+
+ @Test
+ public void bind_requestsContextToBindService() {
+ mConnection.bindLocked();
+ verify(mMockContext).bindServiceAsUser(any(Intent.class), eq(mConnection),
+ eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE),
+ any(UserHandle.class));
+ }
+
+ @Test
+ public void unbind_requestsContextToUnbindService() {
+ mConnection.unbindLocked();
+ verify(mMockContext).unbindService(mConnection);
+ }
+
+ @Test
+ public void bindConnectUnbind_linksAndUnlinksToServiceDeath() throws RemoteException {
+ IBinder mockBinder = mock(IBinder.class);
+ when(mMockUserState.getBindingServicesLocked())
+ .thenReturn(new HashSet<>(Arrays.asList(COMPONENT_NAME)));
+ mConnection.bindLocked();
+ mConnection.onServiceConnected(COMPONENT_NAME, mockBinder);
+ verify(mockBinder).linkToDeath(eq(mConnection), anyInt());
+ mConnection.unbindLocked();
+ verify(mockBinder).unlinkToDeath(eq(mConnection), anyInt());
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/GlobalActionPerformerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/GlobalActionPerformerTest.java
new file mode 100644
index 000000000000..51e6bc90c947
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/accessibility/GlobalActionPerformerTest.java
@@ -0,0 +1,72 @@
+/*
+ ** Copyright 2017, 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 com.android.server.accessibility;
+
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.accessibilityservice.AccessibilityService;
+import android.app.StatusBarManager;
+import android.content.Context;
+import android.view.WindowManagerInternal;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for GlobalActionPerformer
+ */
+public class GlobalActionPerformerTest {
+ GlobalActionPerformer mGlobalActionPerformer;
+
+ @Mock Context mMockContext;
+ @Mock WindowManagerInternal mMockWindowManagerInternal;
+ @Mock StatusBarManager mMockStatusBarManager;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+
+ when(mMockContext.getSystemService(android.app.Service.STATUS_BAR_SERVICE))
+ .thenReturn(mMockStatusBarManager);
+
+ mGlobalActionPerformer =
+ new GlobalActionPerformer(mMockContext, mMockWindowManagerInternal);
+ }
+
+ @Test
+ public void testNotifications_expandsNotificationPanel() {
+ mGlobalActionPerformer
+ .performGlobalAction(AccessibilityService.GLOBAL_ACTION_NOTIFICATIONS);
+ verify(mMockStatusBarManager).expandNotificationsPanel();
+ }
+
+ @Test
+ public void testQuickSettings_requestsQuickSettingsPanel() {
+ mGlobalActionPerformer
+ .performGlobalAction(AccessibilityService.GLOBAL_ACTION_QUICK_SETTINGS);
+ verify(mMockStatusBarManager).expandSettingsPanel();
+ }
+
+ @Test
+ public void testPowerDialog_requestsFromWindowManager() {
+ mGlobalActionPerformer.performGlobalAction(AccessibilityService.GLOBAL_ACTION_POWER_DIALOG);
+ verify(mMockWindowManagerInternal).showGlobalActions();
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java
new file mode 100644
index 000000000000..f63d438fd223
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java
@@ -0,0 +1,160 @@
+/*
+ ** Copyright 2017, 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 com.android.server.accessibility;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.accessibilityservice.IAccessibilityServiceClient;
+import android.app.UiAutomation;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.view.WindowManagerInternal;
+import android.view.accessibility.AccessibilityEvent;
+
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for UiAutomationManager
+ */
+public class UiAutomationManagerTest {
+ static final int SERVICE_ID = 42;
+
+ final UiAutomationManager mUiAutomationManager = new UiAutomationManager();
+
+ @Mock AccessibilityManagerService.UserState mMockUserState;
+ @Mock Context mMockContext;
+ @Mock AccessibilityServiceInfo mMockServiceInfo;
+ @Mock ResolveInfo mMockResolveInfo;
+ @Mock AccessibilityManagerService.SecurityPolicy mMockSecurityPolicy;
+ @Mock AccessibilityClientConnection.SystemSupport mMockSystemSupport;
+ @Mock WindowManagerInternal mMockWindowManagerInternal;
+ @Mock GlobalActionPerformer mMockGlobalActionPerformer;
+ @Mock IBinder mMockOwner;
+ @Mock IAccessibilityServiceClient mMockAccessibilityServiceClient;
+ @Mock IBinder mMockServiceAsBinder;
+
+ @BeforeClass
+ public static void oneTimeInitialization() {
+ if (Looper.myLooper() == null) {
+ Looper.prepare();
+ }
+ }
+
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+
+ when(mMockSystemSupport.getKeyEventDispatcher()).thenReturn(mock(KeyEventDispatcher.class));
+
+ when(mMockServiceInfo.getResolveInfo()).thenReturn(mMockResolveInfo);
+ mMockResolveInfo.serviceInfo = mock(ServiceInfo.class);
+ mMockResolveInfo.serviceInfo.applicationInfo = mock(ApplicationInfo.class);
+
+ when(mMockAccessibilityServiceClient.asBinder()).thenReturn(mMockServiceAsBinder);
+ }
+
+ @Test
+ public void isRunning_returnsTrueOnlyWhenRunning() {
+ assertFalse(mUiAutomationManager.isUiAutomationRunningLocked());
+ register(0);
+ assertTrue(mUiAutomationManager.isUiAutomationRunningLocked());
+ unregister();
+ assertFalse(mUiAutomationManager.isUiAutomationRunningLocked());
+ }
+
+ @Test
+ public void suppressingAccessibilityServicesLocked_dependsOnFlags() {
+ assertFalse(mUiAutomationManager.suppressingAccessibilityServicesLocked());
+ register(0);
+ assertTrue(mUiAutomationManager.suppressingAccessibilityServicesLocked());
+ unregister();
+ assertFalse(mUiAutomationManager.suppressingAccessibilityServicesLocked());
+ register(UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES);
+ assertFalse(mUiAutomationManager.suppressingAccessibilityServicesLocked());
+ unregister();
+ assertFalse(mUiAutomationManager.suppressingAccessibilityServicesLocked());
+ }
+
+ @Test
+ public void isTouchExplorationEnabledLocked_dependsOnInfoFlags() {
+ assertFalse(mUiAutomationManager.isTouchExplorationEnabledLocked());
+ register(0);
+ assertFalse(mUiAutomationManager.isTouchExplorationEnabledLocked());
+ unregister();
+ mMockServiceInfo.flags = AccessibilityServiceInfo.FLAG_REQUEST_TOUCH_EXPLORATION_MODE;
+ register(0);
+ assertTrue(mUiAutomationManager.isTouchExplorationEnabledLocked());
+ unregister();
+ assertFalse(mUiAutomationManager.isTouchExplorationEnabledLocked());
+ }
+
+ @Test
+ public void canRetrieveInteractiveWindowsLocked_dependsOnInfoFlags() {
+ assertFalse(mUiAutomationManager.canRetrieveInteractiveWindowsLocked());
+ register(0);
+ assertFalse(mUiAutomationManager.canRetrieveInteractiveWindowsLocked());
+ unregister();
+ mMockServiceInfo.flags = AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS;
+ register(0);
+ assertTrue(mUiAutomationManager.canRetrieveInteractiveWindowsLocked());
+ unregister();
+ assertFalse(mUiAutomationManager.canRetrieveInteractiveWindowsLocked());
+ }
+
+ @Test
+ public void getRequestedEventMaskLocked_dependsOnInfoEventTypes() {
+ assertEquals(0, mUiAutomationManager.getRequestedEventMaskLocked());
+ mMockServiceInfo.eventTypes = 0;
+ register(0);
+ assertEquals(mMockServiceInfo.eventTypes,
+ mUiAutomationManager.getRequestedEventMaskLocked());
+ unregister();
+ mMockServiceInfo.eventTypes = AccessibilityEvent.TYPES_ALL_MASK;
+ register(0);
+ assertEquals(mMockServiceInfo.eventTypes,
+ mUiAutomationManager.getRequestedEventMaskLocked());
+ unregister();
+ assertEquals(0, mUiAutomationManager.getRequestedEventMaskLocked());
+ }
+
+ private void register(int flags) {
+ mUiAutomationManager.registerUiTestAutomationServiceLocked(mMockOwner,
+ mMockAccessibilityServiceClient, mMockContext, mMockServiceInfo, SERVICE_ID,
+ new Handler(), new Object(), mMockSecurityPolicy, mMockSystemSupport,
+ mMockWindowManagerInternal, mMockGlobalActionPerformer, flags);
+ }
+
+ private void unregister() {
+ mUiAutomationManager.unregisterUiTestAutomationServiceLocked(
+ mMockAccessibilityServiceClient);
+ }
+}