blob: 1873de53867e250b8e3698de1be80c451bbc8035 [file] [log] [blame]
/*
** 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 static android.view.accessibility.AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS;
import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS;
import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK;
import static android.view.accessibility.AccessibilityNodeInfo.ACTION_LONG_CLICK;
import android.accessibilityservice.AccessibilityServiceInfo;
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.PackageManager;
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.PowerManager;
import android.os.RemoteException;
import android.os.SystemClock;
import android.util.Slog;
import android.util.SparseArray;
import android.view.KeyEvent;
import android.view.MagnificationSpec;
import android.view.View;
import android.view.WindowInfo;
import android.view.accessibility.AccessibilityCache;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityWindowInfo;
import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.os.SomeArgs;
import com.android.internal.util.DumpUtils;
import com.android.server.LocalServices;
import com.android.server.accessibility.AccessibilityWindowManager.RemoteAccessibilityConnection;
import com.android.server.wm.ActivityTaskManagerInternal;
import com.android.server.wm.WindowManagerInternal;
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 AbstractAccessibilityServiceConnection extends IAccessibilityServiceConnection.Stub
implements ServiceConnection, IBinder.DeathRecipient, KeyEventDispatcher.KeyEventFilter,
FingerprintGestureDispatcher.FingerprintGestureClient {
private static final boolean DEBUG = false;
private static final String LOG_TAG = "AbstractAccessibilityServiceConnection";
private static final int WAIT_WINDOWS_TIMEOUT_MILLIS = 5000;
protected final Context mContext;
protected final SystemSupport mSystemSupport;
private final WindowManagerInternal mWindowManagerService;
private final GlobalActionPerformer mGlobalActionPerformer;
private final AccessibilityWindowManager mA11yWindowManager;
private final PowerManager mPowerManager;
// Handler for scheduling method invocations on the main thread.
public final InvocationHandler mInvocationHandler;
final int mId;
protected final AccessibilityServiceInfo mAccessibilityServiceInfo;
// Lock must match the one used by AccessibilityManagerService
protected final Object mLock;
protected final AccessibilitySecurityPolicy 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();
/**
* Called back to notify system that the client has changed
* @param serviceInfoChanged True if the service's AccessibilityServiceInfo changed.
*/
void onClientChangeLocked(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 AbstractAccessibilityServiceConnection(Context context, ComponentName componentName,
AccessibilityServiceInfo accessibilityServiceInfo, int id, Handler mainHandler,
Object lock, AccessibilitySecurityPolicy securityPolicy, SystemSupport systemSupport,
WindowManagerInternal windowManagerInternal,
GlobalActionPerformer globalActionPerfomer,
AccessibilityWindowManager a11yWindowManager) {
mContext = context;
mWindowManagerService = windowManagerInternal;
mId = id;
mComponentName = componentName;
mAccessibilityServiceInfo = accessibilityServiceInfo;
mLock = lock;
mSecurityPolicy = securityPolicy;
mGlobalActionPerformer = globalActionPerfomer;
mSystemSupport = systemSupport;
mInvocationHandler = new InvocationHandler(mainHandler.getLooper());
mA11yWindowManager = a11yWindowManager;
mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
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;
}
if (!mSecurityPolicy.checkAccessibilityAccess(this)) {
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 && mService != null);
}
@Override
public void setOnKeyEventResult(boolean handled, int sequence) {
mSystemSupport.getKeyEventDispatcher().setOnKeyEventResult(this, handled, sequence);
}
@Override
public AccessibilityServiceInfo getServiceInfo() {
synchronized (mLock) {
return mAccessibilityServiceInfo;
}
}
public int getCapabilities() {
return mAccessibilityServiceInfo.getCapabilities();
}
int getRelevantEventTypes() {
return (mUsesAccessibilityCache ? AccessibilityCache.CACHE_CRITICAL_EVENTS_MASK
: AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) | mEventTypes;
}
@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.onClientChangeLocked(true);
}
} finally {
Binder.restoreCallingIdentity(identity);
}
}
protected abstract boolean isCalledForCurrentUserLocked();
@Override
public List<AccessibilityWindowInfo> getWindows() {
ensureWindowsAvailableTimed();
synchronized (mLock) {
if (!isCalledForCurrentUserLocked()) {
return null;
}
final boolean permissionGranted =
mSecurityPolicy.canRetrieveWindowsLocked(this);
if (!permissionGranted) {
return null;
}
List<AccessibilityWindowInfo> internalWindowList =
mA11yWindowManager.getWindowListLocked();
if (internalWindowList == null) {
return null;
}
if (!mSecurityPolicy.checkAccessibilityAccess(this)) {
return null;
}
List<AccessibilityWindowInfo> returnedWindowList = new ArrayList<>();
final int windowCount = internalWindowList.size();
for (int i = 0; i < windowCount; i++) {
AccessibilityWindowInfo window = internalWindowList.get(i);
AccessibilityWindowInfo windowClone =
AccessibilityWindowInfo.obtain(window);
windowClone.setConnectionId(mId);
returnedWindowList.add(windowClone);
}
return returnedWindowList;
}
}
@Override
public AccessibilityWindowInfo getWindow(int windowId) {
ensureWindowsAvailableTimed();
synchronized (mLock) {
if (!isCalledForCurrentUserLocked()) {
return null;
}
final boolean permissionGranted =
mSecurityPolicy.canRetrieveWindowsLocked(this);
if (!permissionGranted) {
return null;
}
if (!mSecurityPolicy.checkAccessibilityAccess(this)) {
return null;
}
AccessibilityWindowInfo window = mA11yWindowManager.findA11yWindowInfoById(windowId);
if (window != null) {
AccessibilityWindowInfo windowClone = AccessibilityWindowInfo.obtain(window);
windowClone.setConnectionId(mId);
return windowClone;
}
return null;
}
}
@Override
public String[] findAccessibilityNodeInfosByViewId(int accessibilityWindowId,
long accessibilityNodeId, String viewIdResName, int interactionId,
IAccessibilityInteractionConnectionCallback callback, long interrogatingTid)
throws RemoteException {
final int resolvedWindowId;
RemoteAccessibilityConnection connection;
Region partialInteractiveRegion = Region.obtain();
MagnificationSpec spec;
synchronized (mLock) {
mUsesAccessibilityCache = true;
if (!isCalledForCurrentUserLocked()) {
return null;
}
resolvedWindowId = resolveAccessibilityWindowIdLocked(accessibilityWindowId);
final boolean permissionGranted =
mSecurityPolicy.canGetAccessibilityNodeInfoLocked(
mSystemSupport.getCurrentUserIdLocked(), this, resolvedWindowId);
if (!permissionGranted) {
return null;
} else {
connection = mA11yWindowManager.getConnectionLocked(
mSystemSupport.getCurrentUserIdLocked(), resolvedWindowId);
if (connection == null) {
return null;
}
}
if (!mA11yWindowManager.computePartialInteractiveRegionForWindowLocked(
resolvedWindowId, partialInteractiveRegion)) {
partialInteractiveRegion.recycle();
partialInteractiveRegion = null;
}
spec = mSystemSupport.getCompatibleMagnificationSpecLocked(resolvedWindowId);
}
if (!mSecurityPolicy.checkAccessibilityAccess(this)) {
return null;
}
final int interrogatingPid = Binder.getCallingPid();
callback = replaceCallbackIfNeeded(callback, resolvedWindowId, interactionId,
interrogatingPid, interrogatingTid);
final long identityToken = Binder.clearCallingIdentity();
try {
connection.getRemote().findAccessibilityNodeInfosByViewId(accessibilityNodeId,
viewIdResName, partialInteractiveRegion, interactionId, callback, mFetchFlags,
interrogatingPid, interrogatingTid, spec);
return mSecurityPolicy.computeValidReportedPackages(
connection.getPackageName(), connection.getUid());
} 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.getRemote())) {
partialInteractiveRegion.recycle();
}
}
return null;
}
@Override
public String[] findAccessibilityNodeInfosByText(int accessibilityWindowId,
long accessibilityNodeId, String text, int interactionId,
IAccessibilityInteractionConnectionCallback callback, long interrogatingTid)
throws RemoteException {
final int resolvedWindowId;
RemoteAccessibilityConnection connection;
Region partialInteractiveRegion = Region.obtain();
MagnificationSpec spec;
synchronized (mLock) {
mUsesAccessibilityCache = true;
if (!isCalledForCurrentUserLocked()) {
return null;
}
resolvedWindowId = resolveAccessibilityWindowIdLocked(accessibilityWindowId);
final boolean permissionGranted =
mSecurityPolicy.canGetAccessibilityNodeInfoLocked(
mSystemSupport.getCurrentUserIdLocked(), this, resolvedWindowId);
if (!permissionGranted) {
return null;
} else {
connection = mA11yWindowManager.getConnectionLocked(
mSystemSupport.getCurrentUserIdLocked(), resolvedWindowId);
if (connection == null) {
return null;
}
}
if (!mA11yWindowManager.computePartialInteractiveRegionForWindowLocked(
resolvedWindowId, partialInteractiveRegion)) {
partialInteractiveRegion.recycle();
partialInteractiveRegion = null;
}
spec = mSystemSupport.getCompatibleMagnificationSpecLocked(resolvedWindowId);
}
if (!mSecurityPolicy.checkAccessibilityAccess(this)) {
return null;
}
final int interrogatingPid = Binder.getCallingPid();
callback = replaceCallbackIfNeeded(callback, resolvedWindowId, interactionId,
interrogatingPid, interrogatingTid);
final long identityToken = Binder.clearCallingIdentity();
try {
connection.getRemote().findAccessibilityNodeInfosByText(accessibilityNodeId,
text, partialInteractiveRegion, interactionId, callback, mFetchFlags,
interrogatingPid, interrogatingTid, spec);
return mSecurityPolicy.computeValidReportedPackages(
connection.getPackageName(), connection.getUid());
} 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.getRemote())) {
partialInteractiveRegion.recycle();
}
}
return null;
}
@Override
public String[] findAccessibilityNodeInfoByAccessibilityId(
int accessibilityWindowId, long accessibilityNodeId, int interactionId,
IAccessibilityInteractionConnectionCallback callback, int flags,
long interrogatingTid, Bundle arguments) throws RemoteException {
final int resolvedWindowId;
RemoteAccessibilityConnection connection;
Region partialInteractiveRegion = Region.obtain();
MagnificationSpec spec;
synchronized (mLock) {
mUsesAccessibilityCache = true;
if (!isCalledForCurrentUserLocked()) {
return null;
}
resolvedWindowId = resolveAccessibilityWindowIdLocked(accessibilityWindowId);
final boolean permissionGranted =
mSecurityPolicy.canGetAccessibilityNodeInfoLocked(
mSystemSupport.getCurrentUserIdLocked(), this, resolvedWindowId);
if (!permissionGranted) {
return null;
} else {
connection = mA11yWindowManager.getConnectionLocked(
mSystemSupport.getCurrentUserIdLocked(), resolvedWindowId);
if (connection == null) {
return null;
}
}
if (!mA11yWindowManager.computePartialInteractiveRegionForWindowLocked(
resolvedWindowId, partialInteractiveRegion)) {
partialInteractiveRegion.recycle();
partialInteractiveRegion = null;
}
spec = mSystemSupport.getCompatibleMagnificationSpecLocked(resolvedWindowId);
}
if (!mSecurityPolicy.checkAccessibilityAccess(this)) {
return null;
}
final int interrogatingPid = Binder.getCallingPid();
callback = replaceCallbackIfNeeded(callback, resolvedWindowId, interactionId,
interrogatingPid, interrogatingTid);
final long identityToken = Binder.clearCallingIdentity();
try {
connection.getRemote().findAccessibilityNodeInfoByAccessibilityId(
accessibilityNodeId, partialInteractiveRegion, interactionId, callback,
mFetchFlags | flags, interrogatingPid, interrogatingTid, spec, arguments);
return mSecurityPolicy.computeValidReportedPackages(
connection.getPackageName(), connection.getUid());
} 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.getRemote())) {
partialInteractiveRegion.recycle();
}
}
return null;
}
@Override
public String[] findFocus(int accessibilityWindowId, long accessibilityNodeId,
int focusType, int interactionId,
IAccessibilityInteractionConnectionCallback callback, long interrogatingTid)
throws RemoteException {
final int resolvedWindowId;
RemoteAccessibilityConnection connection;
Region partialInteractiveRegion = Region.obtain();
MagnificationSpec spec;
synchronized (mLock) {
if (!isCalledForCurrentUserLocked()) {
return null;
}
resolvedWindowId = resolveAccessibilityWindowIdForFindFocusLocked(
accessibilityWindowId, focusType);
final boolean permissionGranted =
mSecurityPolicy.canGetAccessibilityNodeInfoLocked(
mSystemSupport.getCurrentUserIdLocked(), this, resolvedWindowId);
if (!permissionGranted) {
return null;
} else {
connection = mA11yWindowManager.getConnectionLocked(
mSystemSupport.getCurrentUserIdLocked(), resolvedWindowId);
if (connection == null) {
return null;
}
}
if (!mA11yWindowManager.computePartialInteractiveRegionForWindowLocked(
resolvedWindowId, partialInteractiveRegion)) {
partialInteractiveRegion.recycle();
partialInteractiveRegion = null;
}
spec = mSystemSupport.getCompatibleMagnificationSpecLocked(resolvedWindowId);
}
if (!mSecurityPolicy.checkAccessibilityAccess(this)) {
return null;
}
final int interrogatingPid = Binder.getCallingPid();
callback = replaceCallbackIfNeeded(callback, resolvedWindowId, interactionId,
interrogatingPid, interrogatingTid);
final long identityToken = Binder.clearCallingIdentity();
try {
connection.getRemote().findFocus(accessibilityNodeId, focusType,
partialInteractiveRegion, interactionId, callback, mFetchFlags,
interrogatingPid, interrogatingTid, spec);
return mSecurityPolicy.computeValidReportedPackages(
connection.getPackageName(), connection.getUid());
} 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.getRemote())) {
partialInteractiveRegion.recycle();
}
}
return null;
}
@Override
public String[] focusSearch(int accessibilityWindowId, long accessibilityNodeId,
int direction, int interactionId,
IAccessibilityInteractionConnectionCallback callback, long interrogatingTid)
throws RemoteException {
final int resolvedWindowId;
RemoteAccessibilityConnection connection;
Region partialInteractiveRegion = Region.obtain();
MagnificationSpec spec;
synchronized (mLock) {
if (!isCalledForCurrentUserLocked()) {
return null;
}
resolvedWindowId = resolveAccessibilityWindowIdLocked(accessibilityWindowId);
final boolean permissionGranted =
mSecurityPolicy.canGetAccessibilityNodeInfoLocked(
mSystemSupport.getCurrentUserIdLocked(), this, resolvedWindowId);
if (!permissionGranted) {
return null;
} else {
connection = mA11yWindowManager.getConnectionLocked(
mSystemSupport.getCurrentUserIdLocked(), resolvedWindowId);
if (connection == null) {
return null;
}
}
if (!mA11yWindowManager.computePartialInteractiveRegionForWindowLocked(
resolvedWindowId, partialInteractiveRegion)) {
partialInteractiveRegion.recycle();
partialInteractiveRegion = null;
}
spec = mSystemSupport.getCompatibleMagnificationSpecLocked(resolvedWindowId);
}
if (!mSecurityPolicy.checkAccessibilityAccess(this)) {
return null;
}
final int interrogatingPid = Binder.getCallingPid();
callback = replaceCallbackIfNeeded(callback, resolvedWindowId, interactionId,
interrogatingPid, interrogatingTid);
final long identityToken = Binder.clearCallingIdentity();
try {
connection.getRemote().focusSearch(accessibilityNodeId, direction,
partialInteractiveRegion, interactionId, callback, mFetchFlags,
interrogatingPid, interrogatingTid, spec);
return mSecurityPolicy.computeValidReportedPackages(
connection.getPackageName(), connection.getUid());
} 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.getRemote())) {
partialInteractiveRegion.recycle();
}
}
return null;
}
@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;
synchronized (mLock) {
if (!isCalledForCurrentUserLocked()) {
return false;
}
resolvedWindowId = resolveAccessibilityWindowIdLocked(accessibilityWindowId);
if (!mSecurityPolicy.canGetAccessibilityNodeInfoLocked(
mSystemSupport.getCurrentUserIdLocked(), this, resolvedWindowId)) {
return false;
}
}
if (!mSecurityPolicy.checkAccessibilityAccess(this)) {
return false;
}
return performAccessibilityActionInternal(
mSystemSupport.getCurrentUserIdLocked(), resolvedWindowId, accessibilityNodeId,
action, arguments, interactionId, callback, mFetchFlags, interrogatingTid);
}
@Override
public boolean performGlobalAction(int action) {
synchronized (mLock) {
if (!isCalledForCurrentUserLocked()) {
return false;
}
}
return mGlobalActionPerformer.performGlobalAction(action);
}
@Override
public boolean isFingerprintGestureDetectionAvailable() {
if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) {
return false;
}
if (isCapturingFingerprintGestures()) {
FingerprintGestureDispatcher dispatcher =
mSystemSupport.getFingerprintGestureDispatcher();
return (dispatcher != null) && dispatcher.isFingerprintGestureDetectionAvailable();
}
return false;
}
@Override
public float getMagnificationScale(int displayId) {
synchronized (mLock) {
if (!isCalledForCurrentUserLocked()) {
return 1.0f;
}
}
final long identity = Binder.clearCallingIdentity();
try {
return mSystemSupport.getMagnificationController().getScale(displayId);
} finally {
Binder.restoreCallingIdentity(identity);
}
}
@Override
public Region getMagnificationRegion(int displayId) {
synchronized (mLock) {
final Region region = Region.obtain();
if (!isCalledForCurrentUserLocked()) {
return region;
}
MagnificationController magnificationController =
mSystemSupport.getMagnificationController();
boolean registeredJustForThisCall =
registerMagnificationIfNeeded(displayId, magnificationController);
final long identity = Binder.clearCallingIdentity();
try {
magnificationController.getMagnificationRegion(displayId, region);
return region;
} finally {
Binder.restoreCallingIdentity(identity);
if (registeredJustForThisCall) {
magnificationController.unregister(displayId);
}
}
}
}
@Override
public float getMagnificationCenterX(int displayId) {
synchronized (mLock) {
if (!isCalledForCurrentUserLocked()) {
return 0.0f;
}
MagnificationController magnificationController =
mSystemSupport.getMagnificationController();
boolean registeredJustForThisCall =
registerMagnificationIfNeeded(displayId, magnificationController);
final long identity = Binder.clearCallingIdentity();
try {
return magnificationController.getCenterX(displayId);
} finally {
Binder.restoreCallingIdentity(identity);
if (registeredJustForThisCall) {
magnificationController.unregister(displayId);
}
}
}
}
@Override
public float getMagnificationCenterY(int displayId) {
synchronized (mLock) {
if (!isCalledForCurrentUserLocked()) {
return 0.0f;
}
MagnificationController magnificationController =
mSystemSupport.getMagnificationController();
boolean registeredJustForThisCall =
registerMagnificationIfNeeded(displayId, magnificationController);
final long identity = Binder.clearCallingIdentity();
try {
return magnificationController.getCenterY(displayId);
} finally {
Binder.restoreCallingIdentity(identity);
if (registeredJustForThisCall) {
magnificationController.unregister(displayId);
}
}
}
}
private boolean registerMagnificationIfNeeded(int displayId,
MagnificationController magnificationController) {
if (!magnificationController.isRegistered(displayId)
&& mSecurityPolicy.canControlMagnification(this)) {
magnificationController.register(displayId);
return true;
}
return false;
}
@Override
public boolean resetMagnification(int displayId, boolean animate) {
synchronized (mLock) {
if (!isCalledForCurrentUserLocked()) {
return false;
}
if (!mSecurityPolicy.canControlMagnification(this)) {
return false;
}
}
final long identity = Binder.clearCallingIdentity();
try {
MagnificationController magnificationController =
mSystemSupport.getMagnificationController();
return (magnificationController.reset(displayId, animate)
|| !magnificationController.isMagnifying(displayId));
} finally {
Binder.restoreCallingIdentity(identity);
}
}
@Override
public boolean setMagnificationScaleAndCenter(int displayId, 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.isRegistered(displayId)) {
magnificationController.register(displayId);
}
return magnificationController
.setScaleAndCenter(displayId, scale, centerX, centerY, animate, mId);
} finally {
Binder.restoreCallingIdentity(identity);
}
}
}
@Override
public void setMagnificationCallbackEnabled(int displayId, boolean enabled) {
mInvocationHandler.setMagnificationCallbackEnabled(displayId, enabled);
}
public boolean isMagnificationCallbackEnabled(int displayId) {
return mInvocationHandler.isMagnificationCallbackEnabled(displayId);
}
@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 int eventType = event.getEventType();
final boolean serviceWantsEvent = wantsEventLocked(event);
final boolean requiredForCacheConsistency = mUsesAccessibilityCache
&& ((AccessibilityCache.CACHE_CRITICAL_EVENTS_MASK & eventType) != 0);
if (!serviceWantsEvent && !requiredForCacheConsistency) {
return;
}
if (!mSecurityPolicy.checkAccessibilityAccess(this)) {
return;
}
// 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(int displayId, @NonNull Region region,
float scale, float centerX, float centerY) {
mInvocationHandler
.notifyMagnificationChangedLocked(displayId, 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(int displayId, @NonNull Region region,
float scale, float centerX, float centerY) {
final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
if (listener != null) {
try {
listener.onMagnificationChanged(displayId, 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 mA11yWindowManager.getActiveWindowId(mSystemSupport.getCurrentUserIdLocked());
}
return accessibilityWindowId;
}
private int resolveAccessibilityWindowIdForFindFocusLocked(int windowId, int focusType) {
if (windowId == AccessibilityWindowInfo.ACTIVE_WINDOW_ID) {
return mA11yWindowManager.getActiveWindowId(mSystemSupport.getCurrentUserIdLocked());
}
if (windowId == AccessibilityWindowInfo.ANY_WINDOW_ID) {
return mA11yWindowManager.getFocusedWindowId(focusType);
}
return windowId;
}
/**
* Request that the system make sure windows are available to interrogate.
*/
private void ensureWindowsAvailableTimed() {
synchronized (mLock) {
if (mA11yWindowManager.getWindowListLocked() != null) {
return;
}
// If we have no registered callback, update the state we
// we may have to register one but it didn't happen yet.
if (!mA11yWindowManager.isTrackingWindowsLocked()) {
// Invokes client change to make sure tracking window enabled.
mSystemSupport.onClientChangeLocked(false);
}
// We have no windows but do not care about them, done.
if (!mA11yWindowManager.isTrackingWindowsLocked()) {
return;
}
// Wait for the windows with a timeout.
final long startMillis = SystemClock.uptimeMillis();
while (mA11yWindowManager.getWindowListLocked() == null) {
final long elapsedMillis = SystemClock.uptimeMillis() - startMillis;
final long remainMillis = WAIT_WINDOWS_TIMEOUT_MILLIS - elapsedMillis;
if (remainMillis <= 0) {
return;
}
try {
mLock.wait(remainMillis);
} catch (InterruptedException ie) {
/* ignore */
}
}
}
}
/**
* 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
*/
private boolean performAccessibilityActionInternal(int userId, int resolvedWindowId,
long accessibilityNodeId, int action, Bundle arguments, int interactionId,
IAccessibilityInteractionConnectionCallback callback, int fetchFlags,
long interrogatingTid) {
RemoteAccessibilityConnection connection;
IBinder activityToken = null;
synchronized (mLock) {
connection = mA11yWindowManager.getConnectionLocked(userId, resolvedWindowId);
if (connection == null) {
return false;
}
final boolean isA11yFocusAction = (action == ACTION_ACCESSIBILITY_FOCUS)
|| (action == ACTION_CLEAR_ACCESSIBILITY_FOCUS);
if (!isA11yFocusAction) {
final WindowInfo windowInfo =
mA11yWindowManager.findWindowInfoById(resolvedWindowId);
if (windowInfo != null) activityToken = windowInfo.activityToken;
}
final AccessibilityWindowInfo a11yWindowInfo =
mA11yWindowManager.findA11yWindowInfoById(resolvedWindowId);
if (a11yWindowInfo != null && a11yWindowInfo.isInPictureInPictureMode()
&& mA11yWindowManager.getPictureInPictureActionReplacingConnection() != null
&& !isA11yFocusAction) {
connection = mA11yWindowManager.getPictureInPictureActionReplacingConnection();
}
}
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 (action == ACTION_CLICK || action == ACTION_LONG_CLICK) {
mA11yWindowManager.notifyOutsideTouch(userId, resolvedWindowId);
}
if (activityToken != null) {
LocalServices.getService(ActivityTaskManagerInternal.class)
.setFocusedActivity(activityToken);
}
connection.getRemote().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;
}
/**
* 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.
*/
private IAccessibilityInteractionConnectionCallback replaceCallbackIfNeeded(
IAccessibilityInteractionConnectionCallback originalCallback, int resolvedWindowId,
int interactionId, int interrogatingPid, long interrogatingTid) {
final RemoteAccessibilityConnection pipActionReplacingConnection =
mA11yWindowManager.getPictureInPictureActionReplacingConnection();
final AccessibilityWindowInfo windowInfo =
mA11yWindowManager.findA11yWindowInfoById(resolvedWindowId);
if ((windowInfo == null) || !windowInfo.isInPictureInPictureMode()
|| (pipActionReplacingConnection == null)) {
return originalCallback;
}
return new ActionReplacingCallback(originalCallback,
pipActionReplacingConnection.getRemote(), interactionId,
interrogatingPid, interrogatingTid);
}
public ComponentName getComponentName() {
return mComponentName;
}
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;
/** List of magnification callback states, mapping from displayId -> Boolean */
@GuardedBy("mlock")
private final SparseArray<Boolean> mMagnificationCallbackState = new SparseArray<>(0);
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;
final int displayId = args.argi1;
notifyMagnificationChangedInternal(displayId, 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(int displayId, @NonNull Region region,
float scale, float centerX, float centerY) {
synchronized (mLock) {
if (mMagnificationCallbackState.get(displayId) == null) {
return;
}
}
final SomeArgs args = SomeArgs.obtain();
args.arg1 = region;
args.arg2 = scale;
args.arg3 = centerX;
args.arg4 = centerY;
args.argi1 = displayId;
final Message msg = obtainMessage(MSG_ON_MAGNIFICATION_CHANGED, args);
msg.sendToTarget();
}
public void setMagnificationCallbackEnabled(int displayId, boolean enabled) {
synchronized (mLock) {
if (enabled) {
mMagnificationCallbackState.put(displayId, true);
} else {
mMagnificationCallbackState.remove(displayId);
}
}
}
public boolean isMagnificationCallbackEnabled(int displayId) {
synchronized (mLock) {
return mMagnificationCallbackState.get(displayId) != null;
}
}
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();
}
}
}