| /* |
| ** Copyright 2009, The Android Open Source Project |
| ** |
| ** Licensed under the Apache License, Version 2.0 (the "License"); |
| ** you may not use this file except in compliance with the License. |
| ** You may obtain a copy of the License at |
| ** |
| ** http://www.apache.org/licenses/LICENSE-2.0 |
| ** |
| ** Unless required by applicable law or agreed to in writing, software |
| ** distributed under the License is distributed on an "AS IS" BASIS, |
| ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| ** See the License for the specific language governing permissions and |
| ** limitations under the License. |
| */ |
| |
| package 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.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.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; |
| import android.content.Context; |
| 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; |
| import android.database.ContentObserver; |
| import android.graphics.Point; |
| 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; |
| 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.Process; |
| import android.os.RemoteCallbackList; |
| import android.os.RemoteException; |
| import android.os.ServiceManager; |
| import android.os.SystemClock; |
| import android.os.UserHandle; |
| import android.os.UserManager; |
| import android.os.UserManagerInternal; |
| import android.provider.Settings; |
| import android.provider.SettingsStringUtil; |
| import android.provider.SettingsStringUtil.ComponentNameSet; |
| import android.provider.SettingsStringUtil.SettingStringHelper; |
| import android.text.TextUtils; |
| import android.text.TextUtils.SimpleStringSplitter; |
| import android.util.IntArray; |
| 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; |
| import android.view.WindowInfo; |
| import android.view.WindowManager; |
| import android.view.WindowManagerInternal; |
| import android.view.accessibility.AccessibilityCache; |
| import android.view.accessibility.AccessibilityEvent; |
| import android.view.accessibility.AccessibilityInteractionClient; |
| import android.view.accessibility.AccessibilityManager; |
| import android.view.accessibility.AccessibilityNodeInfo; |
| import android.view.accessibility.AccessibilityWindowInfo; |
| import android.view.accessibility.IAccessibilityInteractionConnection; |
| import android.view.accessibility.IAccessibilityInteractionConnectionCallback; |
| import android.view.accessibility.IAccessibilityManager; |
| import android.view.accessibility.IAccessibilityManagerClient; |
| |
| import com.android.internal.R; |
| import com.android.internal.annotations.GuardedBy; |
| 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; |
| |
| 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; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.concurrent.CopyOnWriteArrayList; |
| import java.util.function.Consumer; |
| |
| /** |
| * This class is instantiated by the system as a system level service and can be |
| * accessed only by the system. The task of this service is to be a centralized |
| * 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 { |
| |
| private static final boolean DEBUG = false; |
| |
| private static final String LOG_TAG = "AccessibilityManagerService"; |
| |
| // TODO: This is arbitrary. When there is time implement this by watching |
| // when that accessibility services are bound. |
| private static final int WAIT_FOR_USER_STATE_FULLY_INITIALIZED_MILLIS = 3000; |
| |
| private static final int WAIT_WINDOWS_TIMEOUT_MILLIS = 5000; |
| |
| // TODO: Restructure service initialization so services aren't connected before all of |
| // their capabilities are ready. |
| private static final int WAIT_MOTION_INJECTOR_TIMEOUT_MILLIS = 1000; |
| |
| private static final String FUNCTION_REGISTER_UI_TEST_AUTOMATION_SERVICE = |
| "registerUiTestAutomationService"; |
| |
| private static final String TEMPORARY_ENABLE_ACCESSIBILITY_UNTIL_KEYGUARD_REMOVED = |
| "temporaryEnableAccessibilityStateUntilKeyguardRemoved"; |
| |
| private static final String GET_WINDOW_TOKEN = "getWindowToken"; |
| |
| 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 = ':'; |
| |
| private static final int OWN_PROCESS_ID = android.os.Process.myPid(); |
| |
| // Each service has an ID. Also provide one for magnification gesture handling |
| public static final int MAGNIFICATION_GESTURE_HANDLER_ID = 0; |
| |
| private static int sIdCounter = MAGNIFICATION_GESTURE_HANDLER_ID + 1; |
| |
| private static int sNextWindowId; |
| |
| private final Context mContext; |
| |
| private final Object mLock = new Object(); |
| |
| private final SimpleStringSplitter mStringColonSplitter = |
| new SimpleStringSplitter(COMPONENT_NAME_SEPARATOR); |
| |
| private final Rect mTempRect = new Rect(); |
| |
| private final Rect mTempRect1 = new Rect(); |
| |
| private final Point mTempPoint = new Point(); |
| |
| private final PackageManager mPackageManager; |
| |
| private final PowerManager mPowerManager; |
| |
| private final WindowManagerInternal mWindowManagerService; |
| |
| private final SecurityPolicy mSecurityPolicy; |
| |
| private final MainHandler mMainHandler; |
| |
| private MagnificationController mMagnificationController; |
| |
| private InteractionBridge mInteractionBridge; |
| |
| private AlertDialog mEnableTouchExplorationDialog; |
| |
| private AccessibilityInputFilter mInputFilter; |
| |
| private boolean mHasInputFilter; |
| |
| private KeyEventDispatcher mKeyEventDispatcher; |
| |
| private MotionEventInjector mMotionEventInjector; |
| |
| private FingerprintGestureDispatcher mFingerprintGestureDispatcher; |
| |
| private final Set<ComponentName> mTempComponentNameSet = new HashSet<>(); |
| |
| private final List<AccessibilityServiceInfo> mTempAccessibilityServiceInfoList = |
| new ArrayList<>(); |
| |
| private final IntArray mTempIntArray = new IntArray(0); |
| |
| private final RemoteCallbackList<IAccessibilityManagerClient> mGlobalClients = |
| new RemoteCallbackList<>(); |
| |
| private final SparseArray<AccessibilityConnectionWrapper> mGlobalInteractionConnections = |
| new SparseArray<>(); |
| |
| private AccessibilityConnectionWrapper mPictureInPictureActionReplacingConnection; |
| |
| private final SparseArray<IBinder> mGlobalWindowTokens = new SparseArray<>(); |
| |
| private final SparseArray<UserState> mUserStates = new SparseArray<>(); |
| |
| private final UserManager mUserManager; |
| |
| private int mCurrentUserId = UserHandle.USER_SYSTEM; |
| |
| //TODO: Remove this hack |
| private boolean mInitialized; |
| |
| private WindowsForAccessibilityCallback mWindowsForAccessibilityCallback; |
| |
| private boolean mIsAccessibilityButtonShown; |
| |
| private UserState getCurrentUserStateLocked() { |
| return getUserStateLocked(mCurrentUserId); |
| } |
| |
| /** |
| * Creates a new instance. |
| * |
| * @param context A {@link Context} instance. |
| */ |
| public AccessibilityManagerService(Context context) { |
| mContext = context; |
| mPackageManager = mContext.getPackageManager(); |
| mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); |
| mWindowManagerService = LocalServices.getService(WindowManagerInternal.class); |
| mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE); |
| mSecurityPolicy = new SecurityPolicy(); |
| mMainHandler = new MainHandler(mContext.getMainLooper()); |
| registerBroadcastReceivers(); |
| new AccessibilityContentObserver(mMainHandler).register( |
| context.getContentResolver()); |
| } |
| |
| private UserState getUserStateLocked(int userId) { |
| UserState state = mUserStates.get(userId); |
| if (state == null) { |
| state = new UserState(userId); |
| mUserStates.put(userId, state); |
| } |
| return state; |
| } |
| |
| private void registerBroadcastReceivers() { |
| PackageMonitor monitor = new PackageMonitor() { |
| @Override |
| public void onSomePackagesChanged() { |
| synchronized (mLock) { |
| // Only the profile parent can install accessibility services. |
| // Therefore we ignore packages from linked profiles. |
| if (getChangingUserId() != mCurrentUserId) { |
| return; |
| } |
| // We will update when the automation service dies. |
| UserState userState = getCurrentUserStateLocked(); |
| // We have to reload the installed services since some services may |
| // 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); |
| } |
| } |
| } |
| } |
| |
| @Override |
| public void onPackageUpdateFinished(String packageName, int uid) { |
| // Unbind all services from this package, and then update the user state to |
| // re-bind new versions of them. |
| synchronized (mLock) { |
| final int userId = getChangingUserId(); |
| if (userId != mCurrentUserId) { |
| return; |
| } |
| UserState userState = getUserStateLocked(userId); |
| boolean unboundAService = false; |
| for (int i = userState.mBoundServices.size() - 1; i >= 0; i--) { |
| Service boundService = userState.mBoundServices.get(i); |
| String servicePkg = boundService.mComponentName.getPackageName(); |
| if (servicePkg.equals(packageName)) { |
| boundService.unbindLocked(); |
| unboundAService = true; |
| } |
| } |
| if (unboundAService) { |
| onUserStateChangedLocked(userState); |
| } |
| } |
| } |
| |
| @Override |
| public void onPackageRemoved(String packageName, int uid) { |
| synchronized (mLock) { |
| final int userId = getChangingUserId(); |
| // Only the profile parent can install accessibility services. |
| // Therefore we ignore packages from linked profiles. |
| if (userId != mCurrentUserId) { |
| return; |
| } |
| UserState userState = getUserStateLocked(userId); |
| Iterator<ComponentName> it = userState.mEnabledServices.iterator(); |
| while (it.hasNext()) { |
| ComponentName comp = it.next(); |
| String compPkg = comp.getPackageName(); |
| if (compPkg.equals(packageName)) { |
| it.remove(); |
| // Update the enabled services setting. |
| persistComponentNamesToSettingLocked( |
| Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, |
| userState.mEnabledServices, userId); |
| // Update the touch exploration granted services setting. |
| userState.mTouchExplorationGrantedServices.remove(comp); |
| persistComponentNamesToSettingLocked( |
| Settings.Secure. |
| TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES, |
| userState.mTouchExplorationGrantedServices, userId); |
| // We will update when the automation service dies. |
| if (!userState.isUiAutomationSuppressingOtherServices()) { |
| onUserStateChangedLocked(userState); |
| } |
| return; |
| } |
| } |
| } |
| } |
| |
| @Override |
| public boolean onHandleForceStop(Intent intent, String[] packages, |
| int uid, boolean doit) { |
| synchronized (mLock) { |
| final int userId = getChangingUserId(); |
| // Only the profile parent can install accessibility services. |
| // Therefore we ignore packages from linked profiles. |
| if (userId != mCurrentUserId) { |
| return false; |
| } |
| UserState userState = getUserStateLocked(userId); |
| Iterator<ComponentName> it = userState.mEnabledServices.iterator(); |
| while (it.hasNext()) { |
| ComponentName comp = it.next(); |
| String compPkg = comp.getPackageName(); |
| for (String pkg : packages) { |
| if (compPkg.equals(pkg)) { |
| if (!doit) { |
| return true; |
| } |
| it.remove(); |
| persistComponentNamesToSettingLocked( |
| Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, |
| userState.mEnabledServices, userId); |
| // We will update when the automation service dies. |
| if (!userState.isUiAutomationSuppressingOtherServices()) { |
| onUserStateChangedLocked(userState); |
| } |
| } |
| } |
| } |
| return false; |
| } |
| } |
| }; |
| |
| // package changes |
| monitor.register(mContext, null, UserHandle.ALL, true); |
| |
| // user change and unlock |
| IntentFilter intentFilter = new IntentFilter(); |
| intentFilter.addAction(Intent.ACTION_USER_SWITCHED); |
| intentFilter.addAction(Intent.ACTION_USER_UNLOCKED); |
| intentFilter.addAction(Intent.ACTION_USER_REMOVED); |
| intentFilter.addAction(Intent.ACTION_USER_PRESENT); |
| intentFilter.addAction(Intent.ACTION_SETTING_RESTORED); |
| |
| mContext.registerReceiverAsUser(new BroadcastReceiver() { |
| @Override |
| public void onReceive(Context context, Intent intent) { |
| String action = intent.getAction(); |
| if (Intent.ACTION_USER_SWITCHED.equals(action)) { |
| switchUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0)); |
| } else if (Intent.ACTION_USER_UNLOCKED.equals(action)) { |
| unlockUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0)); |
| } else if (Intent.ACTION_USER_REMOVED.equals(action)) { |
| removeUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0)); |
| } else if (Intent.ACTION_USER_PRESENT.equals(action)) { |
| // We will update when the automation service dies. |
| synchronized (mLock) { |
| UserState userState = getCurrentUserStateLocked(); |
| if (!userState.isUiAutomationSuppressingOtherServices()) { |
| if (readConfigurationForUserStateLocked(userState)) { |
| onUserStateChangedLocked(userState); |
| } |
| } |
| } |
| } else if (Intent.ACTION_SETTING_RESTORED.equals(action)) { |
| final String which = intent.getStringExtra(Intent.EXTRA_SETTING_NAME); |
| if (Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES.equals(which)) { |
| synchronized (mLock) { |
| restoreEnabledAccessibilityServicesLocked( |
| intent.getStringExtra(Intent.EXTRA_SETTING_PREVIOUS_VALUE), |
| intent.getStringExtra(Intent.EXTRA_SETTING_NEW_VALUE)); |
| } |
| } |
| } |
| } |
| }, UserHandle.ALL, intentFilter, null, null); |
| } |
| |
| @Override |
| public long addClient(IAccessibilityManagerClient client, int userId) { |
| synchronized (mLock) { |
| // 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(userId); |
| // If the client is from a process that runs across users such as |
| // the system UI or the system we add it to the global state that |
| // is shared across users. |
| UserState userState = getUserStateLocked(resolvedUserId); |
| if (mSecurityPolicy.isCallerInteractingAcrossUsers(userId)) { |
| mGlobalClients.register(client); |
| if (DEBUG) { |
| Slog.i(LOG_TAG, "Added global client for pid:" + Binder.getCallingPid()); |
| } |
| return IntPair.of( |
| userState.getClientState(), userState.mLastSentRelevantEventTypes); |
| } else { |
| userState.mUserClients.register(client); |
| // If this client is not for the current user we do not |
| // return a state since it is not for the foreground user. |
| // We will send the state to the client on a user switch. |
| if (DEBUG) { |
| Slog.i(LOG_TAG, "Added user client for pid:" + Binder.getCallingPid() |
| + " and userId:" + mCurrentUserId); |
| } |
| return IntPair.of( |
| (resolvedUserId == mCurrentUserId) ? userState.getClientState() : 0, |
| userState.mLastSentRelevantEventTypes); |
| } |
| } |
| } |
| |
| @Override |
| public void sendAccessibilityEvent(AccessibilityEvent event, int userId) { |
| boolean dispatchEvent = false; |
| |
| synchronized (mLock) { |
| if (event.getWindowId() == |
| AccessibilityWindowInfo.PICTURE_IN_PICTURE_ACTION_REPLACER_WINDOW_ID) { |
| // The replacer window isn't shown to services. Move its events into the pip. |
| AccessibilityWindowInfo pip = mSecurityPolicy.getPictureInPictureWindow(); |
| if (pip != null) { |
| int pipId = pip.getId(); |
| event.setWindowId(pipId); |
| event.setSealed(true); |
| AccessibilityNodeInfo info = event.getSource(); |
| info.setSealed(false); |
| event.setSealed(false); |
| if (info != null) { |
| info.setSourceNodeId(info.getSourceNodeId(), pipId); |
| event.setSource(info); |
| info.recycle(); |
| } |
| } |
| } |
| |
| // 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(userId); |
| // This method does nothing for a background user. |
| if (resolvedUserId == mCurrentUserId) { |
| if (mSecurityPolicy.canDispatchAccessibilityEventLocked(event)) { |
| mSecurityPolicy.updateActiveAndAccessibilityFocusedWindowLocked( |
| event.getWindowId(), event.getSourceNodeId(), |
| event.getEventType(), event.getAction()); |
| mSecurityPolicy.updateEventSourceLocked(event); |
| dispatchEvent = true; |
| } |
| if (mHasInputFilter && mInputFilter != null) { |
| mMainHandler.obtainMessage( |
| MainHandler.MSG_SEND_ACCESSIBILITY_EVENT_TO_INPUT_FILTER, |
| AccessibilityEvent.obtain(event)).sendToTarget(); |
| } |
| } |
| } |
| |
| if (dispatchEvent) { |
| // Make sure clients receiving this event will be able to get the |
| // current state of the windows as the window manager may be delaying |
| // the computation for performance reasons. |
| if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED |
| && mWindowsForAccessibilityCallback != null) { |
| WindowManagerInternal wm = LocalServices.getService(WindowManagerInternal.class); |
| wm.computeWindowsForAccessibility(); |
| } |
| synchronized (mLock) { |
| notifyAccessibilityServicesDelayedLocked(event, false); |
| notifyAccessibilityServicesDelayedLocked(event, true); |
| } |
| } |
| |
| if (OWN_PROCESS_ID != Binder.getCallingPid()) { |
| event.recycle(); |
| } |
| } |
| |
| @Override |
| public List<AccessibilityServiceInfo> getInstalledAccessibilityServiceList(int userId) { |
| synchronized (mLock) { |
| // 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(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; |
| } |
| } |
| |
| @Override |
| public List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList(int feedbackType, |
| int userId) { |
| synchronized (mLock) { |
| // 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(userId); |
| |
| // The automation service can suppress other services. |
| final UserState userState = getUserStateLocked(resolvedUserId); |
| if (userState.isUiAutomationSuppressingOtherServices()) { |
| return Collections.emptyList(); |
| } |
| |
| final List<Service> 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) { |
| result.add(service.mAccessibilityServiceInfo); |
| } |
| } |
| return result; |
| } |
| } |
| |
| @Override |
| public void interrupt(int userId) { |
| List<IAccessibilityServiceClient> interfacesToInterrupt; |
| synchronized (mLock) { |
| // 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(userId); |
| // This method does nothing for a background user. |
| if (resolvedUserId != mCurrentUserId) { |
| return; |
| } |
| List<Service> services = getUserStateLocked(resolvedUserId).mBoundServices; |
| int numServices = services.size(); |
| interfacesToInterrupt = new ArrayList<>(numServices); |
| for (int i = 0; i < numServices; i++) { |
| Service service = services.get(i); |
| IBinder a11yServiceBinder = service.mService; |
| IAccessibilityServiceClient a11yServiceInterface = service.mServiceInterface; |
| if ((a11yServiceBinder != null) && (a11yServiceInterface != null)) { |
| interfacesToInterrupt.add(a11yServiceInterface); |
| } |
| } |
| } |
| for (int i = 0, count = interfacesToInterrupt.size(); i < count; i++) { |
| try { |
| interfacesToInterrupt.get(i).onInterrupt(); |
| } catch (RemoteException re) { |
| Slog.e(LOG_TAG, "Error sending interrupt request to " |
| + interfacesToInterrupt.get(i), re); |
| } |
| } |
| } |
| |
| @Override |
| public int addAccessibilityInteractionConnection(IWindow windowToken, |
| IAccessibilityInteractionConnection connection, int userId) throws RemoteException { |
| synchronized (mLock) { |
| // 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(userId); |
| final int windowId = sNextWindowId++; |
| // If the window is from a process that runs across users such as |
| // the system UI or the system we add it to the global state that |
| // is shared across users. |
| if (mSecurityPolicy.isCallerInteractingAcrossUsers(userId)) { |
| AccessibilityConnectionWrapper wrapper = new AccessibilityConnectionWrapper( |
| windowId, connection, UserHandle.USER_ALL); |
| wrapper.linkToDeath(); |
| mGlobalInteractionConnections.put(windowId, wrapper); |
| mGlobalWindowTokens.put(windowId, windowToken.asBinder()); |
| if (DEBUG) { |
| Slog.i(LOG_TAG, "Added global connection for pid:" + Binder.getCallingPid() |
| + " with windowId: " + windowId + " and token: " + windowToken.asBinder()); |
| } |
| } else { |
| AccessibilityConnectionWrapper wrapper = new AccessibilityConnectionWrapper( |
| windowId, connection, resolvedUserId); |
| wrapper.linkToDeath(); |
| UserState userState = getUserStateLocked(resolvedUserId); |
| userState.mInteractionConnections.put(windowId, wrapper); |
| userState.mWindowTokens.put(windowId, windowToken.asBinder()); |
| if (DEBUG) { |
| Slog.i(LOG_TAG, "Added user connection for pid:" + Binder.getCallingPid() |
| + " with windowId: " + windowId + " and userId:" + mCurrentUserId |
| + " and token: " + windowToken.asBinder()); |
| } |
| } |
| return windowId; |
| } |
| } |
| |
| @Override |
| public void removeAccessibilityInteractionConnection(IWindow window) { |
| synchronized (mLock) { |
| // 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. |
| mSecurityPolicy.resolveCallingUserIdEnforcingPermissionsLocked( |
| UserHandle.getCallingUserId()); |
| IBinder token = window.asBinder(); |
| final int removedWindowId = removeAccessibilityInteractionConnectionInternalLocked( |
| token, mGlobalWindowTokens, mGlobalInteractionConnections); |
| if (removedWindowId >= 0) { |
| if (DEBUG) { |
| Slog.i(LOG_TAG, "Removed global connection for pid:" + Binder.getCallingPid() |
| + " with windowId: " + removedWindowId + " and token: " + window.asBinder()); |
| } |
| return; |
| } |
| final int userCount = mUserStates.size(); |
| for (int i = 0; i < userCount; i++) { |
| UserState userState = mUserStates.valueAt(i); |
| final int removedWindowIdForUser = |
| removeAccessibilityInteractionConnectionInternalLocked( |
| token, userState.mWindowTokens, userState.mInteractionConnections); |
| if (removedWindowIdForUser >= 0) { |
| if (DEBUG) { |
| Slog.i(LOG_TAG, "Removed user connection for pid:" + Binder.getCallingPid() |
| + " with windowId: " + removedWindowIdForUser + " and userId:" |
| + mUserStates.keyAt(i) + " and token: " + window.asBinder()); |
| } |
| return; |
| } |
| } |
| } |
| } |
| |
| private int removeAccessibilityInteractionConnectionInternalLocked(IBinder windowToken, |
| SparseArray<IBinder> windowTokens, |
| SparseArray<AccessibilityConnectionWrapper> interactionConnections) { |
| final int count = windowTokens.size(); |
| for (int i = 0; i < count; i++) { |
| if (windowTokens.valueAt(i) == windowToken) { |
| final int windowId = windowTokens.keyAt(i); |
| windowTokens.removeAt(i); |
| AccessibilityConnectionWrapper wrapper = interactionConnections.get(windowId); |
| wrapper.unlinkToDeath(); |
| interactionConnections.remove(windowId); |
| return windowId; |
| } |
| } |
| return -1; |
| } |
| |
| @Override |
| public void setPictureInPictureActionReplacingConnection( |
| IAccessibilityInteractionConnection connection) throws RemoteException { |
| mSecurityPolicy.enforceCallingPermission(Manifest.permission.MODIFY_ACCESSIBILITY_DATA, |
| SET_PIP_ACTION_REPLACEMENT); |
| synchronized (mLock) { |
| if (mPictureInPictureActionReplacingConnection != null) { |
| mPictureInPictureActionReplacingConnection.unlinkToDeath(); |
| mPictureInPictureActionReplacingConnection = null; |
| } |
| if (connection != null) { |
| AccessibilityConnectionWrapper wrapper = new AccessibilityConnectionWrapper( |
| AccessibilityWindowInfo.PICTURE_IN_PICTURE_ACTION_REPLACER_WINDOW_ID, |
| connection, UserHandle.USER_ALL); |
| mPictureInPictureActionReplacingConnection = wrapper; |
| wrapper.linkToDeath(); |
| } |
| mSecurityPolicy.notifyWindowsChanged(); |
| } |
| } |
| |
| @Override |
| public void registerUiTestAutomationService(IBinder owner, |
| IAccessibilityServiceClient serviceClient, |
| AccessibilityServiceInfo accessibilityServiceInfo, |
| int flags) { |
| 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); |
| } |
| } |
| |
| @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!"); |
| } |
| } |
| } |
| |
| @Override |
| public void temporaryEnableAccessibilityStateUntilKeyguardRemoved( |
| ComponentName service, boolean touchExplorationEnabled) { |
| mSecurityPolicy.enforceCallingPermission( |
| Manifest.permission.TEMPORARY_ENABLE_ACCESSIBILITY, |
| TEMPORARY_ENABLE_ACCESSIBILITY_UNTIL_KEYGUARD_REMOVED); |
| if (!mWindowManagerService.isKeyguardLocked()) { |
| return; |
| } |
| synchronized (mLock) { |
| // 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; |
| userState.mIsAutoclickEnabled = false; |
| userState.mEnabledServices.clear(); |
| userState.mEnabledServices.add(service); |
| userState.mBindingServices.clear(); |
| userState.mTouchExplorationGrantedServices.clear(); |
| userState.mTouchExplorationGrantedServices.add(service); |
| |
| // User the current state instead settings. |
| onUserStateChangedLocked(userState); |
| } |
| } |
| |
| @Override |
| public IBinder getWindowToken(int windowId, int userId) { |
| mSecurityPolicy.enforceCallingPermission( |
| Manifest.permission.RETRIEVE_WINDOW_TOKEN, |
| GET_WINDOW_TOKEN); |
| synchronized (mLock) { |
| // 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(userId); |
| if (resolvedUserId != mCurrentUserId) { |
| return null; |
| } |
| if (mSecurityPolicy.findWindowById(windowId) == null) { |
| return null; |
| } |
| IBinder token = mGlobalWindowTokens.get(windowId); |
| if (token != null) { |
| return token; |
| } |
| return getCurrentUserStateLocked().mWindowTokens.get(windowId); |
| } |
| } |
| |
| /** |
| * Invoked remotely over AIDL by SysUi when the accessibility button within the system's |
| * navigation area has been clicked. |
| */ |
| @Override |
| public void notifyAccessibilityButtonClicked() { |
| if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR_SERVICE) |
| != PackageManager.PERMISSION_GRANTED) { |
| throw new SecurityException("Caller does not hold permission " |
| + android.Manifest.permission.STATUS_BAR_SERVICE); |
| } |
| synchronized (mLock) { |
| notifyAccessibilityButtonClickedLocked(); |
| } |
| } |
| |
| /** |
| * Invoked remotely over AIDL by SysUi when the visibility of the accessibility |
| * button within the system's navigation area has changed. |
| * |
| * @param shown {@code true} if the accessibility button is shown to the |
| * user, {@code false} otherwise |
| */ |
| @Override |
| public void notifyAccessibilityButtonVisibilityChanged(boolean shown) { |
| if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR_SERVICE) |
| != PackageManager.PERMISSION_GRANTED) { |
| throw new SecurityException("Caller does not hold permission " |
| + android.Manifest.permission.STATUS_BAR_SERVICE); |
| } |
| synchronized (mLock) { |
| notifyAccessibilityButtonVisibilityChangedLocked(shown); |
| } |
| } |
| |
| |
| boolean onGesture(int gestureId) { |
| synchronized (mLock) { |
| boolean handled = notifyGestureLocked(gestureId, false); |
| if (!handled) { |
| handled = notifyGestureLocked(gestureId, true); |
| } |
| return handled; |
| } |
| } |
| |
| boolean notifyKeyEvent(KeyEvent event, int policyFlags) { |
| synchronized (mLock) { |
| List<Service> boundServices = getCurrentUserStateLocked().mBoundServices; |
| if (boundServices.isEmpty()) { |
| return false; |
| } |
| return getKeyEventDispatcher().notifyKeyEventLocked(event, policyFlags, boundServices); |
| } |
| } |
| |
| /** |
| * Called by the MagnificationController when the state of display |
| * magnification changes. |
| * |
| * @param region the new magnified region, may be empty if |
| * magnification is not enabled (e.g. scale is 1) |
| * @param scale the new scale |
| * @param centerX the new screen-relative center X coordinate |
| * @param centerY the new screen-relative center Y coordinate |
| */ |
| public void notifyMagnificationChanged(@NonNull Region region, |
| float scale, float centerX, float centerY) { |
| synchronized (mLock) { |
| notifyClearAccessibilityCacheLocked(); |
| notifyMagnificationChangedLocked(region, scale, centerX, centerY); |
| } |
| } |
| |
| /** |
| * Called by AccessibilityInputFilter when it creates or destroys the motionEventInjector. |
| * Not using a getter because the AccessibilityInputFilter isn't thread-safe |
| * |
| * @param motionEventInjector The new value of the motionEventInjector. May be null. |
| */ |
| void setMotionEventInjector(MotionEventInjector motionEventInjector) { |
| synchronized (mLock) { |
| mMotionEventInjector = motionEventInjector; |
| // We may be waiting on this object being set |
| mLock.notifyAll(); |
| } |
| } |
| |
| /** |
| * Gets a point within the accessibility focused node where we can send down |
| * and up events to perform a click. |
| * |
| * @param outPoint The click point to populate. |
| * @return Whether accessibility a click point was found and set. |
| */ |
| // TODO: (multi-display) Make sure this works for multiple displays. |
| boolean getAccessibilityFocusClickPointInScreen(Point outPoint) { |
| return getInteractionBridge().getAccessibilityFocusClickPointInScreenNotLocked(outPoint); |
| } |
| |
| /** |
| * Perform an accessibility action on the view that currently has accessibility focus. |
| * Has no effect if no item has accessibility focus, if the item with accessibility |
| * focus does not expose the specified action, or if the action fails. |
| * |
| * @param actionId The id of the action to perform. |
| * |
| * @return {@code true} if the action was performed. {@code false} if it was not. |
| */ |
| public boolean performActionOnAccessibilityFocusedItem( |
| AccessibilityNodeInfo.AccessibilityAction action) { |
| return getInteractionBridge().performActionOnAccessibilityFocusedItemNotLocked(action); |
| } |
| |
| /** |
| * Gets the bounds of a window. |
| * |
| * @param outBounds The output to which to write the bounds. |
| */ |
| boolean getWindowBounds(int windowId, Rect outBounds) { |
| IBinder token; |
| synchronized (mLock) { |
| token = mGlobalWindowTokens.get(windowId); |
| if (token == null) { |
| token = getCurrentUserStateLocked().mWindowTokens.get(windowId); |
| } |
| } |
| mWindowManagerService.getWindowFrame(token, outBounds); |
| if (!outBounds.isEmpty()) { |
| return true; |
| } |
| return false; |
| } |
| |
| boolean accessibilityFocusOnlyInActiveWindow() { |
| synchronized (mLock) { |
| return mWindowsForAccessibilityCallback == null; |
| } |
| } |
| |
| int getActiveWindowId() { |
| return mSecurityPolicy.getActiveWindowId(); |
| } |
| |
| void onTouchInteractionStart() { |
| mSecurityPolicy.onTouchInteractionStart(); |
| } |
| |
| void onTouchInteractionEnd() { |
| mSecurityPolicy.onTouchInteractionEnd(); |
| } |
| |
| private void switchUser(int userId) { |
| synchronized (mLock) { |
| if (mCurrentUserId == userId && mInitialized) { |
| return; |
| } |
| |
| // Disconnect from services for the old user. |
| UserState oldUserState = getCurrentUserStateLocked(); |
| oldUserState.onSwitchToAnotherUser(); |
| |
| // Disable the local managers for the old user. |
| if (oldUserState.mUserClients.getRegisteredCallbackCount() > 0) { |
| mMainHandler.obtainMessage(MainHandler.MSG_SEND_CLEARED_STATE_TO_CLIENTS_FOR_USER, |
| oldUserState.mUserId, 0).sendToTarget(); |
| } |
| |
| // Announce user changes only if more that one exist. |
| UserManager userManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE); |
| final boolean announceNewUser = userManager.getUsers().size() > 1; |
| |
| // The user changed. |
| 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 |
| // the state since the context in which the current user |
| // state was used has changed since it was inactive. |
| onUserStateChangedLocked(userState); |
| |
| if (announceNewUser) { |
| // Schedule announcement of the current user if needed. |
| mMainHandler.sendEmptyMessageDelayed(MainHandler.MSG_ANNOUNCE_NEW_USER_IF_NEEDED, |
| WAIT_FOR_USER_STATE_FULLY_INITIALIZED_MILLIS); |
| } |
| } |
| } |
| |
| private void unlockUser(int userId) { |
| synchronized (mLock) { |
| int parentUserId = mSecurityPolicy.resolveProfileParentLocked(userId); |
| if (parentUserId == mCurrentUserId) { |
| UserState userState = getUserStateLocked(mCurrentUserId); |
| onUserStateChangedLocked(userState); |
| } |
| } |
| } |
| |
| private void removeUser(int userId) { |
| synchronized (mLock) { |
| mUserStates.remove(userId); |
| } |
| } |
| |
| // Called only during settings restore; currently supports only the owner user |
| // TODO: http://b/22388012 |
| void restoreEnabledAccessibilityServicesLocked(String oldSetting, String newSetting) { |
| readComponentNamesFromStringLocked(oldSetting, mTempComponentNameSet, false); |
| readComponentNamesFromStringLocked(newSetting, mTempComponentNameSet, true); |
| |
| UserState userState = getUserStateLocked(UserHandle.USER_SYSTEM); |
| userState.mEnabledServices.clear(); |
| userState.mEnabledServices.addAll(mTempComponentNameSet); |
| persistComponentNamesToSettingLocked( |
| Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, |
| userState.mEnabledServices, |
| UserHandle.USER_SYSTEM); |
| onUserStateChangedLocked(userState); |
| } |
| |
| private InteractionBridge getInteractionBridge() { |
| synchronized (mLock) { |
| if (mInteractionBridge == null) { |
| mInteractionBridge = new InteractionBridge(); |
| } |
| return mInteractionBridge; |
| } |
| } |
| |
| private boolean notifyGestureLocked(int gestureId, boolean isDefault) { |
| // TODO: Now we are giving the gestures to the last enabled |
| // service that can handle them which is the last one |
| // in our list since we write the last enabled as the |
| // last record in the enabled services setting. Ideally, |
| // the user should make the call which service handles |
| // gestures. However, only one service should handle |
| // gestures to avoid user frustration when different |
| // behavior is observed from different combinations of |
| // enabled accessibility services. |
| UserState state = getCurrentUserStateLocked(); |
| for (int i = state.mBoundServices.size() - 1; i >= 0; i--) { |
| Service service = state.mBoundServices.get(i); |
| if (service.mRequestTouchExplorationMode && service.mIsDefault == isDefault) { |
| service.notifyGesture(gestureId); |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| private void notifyClearAccessibilityCacheLocked() { |
| UserState state = getCurrentUserStateLocked(); |
| for (int i = state.mBoundServices.size() - 1; i >= 0; i--) { |
| Service service = state.mBoundServices.get(i); |
| service.notifyClearAccessibilityNodeInfoCache(); |
| } |
| } |
| |
| private void notifyMagnificationChangedLocked(@NonNull Region region, |
| 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); |
| service.notifyMagnificationChangedLocked(region, scale, centerX, centerY); |
| } |
| } |
| |
| 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); |
| service.notifySoftKeyboardShowModeChangedLocked(showMode); |
| } |
| } |
| |
| private void notifyAccessibilityButtonClickedLocked() { |
| final UserState state = getCurrentUserStateLocked(); |
| |
| int potentialTargets = state.mIsNavBarMagnificationEnabled ? 1 : 0; |
| for (int i = state.mBoundServices.size() - 1; i >= 0; i--) { |
| final Service service = state.mBoundServices.get(i); |
| if (service.mRequestAccessibilityButton) { |
| potentialTargets++; |
| } |
| } |
| |
| if (potentialTargets == 0) { |
| return; |
| } |
| if (potentialTargets == 1) { |
| if (state.mIsNavBarMagnificationEnabled) { |
| mMainHandler.obtainMessage( |
| MainHandler.MSG_SEND_ACCESSIBILITY_BUTTON_TO_INPUT_FILTER).sendToTarget(); |
| return; |
| } else { |
| for (int i = state.mBoundServices.size() - 1; i >= 0; i--) { |
| final Service service = state.mBoundServices.get(i); |
| if (service.mRequestAccessibilityButton) { |
| service.notifyAccessibilityButtonClickedLocked(); |
| return; |
| } |
| } |
| } |
| } else { |
| if (state.mServiceAssignedToAccessibilityButton == null |
| && !state.mIsNavBarMagnificationAssignedToAccessibilityButton) { |
| mMainHandler.obtainMessage( |
| MainHandler.MSG_SHOW_ACCESSIBILITY_BUTTON_CHOOSER).sendToTarget(); |
| } else if (state.mIsNavBarMagnificationEnabled |
| && state.mIsNavBarMagnificationAssignedToAccessibilityButton) { |
| mMainHandler.obtainMessage( |
| MainHandler.MSG_SEND_ACCESSIBILITY_BUTTON_TO_INPUT_FILTER).sendToTarget(); |
| return; |
| } else { |
| for (int i = state.mBoundServices.size() - 1; i >= 0; i--) { |
| final Service service = state.mBoundServices.get(i); |
| if (service.mRequestAccessibilityButton && (service.mComponentName.equals( |
| state.mServiceAssignedToAccessibilityButton))) { |
| service.notifyAccessibilityButtonClickedLocked(); |
| return; |
| } |
| } |
| } |
| // The user may have turned off the assigned service or feature |
| mMainHandler.obtainMessage( |
| MainHandler.MSG_SHOW_ACCESSIBILITY_BUTTON_CHOOSER).sendToTarget(); |
| } |
| } |
| |
| private void notifyAccessibilityButtonVisibilityChangedLocked(boolean available) { |
| 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)); |
| } |
| } |
| } |
| |
| /** |
| * Removes an AccessibilityInteractionConnection. |
| * |
| * @param windowId The id of the window to which the connection is targeted. |
| * @param userId The id of the user owning the connection. UserHandle.USER_ALL |
| * if global. |
| */ |
| private void removeAccessibilityInteractionConnectionLocked(int windowId, int userId) { |
| if (userId == UserHandle.USER_ALL) { |
| mGlobalWindowTokens.remove(windowId); |
| mGlobalInteractionConnections.remove(windowId); |
| } else { |
| UserState userState = getCurrentUserStateLocked(); |
| userState.mWindowTokens.remove(windowId); |
| userState.mInteractionConnections.remove(windowId); |
| } |
| if (DEBUG) { |
| Slog.i(LOG_TAG, "Removing interaction connection to windowId: " + windowId); |
| } |
| } |
| |
| private boolean readInstalledAccessibilityServiceLocked(UserState userState) { |
| mTempAccessibilityServiceInfoList.clear(); |
| |
| List<ResolveInfo> installedServices = mPackageManager.queryIntentServicesAsUser( |
| new Intent(AccessibilityService.SERVICE_INTERFACE), |
| PackageManager.GET_SERVICES |
| | PackageManager.GET_META_DATA |
| | PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS |
| | PackageManager.MATCH_DIRECT_BOOT_AWARE |
| | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, |
| mCurrentUserId); |
| |
| for (int i = 0, count = installedServices.size(); i < count; i++) { |
| ResolveInfo resolveInfo = installedServices.get(i); |
| ServiceInfo serviceInfo = resolveInfo.serviceInfo; |
| if (!android.Manifest.permission.BIND_ACCESSIBILITY_SERVICE.equals( |
| serviceInfo.permission)) { |
| Slog.w(LOG_TAG, "Skipping accessibilty service " + new ComponentName( |
| serviceInfo.packageName, serviceInfo.name).flattenToShortString() |
| + ": it does not require the permission " |
| + android.Manifest.permission.BIND_ACCESSIBILITY_SERVICE); |
| continue; |
| } |
| AccessibilityServiceInfo accessibilityServiceInfo; |
| try { |
| accessibilityServiceInfo = new AccessibilityServiceInfo(resolveInfo, mContext); |
| mTempAccessibilityServiceInfoList.add(accessibilityServiceInfo); |
| } catch (XmlPullParserException | IOException xppe) { |
| Slog.e(LOG_TAG, "Error while initializing AccessibilityServiceInfo", xppe); |
| } |
| } |
| |
| if (!mTempAccessibilityServiceInfoList.equals(userState.mInstalledServices)) { |
| userState.mInstalledServices.clear(); |
| userState.mInstalledServices.addAll(mTempAccessibilityServiceInfoList); |
| mTempAccessibilityServiceInfoList.clear(); |
| return true; |
| } |
| |
| mTempAccessibilityServiceInfoList.clear(); |
| return false; |
| } |
| |
| private boolean readEnabledAccessibilityServicesLocked(UserState userState) { |
| mTempComponentNameSet.clear(); |
| readComponentNamesFromSettingLocked(Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, |
| userState.mUserId, mTempComponentNameSet); |
| if (!mTempComponentNameSet.equals(userState.mEnabledServices)) { |
| userState.mEnabledServices.clear(); |
| userState.mEnabledServices.addAll(mTempComponentNameSet); |
| if (userState.mUiAutomationService != null) { |
| userState.mEnabledServices.add(sFakeAccessibilityServiceComponentName); |
| } |
| mTempComponentNameSet.clear(); |
| return true; |
| } |
| mTempComponentNameSet.clear(); |
| return false; |
| } |
| |
| private boolean readTouchExplorationGrantedAccessibilityServicesLocked( |
| UserState userState) { |
| mTempComponentNameSet.clear(); |
| readComponentNamesFromSettingLocked( |
| Settings.Secure.TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES, |
| userState.mUserId, mTempComponentNameSet); |
| if (!mTempComponentNameSet.equals(userState.mTouchExplorationGrantedServices)) { |
| userState.mTouchExplorationGrantedServices.clear(); |
| userState.mTouchExplorationGrantedServices.addAll(mTempComponentNameSet); |
| mTempComponentNameSet.clear(); |
| return true; |
| } |
| mTempComponentNameSet.clear(); |
| return false; |
| } |
| |
| /** |
| * Performs {@link AccessibilityService}s delayed notification. The delay is configurable |
| * and denotes the period after the last event before notifying the service. |
| * |
| * @param event The event. |
| * @param isDefault True to notify default listeners, not default services. |
| */ |
| private void notifyAccessibilityServicesDelayedLocked(AccessibilityEvent event, |
| boolean isDefault) { |
| try { |
| UserState state = getCurrentUserStateLocked(); |
| for (int i = 0, count = state.mBoundServices.size(); i < count; i++) { |
| Service 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); |
| } |
| } |
| } |
| } catch (IndexOutOfBoundsException oobe) { |
| // An out of bounds exception can happen if services are going away |
| // as the for loop is running. If that happens, just bail because |
| // there are no more services to notify. |
| } |
| } |
| |
| 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) { |
| relevantEventTypes |= service.mEventTypes; |
| } |
| int finalRelevantEventTypes = relevantEventTypes; |
| |
| if (userState.mLastSentRelevantEventTypes != finalRelevantEventTypes) { |
| userState.mLastSentRelevantEventTypes = finalRelevantEventTypes; |
| mMainHandler.obtainMessage(MainHandler.MSG_SEND_RELEVANT_EVENTS_CHANGED_TO_CLIENTS, |
| userState.mUserId, finalRelevantEventTypes); |
| mMainHandler.post(() -> { |
| broadcastToClients(userState, (client) -> { |
| try { |
| client.setRelevantEventTypes(finalRelevantEventTypes); |
| } catch (RemoteException re) { |
| /* ignore */ |
| } |
| }); |
| }); |
| } |
| } |
| |
| private void broadcastToClients( |
| UserState userState, Consumer<IAccessibilityManagerClient> clientAction) { |
| mGlobalClients.broadcast(clientAction); |
| 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--; |
| } |
| } |
| } |
| |
| /** |
| * Populates a set with the {@link ComponentName}s stored in a colon |
| * separated value setting for a given user. |
| * |
| * @param settingName The setting to parse. |
| * @param userId The user id. |
| * @param outComponentNames The output component names. |
| */ |
| private void readComponentNamesFromSettingLocked(String settingName, int userId, |
| Set<ComponentName> outComponentNames) { |
| String settingValue = Settings.Secure.getStringForUser(mContext.getContentResolver(), |
| settingName, userId); |
| readComponentNamesFromStringLocked(settingValue, outComponentNames, false); |
| } |
| |
| /** |
| * Populates a set with the {@link ComponentName}s contained in a colon-delimited string. |
| * |
| * @param names The colon-delimited string to parse. |
| * @param outComponentNames The set of component names to be populated based on |
| * the contents of the <code>names</code> string. |
| * @param doMerge If true, the parsed component names will be merged into the output |
| * set, rather than replacing the set's existing contents entirely. |
| */ |
| private void readComponentNamesFromStringLocked(String names, |
| Set<ComponentName> outComponentNames, |
| boolean doMerge) { |
| if (!doMerge) { |
| outComponentNames.clear(); |
| } |
| if (names != null) { |
| TextUtils.SimpleStringSplitter splitter = mStringColonSplitter; |
| splitter.setString(names); |
| while (splitter.hasNext()) { |
| String str = splitter.next(); |
| if (str == null || str.length() <= 0) { |
| continue; |
| } |
| ComponentName enabledService = ComponentName.unflattenFromString(str); |
| if (enabledService != null) { |
| outComponentNames.add(enabledService); |
| } |
| } |
| } |
| } |
| |
| /** |
| * 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, |
| Set<ComponentName> componentNames, int userId) { |
| StringBuilder builder = new StringBuilder(); |
| for (ComponentName componentName : componentNames) { |
| if (builder.length() > 0) { |
| builder.append(COMPONENT_NAME_SEPARATOR); |
| } |
| builder.append(componentName.flattenToShortString()); |
| } |
| final long identity = Binder.clearCallingIdentity(); |
| try { |
| Settings.Secure.putStringForUser(mContext.getContentResolver(), |
| settingName, builder.toString(), userId); |
| } finally { |
| Binder.restoreCallingIdentity(identity); |
| } |
| } |
| |
| private void updateServicesLocked(UserState userState) { |
| Map<ComponentName, Service> componentNameToServiceMap = |
| userState.mComponentNameToServiceMap; |
| boolean isUnlockingOrUnlocked = LocalServices.getService(UserManagerInternal.class) |
| .isUserUnlockingOrUnlocked(userState.mUserId); |
| |
| for (int i = 0, count = userState.mInstalledServices.size(); i < count; i++) { |
| AccessibilityServiceInfo installedService = userState.mInstalledServices.get(i); |
| ComponentName componentName = ComponentName.unflattenFromString( |
| installedService.getId()); |
| |
| Service service = componentNameToServiceMap.get(componentName); |
| |
| // Ignore non-encryption-aware services until user is unlocked |
| if (!isUnlockingOrUnlocked && !installedService.isDirectBootAware()) { |
| Slog.d(LOG_TAG, "Ignoring non-encryption-aware service " + componentName); |
| continue; |
| } |
| |
| // Wait for the binding if it is in process. |
| if (userState.mBindingServices.contains(componentName)) { |
| continue; |
| } |
| if (userState.mEnabledServices.contains(componentName)) { |
| if (service == null) { |
| service = new Service(userState.mUserId, componentName, installedService); |
| } else if (userState.mBoundServices.contains(service)) { |
| continue; |
| } |
| service.bindLocked(); |
| } else { |
| if (service != null) { |
| service.unbindLocked(); |
| } |
| } |
| } |
| |
| final int count = userState.mBoundServices.size(); |
| mTempIntArray.clear(); |
| for (int i = 0; i < count; i++) { |
| final ResolveInfo resolveInfo = |
| userState.mBoundServices.get(i).mAccessibilityServiceInfo.getResolveInfo(); |
| if (resolveInfo != null) { |
| mTempIntArray.add(resolveInfo.serviceInfo.applicationInfo.uid); |
| } |
| } |
| // Calling out with lock held, but to a lower-level service |
| final AudioManagerInternal audioManager = |
| LocalServices.getService(AudioManagerInternal.class); |
| if (audioManager != null) { |
| audioManager.setAccessibilityServiceUids(mTempIntArray); |
| } |
| updateAccessibilityEnabledSetting(userState); |
| } |
| |
| private void scheduleUpdateClientsIfNeededLocked(UserState userState) { |
| final int clientState = userState.getClientState(); |
| if (userState.mLastSentClientState != clientState |
| && (mGlobalClients.getRegisteredCallbackCount() > 0 |
| || userState.mUserClients.getRegisteredCallbackCount() > 0)) { |
| userState.mLastSentClientState = clientState; |
| mMainHandler.obtainMessage(MainHandler.MSG_SEND_STATE_TO_CLIENTS, |
| clientState, userState.mUserId).sendToTarget(); |
| } |
| } |
| |
| private void showAccessibilityButtonTargetSelection() { |
| Intent intent = new Intent(AccessibilityManager.ACTION_CHOOSE_ACCESSIBILITY_BUTTON); |
| intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); |
| mContext.startActivityAsUser(intent, UserHandle.of(mCurrentUserId)); |
| } |
| |
| private void scheduleNotifyClientsOfServicesStateChange(UserState userState) { |
| mMainHandler.obtainMessage(MainHandler.MSG_SEND_SERVICES_STATE_CHANGED_TO_CLIENTS, |
| userState.mUserId).sendToTarget(); |
| } |
| |
| private void scheduleUpdateInputFilter(UserState userState) { |
| mMainHandler.obtainMessage(MainHandler.MSG_UPDATE_INPUT_FILTER, userState).sendToTarget(); |
| } |
| |
| private void scheduleUpdateFingerprintGestureHandling(UserState userState) { |
| mMainHandler.obtainMessage(MainHandler.MSG_UPDATE_FINGERPRINT, userState).sendToTarget(); |
| } |
| |
| private void updateInputFilter(UserState userState) { |
| boolean setInputFilter = false; |
| AccessibilityInputFilter inputFilter = null; |
| synchronized (mLock) { |
| int flags = 0; |
| if (userState.mIsDisplayMagnificationEnabled) { |
| flags |= AccessibilityInputFilter.FLAG_FEATURE_SCREEN_MAGNIFIER; |
| } |
| if (userState.mIsNavBarMagnificationEnabled) { |
| flags |= AccessibilityInputFilter.FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER; |
| } |
| if (userHasMagnificationServicesLocked(userState)) { |
| flags |= AccessibilityInputFilter.FLAG_FEATURE_CONTROL_SCREEN_MAGNIFIER; |
| } |
| // Touch exploration without accessibility makes no sense. |
| if (userState.isHandlingAccessibilityEvents() |
| && userState.mIsTouchExplorationEnabled) { |
| flags |= AccessibilityInputFilter.FLAG_FEATURE_TOUCH_EXPLORATION; |
| } |
| if (userState.mIsFilterKeyEventsEnabled) { |
| flags |= AccessibilityInputFilter.FLAG_FEATURE_FILTER_KEY_EVENTS; |
| } |
| if (userState.mIsAutoclickEnabled) { |
| flags |= AccessibilityInputFilter.FLAG_FEATURE_AUTOCLICK; |
| } |
| if (userState.mIsPerformGesturesEnabled) { |
| flags |= AccessibilityInputFilter.FLAG_FEATURE_INJECT_MOTION_EVENTS; |
| } |
| if (flags != 0) { |
| if (!mHasInputFilter) { |
| mHasInputFilter = true; |
| if (mInputFilter == null) { |
| mInputFilter = new AccessibilityInputFilter(mContext, |
| AccessibilityManagerService.this); |
| } |
| inputFilter = mInputFilter; |
| setInputFilter = true; |
| } |
| mInputFilter.setUserAndEnabledFeatures(userState.mUserId, flags); |
| } else { |
| if (mHasInputFilter) { |
| mHasInputFilter = false; |
| mInputFilter.setUserAndEnabledFeatures(userState.mUserId, 0); |
| inputFilter = null; |
| setInputFilter = true; |
| } |
| } |
| } |
| if (setInputFilter) { |
| mWindowManagerService.setInputFilter(inputFilter); |
| } |
| } |
| |
| private void showEnableTouchExplorationDialog(final Service service) { |
| synchronized (mLock) { |
| String label = service.mResolveInfo.loadLabel( |
| mContext.getPackageManager()).toString(); |
| |
| final UserState state = getCurrentUserStateLocked(); |
| if (state.mIsTouchExplorationEnabled) { |
| return; |
| } |
| if (mEnableTouchExplorationDialog != null |
| && mEnableTouchExplorationDialog.isShowing()) { |
| return; |
| } |
| mEnableTouchExplorationDialog = new AlertDialog.Builder(mContext) |
| .setIconAttribute(android.R.attr.alertDialogIcon) |
| .setPositiveButton(android.R.string.ok, new OnClickListener() { |
| @Override |
| public void onClick(DialogInterface dialog, int which) { |
| // The user allowed the service to toggle touch exploration. |
| state.mTouchExplorationGrantedServices.add(service.mComponentName); |
| persistComponentNamesToSettingLocked( |
| Settings.Secure.TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES, |
| state.mTouchExplorationGrantedServices, state.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); |
| } finally { |
| Binder.restoreCallingIdentity(identity); |
| } |
| onUserStateChangedLocked(userState); |
| } |
| }) |
| .setNegativeButton(android.R.string.cancel, new OnClickListener() { |
| @Override |
| public void onClick(DialogInterface dialog, int which) { |
| dialog.dismiss(); |
| } |
| }) |
| .setTitle(R.string.enable_explore_by_touch_warning_title) |
| .setMessage(mContext.getString( |
| R.string.enable_explore_by_touch_warning_message, label)) |
| .create(); |
| mEnableTouchExplorationDialog.getWindow().setType( |
| WindowManager.LayoutParams.TYPE_SYSTEM_ALERT); |
| mEnableTouchExplorationDialog.getWindow().getAttributes().privateFlags |
| |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS; |
| mEnableTouchExplorationDialog.setCanceledOnTouchOutside(true); |
| mEnableTouchExplorationDialog.show(); |
| } |
| } |
| |
| /** |
| * Called when any property of the user state has changed. |
| * |
| * @param userState the new user state |
| */ |
| private void onUserStateChangedLocked(UserState userState) { |
| // TODO: Remove this hack |
| mInitialized = true; |
| updateLegacyCapabilitiesLocked(userState); |
| updateServicesLocked(userState); |
| updateAccessibilityShortcutLocked(userState); |
| updateWindowsForAccessibilityCallbackLocked(userState); |
| updateAccessibilityFocusBehaviorLocked(userState); |
| updateFilterKeyEventsLocked(userState); |
| updateTouchExplorationLocked(userState); |
| updatePerformGesturesLocked(userState); |
| updateDisplayDaltonizerLocked(userState); |
| updateDisplayInversionLocked(userState); |
| updateMagnificationLocked(userState); |
| updateSoftKeyboardShowModeLocked(userState); |
| scheduleUpdateFingerprintGestureHandling(userState); |
| scheduleUpdateInputFilter(userState); |
| scheduleUpdateClientsIfNeededLocked(userState); |
| updateRelevantEventsLocked(userState); |
| updateAccessibilityButtonTargetsLocked(userState); |
| } |
| |
| private void updateAccessibilityFocusBehaviorLocked(UserState userState) { |
| // If there is no service that can operate with interactive windows |
| // then we keep the old behavior where a window loses accessibility |
| // focus if it is no longer active. This still changes the behavior |
| // for services that do not operate with interactive windows and run |
| // at the same time as the one(s) which does. In practice however, |
| // there is only one service that uses accessibility focus and it |
| // 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; |
| final int boundServiceCount = boundServices.size(); |
| for (int i = 0; i < boundServiceCount; i++) { |
| Service boundService = boundServices.get(i); |
| if (boundService.canRetrieveInteractiveWindowsLocked()) { |
| userState.mAccessibilityFocusOnlyInActiveWindow = false; |
| return; |
| } |
| } |
| userState.mAccessibilityFocusOnlyInActiveWindow = true; |
| } |
| |
| private void updateWindowsForAccessibilityCallbackLocked(UserState userState) { |
| // We observe windows for accessibility only if there is at least |
| // one bound service that can retrieve window content that specified |
| // it is interested in accessing such windows. For services that are |
| // 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; |
| final int boundServiceCount = boundServices.size(); |
| for (int i = 0; i < boundServiceCount; i++) { |
| Service boundService = boundServices.get(i); |
| if (boundService.canRetrieveInteractiveWindowsLocked()) { |
| if (mWindowsForAccessibilityCallback == null) { |
| mWindowsForAccessibilityCallback = new WindowsForAccessibilityCallback(); |
| mWindowManagerService.setWindowsForAccessibilityCallback( |
| mWindowsForAccessibilityCallback); |
| } |
| return; |
| } |
| } |
| |
| if (mWindowsForAccessibilityCallback != null) { |
| mWindowsForAccessibilityCallback = null; |
| mWindowManagerService.setWindowsForAccessibilityCallback(null); |
| // Drop all windows we know about. |
| mSecurityPolicy.clearWindowsLocked(); |
| } |
| } |
| |
| private void updateLegacyCapabilitiesLocked(UserState userState) { |
| // 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. |
| final int installedServiceCount = userState.mInstalledServices.size(); |
| for (int i = 0; i < installedServiceCount; i++) { |
| AccessibilityServiceInfo serviceInfo = userState.mInstalledServices.get(i); |
| ResolveInfo resolveInfo = serviceInfo.getResolveInfo(); |
| if ((serviceInfo.getCapabilities() |
| & AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION) == 0 |
| && resolveInfo.serviceInfo.applicationInfo.targetSdkVersion |
| <= Build.VERSION_CODES.JELLY_BEAN_MR1) { |
| ComponentName componentName = new ComponentName( |
| resolveInfo.serviceInfo.packageName, resolveInfo.serviceInfo.name); |
| if (userState.mTouchExplorationGrantedServices.contains(componentName)) { |
| serviceInfo.setCapabilities(serviceInfo.getCapabilities() |
| | AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION); |
| } |
| } |
| } |
| } |
| |
| private void updatePerformGesturesLocked(UserState userState) { |
| final int serviceCount = userState.mBoundServices.size(); |
| for (int i = 0; i < serviceCount; i++) { |
| Service service = userState.mBoundServices.get(i); |
| if ((service.mAccessibilityServiceInfo.getCapabilities() |
| & AccessibilityServiceInfo.CAPABILITY_CAN_PERFORM_GESTURES) != 0) { |
| userState.mIsPerformGesturesEnabled = true; |
| return; |
| } |
| } |
| userState.mIsPerformGesturesEnabled = false; |
| } |
| |
| private void updateFilterKeyEventsLocked(UserState userState) { |
| final int serviceCount = userState.mBoundServices.size(); |
| for (int i = 0; i < serviceCount; i++) { |
| Service service = userState.mBoundServices.get(i); |
| if (service.mRequestFilterKeyEvents |
| && (service.mAccessibilityServiceInfo.getCapabilities() |
| & AccessibilityServiceInfo |
| .CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS) != 0) { |
| userState.mIsFilterKeyEventsEnabled = true; |
| return; |
| } |
| } |
| userState.mIsFilterKeyEventsEnabled = false; |
| } |
| |
| private boolean readConfigurationForUserStateLocked(UserState userState) { |
| boolean somethingChanged = readInstalledAccessibilityServiceLocked(userState); |
| somethingChanged |= readEnabledAccessibilityServicesLocked(userState); |
| somethingChanged |= readTouchExplorationGrantedAccessibilityServicesLocked(userState); |
| somethingChanged |= readTouchExplorationEnabledSettingLocked(userState); |
| somethingChanged |= readHighTextContrastEnabledSettingLocked(userState); |
| somethingChanged |= readMagnificationEnabledSettingsLocked(userState); |
| somethingChanged |= readAutoclickEnabledSettingLocked(userState); |
| somethingChanged |= readAccessibilityShortcutSettingLocked(userState); |
| somethingChanged |= readAccessibilityButtonSettingsLocked(userState); |
| return somethingChanged; |
| } |
| |
| private void updateAccessibilityEnabledSetting(UserState userState) { |
| final long identity = Binder.clearCallingIdentity(); |
| try { |
| Settings.Secure.putIntForUser(mContext.getContentResolver(), |
| Settings.Secure.ACCESSIBILITY_ENABLED, |
| userState.isHandlingAccessibilityEvents() ? 1 : 0, |
| userState.mUserId); |
| } finally { |
| Binder.restoreCallingIdentity(identity); |
| } |
| } |
| |
| private boolean readTouchExplorationEnabledSettingLocked(UserState userState) { |
| final boolean touchExplorationEnabled = Settings.Secure.getIntForUser( |
| mContext.getContentResolver(), |
| Settings.Secure.TOUCH_EXPLORATION_ENABLED, 0, userState.mUserId) == 1; |
| if (touchExplorationEnabled != userState.mIsTouchExplorationEnabled) { |
| userState.mIsTouchExplorationEnabled = touchExplorationEnabled; |
| return true; |
| } |
| return false; |
| } |
| |
| private boolean readMagnificationEnabledSettingsLocked(UserState userState) { |
| final boolean displayMagnificationEnabled = Settings.Secure.getIntForUser( |
| mContext.getContentResolver(), |
| Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED, |
| 0, userState.mUserId) == 1; |
| final boolean navBarMagnificationEnabled = Settings.Secure.getIntForUser( |
| mContext.getContentResolver(), |
| Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED, |
| 0, userState.mUserId) == 1; |
| if ((displayMagnificationEnabled != userState.mIsDisplayMagnificationEnabled) |
| || (navBarMagnificationEnabled != userState.mIsNavBarMagnificationEnabled)) { |
| userState.mIsDisplayMagnificationEnabled = displayMagnificationEnabled; |
| userState.mIsNavBarMagnificationEnabled = navBarMagnificationEnabled; |
| return true; |
| } |
| return false; |
| } |
| |
| private boolean readAutoclickEnabledSettingLocked(UserState userState) { |
| final boolean autoclickEnabled = Settings.Secure.getIntForUser( |
| mContext.getContentResolver(), |
| Settings.Secure.ACCESSIBILITY_AUTOCLICK_ENABLED, |
| 0, userState.mUserId) == 1; |
| if (autoclickEnabled != userState.mIsAutoclickEnabled) { |
| userState.mIsAutoclickEnabled = autoclickEnabled; |
| return true; |
| } |
| return false; |
| } |
| |
| private boolean readHighTextContrastEnabledSettingLocked(UserState userState) { |
| final boolean highTextContrastEnabled = Settings.Secure.getIntForUser( |
| mContext.getContentResolver(), |
| Settings.Secure.ACCESSIBILITY_HIGH_TEXT_CONTRAST_ENABLED, 0, |
| userState.mUserId) == 1; |
| if (highTextContrastEnabled != userState.mIsTextHighContrastEnabled) { |
| userState.mIsTextHighContrastEnabled = highTextContrastEnabled; |
| return true; |
| } |
| return false; |
| } |
| |
| private boolean readSoftKeyboardShowModeChangedLocked(UserState userState) { |
| final int softKeyboardShowMode = Settings.Secure.getIntForUser( |
| mContext.getContentResolver(), |
| Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE, 0, |
| userState.mUserId); |
| if (softKeyboardShowMode != userState.mSoftKeyboardShowMode) { |
| userState.mSoftKeyboardShowMode = softKeyboardShowMode; |
| return true; |
| } |
| return false; |
| } |
| |
| private void updateTouchExplorationLocked(UserState userState) { |
| boolean enabled = false; |
| final int serviceCount = userState.mBoundServices.size(); |
| for (int i = 0; i < serviceCount; i++) { |
| Service service = userState.mBoundServices.get(i); |
| if (canRequestAndRequestsTouchExplorationLocked(service)) { |
| enabled = true; |
| break; |
| } |
| } |
| if (enabled != userState.mIsTouchExplorationEnabled) { |
| userState.mIsTouchExplorationEnabled = enabled; |
| final long identity = Binder.clearCallingIdentity(); |
| try { |
| Settings.Secure.putIntForUser(mContext.getContentResolver(), |
| Settings.Secure.TOUCH_EXPLORATION_ENABLED, enabled ? 1 : 0, |
| userState.mUserId); |
| } finally { |
| Binder.restoreCallingIdentity(identity); |
| } |
| } |
| } |
| |
| private boolean readAccessibilityShortcutSettingLocked(UserState userState) { |
| String componentNameToEnableString = AccessibilityShortcutController |
| .getTargetServiceComponentNameString(mContext, userState.mUserId); |
| if ((componentNameToEnableString == null) || componentNameToEnableString.isEmpty()) { |
| if (userState.mServiceToEnableWithShortcut == null) { |
| return false; |
| } |
| userState.mServiceToEnableWithShortcut = null; |
| return true; |
| } |
| ComponentName componentNameToEnable = |
| ComponentName.unflattenFromString(componentNameToEnableString); |
| if ((componentNameToEnable != null) |
| && componentNameToEnable.equals(userState.mServiceToEnableWithShortcut)) { |
| return false; |
| } |
| userState.mServiceToEnableWithShortcut = componentNameToEnable; |
| return true; |
| } |
| |
| private boolean readAccessibilityButtonSettingsLocked(UserState userState) { |
| String componentId = Settings.Secure.getStringForUser(mContext.getContentResolver(), |
| Settings.Secure.ACCESSIBILITY_BUTTON_TARGET_COMPONENT, userState.mUserId); |
| if (TextUtils.isEmpty(componentId)) { |
| if ((userState.mServiceAssignedToAccessibilityButton == null) |
| && !userState.mIsNavBarMagnificationAssignedToAccessibilityButton) { |
| return false; |
| } |
| userState.mServiceAssignedToAccessibilityButton = null; |
| userState.mIsNavBarMagnificationAssignedToAccessibilityButton = false; |
| return true; |
| } |
| |
| if (componentId.equals(MagnificationController.class.getName())) { |
| if (userState.mIsNavBarMagnificationAssignedToAccessibilityButton) { |
| return false; |
| } |
| userState.mServiceAssignedToAccessibilityButton = null; |
| userState.mIsNavBarMagnificationAssignedToAccessibilityButton = true; |
| return true; |
| } |
| |
| ComponentName componentName = ComponentName.unflattenFromString(componentId); |
| if (componentName.equals(userState.mServiceAssignedToAccessibilityButton)) { |
| return false; |
| } |
| userState.mServiceAssignedToAccessibilityButton = componentName; |
| userState.mIsNavBarMagnificationAssignedToAccessibilityButton = false; |
| return true; |
| } |
| |
| /** |
| * Check if the service that will be enabled by the shortcut is installed. If it isn't, |
| * clear the value and the associated setting so a sideloaded service can't spoof the |
| * package name of the default service. |
| * |
| * @param userState |
| */ |
| private void updateAccessibilityShortcutLocked(UserState userState) { |
| if (userState.mServiceToEnableWithShortcut == null) { |
| return; |
| } |
| boolean shortcutServiceIsInstalled = false; |
| for (int i = 0; i < userState.mInstalledServices.size(); i++) { |
| if (userState.mInstalledServices.get(i).getComponentName() |
| .equals(userState.mServiceToEnableWithShortcut)) { |
| shortcutServiceIsInstalled = true; |
| } |
| } |
| if (!shortcutServiceIsInstalled) { |
| userState.mServiceToEnableWithShortcut = null; |
| final long identity = Binder.clearCallingIdentity(); |
| try { |
| Settings.Secure.putStringForUser(mContext.getContentResolver(), |
| Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, null, userState.mUserId); |
| |
| Settings.Secure.putIntForUser(mContext.getContentResolver(), |
| Settings.Secure.ACCESSIBILITY_SHORTCUT_ENABLED, 0, userState.mUserId); |
| } finally { |
| Binder.restoreCallingIdentity(identity); |
| } |
| } |
| } |
| |
| private boolean canRequestAndRequestsTouchExplorationLocked(Service service) { |
| // 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 |
| <= 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 |
| || !mEnableTouchExplorationDialog.isShowing()) { |
| mMainHandler.obtainMessage( |
| MainHandler.MSG_SHOW_ENABLED_TOUCH_EXPLORATION_DIALOG, |
| service).sendToTarget(); |
| } |
| } else { |
| // Starting in JB-MR2 we request an accessibility service to declare |
| // certain capabilities in its meta-data to allow it to enable the |
| // corresponding features. |
| if ((service.mAccessibilityServiceInfo.getCapabilities() |
| & AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION) != 0) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| private void updateDisplayDaltonizerLocked(UserState userState) { |
| DisplayAdjustmentUtils.applyDaltonizerSetting(mContext, userState.mUserId); |
| } |
| |
| private void updateDisplayInversionLocked(UserState userState) { |
| DisplayAdjustmentUtils.applyInversionSetting(mContext, userState.mUserId); |
| } |
| |
| private void updateMagnificationLocked(UserState userState) { |
| if (userState.mUserId != mCurrentUserId) { |
| return; |
| } |
| |
| if (userState.mIsDisplayMagnificationEnabled || userState.mIsNavBarMagnificationEnabled |
| || userHasListeningMagnificationServicesLocked(userState)) { |
| // Initialize the magnification controller if necessary |
| getMagnificationController(); |
| mMagnificationController.register(); |
| } else if (mMagnificationController != null) { |
| mMagnificationController.unregister(); |
| } |
| } |
| |
| /** |
| * Returns whether the specified user has any services that are capable of |
| * controlling magnification. |
| */ |
| private boolean userHasMagnificationServicesLocked(UserState userState) { |
| final List<Service> services = userState.mBoundServices; |
| for (int i = 0, count = services.size(); i < count; i++) { |
| final Service service = services.get(i); |
| if (mSecurityPolicy.canControlMagnification(service)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Returns whether the specified user has any services that are capable of |
| * controlling magnification and are actively listening for magnification updates. |
| */ |
| private boolean userHasListeningMagnificationServicesLocked(UserState userState) { |
| final List<Service> services = userState.mBoundServices; |
| for (int i = 0, count = services.size(); i < count; i++) { |
| final Service service = services.get(i); |
| if (mSecurityPolicy.canControlMagnification(service) |
| && service.mInvocationHandler.mIsMagnificationCallbackEnabled) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| private void updateSoftKeyboardShowModeLocked(UserState userState) { |
| final int userId = userState.mUserId; |
| // 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 |
| // something other than the default is still enabled and, if not, remove flag and |
| // reset to the default soft keyboard behavior. |
| boolean serviceChangingSoftKeyboardModeIsEnabled = |
| userState.mEnabledServices.contains(userState.mServiceChangingSoftKeyboardMode); |
| |
| if (!serviceChangingSoftKeyboardModeIsEnabled) { |
| final long identity = Binder.clearCallingIdentity(); |
| try { |
| Settings.Secure.putIntForUser(mContext.getContentResolver(), |
| Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE, |
| 0, |
| userState.mUserId); |
| } finally { |
| Binder.restoreCallingIdentity(identity); |
| } |
| userState.mSoftKeyboardShowMode = 0; |
| userState.mServiceChangingSoftKeyboardMode = null; |
| notifySoftKeyboardShowModeChangedLocked(userState.mSoftKeyboardShowMode); |
| } |
| } |
| } |
| |
| private void updateFingerprintGestureHandling(UserState userState) { |
| final List<Service> services; |
| synchronized (mLock) { |
| services = userState.mBoundServices; |
| if ((mFingerprintGestureDispatcher == null) |
| && mPackageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) { |
| // Only create the controller when a service wants to use the feature |
| int numServices = services.size(); |
| for (int i = 0; i < numServices; i++) { |
| if (services.get(i).isCapturingFingerprintGestures()) { |
| final long identity = Binder.clearCallingIdentity(); |
| IFingerprintService service = null; |
| try { |
| service = IFingerprintService.Stub.asInterface( |
| ServiceManager.getService(Context.FINGERPRINT_SERVICE)); |
| } finally { |
| Binder.restoreCallingIdentity(identity); |
| } |
| if (service != null) { |
| mFingerprintGestureDispatcher = new FingerprintGestureDispatcher( |
| service, mLock); |
| break; |
| } |
| } |
| } |
| } |
| } |
| if (mFingerprintGestureDispatcher != null) { |
| mFingerprintGestureDispatcher.updateClientList(services); |
| } |
| } |
| |
| private void updateAccessibilityButtonTargetsLocked(UserState userState) { |
| for (int i = userState.mBoundServices.size() - 1; i >= 0; i--) { |
| final Service service = userState.mBoundServices.get(i); |
| if (service.mRequestAccessibilityButton) { |
| service.notifyAccessibilityButtonAvailabilityChangedLocked( |
| service.isAccessibilityButtonAvailableLocked(userState)); |
| } |
| } |
| } |
| |
| @GuardedBy("mLock") |
| private MagnificationSpec getCompatibleMagnificationSpecLocked(int windowId) { |
| IBinder windowToken = mGlobalWindowTokens.get(windowId); |
| if (windowToken == null) { |
| windowToken = getCurrentUserStateLocked().mWindowTokens.get(windowId); |
| } |
| if (windowToken != null) { |
| return mWindowManagerService.getCompatibleMagnificationSpecForWindow( |
| windowToken); |
| } |
| return null; |
| } |
| |
| private KeyEventDispatcher getKeyEventDispatcher() { |
| if (mKeyEventDispatcher == null) { |
| mKeyEventDispatcher = new KeyEventDispatcher( |
| mMainHandler, MainHandler.MSG_SEND_KEY_EVENT_TO_INPUT_FILTER, mLock, |
| mPowerManager); |
| } |
| return mKeyEventDispatcher; |
| } |
| |
| /** |
| * 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 |
| * accessibility services themselves. |
| */ |
| public void performAccessibilityShortcut() { |
| if ((UserHandle.getAppId(Binder.getCallingUid()) != Process.SYSTEM_UID) |
| && (mContext.checkCallingPermission(Manifest.permission.WRITE_SECURE_SETTINGS) |
| != PackageManager.PERMISSION_GRANTED)) { |
| throw new SecurityException( |
| "performAccessibilityShortcut requires the WRITE_SECURE_SETTINGS permission"); |
| } |
| synchronized(mLock) { |
| UserState userState = getUserStateLocked(mCurrentUserId); |
| ComponentName serviceName = userState.mServiceToEnableWithShortcut; |
| if (serviceName == null) { |
| return; |
| } |
| final long identity = Binder.clearCallingIdentity(); |
| try { |
| if (userState.mComponentNameToServiceMap.get(serviceName) == null) { |
| enableAccessibilityServiceLocked(serviceName, mCurrentUserId); |
| } else { |
| disableAccessibilityServiceLocked(serviceName, mCurrentUserId); |
| } |
| } finally { |
| Binder.restoreCallingIdentity(identity); |
| } |
| } |
| }; |
| |
| /** |
| * Enables accessibility service specified by {@param componentName} for the {@param userId}. |
| */ |
| private void enableAccessibilityServiceLocked(ComponentName componentName, int userId) { |
| final SettingStringHelper setting = |
| new SettingStringHelper( |
| mContext.getContentResolver(), |
| Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, |
| userId); |
| setting.write(ComponentNameSet.add(setting.read(), componentName)); |
| |
| UserState userState = getUserStateLocked(userId); |
| if (userState.mEnabledServices.add(componentName)) { |
| onUserStateChangedLocked(userState); |
| } |
| } |
| |
| /** |
| * Disables accessibility service specified by {@param componentName} for the {@param userId}. |
| */ |
| private void disableAccessibilityServiceLocked(ComponentName componentName, int userId) { |
| final SettingsStringUtil.SettingStringHelper setting = |
| new SettingStringHelper( |
| mContext.getContentResolver(), |
| Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, |
| userId); |
| setting.write(ComponentNameSet.remove(setting.read(), componentName)); |
| |
| UserState userState = getUserStateLocked(userId); |
| if (userState.mEnabledServices.remove(componentName)) { |
| onUserStateChangedLocked(userState); |
| } |
| } |
| |
| /** |
| * AIDL-exposed method. System only. |
| * Inform accessibility that a fingerprint gesture was performed |
| * |
| * @param gestureKeyCode The key code corresponding to the fingerprint gesture. |
| * @return {@code true} if accessibility consumes the fingerprint gesture, {@code false} if it |
| * doesn't. |
| */ |
| @Override |
| public boolean sendFingerprintGesture(int gestureKeyCode) { |
| synchronized(mLock) { |
| if (UserHandle.getAppId(Binder.getCallingUid()) != Process.SYSTEM_UID) { |
| throw new SecurityException("Only SYSTEM can call sendFingerprintGesture"); |
| } |
| } |
| if (mFingerprintGestureDispatcher == null) { |
| return false; |
| } |
| return mFingerprintGestureDispatcher.onFingerprintGesture(gestureKeyCode); |
| } |
| |
| @Override |
| public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) { |
| if (!DumpUtils.checkDumpPermission(mContext, LOG_TAG, pw)) return; |
| synchronized (mLock) { |
| pw.println("ACCESSIBILITY MANAGER (dumpsys accessibility)"); |
| pw.println(); |
| final int userCount = mUserStates.size(); |
| for (int i = 0; i < userCount; i++) { |
| UserState userState = mUserStates.valueAt(i); |
| pw.append("User state[attributes:{id=" + userState.mUserId); |
| pw.append(", currentUser=" + (userState.mUserId == mCurrentUserId)); |
| pw.append(", touchExplorationEnabled=" + userState.mIsTouchExplorationEnabled); |
| pw.append(", displayMagnificationEnabled=" |
| + userState.mIsDisplayMagnificationEnabled); |
| pw.append(", navBarMagnificationEnabled=" |
| + userState.mIsNavBarMagnificationEnabled); |
| pw.append(", autoclickEnabled=" + userState.mIsAutoclickEnabled); |
| if (userState.mUiAutomationService != null) { |
| pw.append(", "); |
| userState.mUiAutomationService.dump(fd, pw, args); |
| pw.println(); |
| } |
| pw.append("}"); |
| pw.println(); |
| pw.append(" services:{"); |
| final int serviceCount = userState.mBoundServices.size(); |
| for (int j = 0; j < serviceCount; j++) { |
| if (j > 0) { |
| pw.append(", "); |
| pw.println(); |
| pw.append(" "); |
| } |
| Service service = userState.mBoundServices.get(j); |
| service.dump(fd, pw, args); |
| } |
| pw.println("}]"); |
| pw.println(); |
| } |
| if (mSecurityPolicy.mWindows != null) { |
| final int windowCount = mSecurityPolicy.mWindows.size(); |
| for (int j = 0; j < windowCount; j++) { |
| if (j > 0) { |
| pw.append(','); |
| pw.println(); |
| } |
| pw.append("Window["); |
| AccessibilityWindowInfo window = mSecurityPolicy.mWindows.get(j); |
| pw.append(window.toString()); |
| pw.append(']'); |
| } |
| } |
| } |
| } |
| |
| private class AccessibilityConnectionWrapper implements DeathRecipient { |
| private final int mWindowId; |
| private final int mUserId; |
| private final IAccessibilityInteractionConnection mConnection; |
| |
| public AccessibilityConnectionWrapper(int windowId, |
| IAccessibilityInteractionConnection connection, int userId) { |
| mWindowId = windowId; |
| mUserId = userId; |
| mConnection = connection; |
| } |
| |
| public void linkToDeath() throws RemoteException { |
| mConnection.asBinder().linkToDeath(this, 0); |
| } |
| |
| public void unlinkToDeath() { |
| mConnection.asBinder().unlinkToDeath(this, 0); |
| } |
| |
| @Override |
| public void binderDied() { |
| unlinkToDeath(); |
| synchronized (mLock) { |
| removeAccessibilityInteractionConnectionLocked(mWindowId, mUserId); |
| } |
| } |
| } |
| |
| private 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; |
| public static final int MSG_ANNOUNCE_NEW_USER_IF_NEEDED = 5; |
| public static final int MSG_UPDATE_INPUT_FILTER = 6; |
| public static final int MSG_SHOW_ENABLED_TOUCH_EXPLORATION_DIALOG = 7; |
| public static final int MSG_SEND_KEY_EVENT_TO_INPUT_FILTER = 8; |
| public static final int MSG_CLEAR_ACCESSIBILITY_FOCUS = 9; |
| public static final int MSG_SEND_SERVICES_STATE_CHANGED_TO_CLIENTS = 10; |
| public static final int MSG_UPDATE_FINGERPRINT = 11; |
| public static final int MSG_SEND_RELEVANT_EVENTS_CHANGED_TO_CLIENTS = 12; |
| public static final int MSG_SEND_ACCESSIBILITY_BUTTON_TO_INPUT_FILTER = 13; |
| public static final int MSG_SHOW_ACCESSIBILITY_BUTTON_CHOOSER = 14; |
| |
| public MainHandler(Looper looper) { |
| super(looper); |
| } |
| |
| @Override |
| public void handleMessage(Message msg) { |
| final int type = msg.what; |
| switch (type) { |
| case MSG_SEND_ACCESSIBILITY_EVENT_TO_INPUT_FILTER: { |
| AccessibilityEvent event = (AccessibilityEvent) msg.obj; |
| synchronized (mLock) { |
| if (mHasInputFilter && mInputFilter != null) { |
| mInputFilter.notifyAccessibilityEvent(event); |
| } |
| } |
| event.recycle(); |
| } break; |
| |
| case MSG_SEND_KEY_EVENT_TO_INPUT_FILTER: { |
| KeyEvent event = (KeyEvent) msg.obj; |
| final int policyFlags = msg.arg1; |
| synchronized (mLock) { |
| if (mHasInputFilter && mInputFilter != null) { |
| mInputFilter.sendInputEvent(event, policyFlags); |
| } |
| } |
| event.recycle(); |
| } break; |
| |
| case MSG_SEND_STATE_TO_CLIENTS: { |
| final int clientState = msg.arg1; |
| final int userId = msg.arg2; |
| sendStateToClients(clientState, mGlobalClients); |
| sendStateToClients(clientState, getUserClientsForId(userId)); |
| } break; |
| |
| case MSG_SEND_CLEARED_STATE_TO_CLIENTS_FOR_USER: { |
| final int userId = msg.arg1; |
| sendStateToClients(0, getUserClientsForId(userId)); |
| } break; |
| |
| case MSG_ANNOUNCE_NEW_USER_IF_NEEDED: { |
| announceNewUserIfNeeded(); |
| } break; |
| |
| case MSG_UPDATE_INPUT_FILTER: { |
| UserState userState = (UserState) msg.obj; |
| updateInputFilter(userState); |
| } break; |
| |
| case MSG_SHOW_ENABLED_TOUCH_EXPLORATION_DIALOG: { |
| Service service = (Service) msg.obj; |
| showEnableTouchExplorationDialog(service); |
| } break; |
| |
| case MSG_CLEAR_ACCESSIBILITY_FOCUS: { |
| final int windowId = msg.arg1; |
| getInteractionBridge().clearAccessibilityFocusNotLocked(windowId); |
| } break; |
| |
| case MSG_SEND_SERVICES_STATE_CHANGED_TO_CLIENTS: { |
| final int userId = msg.arg1; |
| notifyClientsOfServicesStateChange(mGlobalClients); |
| notifyClientsOfServicesStateChange(getUserClientsForId(userId)); |
| } break; |
| |
| case MSG_UPDATE_FINGERPRINT: { |
| updateFingerprintGestureHandling((UserState) msg.obj); |
| } break; |
| |
| case MSG_SEND_RELEVANT_EVENTS_CHANGED_TO_CLIENTS: { |
| final int userId = msg.arg1; |
| final int relevantEventTypes = msg.arg2; |
| final UserState userState; |
| synchronized (mLock) { |
| userState = getUserStateLocked(userId); |
| } |
| broadcastToClients(userState, (client) -> { |
| try { |
| client.setRelevantEventTypes(relevantEventTypes); |
| } catch (RemoteException re) { |
| /* ignore */ |
| } |
| }); |
| } break; |
| |
| case MSG_SEND_ACCESSIBILITY_BUTTON_TO_INPUT_FILTER: { |
| synchronized (mLock) { |
| if (mHasInputFilter && mInputFilter != null) { |
| mInputFilter.notifyAccessibilityButtonClicked(); |
| } |
| } |
| } break; |
| |
| case MSG_SHOW_ACCESSIBILITY_BUTTON_CHOOSER: { |
| showAccessibilityButtonTargetSelection(); |
| } break; |
| } |
| } |
| |
| private void announceNewUserIfNeeded() { |
| synchronized (mLock) { |
| UserState userState = getCurrentUserStateLocked(); |
| if (userState.isHandlingAccessibilityEvents()) { |
| UserManager userManager = (UserManager) mContext.getSystemService( |
| Context.USER_SERVICE); |
| String message = mContext.getString(R.string.user_switched, |
| userManager.getUserInfo(mCurrentUserId).name); |
| AccessibilityEvent event = AccessibilityEvent.obtain( |
| AccessibilityEvent.TYPE_ANNOUNCEMENT); |
| event.getText().add(message); |
| sendAccessibilityEvent(event, mCurrentUserId); |
| } |
| } |
| } |
| |
| private RemoteCallbackList<IAccessibilityManagerClient> getUserClientsForId(int userId) { |
| final UserState userState; |
| synchronized (mLock) { |
| userState = getUserStateLocked(userId); |
| } |
| return userState.mUserClients; |
| } |
| |
| private void sendStateToClients(int clientState, |
| RemoteCallbackList<IAccessibilityManagerClient> clients) { |
| clients.broadcast((client) -> { |
| try { |
| client.setState(clientState); |
| } catch (RemoteException re) { |
| /* ignore */ |
| } |
| }); |
| } |
| |
| private void notifyClientsOfServicesStateChange( |
| RemoteCallbackList<IAccessibilityManagerClient> clients) { |
| try { |
| final int userClientCount = clients.beginBroadcast(); |
| for (int i = 0; i < userClientCount; i++) { |
| IAccessibilityManagerClient client = clients.getBroadcastItem(i); |
| try { |
| client.notifyServicesStateChanged(); |
| } catch (RemoteException re) { |
| /* ignore */ |
| } |
| } |
| } finally { |
| clients.finishBroadcast(); |
| } |
| } |
| } |
| |
| private int findWindowIdLocked(IBinder token) { |
| final int globalIndex = mGlobalWindowTokens.indexOfValue(token); |
| if (globalIndex >= 0) { |
| return mGlobalWindowTokens.keyAt(globalIndex); |
| } |
| UserState userState = getCurrentUserStateLocked(); |
| final int userIndex = userState.mWindowTokens.indexOfValue(token); |
| if (userIndex >= 0) { |
| return userState.mWindowTokens.keyAt(userIndex); |
| } |
| return -1; |
| } |
| |
| private void ensureWindowsAvailableTimed() { |
| synchronized (mLock) { |
| if (mSecurityPolicy.mWindows != 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 (mWindowsForAccessibilityCallback == null) { |
| UserState userState = getCurrentUserStateLocked(); |
| onUserStateChangedLocked(userState); |
| } |
| // We have no windows but do not care about them, done. |
| if (mWindowsForAccessibilityCallback == null) { |
| return; |
| } |
| |
| // Wait for the windows with a timeout. |
| final long startMillis = SystemClock.uptimeMillis(); |
| while (mSecurityPolicy.mWindows == 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 */ |
| } |
| } |
| } |
| } |
| |
| MagnificationController getMagnificationController() { |
| synchronized (mLock) { |
| if (mMagnificationController == null) { |
| mMagnificationController = new MagnificationController(mContext, this, mLock); |
| mMagnificationController.setUserId(mCurrentUserId); |
| } |
| return mMagnificationController; |
| } |
| } |
| |
| /** |
| * 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; |
| try { |
| mServiceInterface.init(this, mId, mOverlayWindowToken); |
| onUserStateChangedLocked(userState); |
| } catch (RemoteException re) { |
| Slog.w(LOG_TAG, "Error while setting connection for service: " |
| + service, re); |
| binderDied(); |
| } |
| } else { |
| 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.findWindowById(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; |
| 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; |
| } |
| AccessibilityWindowInfo windowInfo = |
| mSecurityPolicy.findWindowById(resolvedWindowId); |
| if ((windowInfo != null) && windowInfo.inPictureInPicture()) { |
| boolean isA11yFocusAction = |
| (action == AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS) |
| || (action == |
| AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS); |
| 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); |
| |
| 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 forceRegistration = mSecurityPolicy.canControlMagnification(this); |
| boolean initiallyRegistered = magnificationController.isRegisteredLocked(); |
| if (!initiallyRegistered && forceRegistration) { |
| magnificationController.register(); |
| } |
| final long identity = Binder.clearCallingIdentity(); |
| try { |
| magnificationController.getMagnificationRegion(region); |
| return region; |
| } finally { |
| Binder.restoreCallingIdentity(identity); |
| if (!initiallyRegistered && forceRegistration) { |
| magnificationController.unregister(); |
| } |
| } |
| } |
| } |
| |
| @Override |
| public float getMagnificationCenterX() { |
| synchronized (mLock) { |
| if (!isCalledForCurrentUserLocked()) { |
| return 0.0f; |
| } |
| } |
| final long identity = Binder.clearCallingIdentity(); |
| try { |
| return getMagnificationController().getCenterX(); |
| } finally { |
| Binder.restoreCallingIdentity(identity); |
| } |
| } |
| |
| @Override |
| public float getMagnificationCenterY() { |
| synchronized (mLock) { |
| if (!isCalledForCurrentUserLocked()) { |
| return 0.0f; |
| } |
| } |
| final long identity = Binder.clearCallingIdentity(); |
| try { |
| return getMagnificationController().getCenterY(); |
| } finally { |
| Binder.restoreCallingIdentity(identity); |
| } |
| } |
| |
| @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(); |
| } |
| |
| 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; |
| } |
| |
| 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 IAccessibilityInteractionConnectionCallback replaceCallbackIfNeeded( |
| IAccessibilityInteractionConnectionCallback originalCallback, |
| int resolvedWindowId, int interactionId, int interrogatingPid, |
| long interrogatingTid) { |
| AccessibilityWindowInfo windowInfo = mSecurityPolicy.findWindowById(resolvedWindowId); |
| if ((windowInfo == null) || !windowInfo.inPictureInPicture() |
| || (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(); |
| } |
| } |
| } |
| |
| final class WindowsForAccessibilityCallback implements |
| WindowManagerInternal.WindowsForAccessibilityCallback { |
| |
| @Override |
| public void onWindowsForAccessibilityChanged(List<WindowInfo> windows) { |
| synchronized (mLock) { |
| // Populate the windows to report. |
| List<AccessibilityWindowInfo> reportedWindows = new ArrayList<>(); |
| final int receivedWindowCount = windows.size(); |
| for (int i = 0; i < receivedWindowCount; i++) { |
| WindowInfo receivedWindow = windows.get(i); |
| AccessibilityWindowInfo reportedWindow = populateReportedWindow( |
| receivedWindow); |
| if (reportedWindow != null) { |
| reportedWindows.add(reportedWindow); |
| } |
| } |
| |
| if (DEBUG) { |
| Slog.i(LOG_TAG, "Windows changed: " + reportedWindows); |
| } |
| |
| // Let the policy update the focused and active windows. |
| mSecurityPolicy.updateWindowsLocked(reportedWindows); |
| |
| // Someone may be waiting for the windows - advertise it. |
| mLock.notifyAll(); |
| } |
| } |
| |
| private AccessibilityWindowInfo populateReportedWindow(WindowInfo window) { |
| final int windowId = findWindowIdLocked(window.token); |
| if (windowId < 0) { |
| return null; |
| } |
| |
| AccessibilityWindowInfo reportedWindow = AccessibilityWindowInfo.obtain(); |
| |
| reportedWindow.setId(windowId); |
| reportedWindow.setType(getTypeForWindowManagerWindowType(window.type)); |
| reportedWindow.setLayer(window.layer); |
| reportedWindow.setFocused(window.focused); |
| reportedWindow.setBoundsInScreen(window.boundsInScreen); |
| reportedWindow.setTitle(window.title); |
| reportedWindow.setAnchorId(window.accessibilityIdOfAnchor); |
| reportedWindow.setPictureInPicture(window.inPictureInPicture); |
| |
| final int parentId = findWindowIdLocked(window.parentToken); |
| if (parentId >= 0) { |
| reportedWindow.setParentId(parentId); |
| } |
| |
| if (window.childTokens != null) { |
| final int childCount = window.childTokens.size(); |
| for (int i = 0; i < childCount; i++) { |
| IBinder childToken = window.childTokens.get(i); |
| final int childId = findWindowIdLocked(childToken); |
| if (childId >= 0) { |
| reportedWindow.addChild(childId); |
| } |
| } |
| } |
| |
| return reportedWindow; |
| } |
| |
| private int getTypeForWindowManagerWindowType(int windowType) { |
| switch (windowType) { |
| case WindowManager.LayoutParams.TYPE_APPLICATION: |
| case WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA: |
| case WindowManager.LayoutParams.TYPE_APPLICATION_PANEL: |
| case WindowManager.LayoutParams.TYPE_APPLICATION_STARTING: |
| case WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL: |
| case WindowManager.LayoutParams.TYPE_APPLICATION_ABOVE_SUB_PANEL: |
| case WindowManager.LayoutParams.TYPE_BASE_APPLICATION: |
| case WindowManager.LayoutParams.TYPE_DRAWN_APPLICATION: |
| case WindowManager.LayoutParams.TYPE_PHONE: |
| case WindowManager.LayoutParams.TYPE_PRIORITY_PHONE: |
| case WindowManager.LayoutParams.TYPE_TOAST: |
| case WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG: { |
| return AccessibilityWindowInfo.TYPE_APPLICATION; |
| } |
| |
| case WindowManager.LayoutParams.TYPE_INPUT_METHOD: |
| case WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG: { |
| return AccessibilityWindowInfo.TYPE_INPUT_METHOD; |
| } |
| |
| case WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG: |
| case WindowManager.LayoutParams.TYPE_NAVIGATION_BAR: |
| case WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL: |
| case WindowManager.LayoutParams.TYPE_SEARCH_BAR: |
| case WindowManager.LayoutParams.TYPE_STATUS_BAR: |
| case WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL: |
| case WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL: |
| case WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY: |
| case WindowManager.LayoutParams.TYPE_SYSTEM_ALERT: |
| case WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG: |
| case WindowManager.LayoutParams.TYPE_SYSTEM_ERROR: |
| case WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY: |
| case WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY: |
| case WindowManager.LayoutParams.TYPE_SCREENSHOT: { |
| return AccessibilityWindowInfo.TYPE_SYSTEM; |
| } |
| |
| case WindowManager.LayoutParams.TYPE_DOCK_DIVIDER: { |
| return AccessibilityWindowInfo.TYPE_SPLIT_SCREEN_DIVIDER; |
| } |
| |
| case TYPE_ACCESSIBILITY_OVERLAY: { |
| return AccessibilityWindowInfo.TYPE_ACCESSIBILITY_OVERLAY; |
| } |
| |
| default: { |
| return -1; |
| } |
| } |
| } |
| } |
| |
| private final class InteractionBridge { |
| private final Display mDefaultDisplay; |
| private final int mConnectionId; |
| private final AccessibilityInteractionClient mClient; |
| |
| public InteractionBridge() { |
| 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); |
| |
| mConnectionId = service.mId; |
| |
| mClient = AccessibilityInteractionClient.getInstance(); |
| mClient.addConnection(mConnectionId, service); |
| |
| //TODO: (multi-display) We need to support multiple displays. |
| DisplayManager displayManager = (DisplayManager) |
| mContext.getSystemService(Context.DISPLAY_SERVICE); |
| mDefaultDisplay = displayManager.getDisplay(Display.DEFAULT_DISPLAY); |
| } |
| |
| public void clearAccessibilityFocusNotLocked(int windowId) { |
| AccessibilityNodeInfo focus = getAccessibilityFocusNotLocked(windowId); |
| if (focus != null) { |
| focus.performAction(AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS); |
| } |
| } |
| |
| /** |
| * Perform an accessibility action on the view that currently has accessibility focus. |
| * Has no effect if no item has accessibility focus, if the item with accessibility |
| * focus does not expose the specified action, or if the action fails. |
| * |
| * @param actionId The id of the action to perform. |
| * |
| * @return {@code true} if the action was performed. {@code false} if it was not. |
| */ |
| public boolean performActionOnAccessibilityFocusedItemNotLocked( |
| AccessibilityNodeInfo.AccessibilityAction action) { |
| AccessibilityNodeInfo focus = getAccessibilityFocusNotLocked(); |
| if ((focus == null) || !focus.getActionList().contains(action)) { |
| return false; |
| } |
| return focus.performAction(action.getId()); |
| } |
| |
| public boolean getAccessibilityFocusClickPointInScreenNotLocked(Point outPoint) { |
| AccessibilityNodeInfo focus = getAccessibilityFocusNotLocked(); |
| if (focus == null) { |
| return false; |
| } |
| |
| synchronized (mLock) { |
| Rect boundsInScreen = mTempRect; |
| focus.getBoundsInScreen(boundsInScreen); |
| |
| // Apply magnification if needed. |
| MagnificationSpec spec = getCompatibleMagnificationSpecLocked(focus.getWindowId()); |
| if (spec != null && !spec.isNop()) { |
| boundsInScreen.offset((int) -spec.offsetX, (int) -spec.offsetY); |
| boundsInScreen.scale(1 / spec.scale); |
| } |
| |
| // Clip to the window bounds. |
| Rect windowBounds = mTempRect1; |
| getWindowBounds(focus.getWindowId(), windowBounds); |
| if (!boundsInScreen.intersect(windowBounds)) { |
| return false; |
| } |
| |
| // Clip to the screen bounds. |
| Point screenSize = mTempPoint; |
| mDefaultDisplay.getRealSize(screenSize); |
| if (!boundsInScreen.intersect(0, 0, screenSize.x, screenSize.y)) { |
| return false; |
| } |
| |
| outPoint.set(boundsInScreen.centerX(), boundsInScreen.centerY()); |
| } |
| |
| return true; |
| } |
| |
| private AccessibilityNodeInfo getAccessibilityFocusNotLocked() { |
| final int focusedWindowId; |
| synchronized (mLock) { |
| focusedWindowId = mSecurityPolicy.mAccessibilityFocusedWindowId; |
| if (focusedWindowId == SecurityPolicy.INVALID_WINDOW_ID) { |
| return null; |
| } |
| } |
| return getAccessibilityFocusNotLocked(focusedWindowId); |
| } |
| |
| private AccessibilityNodeInfo getAccessibilityFocusNotLocked(int windowId) { |
| return mClient.findFocus(mConnectionId, |
| windowId, AccessibilityNodeInfo.ROOT_NODE_ID, |
| AccessibilityNodeInfo.FOCUS_ACCESSIBILITY); |
| } |
| } |
| |
| final class SecurityPolicy { |
| public static final int INVALID_WINDOW_ID = -1; |
| |
| private static final int RETRIEVAL_ALLOWING_EVENT_TYPES = |
| AccessibilityEvent.TYPE_VIEW_CLICKED |
| | AccessibilityEvent.TYPE_VIEW_FOCUSED |
| | AccessibilityEvent.TYPE_VIEW_HOVER_ENTER |
| | AccessibilityEvent.TYPE_VIEW_HOVER_EXIT |
| | AccessibilityEvent.TYPE_VIEW_LONG_CLICKED |
| | AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED |
| | AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED |
| | AccessibilityEvent.TYPE_VIEW_SELECTED |
| | AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED |
| | AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED |
| | AccessibilityEvent.TYPE_VIEW_SCROLLED |
| | AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED |
| | AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED |
| | AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY; |
| |
| // In Z order |
| public List<AccessibilityWindowInfo> mWindows; |
| public SparseArray<AccessibilityWindowInfo> mWindowsById = new SparseArray<>(); |
| |
| public int mActiveWindowId = INVALID_WINDOW_ID; |
| public int mFocusedWindowId = INVALID_WINDOW_ID; |
| public int mAccessibilityFocusedWindowId = INVALID_WINDOW_ID; |
| public long mAccessibilityFocusNodeId = AccessibilityNodeInfo.UNDEFINED_ITEM_ID; |
| |
| private boolean mTouchInteractionInProgress; |
| |
| private boolean canDispatchAccessibilityEventLocked(AccessibilityEvent event) { |
| final int eventType = event.getEventType(); |
| switch (eventType) { |
| // All events that are for changes in a global window |
| // state should *always* be dispatched. |
| case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED: |
| case AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED: |
| case AccessibilityEvent.TYPE_ANNOUNCEMENT: |
| // All events generated by the user touching the |
| // screen should *always* be dispatched. |
| case AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START: |
| case AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END: |
| case AccessibilityEvent.TYPE_GESTURE_DETECTION_START: |
| case AccessibilityEvent.TYPE_GESTURE_DETECTION_END: |
| case AccessibilityEvent.TYPE_TOUCH_INTERACTION_START: |
| case AccessibilityEvent.TYPE_TOUCH_INTERACTION_END: |
| case AccessibilityEvent.TYPE_VIEW_HOVER_ENTER: |
| case AccessibilityEvent.TYPE_VIEW_HOVER_EXIT: |
| // Also always dispatch the event that assist is reading context. |
| case AccessibilityEvent.TYPE_ASSIST_READING_CONTEXT: |
| // Also windows changing should always be anounced. |
| case AccessibilityEvent.TYPE_WINDOWS_CHANGED: { |
| return true; |
| } |
| // All events for changes in window content should be |
| // dispatched *only* if this window is one of the windows |
| // the accessibility layer reports which are windows |
| // that a sighted user can touch. |
| default: { |
| return isRetrievalAllowingWindow(event.getWindowId()); |
| } |
| } |
| } |
| |
| public void clearWindowsLocked() { |
| List<AccessibilityWindowInfo> windows = Collections.emptyList(); |
| final int activeWindowId = mActiveWindowId; |
| updateWindowsLocked(windows); |
| mActiveWindowId = activeWindowId; |
| mWindows = null; |
| } |
| |
| public void updateWindowsLocked(List<AccessibilityWindowInfo> windows) { |
| if (mWindows == null) { |
| mWindows = new ArrayList<>(); |
| } |
| |
| final int oldWindowCount = mWindows.size(); |
| for (int i = oldWindowCount - 1; i >= 0; i--) { |
| mWindows.remove(i).recycle(); |
| } |
| mWindowsById.clear(); |
| |
| mFocusedWindowId = INVALID_WINDOW_ID; |
| if (!mTouchInteractionInProgress) { |
| mActiveWindowId = INVALID_WINDOW_ID; |
| } |
| |
| // If the active window goes away while the user is touch exploring we |
| // reset the active window id and wait for the next hover event from |
| // under the user's finger to determine which one is the new one. It |
| // is possible that the finger is not moving and the input system |
| // filters out such events. |
| boolean activeWindowGone = true; |
| |
| final int windowCount = windows.size(); |
| if (windowCount > 0) { |
| for (int i = 0; i < windowCount; i++) { |
| AccessibilityWindowInfo window = windows.get(i); |
| final int windowId = window.getId(); |
| if (window.isFocused()) { |
| mFocusedWindowId = windowId; |
| if (!mTouchInteractionInProgress) { |
| mActiveWindowId = windowId; |
| window.setActive(true); |
| } else if (windowId == mActiveWindowId) { |
| activeWindowGone = false; |
| } |
| } |
| mWindows.add(window); |
| mWindowsById.put(windowId, window); |
| } |
| |
| if (mTouchInteractionInProgress && activeWindowGone) { |
| mActiveWindowId = mFocusedWindowId; |
| } |
| |
| // Focused window may change the active one, so set the |
| // active window once we decided which it is. |
| for (int i = 0; i < windowCount; i++) { |
| AccessibilityWindowInfo window = mWindows.get(i); |
| if (window.getId() == mActiveWindowId) { |
| window.setActive(true); |
| } |
| if (window.getId() == mAccessibilityFocusedWindowId) { |
| window.setAccessibilityFocused(true); |
| } |
| } |
| } |
| |
| notifyWindowsChanged(); |
| } |
| |
| public boolean computePartialInteractiveRegionForWindowLocked(int windowId, |
| Region outRegion) { |
| if (mWindows == null) { |
| return false; |
| } |
| |
| // Windows are ordered in z order so start from the bottom and find |
| // the window of interest. After that all windows that cover it should |
| // be subtracted from the resulting region. Note that for accessibility |
| // we are returning only interactive windows. |
| Region windowInteractiveRegion = null; |
| boolean windowInteractiveRegionChanged = false; |
| |
| final int windowCount = mWindows.size(); |
| for (int i = windowCount - 1; i >= 0; i--) { |
| AccessibilityWindowInfo currentWindow = mWindows.get(i); |
| if (windowInteractiveRegion == null) { |
| if (currentWindow.getId() == windowId) { |
| Rect currentWindowBounds = mTempRect; |
| currentWindow.getBoundsInScreen(currentWindowBounds); |
| outRegion.set(currentWindowBounds); |
| windowInteractiveRegion = outRegion; |
| continue; |
| } |
| } else if (currentWindow.getType() |
| != AccessibilityWindowInfo.TYPE_ACCESSIBILITY_OVERLAY) { |
| Rect currentWindowBounds = mTempRect; |
| currentWindow.getBoundsInScreen(currentWindowBounds); |
| if (windowInteractiveRegion.op(currentWindowBounds, Region.Op.DIFFERENCE)) { |
| windowInteractiveRegionChanged = true; |
| } |
| } |
| } |
| |
| return windowInteractiveRegionChanged; |
| } |
| |
| public void updateEventSourceLocked(AccessibilityEvent event) { |
| if ((event.getEventType() & RETRIEVAL_ALLOWING_EVENT_TYPES) == 0) { |
| event.setSource((View) null); |
| } |
| } |
| |
| public void updateActiveAndAccessibilityFocusedWindowLocked(int windowId, long nodeId, |
| int eventType, int eventAction) { |
| // The active window is either the window that has input focus or |
| // the window that the user is currently touching. If the user is |
| // touching a window that does not have input focus as soon as the |
| // the user stops touching that window the focused window becomes |
| // the active one. Here we detect the touched window and make it |
| // active. In updateWindowsLocked() we update the focused window |
| // and if the user is not touching the screen, we make the focused |
| // window the active one. |
| switch (eventType) { |
| case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED: { |
| // If no service has the capability to introspect screen, |
| // we do not register callback in the window manager for |
| // window changes, so we have to ask the window manager |
| // what the focused window is to update the active one. |
| // The active window also determined events from which |
| // windows are delivered. |
| synchronized (mLock) { |
| if (mWindowsForAccessibilityCallback == null) { |
| mFocusedWindowId = getFocusedWindowId(); |
| if (windowId == mFocusedWindowId) { |
| mActiveWindowId = windowId; |
| } |
| } |
| } |
| } break; |
| |
| case AccessibilityEvent.TYPE_VIEW_HOVER_ENTER: { |
| // Do not allow delayed hover events to confuse us |
| // which the active window is. |
| synchronized (mLock) { |
| if (mTouchInteractionInProgress && mActiveWindowId != windowId) { |
| setActiveWindowLocked(windowId); |
| } |
| } |
| } break; |
| |
| case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED: { |
| synchronized (mLock) { |
| if (mAccessibilityFocusedWindowId != windowId) { |
| mMainHandler.obtainMessage(MainHandler.MSG_CLEAR_ACCESSIBILITY_FOCUS, |
| mAccessibilityFocusedWindowId, 0).sendToTarget(); |
| mSecurityPolicy.setAccessibilityFocusedWindowLocked(windowId); |
| mAccessibilityFocusNodeId = nodeId; |
| } |
| } |
| } break; |
| |
| case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED: { |
| synchronized (mLock) { |
| if (mAccessibilityFocusNodeId == nodeId) { |
| mAccessibilityFocusNodeId = AccessibilityNodeInfo.UNDEFINED_ITEM_ID; |
| } |
| // Clear the window with focus if it no longer has focus and we aren't |
| // just moving focus from one view to the other in the same window |
| if ((mAccessibilityFocusNodeId == AccessibilityNodeInfo.UNDEFINED_ITEM_ID) |
| && (mAccessibilityFocusedWindowId == windowId) |
| && (eventAction != AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS) |
| ) { |
| mAccessibilityFocusedWindowId = INVALID_WINDOW_ID; |
| } |
| } |
| } break; |
| } |
| } |
| |
| public void onTouchInteractionStart() { |
| synchronized (mLock) { |
| mTouchInteractionInProgress = true; |
| } |
| } |
| |
| public void onTouchInteractionEnd() { |
| synchronized (mLock) { |
| mTouchInteractionInProgress = false; |
| // We want to set the active window to be current immediately |
| // after the user has stopped touching the screen since if the |
| // user types with the IME he should get a feedback for the |
| // letter typed in the text view which is in the input focused |
| // window. Note that we always deliver hover accessibility events |
| // (they are a result of user touching the screen) so change of |
| // the active window before all hover accessibility events from |
| // the touched window are delivered is fine. |
| final int oldActiveWindow = mSecurityPolicy.mActiveWindowId; |
| setActiveWindowLocked(mFocusedWindowId); |
| |
| // If there is no service that can operate with active windows |
| // we keep accessibility focus behavior to constrain it only in |
| // the active window. Look at updateAccessibilityFocusBehaviorLocked |
| // for details. |
| if (oldActiveWindow != mSecurityPolicy.mActiveWindowId |
| && mAccessibilityFocusedWindowId == oldActiveWindow |
| && getCurrentUserStateLocked().mAccessibilityFocusOnlyInActiveWindow) { |
| mMainHandler.obtainMessage(MainHandler.MSG_CLEAR_ACCESSIBILITY_FOCUS, |
| oldActiveWindow, 0).sendToTarget(); |
| } |
| } |
| } |
| |
| public int getActiveWindowId() { |
| if (mActiveWindowId == INVALID_WINDOW_ID && !mTouchInteractionInProgress) { |
| mActiveWindowId = getFocusedWindowId(); |
| } |
| return mActiveWindowId; |
| } |
| |
| private void setActiveWindowLocked(int windowId) { |
| if (mActiveWindowId != windowId) { |
| mActiveWindowId = windowId; |
| if (mWindows != null) { |
| final int windowCount = mWindows.size(); |
| for (int i = 0; i < windowCount; i++) { |
| AccessibilityWindowInfo window = mWindows.get(i); |
| window.setActive(window.getId() == windowId); |
| } |
| } |
| notifyWindowsChanged(); |
| } |
| } |
| |
| private void setAccessibilityFocusedWindowLocked(int windowId) { |
| if (mAccessibilityFocusedWindowId != windowId) { |
| mAccessibilityFocusedWindowId = windowId; |
| if (mWindows != null) { |
| final int windowCount = mWindows.size(); |
| for (int i = 0; i < windowCount; i++) { |
| AccessibilityWindowInfo window = mWindows.get(i); |
| window.setAccessibilityFocused(window.getId() == windowId); |
| } |
| } |
| |
| notifyWindowsChanged(); |
| } |
| } |
| |
| public void notifyWindowsChanged() { |
| if (mWindowsForAccessibilityCallback == null) { |
| return; |
| } |
| final long identity = Binder.clearCallingIdentity(); |
| try { |
| // Let the client know the windows changed. |
| AccessibilityEvent event = AccessibilityEvent.obtain( |
| AccessibilityEvent.TYPE_WINDOWS_CHANGED); |
| event.setEventTime(SystemClock.uptimeMillis()); |
| sendAccessibilityEvent(event, mCurrentUserId); |
| } finally { |
| Binder.restoreCallingIdentity(identity); |
| } |
| } |
| |
| public boolean canGetAccessibilityNodeInfoLocked(Service service, int windowId) { |
| return canRetrieveWindowContentLocked(service) && isRetrievalAllowingWindow(windowId); |
| } |
| |
| public boolean canRetrieveWindowsLocked(Service service) { |
| return canRetrieveWindowContentLocked(service) && service.mRetrieveInteractiveWindows; |
| } |
| |
| public boolean canRetrieveWindowContentLocked(Service service) { |
| return (service.mAccessibilityServiceInfo.getCapabilities() |
| & AccessibilityServiceInfo.CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT) != 0; |
| } |
| |
| public boolean canControlMagnification(Service service) { |
| return (service.mAccessibilityServiceInfo.getCapabilities() |
| & AccessibilityServiceInfo.CAPABILITY_CAN_CONTROL_MAGNIFICATION) != 0; |
| } |
| |
| public boolean canPerformGestures(Service service) { |
| return (service.mAccessibilityServiceInfo.getCapabilities() |
| & AccessibilityServiceInfo.CAPABILITY_CAN_PERFORM_GESTURES) != 0; |
| } |
| |
| public boolean canCaptureFingerprintGestures(Service service) { |
| return (service.mAccessibilityServiceInfo.getCapabilities() |
| & AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_FINGERPRINT_GESTURES) != 0; |
| } |
| |
| private int resolveProfileParentLocked(int userId) { |
| if (userId != mCurrentUserId) { |
| final long identity = Binder.clearCallingIdentity(); |
| try { |
| UserInfo parent = mUserManager.getProfileParent(userId); |
| if (parent != null) { |
| return parent.getUserHandle().getIdentifier(); |
| } |
| } finally { |
| Binder.restoreCallingIdentity(identity); |
| } |
| } |
| return userId; |
| } |
| |
| public int resolveCallingUserIdEnforcingPermissionsLocked(int userId) { |
| final int callingUid = Binder.getCallingUid(); |
| if (callingUid == 0 |
| || callingUid == Process.SYSTEM_UID |
| || callingUid == Process.SHELL_UID) { |
| if (userId == UserHandle.USER_CURRENT |
| || userId == UserHandle.USER_CURRENT_OR_SELF) { |
| return mCurrentUserId; |
| } |
| return resolveProfileParentLocked(userId); |
| } |
| final int callingUserId = UserHandle.getUserId(callingUid); |
| if (callingUserId == userId) { |
| return resolveProfileParentLocked(userId); |
| } |
| final int callingUserParentId = resolveProfileParentLocked(callingUserId); |
| if (callingUserParentId == mCurrentUserId && |
| (userId == UserHandle.USER_CURRENT |
| || userId == UserHandle.USER_CURRENT_OR_SELF)) { |
| return mCurrentUserId; |
| } |
| if (!hasPermission(Manifest.permission.INTERACT_ACROSS_USERS) |
| && !hasPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL)) { |
| throw new SecurityException("Call from user " + callingUserId + " as user " |
| + userId + " without permission INTERACT_ACROSS_USERS or " |
| + "INTERACT_ACROSS_USERS_FULL not allowed."); |
| } |
| if (userId == UserHandle.USER_CURRENT |
| || userId == UserHandle.USER_CURRENT_OR_SELF) { |
| return mCurrentUserId; |
| } |
| throw new IllegalArgumentException("Calling user can be changed to only " |
| + "UserHandle.USER_CURRENT or UserHandle.USER_CURRENT_OR_SELF."); |
| } |
| |
| public boolean isCallerInteractingAcrossUsers(int userId) { |
| final int callingUid = Binder.getCallingUid(); |
| return (Binder.getCallingPid() == android.os.Process.myPid() |
| || callingUid == Process.SHELL_UID |
| || userId == UserHandle.USER_CURRENT |
| || userId == UserHandle.USER_CURRENT_OR_SELF); |
| } |
| |
| private boolean isRetrievalAllowingWindow(int windowId) { |
| // The system gets to interact with any window it wants. |
| if (Binder.getCallingUid() == Process.SYSTEM_UID) { |
| return true; |
| } |
| if (windowId == mActiveWindowId) { |
| return true; |
| } |
| return findWindowById(windowId) != null; |
| } |
| |
| private AccessibilityWindowInfo findWindowById(int windowId) { |
| return mWindowsById.get(windowId); |
| } |
| |
| private AccessibilityWindowInfo getPictureInPictureWindow() { |
| if (mWindows != null) { |
| final int windowCount = mWindows.size(); |
| for (int i = 0; i < windowCount; i++) { |
| AccessibilityWindowInfo window = mWindows.get(i); |
| if (window.inPictureInPicture()) { |
| return window; |
| } |
| } |
| } |
| return null; |
| } |
| |
| private void enforceCallingPermission(String permission, String function) { |
| if (OWN_PROCESS_ID == Binder.getCallingPid()) { |
| return; |
| } |
| if (!hasPermission(permission)) { |
| throw new SecurityException("You do not have " + permission |
| + " required to call " + function + " from pid=" |
| + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()); |
| } |
| } |
| |
| private boolean hasPermission(String permission) { |
| return mContext.checkCallingPermission(permission) == PackageManager.PERMISSION_GRANTED; |
| } |
| |
| private int getFocusedWindowId() { |
| IBinder token = mWindowManagerService.getFocusedWindowToken(); |
| synchronized (mLock) { |
| return findWindowIdLocked(token); |
| } |
| } |
| } |
| |
| private class UserState { |
| public final int mUserId; |
| |
| // Non-transient state. |
| |
| public final RemoteCallbackList<IAccessibilityManagerClient> mUserClients = |
| new RemoteCallbackList<>(); |
| |
| public final SparseArray<AccessibilityConnectionWrapper> mInteractionConnections = |
| new SparseArray<>(); |
| |
| public final SparseArray<IBinder> mWindowTokens = new SparseArray<>(); |
| |
| // Transient state. |
| |
| public final CopyOnWriteArrayList<Service> mBoundServices = |
| new CopyOnWriteArrayList<>(); |
| |
| public int mLastSentRelevantEventTypes = AccessibilityEvent.TYPES_ALL_MASK; |
| |
| public final Map<ComponentName, Service> mComponentNameToServiceMap = |
| new HashMap<>(); |
| |
| public final List<AccessibilityServiceInfo> mInstalledServices = |
| new ArrayList<>(); |
| |
| public final Set<ComponentName> mBindingServices = new HashSet<>(); |
| |
| public final Set<ComponentName> mEnabledServices = new HashSet<>(); |
| |
| public final Set<ComponentName> mTouchExplorationGrantedServices = |
| new HashSet<>(); |
| |
| public ComponentName mServiceChangingSoftKeyboardMode; |
| |
| public ComponentName mServiceToEnableWithShortcut; |
| |
| public int mLastSentClientState = -1; |
| |
| public int mSoftKeyboardShowMode = 0; |
| |
| public boolean mIsNavBarMagnificationAssignedToAccessibilityButton; |
| public ComponentName mServiceAssignedToAccessibilityButton; |
| |
| public boolean mIsTouchExplorationEnabled; |
| public boolean mIsTextHighContrastEnabled; |
| public boolean mIsDisplayMagnificationEnabled; |
| public boolean mIsNavBarMagnificationEnabled; |
| public boolean mIsAutoclickEnabled; |
| public boolean mIsPerformGesturesEnabled; |
| 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()) { |
| clientState |= AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED; |
| } |
| // Touch exploration relies on enabled accessibility. |
| if (isHandlingAccessibilityEvents() && mIsTouchExplorationEnabled) { |
| clientState |= AccessibilityManager.STATE_FLAG_TOUCH_EXPLORATION_ENABLED; |
| } |
| if (mIsTextHighContrastEnabled) { |
| clientState |= AccessibilityManager.STATE_FLAG_HIGH_TEXT_CONTRAST_ENABLED; |
| } |
| return clientState; |
| } |
| |
| public boolean isHandlingAccessibilityEvents() { |
| return !mBoundServices.isEmpty() || !mBindingServices.isEmpty(); |
| } |
| |
| public void onSwitchToAnotherUser() { |
| // Clear UI test automation state. |
| if (mUiAutomationService != null) { |
| mUiAutomationService.binderDied(); |
| } |
| |
| // Unbind all services. |
| unbindAllServicesLocked(this); |
| |
| // Clear service management state. |
| mBoundServices.clear(); |
| mBindingServices.clear(); |
| |
| // Clear event management state. |
| mLastSentClientState = -1; |
| |
| // Clear state persisted in settings. |
| mEnabledServices.clear(); |
| mTouchExplorationGrantedServices.clear(); |
| mIsTouchExplorationEnabled = false; |
| mIsDisplayMagnificationEnabled = false; |
| mIsNavBarMagnificationEnabled = false; |
| mServiceAssignedToAccessibilityButton = null; |
| mIsNavBarMagnificationAssignedToAccessibilityButton = false; |
| mIsAutoclickEnabled = false; |
| mSoftKeyboardShowMode = 0; |
| } |
| |
| public void destroyUiAutomationService() { |
| mUiAutomationService = null; |
| mUiAutomationFlags = 0; |
| mUiAutomationServiceClient = null; |
| if (mUiAutomationServiceOwner != null) { |
| mUiAutomationServiceOwner.unlinkToDeath( |
| mUiAutomationSerivceOnwerDeathRecipient, 0); |
| mUiAutomationServiceOwner = null; |
| } |
| } |
| |
| boolean isUiAutomationSuppressingOtherServices() { |
| return ((mUiAutomationService != null) && (mUiAutomationFlags |
| & UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES) == 0); |
| } |
| } |
| |
| private final class AccessibilityContentObserver extends ContentObserver { |
| |
| private final Uri mTouchExplorationEnabledUri = Settings.Secure.getUriFor( |
| Settings.Secure.TOUCH_EXPLORATION_ENABLED); |
| |
| private final Uri mDisplayMagnificationEnabledUri = Settings.Secure.getUriFor( |
| Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED); |
| |
| private final Uri mNavBarMagnificationEnabledUri = Settings.Secure.getUriFor( |
| Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED); |
| |
| private final Uri mAutoclickEnabledUri = Settings.Secure.getUriFor( |
| Settings.Secure.ACCESSIBILITY_AUTOCLICK_ENABLED); |
| |
| private final Uri mEnabledAccessibilityServicesUri = Settings.Secure.getUriFor( |
| Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES); |
| |
| private final Uri mTouchExplorationGrantedAccessibilityServicesUri = Settings.Secure |
| .getUriFor(Settings.Secure.TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES); |
| |
| private final Uri mDisplayInversionEnabledUri = Settings.Secure.getUriFor( |
| Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED); |
| |
| private final Uri mDisplayDaltonizerEnabledUri = Settings.Secure.getUriFor( |
| Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED); |
| |
| private final Uri mDisplayDaltonizerUri = Settings.Secure.getUriFor( |
| Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER); |
| |
| private final Uri mHighTextContrastUri = Settings.Secure.getUriFor( |
| Settings.Secure.ACCESSIBILITY_HIGH_TEXT_CONTRAST_ENABLED); |
| |
| private final Uri mAccessibilitySoftKeyboardModeUri = Settings.Secure.getUriFor( |
| Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE); |
| |
| private final Uri mAccessibilityShortcutServiceIdUri = Settings.Secure.getUriFor( |
| Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE); |
| |
| private final Uri mAccessibilityButtonComponentIdUri = Settings.Secure.getUriFor( |
| Settings.Secure.ACCESSIBILITY_BUTTON_TARGET_COMPONENT); |
| |
| public AccessibilityContentObserver(Handler handler) { |
| super(handler); |
| } |
| |
| public void register(ContentResolver contentResolver) { |
| contentResolver.registerContentObserver(mTouchExplorationEnabledUri, |
| false, this, UserHandle.USER_ALL); |
| contentResolver.registerContentObserver(mDisplayMagnificationEnabledUri, |
| false, this, UserHandle.USER_ALL); |
| contentResolver.registerContentObserver(mNavBarMagnificationEnabledUri, |
| false, this, UserHandle.USER_ALL); |
| contentResolver.registerContentObserver(mAutoclickEnabledUri, |
| false, this, UserHandle.USER_ALL); |
| contentResolver.registerContentObserver(mEnabledAccessibilityServicesUri, |
| false, this, UserHandle.USER_ALL); |
| contentResolver.registerContentObserver( |
| mTouchExplorationGrantedAccessibilityServicesUri, |
| false, this, UserHandle.USER_ALL); |
| contentResolver.registerContentObserver( |
| mDisplayInversionEnabledUri, false, this, UserHandle.USER_ALL); |
| contentResolver.registerContentObserver( |
| mDisplayDaltonizerEnabledUri, false, this, UserHandle.USER_ALL); |
| contentResolver.registerContentObserver( |
| mDisplayDaltonizerUri, false, this, UserHandle.USER_ALL); |
| contentResolver.registerContentObserver( |
| mHighTextContrastUri, false, this, UserHandle.USER_ALL); |
| contentResolver.registerContentObserver( |
| mAccessibilitySoftKeyboardModeUri, false, this, UserHandle.USER_ALL); |
| contentResolver.registerContentObserver( |
| mAccessibilityShortcutServiceIdUri, false, this, UserHandle.USER_ALL); |
| contentResolver.registerContentObserver( |
| mAccessibilityButtonComponentIdUri, false, this, UserHandle.USER_ALL); |
| } |
| |
| @Override |
| public void onChange(boolean selfChange, Uri uri) { |
| synchronized (mLock) { |
| // Profiles share the accessibility state of the parent. Therefore, |
| // 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); |
| } |
| } else if (mDisplayMagnificationEnabledUri.equals(uri) |
| || mNavBarMagnificationEnabledUri.equals(uri)) { |
| if (readMagnificationEnabledSettingsLocked(userState)) { |
| onUserStateChangedLocked(userState); |
| } |
| } else if (mAutoclickEnabledUri.equals(uri)) { |
| if (readAutoclickEnabledSettingLocked(userState)) { |
| onUserStateChangedLocked(userState); |
| } |
| } else if (mEnabledAccessibilityServicesUri.equals(uri)) { |
| if (readEnabledAccessibilityServicesLocked(userState)) { |
| onUserStateChangedLocked(userState); |
| } |
| } else if (mTouchExplorationGrantedAccessibilityServicesUri.equals(uri)) { |
| if (readTouchExplorationGrantedAccessibilityServicesLocked(userState)) { |
| onUserStateChangedLocked(userState); |
| } |
| } else if (mDisplayDaltonizerEnabledUri.equals(uri) |
| || mDisplayDaltonizerUri.equals(uri)) { |
| updateDisplayDaltonizerLocked(userState); |
| } else if (mDisplayInversionEnabledUri.equals(uri)) { |
| updateDisplayInversionLocked(userState); |
| } else if (mHighTextContrastUri.equals(uri)) { |
| if (readHighTextContrastEnabledSettingLocked(userState)) { |
| onUserStateChangedLocked(userState); |
| } |
| } else if (mAccessibilitySoftKeyboardModeUri.equals(uri)) { |
| if (readSoftKeyboardShowModeChangedLocked(userState)) { |
| notifySoftKeyboardShowModeChangedLocked(userState.mSoftKeyboardShowMode); |
| onUserStateChangedLocked(userState); |
| } |
| } else if (mAccessibilityShortcutServiceIdUri.equals(uri)) { |
| if (readAccessibilityShortcutSettingLocked(userState)) { |
| onUserStateChangedLocked(userState); |
| } |
| } else if (mAccessibilityButtonComponentIdUri.equals(uri)) { |
| if (readAccessibilityButtonSettingsLocked(userState)) { |
| onUserStateChangedLocked(userState); |
| } |
| } |
| } |
| } |
| } |
| } |