diff options
| author | 2017-08-25 17:37:04 +0000 | |
|---|---|---|
| committer | 2017-08-25 17:37:04 +0000 | |
| commit | 1f3f7a858c4bb079d52cb6628335ee12c950a46c (patch) | |
| tree | 18b8fc0d31840e252124a706d88a7ef62a284cf7 | |
| parent | 0c8030dffe675dfcf8b77e86ca83b73d840db882 (diff) | |
| parent | 015847aa4f5ba31d6a4fd6695202ae60aaec926a (diff) | |
Merge "Refactoring A11yService and UiAutomation handling"
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); + } +} |