| /* |
| ** 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.provider.Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED; |
| import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON; |
| import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY; |
| import static android.view.accessibility.AccessibilityManager.ShortcutType; |
| |
| import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_COMPONENT_NAME; |
| import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME; |
| import static com.android.internal.accessibility.common.ShortcutConstants.CHOOSER_PACKAGE_NAME; |
| import static com.android.internal.accessibility.util.AccessibilityStatsLogUtils.logAccessibilityShortcutActivated; |
| import static com.android.internal.util.FunctionalUtils.ignoreRemoteException; |
| import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; |
| import static com.android.server.accessibility.AccessibilityUserState.doesShortcutTargetsStringContain; |
| |
| import android.Manifest; |
| import android.accessibilityservice.AccessibilityGestureEvent; |
| import android.accessibilityservice.AccessibilityService; |
| import android.accessibilityservice.AccessibilityServiceInfo; |
| import android.accessibilityservice.AccessibilityShortcutInfo; |
| import android.accessibilityservice.IAccessibilityServiceClient; |
| import android.annotation.NonNull; |
| import android.annotation.Nullable; |
| import android.app.ActivityOptions; |
| import android.app.AlertDialog; |
| import android.app.PendingIntent; |
| import android.app.RemoteAction; |
| import android.appwidget.AppWidgetManagerInternal; |
| import android.content.ActivityNotFoundException; |
| 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.pm.PackageManager; |
| import android.content.pm.PackageManagerInternal; |
| import android.content.pm.ResolveInfo; |
| import android.content.pm.ServiceInfo; |
| 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.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.ResultReceiver; |
| import android.os.ServiceManager; |
| import android.os.ShellCallback; |
| import android.os.SystemClock; |
| import android.os.UserHandle; |
| import android.os.UserManager; |
| import android.os.UserManagerInternal; |
| import android.provider.Settings; |
| import android.provider.SettingsStringUtil.SettingStringHelper; |
| import android.text.TextUtils; |
| import android.text.TextUtils.SimpleStringSplitter; |
| import android.util.ArraySet; |
| import android.util.IntArray; |
| import android.util.Slog; |
| import android.util.SparseArray; |
| import android.view.Display; |
| import android.view.IWindow; |
| import android.view.KeyEvent; |
| import android.view.MagnificationSpec; |
| import android.view.WindowManager; |
| 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.IAccessibilityManager; |
| import android.view.accessibility.IAccessibilityManagerClient; |
| import android.view.accessibility.IWindowMagnificationConnection; |
| |
| import com.android.internal.R; |
| import com.android.internal.accessibility.AccessibilityShortcutController; |
| import com.android.internal.accessibility.AccessibilityShortcutController.ToggleableFrameworkFeatureInfo; |
| import com.android.internal.accessibility.dialog.AccessibilityButtonChooserActivity; |
| import com.android.internal.accessibility.dialog.AccessibilityShortcutChooserActivity; |
| import com.android.internal.annotations.GuardedBy; |
| import com.android.internal.annotations.VisibleForTesting; |
| import com.android.internal.content.PackageMonitor; |
| import com.android.internal.util.ArrayUtils; |
| import com.android.internal.util.DumpUtils; |
| import com.android.internal.util.IntPair; |
| import com.android.server.LocalServices; |
| import com.android.server.SystemService; |
| import com.android.server.accessibility.magnification.WindowMagnificationManager; |
| import com.android.server.wm.ActivityTaskManagerInternal; |
| import com.android.server.wm.WindowManagerInternal; |
| |
| 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.HashSet; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.function.Consumer; |
| import java.util.function.Function; |
| import java.util.function.Predicate; |
| |
| /** |
| * 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 |
| implements AbstractAccessibilityServiceConnection.SystemSupport, |
| AccessibilityUserState.ServiceInfoChangeListener, |
| AccessibilityWindowManager.AccessibilityEventSender, |
| AccessibilitySecurityPolicy.AccessibilityUserManager, |
| SystemActionPerformer.SystemActionsChangedListener { |
| |
| 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; |
| |
| // 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; |
| |
| static final String FUNCTION_REGISTER_SYSTEM_ACTION = "registerSystemAction"; |
| static final String FUNCTION_UNREGISTER_SYSTEM_ACTION = "unregisterSystemAction"; |
| 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 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 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 PackageManager mPackageManager; |
| |
| private final PowerManager mPowerManager; |
| |
| private final WindowManagerInternal mWindowManagerService; |
| |
| private final AccessibilitySecurityPolicy mSecurityPolicy; |
| |
| private final AccessibilityWindowManager mA11yWindowManager; |
| |
| private final AccessibilityDisplayListener mA11yDisplayListener; |
| |
| private final ActivityTaskManagerInternal mActivityTaskManagerService; |
| |
| private final MainHandler mMainHandler; |
| |
| // Lazily initialized - access through getSystemActionPerfomer() |
| private SystemActionPerformer mSystemActionPerformer; |
| |
| private MagnificationController mMagnificationController; |
| |
| private InteractionBridge mInteractionBridge; |
| |
| private AlertDialog mEnableTouchExplorationDialog; |
| |
| private AccessibilityInputFilter mInputFilter; |
| |
| private WindowMagnificationManager mWindowMagnificationMgr; |
| |
| private boolean mHasInputFilter; |
| |
| private KeyEventDispatcher mKeyEventDispatcher; |
| |
| private SparseArray<MotionEventInjector> mMotionEventInjectors; |
| |
| 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<AccessibilityUserState> mUserStates = new SparseArray<>(); |
| |
| private final UiAutomationManager mUiAutomationManager = new UiAutomationManager(mLock); |
| |
| private int mCurrentUserId = UserHandle.USER_SYSTEM; |
| |
| //TODO: Remove this hack |
| private boolean mInitialized; |
| |
| private Point mTempPoint = new Point(); |
| private boolean mIsAccessibilityButtonShown; |
| |
| private AccessibilityUserState getCurrentUserStateLocked() { |
| return getUserStateLocked(mCurrentUserId); |
| } |
| |
| public static final class Lifecycle extends SystemService { |
| private final AccessibilityManagerService mService; |
| |
| public Lifecycle(Context context) { |
| super(context); |
| mService = new AccessibilityManagerService(context); |
| } |
| |
| @Override |
| public void onStart() { |
| publishBinderService(Context.ACCESSIBILITY_SERVICE, mService); |
| } |
| |
| @Override |
| public void onBootPhase(int phase) { |
| mService.onBootPhase(phase); |
| } |
| } |
| |
| @VisibleForTesting |
| AccessibilityManagerService( |
| Context context, |
| PackageManager packageManager, |
| AccessibilitySecurityPolicy securityPolicy, |
| SystemActionPerformer systemActionPerformer, |
| AccessibilityWindowManager a11yWindowManager, |
| AccessibilityDisplayListener a11yDisplayListener) { |
| mContext = context; |
| mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); |
| mWindowManagerService = LocalServices.getService(WindowManagerInternal.class); |
| mMainHandler = new MainHandler(mContext.getMainLooper()); |
| mActivityTaskManagerService = LocalServices.getService(ActivityTaskManagerInternal.class); |
| mPackageManager = packageManager; |
| mSecurityPolicy = securityPolicy; |
| mSystemActionPerformer = systemActionPerformer; |
| mA11yWindowManager = a11yWindowManager; |
| mA11yDisplayListener = a11yDisplayListener; |
| init(); |
| } |
| |
| /** |
| * Creates a new instance. |
| * |
| * @param context A {@link Context} instance. |
| */ |
| public AccessibilityManagerService(Context context) { |
| mContext = context; |
| mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); |
| mWindowManagerService = LocalServices.getService(WindowManagerInternal.class); |
| mMainHandler = new MainHandler(mContext.getMainLooper()); |
| mActivityTaskManagerService = LocalServices.getService(ActivityTaskManagerInternal.class); |
| mPackageManager = mContext.getPackageManager(); |
| mSecurityPolicy = new AccessibilitySecurityPolicy(mContext, this); |
| mA11yWindowManager = new AccessibilityWindowManager(mLock, mMainHandler, |
| mWindowManagerService, this, mSecurityPolicy, this); |
| mA11yDisplayListener = new AccessibilityDisplayListener(mContext, mMainHandler); |
| init(); |
| } |
| |
| private void init() { |
| mSecurityPolicy.setAccessibilityWindowManager(mA11yWindowManager); |
| registerBroadcastReceivers(); |
| new AccessibilityContentObserver(mMainHandler).register( |
| mContext.getContentResolver()); |
| } |
| |
| @Override |
| public int getCurrentUserIdLocked() { |
| return mCurrentUserId; |
| } |
| |
| @Override |
| public boolean isAccessibilityButtonShown() { |
| return mIsAccessibilityButtonShown; |
| } |
| |
| @Override |
| public void onServiceInfoChangedLocked(AccessibilityUserState userState) { |
| scheduleNotifyClientsOfServicesStateChangeLocked(userState); |
| } |
| |
| @Nullable |
| public FingerprintGestureDispatcher getFingerprintGestureDispatcher() { |
| return mFingerprintGestureDispatcher; |
| } |
| |
| private void onBootPhase(int phase) { |
| if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) { |
| if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_APP_WIDGETS)) { |
| mSecurityPolicy.setAppWidgetManager( |
| LocalServices.getService(AppWidgetManagerInternal.class)); |
| } |
| } |
| } |
| |
| private AccessibilityUserState getUserState(int userId) { |
| synchronized (mLock) { |
| return getUserStateLocked(userId); |
| } |
| } |
| |
| @NonNull |
| private AccessibilityUserState getUserStateLocked(int userId) { |
| AccessibilityUserState state = mUserStates.get(userId); |
| if (state == null) { |
| state = new AccessibilityUserState(userId, mContext, this); |
| mUserStates.put(userId, state); |
| } |
| return state; |
| } |
| |
| boolean getBindInstantServiceAllowed(int userId) { |
| synchronized (mLock) { |
| final AccessibilityUserState userState = getUserStateLocked(userId); |
| return userState.getBindInstantServiceAllowedLocked(); |
| } |
| } |
| |
| void setBindInstantServiceAllowed(int userId, boolean allowed) { |
| mContext.enforceCallingOrSelfPermission( |
| Manifest.permission.MANAGE_BIND_INSTANT_SERVICE, |
| "setBindInstantServiceAllowed"); |
| synchronized (mLock) { |
| final AccessibilityUserState userState = getUserStateLocked(userId); |
| if (allowed != userState.getBindInstantServiceAllowedLocked()) { |
| userState.setBindInstantServiceAllowedLocked(allowed); |
| onUserStateChangedLocked(userState); |
| } |
| } |
| } |
| |
| 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. |
| final AccessibilityUserState 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 (readConfigurationForUserStateLocked(userState)) { |
| onUserStateChangedLocked(userState); |
| } |
| } |
| } |
| |
| @Override |
| public void onPackageUpdateFinished(String packageName, int uid) { |
| // The package should already be removed from mBoundServices, and added into |
| // mBindingServices in binderDied() during updating. Remove services from this |
| // package from mBindingServices, and then update the user state to re-bind new |
| // versions of them. |
| synchronized (mLock) { |
| final int userId = getChangingUserId(); |
| if (userId != mCurrentUserId) { |
| return; |
| } |
| final AccessibilityUserState userState = getUserStateLocked(userId); |
| final boolean reboundAService = userState.getBindingServicesLocked().removeIf( |
| component -> component != null |
| && component.getPackageName().equals(packageName)) |
| || userState.mCrashedServices.removeIf(component -> component != null |
| && component.getPackageName().equals(packageName)); |
| // Reloads the installed services info to make sure the rebound service could |
| // get a new one. |
| userState.mInstalledServices.clear(); |
| final boolean configurationChanged = |
| readConfigurationForUserStateLocked(userState); |
| if (reboundAService || configurationChanged) { |
| onUserStateChangedLocked(userState); |
| } |
| migrateAccessibilityButtonSettingsIfNecessaryLocked(userState, packageName); |
| } |
| } |
| |
| @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; |
| } |
| final AccessibilityUserState userState = getUserStateLocked(userId); |
| final Predicate<ComponentName> filter = |
| component -> component != null && component.getPackageName().equals( |
| packageName); |
| userState.mBindingServices.removeIf(filter); |
| userState.mCrashedServices.removeIf(filter); |
| final Iterator<ComponentName> it = userState.mEnabledServices.iterator(); |
| while (it.hasNext()) { |
| final ComponentName comp = it.next(); |
| final 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); |
| 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; |
| } |
| final AccessibilityUserState userState = getUserStateLocked(userId); |
| final Iterator<ComponentName> it = userState.mEnabledServices.iterator(); |
| while (it.hasNext()) { |
| final ComponentName comp = it.next(); |
| final String compPkg = comp.getPackageName(); |
| for (String pkg : packages) { |
| if (compPkg.equals(pkg)) { |
| if (!doit) { |
| return true; |
| } |
| it.remove(); |
| userState.getBindingServicesLocked().remove(comp); |
| persistComponentNamesToSettingLocked( |
| Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, |
| userState.mEnabledServices, userId); |
| 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) { |
| AccessibilityUserState userState = getCurrentUserStateLocked(); |
| 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)); |
| } |
| } else if (ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED.equals(which)) { |
| synchronized (mLock) { |
| restoreLegacyDisplayMagnificationNavBarIfNeededLocked( |
| intent.getStringExtra(Intent.EXTRA_SETTING_NEW_VALUE), |
| intent.getIntExtra(Intent.EXTRA_SETTING_RESTORED_FROM_SDK_INT, |
| 0)); |
| } |
| } |
| } |
| } |
| }, UserHandle.ALL, intentFilter, null, null); |
| } |
| |
| // Called only during settings restore; currently supports only the owner user |
| // TODO: b/22388012 |
| private void restoreLegacyDisplayMagnificationNavBarIfNeededLocked(String newSetting, |
| int restoreFromSdkInt) { |
| if (restoreFromSdkInt >= Build.VERSION_CODES.R) { |
| return; |
| } |
| |
| boolean displayMagnificationNavBarEnabled; |
| try { |
| displayMagnificationNavBarEnabled = Integer.parseInt(newSetting) == 1; |
| } catch (NumberFormatException e) { |
| Slog.w(LOG_TAG, "number format is incorrect" + e); |
| return; |
| } |
| |
| final AccessibilityUserState userState = getUserStateLocked(UserHandle.USER_SYSTEM); |
| final Set<String> targetsFromSetting = new ArraySet<>(); |
| readColonDelimitedSettingToSet(Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, |
| userState.mUserId, targetsFromSetting, str -> str); |
| final boolean targetsContainMagnification = targetsFromSetting.contains( |
| MAGNIFICATION_CONTROLLER_NAME); |
| if (targetsContainMagnification == displayMagnificationNavBarEnabled) { |
| return; |
| } |
| |
| if (displayMagnificationNavBarEnabled) { |
| targetsFromSetting.add(MAGNIFICATION_CONTROLLER_NAME); |
| } else { |
| targetsFromSetting.remove(MAGNIFICATION_CONTROLLER_NAME); |
| } |
| persistColonDelimitedSetToSettingLocked(Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, |
| userState.mUserId, targetsFromSetting, str -> str); |
| readAccessibilityButtonTargetsLocked(userState); |
| onUserStateChangedLocked(userState); |
| } |
| |
| @Override |
| public long addClient(IAccessibilityManagerClient callback, 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. |
| AccessibilityUserState userState = getUserStateLocked(resolvedUserId); |
| Client client = new Client(callback, Binder.getCallingUid(), userState); |
| if (mSecurityPolicy.isCallerInteractingAcrossUsers(userId)) { |
| mGlobalClients.register(callback, client); |
| if (DEBUG) { |
| Slog.i(LOG_TAG, "Added global client for pid:" + Binder.getCallingPid()); |
| } |
| return IntPair.of( |
| getClientStateLocked(userState), |
| client.mLastSentRelevantEventTypes); |
| } else { |
| userState.mUserClients.register(callback, 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) ? getClientStateLocked(userState) : 0, |
| client.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 = mA11yWindowManager.getPictureInPictureWindowLocked(); |
| if (pip != null) { |
| int pipId = pip.getId(); |
| event.setWindowId(pipId); |
| } |
| } |
| |
| // 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); |
| |
| // Make sure the reported package is one the caller has access to. |
| event.setPackageName(mSecurityPolicy.resolveValidReportedPackageLocked( |
| event.getPackageName(), UserHandle.getCallingAppId(), resolvedUserId, |
| getCallingPid())); |
| |
| // This method does nothing for a background user. |
| if (resolvedUserId == mCurrentUserId) { |
| if (mSecurityPolicy.canDispatchAccessibilityEventLocked(mCurrentUserId, event)) { |
| mA11yWindowManager.updateActiveAndAccessibilityFocusedWindowLocked( |
| mCurrentUserId, event.getWindowId(), event.getSourceNodeId(), |
| event.getEventType(), event.getAction()); |
| mSecurityPolicy.updateEventSourceLocked(event); |
| dispatchEvent = true; |
| } |
| if (mHasInputFilter && mInputFilter != null) { |
| mMainHandler.sendMessage(obtainMessage( |
| AccessibilityManagerService::sendAccessibilityEventToInputFilter, |
| this, AccessibilityEvent.obtain(event))); |
| } |
| } |
| } |
| |
| 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. |
| boolean shouldComputeWindows = false; |
| int displayId = Display.INVALID_DISPLAY; |
| synchronized (mLock) { |
| final int windowId = event.getWindowId(); |
| if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED |
| && windowId != AccessibilityWindowInfo.UNDEFINED_WINDOW_ID) { |
| displayId = mA11yWindowManager.getDisplayIdByUserIdAndWindowIdLocked( |
| mCurrentUserId, windowId); |
| } |
| if (displayId != Display.INVALID_DISPLAY |
| && mA11yWindowManager.isTrackingWindowsLocked(displayId)) { |
| shouldComputeWindows = true; |
| } |
| } |
| if (shouldComputeWindows) { |
| final WindowManagerInternal wm = LocalServices.getService( |
| WindowManagerInternal.class); |
| wm.computeWindowsForAccessibility(displayId); |
| } |
| synchronized (mLock) { |
| notifyAccessibilityServicesDelayedLocked(event, false); |
| notifyAccessibilityServicesDelayedLocked(event, true); |
| mUiAutomationManager.sendAccessibilityEventLocked(event); |
| } |
| } |
| |
| if (OWN_PROCESS_ID != Binder.getCallingPid()) { |
| event.recycle(); |
| } |
| } |
| |
| private void sendAccessibilityEventToInputFilter(AccessibilityEvent event) { |
| synchronized (mLock) { |
| if (mHasInputFilter && mInputFilter != null) { |
| mInputFilter.notifyAccessibilityEvent(event); |
| } |
| } |
| event.recycle(); |
| } |
| |
| /** |
| * This is the implementation of AccessibilityManager system API. |
| * System UI calls into this method through AccessibilityManager system API to register a |
| * system action. |
| */ |
| @Override |
| public void registerSystemAction(RemoteAction action, int actionId) { |
| mSecurityPolicy.enforceCallerIsRecentsOrHasPermission( |
| Manifest.permission.MANAGE_ACCESSIBILITY, |
| FUNCTION_REGISTER_SYSTEM_ACTION); |
| getSystemActionPerformer().registerSystemAction(actionId, action); |
| } |
| |
| /** |
| * This is the implementation of AccessibilityManager system API. |
| * System UI calls into this method through AccessibilityManager system API to unregister a |
| * system action. |
| */ |
| @Override |
| public void unregisterSystemAction(int actionId) { |
| mSecurityPolicy.enforceCallerIsRecentsOrHasPermission( |
| Manifest.permission.MANAGE_ACCESSIBILITY, |
| FUNCTION_UNREGISTER_SYSTEM_ACTION); |
| getSystemActionPerformer().unregisterSystemAction(actionId); |
| } |
| |
| private SystemActionPerformer getSystemActionPerformer() { |
| if (mSystemActionPerformer == null) { |
| mSystemActionPerformer = |
| new SystemActionPerformer(mContext, mWindowManagerService, null, this); |
| } |
| return mSystemActionPerformer; |
| } |
| |
| @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); |
| return getUserStateLocked(resolvedUserId).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 AccessibilityUserState userState = getUserStateLocked(resolvedUserId); |
| if (mUiAutomationManager.suppressingAccessibilityServicesLocked()) { |
| return Collections.emptyList(); |
| } |
| |
| final List<AccessibilityServiceConnection> services = userState.mBoundServices; |
| final int serviceCount = services.size(); |
| final List<AccessibilityServiceInfo> result = new ArrayList<>(serviceCount); |
| for (int i = 0; i < serviceCount; ++i) { |
| final AccessibilityServiceConnection service = services.get(i); |
| if ((service.mFeedbackType & feedbackType) != 0) { |
| result.add(service.getServiceInfo()); |
| } |
| } |
| 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<AccessibilityServiceConnection> services = |
| getUserStateLocked(resolvedUserId).mBoundServices; |
| int numServices = services.size(); |
| interfacesToInterrupt = new ArrayList<>(numServices); |
| for (int i = 0; i < numServices; i++) { |
| AccessibilityServiceConnection 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, IBinder leashToken, |
| IAccessibilityInteractionConnection connection, String packageName, |
| int userId) throws RemoteException { |
| return mA11yWindowManager.addAccessibilityInteractionConnection( |
| windowToken, leashToken, connection, packageName, userId); |
| } |
| |
| @Override |
| public void removeAccessibilityInteractionConnection(IWindow window) { |
| mA11yWindowManager.removeAccessibilityInteractionConnection(window); |
| } |
| |
| @Override |
| public void setPictureInPictureActionReplacingConnection( |
| IAccessibilityInteractionConnection connection) throws RemoteException { |
| mSecurityPolicy.enforceCallingPermission(Manifest.permission.MODIFY_ACCESSIBILITY_DATA, |
| SET_PIP_ACTION_REPLACEMENT); |
| mA11yWindowManager.setPictureInPictureActionReplacingConnection(connection); |
| } |
| |
| @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); |
| |
| synchronized (mLock) { |
| mUiAutomationManager.registerUiTestAutomationServiceLocked(owner, serviceClient, |
| mContext, accessibilityServiceInfo, sIdCounter++, mMainHandler, |
| mSecurityPolicy, this, mWindowManagerService, getSystemActionPerformer(), |
| mA11yWindowManager, flags); |
| onUserStateChangedLocked(getCurrentUserStateLocked()); |
| } |
| } |
| |
| @Override |
| public void unregisterUiTestAutomationService(IAccessibilityServiceClient serviceClient) { |
| synchronized (mLock) { |
| mUiAutomationManager.unregisterUiTestAutomationServiceLocked(serviceClient); |
| } |
| } |
| |
| @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. |
| AccessibilityUserState userState = getCurrentUserStateLocked(); |
| |
| userState.setTouchExplorationEnabledLocked(touchExplorationEnabled); |
| userState.setDisplayMagnificationEnabledLocked(false); |
| userState.disableShortcutMagnificationLocked(); |
| userState.setAutoclickEnabledLocked(false); |
| userState.mEnabledServices.clear(); |
| userState.mEnabledServices.add(service); |
| userState.getBindingServicesLocked().clear(); |
| userState.getCrashedServicesLocked().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 (mA11yWindowManager.findA11yWindowInfoByIdLocked(windowId) == null) { |
| return null; |
| } |
| return mA11yWindowManager.getWindowTokenForUserAndWindowIdLocked(userId, windowId); |
| } |
| } |
| |
| /** |
| * Invoked remotely over AIDL by SysUi when the accessibility button within the system's |
| * navigation area has been clicked. |
| * |
| * @param displayId The logical display id. |
| * @param targetName The flattened {@link ComponentName} string or the class name of a system |
| * class implementing a supported accessibility feature, or {@code null} if there's no |
| * specified target. |
| */ |
| @Override |
| public void notifyAccessibilityButtonClicked(int displayId, String targetName) { |
| 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); |
| } |
| if (targetName == null) { |
| synchronized (mLock) { |
| final AccessibilityUserState userState = getCurrentUserStateLocked(); |
| targetName = userState.getTargetAssignedToAccessibilityButton(); |
| } |
| } |
| mMainHandler.sendMessage(obtainMessage( |
| AccessibilityManagerService::performAccessibilityShortcutInternal, this, |
| displayId, ACCESSIBILITY_BUTTON, targetName)); |
| } |
| |
| /** |
| * 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) { |
| mSecurityPolicy.enforceCallingOrSelfPermission( |
| android.Manifest.permission.STATUS_BAR_SERVICE); |
| synchronized (mLock) { |
| notifyAccessibilityButtonVisibilityChangedLocked(shown); |
| } |
| } |
| |
| /** |
| * Called when a gesture is detected on a display. |
| * |
| * @param gestureEvent the detail of the gesture. |
| * @return true if the event is handled. |
| */ |
| public boolean onGesture(AccessibilityGestureEvent gestureEvent) { |
| synchronized (mLock) { |
| boolean handled = notifyGestureLocked(gestureEvent, false); |
| if (!handled) { |
| handled = notifyGestureLocked(gestureEvent, true); |
| } |
| return handled; |
| } |
| } |
| |
| /** |
| * Called when the system action list is changed. |
| */ |
| @Override |
| public void onSystemActionsChanged() { |
| synchronized (mLock) { |
| AccessibilityUserState state = getCurrentUserStateLocked(); |
| notifySystemActionsChangedLocked(state); |
| } |
| } |
| |
| @VisibleForTesting |
| void notifySystemActionsChangedLocked(AccessibilityUserState userState) { |
| for (int i = userState.mBoundServices.size() - 1; i >= 0; i--) { |
| AccessibilityServiceConnection service = userState.mBoundServices.get(i); |
| service.notifySystemActionsChangedLocked(); |
| } |
| } |
| |
| @VisibleForTesting |
| public boolean notifyKeyEvent(KeyEvent event, int policyFlags) { |
| synchronized (mLock) { |
| List<AccessibilityServiceConnection> 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 displayId The logical display id. |
| * @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(int displayId, @NonNull Region region, |
| float scale, float centerX, float centerY) { |
| synchronized (mLock) { |
| notifyClearAccessibilityCacheLocked(); |
| notifyMagnificationChangedLocked(displayId, 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 motionEventInjectors The array of motionEventInjectors. May be null. |
| * |
| */ |
| void setMotionEventInjectors(SparseArray<MotionEventInjector> motionEventInjectors) { |
| synchronized (mLock) { |
| mMotionEventInjectors = motionEventInjectors; |
| // We may be waiting on this object being set |
| mLock.notifyAll(); |
| } |
| } |
| |
| @Override |
| public @Nullable MotionEventInjector getMotionEventInjectorForDisplayLocked(int displayId) { |
| final long endMillis = SystemClock.uptimeMillis() + WAIT_MOTION_INJECTOR_TIMEOUT_MILLIS; |
| MotionEventInjector motionEventInjector = null; |
| while ((mMotionEventInjectors == null) && (SystemClock.uptimeMillis() < endMillis)) { |
| try { |
| mLock.wait(endMillis - SystemClock.uptimeMillis()); |
| } catch (InterruptedException ie) { |
| /* ignore */ |
| } |
| } |
| if (mMotionEventInjectors == null) { |
| Slog.e(LOG_TAG, "MotionEventInjector installation timed out"); |
| } else { |
| motionEventInjector = mMotionEventInjectors.get(displayId); |
| } |
| return motionEventInjector; |
| } |
| |
| /** |
| * 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. |
| public 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 action 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); |
| } |
| |
| /** |
| * Returns true if accessibility focus is confined to the active window. |
| */ |
| public boolean accessibilityFocusOnlyInActiveWindow() { |
| synchronized (mLock) { |
| return mA11yWindowManager.isTrackingWindowsLocked(); |
| } |
| } |
| |
| /** |
| * 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 = getWindowToken(windowId, mCurrentUserId); |
| } |
| mWindowManagerService.getWindowFrame(token, outBounds); |
| if (!outBounds.isEmpty()) { |
| return true; |
| } |
| return false; |
| } |
| |
| public int getActiveWindowId() { |
| return mA11yWindowManager.getActiveWindowId(mCurrentUserId); |
| } |
| |
| public void onTouchInteractionStart() { |
| mA11yWindowManager.onTouchInteractionStart(); |
| } |
| |
| public void onTouchInteractionEnd() { |
| mA11yWindowManager.onTouchInteractionEnd(); |
| } |
| |
| private void switchUser(int userId) { |
| synchronized (mLock) { |
| if (mCurrentUserId == userId && mInitialized) { |
| return; |
| } |
| |
| // Disconnect from services for the old user. |
| AccessibilityUserState oldUserState = getCurrentUserStateLocked(); |
| oldUserState.onSwitchToAnotherUserLocked(); |
| |
| // Disable the local managers for the old user. |
| if (oldUserState.mUserClients.getRegisteredCallbackCount() > 0) { |
| mMainHandler.sendMessage(obtainMessage( |
| AccessibilityManagerService::sendStateToClients, |
| this, 0, oldUserState.mUserId)); |
| } |
| |
| // 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; |
| |
| AccessibilityUserState userState = getCurrentUserStateLocked(); |
| |
| 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); |
| migrateAccessibilityButtonSettingsIfNecessaryLocked(userState, null); |
| |
| if (announceNewUser) { |
| // Schedule announcement of the current user if needed. |
| mMainHandler.sendMessageDelayed( |
| obtainMessage(AccessibilityManagerService::announceNewUserIfNeeded, this), |
| WAIT_FOR_USER_STATE_FULLY_INITIALIZED_MILLIS); |
| } |
| } |
| } |
| |
| private void announceNewUserIfNeeded() { |
| synchronized (mLock) { |
| AccessibilityUserState userState = getCurrentUserStateLocked(); |
| if (userState.isHandlingAccessibilityEventsLocked()) { |
| 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); |
| sendAccessibilityEventLocked(event, mCurrentUserId); |
| } |
| } |
| } |
| |
| private void unlockUser(int userId) { |
| synchronized (mLock) { |
| int parentUserId = mSecurityPolicy.resolveProfileParentLocked(userId); |
| if (parentUserId == mCurrentUserId) { |
| AccessibilityUserState 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); |
| |
| AccessibilityUserState 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); |
| migrateAccessibilityButtonSettingsIfNecessaryLocked(userState, null); |
| } |
| |
| private int getClientStateLocked(AccessibilityUserState userState) { |
| return userState.getClientStateLocked(mUiAutomationManager.isUiAutomationRunningLocked()); |
| } |
| |
| private InteractionBridge getInteractionBridge() { |
| synchronized (mLock) { |
| if (mInteractionBridge == null) { |
| mInteractionBridge = new InteractionBridge(); |
| } |
| return mInteractionBridge; |
| } |
| } |
| |
| private boolean notifyGestureLocked(AccessibilityGestureEvent gestureEvent, 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. |
| AccessibilityUserState state = getCurrentUserStateLocked(); |
| for (int i = state.mBoundServices.size() - 1; i >= 0; i--) { |
| AccessibilityServiceConnection service = state.mBoundServices.get(i); |
| if (service.mRequestTouchExplorationMode && service.mIsDefault == isDefault) { |
| service.notifyGesture(gestureEvent); |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| private void notifyClearAccessibilityCacheLocked() { |
| AccessibilityUserState state = getCurrentUserStateLocked(); |
| for (int i = state.mBoundServices.size() - 1; i >= 0; i--) { |
| AccessibilityServiceConnection service = state.mBoundServices.get(i); |
| service.notifyClearAccessibilityNodeInfoCache(); |
| } |
| } |
| |
| private void notifyMagnificationChangedLocked(int displayId, @NonNull Region region, |
| float scale, float centerX, float centerY) { |
| final AccessibilityUserState state = getCurrentUserStateLocked(); |
| for (int i = state.mBoundServices.size() - 1; i >= 0; i--) { |
| final AccessibilityServiceConnection service = state.mBoundServices.get(i); |
| service.notifyMagnificationChangedLocked(displayId, region, scale, centerX, centerY); |
| } |
| } |
| |
| private void sendAccessibilityButtonToInputFilter(int displayId) { |
| synchronized (mLock) { |
| if (mHasInputFilter && mInputFilter != null) { |
| mInputFilter.notifyAccessibilityButtonClicked(displayId); |
| } |
| } |
| } |
| |
| private void showAccessibilityTargetsSelection(int displayId, |
| @ShortcutType int shortcutType) { |
| final Intent intent = new Intent(AccessibilityManager.ACTION_CHOOSE_ACCESSIBILITY_BUTTON); |
| final String chooserClassName = (shortcutType == ACCESSIBILITY_SHORTCUT_KEY) |
| ? AccessibilityShortcutChooserActivity.class.getName() |
| : AccessibilityButtonChooserActivity.class.getName(); |
| intent.setClassName(CHOOSER_PACKAGE_NAME, chooserClassName); |
| intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); |
| final Bundle bundle = ActivityOptions.makeBasic().setLaunchDisplayId(displayId).toBundle(); |
| mContext.startActivityAsUser(intent, bundle, UserHandle.of(mCurrentUserId)); |
| } |
| |
| private void launchShortcutTargetActivity(int displayId, ComponentName name) { |
| final Intent intent = new Intent(); |
| final Bundle bundle = ActivityOptions.makeBasic().setLaunchDisplayId(displayId).toBundle(); |
| intent.setComponent(name); |
| intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); |
| try { |
| mContext.startActivityAsUser(intent, bundle, UserHandle.of(mCurrentUserId)); |
| } catch (ActivityNotFoundException ignore) { |
| // ignore the exception |
| } |
| } |
| |
| private void notifyAccessibilityButtonVisibilityChangedLocked(boolean available) { |
| final AccessibilityUserState state = getCurrentUserStateLocked(); |
| mIsAccessibilityButtonShown = available; |
| for (int i = state.mBoundServices.size() - 1; i >= 0; i--) { |
| final AccessibilityServiceConnection clientConnection = state.mBoundServices.get(i); |
| if (clientConnection.mRequestAccessibilityButton) { |
| clientConnection.notifyAccessibilityButtonAvailabilityChangedLocked( |
| clientConnection.isAccessibilityButtonAvailableLocked(state)); |
| } |
| } |
| } |
| |
| private boolean readInstalledAccessibilityServiceLocked(AccessibilityUserState userState) { |
| mTempAccessibilityServiceInfoList.clear(); |
| |
| int flags = PackageManager.GET_SERVICES |
| | PackageManager.GET_META_DATA |
| | PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS |
| | PackageManager.MATCH_DIRECT_BOOT_AWARE |
| | PackageManager.MATCH_DIRECT_BOOT_UNAWARE; |
| |
| if (userState.getBindInstantServiceAllowedLocked()) { |
| flags |= PackageManager.MATCH_INSTANT; |
| } |
| |
| List<ResolveInfo> installedServices = mPackageManager.queryIntentServicesAsUser( |
| new Intent(AccessibilityService.SERVICE_INTERFACE), flags, mCurrentUserId); |
| |
| for (int i = 0, count = installedServices.size(); i < count; i++) { |
| ResolveInfo resolveInfo = installedServices.get(i); |
| ServiceInfo serviceInfo = resolveInfo.serviceInfo; |
| |
| if (!mSecurityPolicy.canRegisterService(serviceInfo)) { |
| continue; |
| } |
| |
| AccessibilityServiceInfo accessibilityServiceInfo; |
| try { |
| accessibilityServiceInfo = new AccessibilityServiceInfo(resolveInfo, mContext); |
| if (userState.mCrashedServices.contains(serviceInfo.getComponentName())) { |
| // Restore the crashed attribute. |
| accessibilityServiceInfo.crashed = true; |
| } |
| 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 readInstalledAccessibilityShortcutLocked(AccessibilityUserState userState) { |
| final List<AccessibilityShortcutInfo> shortcutInfos = AccessibilityManager |
| .getInstance(mContext).getInstalledAccessibilityShortcutListAsUser( |
| mContext, mCurrentUserId); |
| if (!shortcutInfos.equals(userState.mInstalledShortcuts)) { |
| userState.mInstalledShortcuts.clear(); |
| userState.mInstalledShortcuts.addAll(shortcutInfos); |
| return true; |
| } |
| return false; |
| } |
| |
| private boolean readEnabledAccessibilityServicesLocked(AccessibilityUserState userState) { |
| mTempComponentNameSet.clear(); |
| readComponentNamesFromSettingLocked(Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, |
| userState.mUserId, mTempComponentNameSet); |
| if (!mTempComponentNameSet.equals(userState.mEnabledServices)) { |
| userState.mEnabledServices.clear(); |
| userState.mEnabledServices.addAll(mTempComponentNameSet); |
| mTempComponentNameSet.clear(); |
| return true; |
| } |
| mTempComponentNameSet.clear(); |
| return false; |
| } |
| |
| private boolean readTouchExplorationGrantedAccessibilityServicesLocked( |
| AccessibilityUserState 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 { |
| AccessibilityUserState state = getCurrentUserStateLocked(); |
| for (int i = 0, count = state.mBoundServices.size(); i < count; i++) { |
| AccessibilityServiceConnection service = state.mBoundServices.get(i); |
| |
| if (service.mIsDefault == isDefault) { |
| service.notifyAccessibilityEvent(event); |
| } |
| } |
| } 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 updateRelevantEventsLocked(AccessibilityUserState userState) { |
| mMainHandler.post(() -> { |
| broadcastToClients(userState, ignoreRemoteException(client -> { |
| int relevantEventTypes; |
| boolean changed = false; |
| synchronized (mLock) { |
| relevantEventTypes = computeRelevantEventTypesLocked(userState, client); |
| |
| if (client.mLastSentRelevantEventTypes != relevantEventTypes) { |
| client.mLastSentRelevantEventTypes = relevantEventTypes; |
| changed = true; |
| } |
| } |
| if (changed) { |
| client.mCallback.setRelevantEventTypes(relevantEventTypes); |
| } |
| })); |
| }); |
| } |
| |
| private int computeRelevantEventTypesLocked(AccessibilityUserState userState, Client client) { |
| int relevantEventTypes = 0; |
| |
| int serviceCount = userState.mBoundServices.size(); |
| for (int i = 0; i < serviceCount; i++) { |
| AccessibilityServiceConnection service = userState.mBoundServices.get(i); |
| relevantEventTypes |= isClientInPackageWhitelist(service.getServiceInfo(), client) |
| ? service.getRelevantEventTypes() |
| : 0; |
| } |
| |
| relevantEventTypes |= isClientInPackageWhitelist( |
| mUiAutomationManager.getServiceInfo(), client) |
| ? mUiAutomationManager.getRelevantEventTypes() |
| : 0; |
| return relevantEventTypes; |
| } |
| |
| private static boolean isClientInPackageWhitelist( |
| @Nullable AccessibilityServiceInfo serviceInfo, Client client) { |
| if (serviceInfo == null) return false; |
| |
| String[] clientPackages = client.mPackageNames; |
| boolean result = ArrayUtils.isEmpty(serviceInfo.packageNames); |
| if (!result && clientPackages != null) { |
| for (String packageName : clientPackages) { |
| if (ArrayUtils.contains(serviceInfo.packageNames, packageName)) { |
| result = true; |
| break; |
| } |
| } |
| } |
| if (!result) { |
| if (DEBUG) { |
| Slog.d(LOG_TAG, "Dropping events: " |
| + Arrays.toString(clientPackages) + " -> " |
| + serviceInfo.getComponentName().flattenToShortString() |
| + " due to not being in package whitelist " |
| + Arrays.toString(serviceInfo.packageNames)); |
| } |
| } |
| |
| return result; |
| } |
| |
| private void broadcastToClients( |
| AccessibilityUserState userState, Consumer<Client> clientAction) { |
| mGlobalClients.broadcastForEachCookie(clientAction); |
| userState.mUserClients.broadcastForEachCookie(clientAction); |
| } |
| |
| /** |
| * 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) { |
| readColonDelimitedSettingToSet(settingName, userId, outComponentNames, |
| str -> ComponentName.unflattenFromString(str)); |
| } |
| |
| /** |
| * 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) { |
| readColonDelimitedStringToSet(names, outComponentNames, doMerge, |
| str -> ComponentName.unflattenFromString(str)); |
| } |
| |
| @Override |
| public void persistComponentNamesToSettingLocked(String settingName, |
| Set<ComponentName> componentNames, int userId) { |
| persistColonDelimitedSetToSettingLocked(settingName, userId, componentNames, |
| componentName -> componentName.flattenToShortString()); |
| } |
| |
| private <T> void readColonDelimitedSettingToSet(String settingName, int userId, Set<T> outSet, |
| Function<String, T> toItem) { |
| final String settingValue = Settings.Secure.getStringForUser(mContext.getContentResolver(), |
| settingName, userId); |
| readColonDelimitedStringToSet(settingValue, outSet, false, toItem); |
| } |
| |
| private <T> void readColonDelimitedStringToSet(String names, Set<T> outSet, boolean doMerge, |
| Function<String, T> toItem) { |
| if (!doMerge) { |
| outSet.clear(); |
| } |
| if (!TextUtils.isEmpty(names)) { |
| final TextUtils.SimpleStringSplitter splitter = mStringColonSplitter; |
| splitter.setString(names); |
| while (splitter.hasNext()) { |
| final String str = splitter.next(); |
| if (TextUtils.isEmpty(str)) { |
| continue; |
| } |
| final T item = toItem.apply(str); |
| if (item != null) { |
| outSet.add(item); |
| } |
| } |
| } |
| } |
| |
| private <T> void persistColonDelimitedSetToSettingLocked(String settingName, int userId, |
| Set<T> set, Function<T, String> toString) { |
| final StringBuilder builder = new StringBuilder(); |
| for (T item : set) { |
| final String str = (item != null ? toString.apply(item) : null); |
| if (TextUtils.isEmpty(str)) { |
| continue; |
| } |
| if (builder.length() > 0) { |
| builder.append(COMPONENT_NAME_SEPARATOR); |
| } |
| builder.append(str); |
| } |
| final long identity = Binder.clearCallingIdentity(); |
| try { |
| final String settingValue = builder.toString(); |
| Settings.Secure.putStringForUser(mContext.getContentResolver(), |
| settingName, TextUtils.isEmpty(settingValue) ? null : settingValue, userId); |
| } finally { |
| Binder.restoreCallingIdentity(identity); |
| } |
| } |
| |
| private void updateServicesLocked(AccessibilityUserState userState) { |
| Map<ComponentName, AccessibilityServiceConnection> 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()); |
| |
| AccessibilityServiceConnection 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; |
| } |
| |
| // Skip the component since it may be in process or crashed. |
| if (userState.getBindingServicesLocked().contains(componentName) |
| || userState.getCrashedServicesLocked().contains(componentName)) { |
| continue; |
| } |
| if (userState.mEnabledServices.contains(componentName) |
| && !mUiAutomationManager.suppressingAccessibilityServicesLocked()) { |
| if (service == null) { |
| service = new AccessibilityServiceConnection(userState, mContext, componentName, |
| installedService, sIdCounter++, mMainHandler, mLock, mSecurityPolicy, |
| this, mWindowManagerService, getSystemActionPerformer(), |
| mA11yWindowManager, mActivityTaskManagerService); |
| } else if (userState.mBoundServices.contains(service)) { |
| continue; |
| } |
| service.bindLocked(); |
| } else { |
| if (service != null) { |
| service.unbindLocked(); |
| removeShortcutTargetForUnboundServiceLocked(userState, service); |
| } |
| } |
| } |
| |
| 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); |
| } |
| updateAccessibilityEnabledSettingLocked(userState); |
| } |
| |
| private void scheduleUpdateClientsIfNeededLocked(AccessibilityUserState userState) { |
| final int clientState = getClientStateLocked(userState); |
| if (userState.getLastSentClientStateLocked() != clientState |
| && (mGlobalClients.getRegisteredCallbackCount() > 0 |
| || userState.mUserClients.getRegisteredCallbackCount() > 0)) { |
| userState.setLastSentClientStateLocked(clientState); |
| mMainHandler.sendMessage(obtainMessage( |
| AccessibilityManagerService::sendStateToAllClients, |
| this, clientState, userState.mUserId)); |
| } |
| } |
| |
| private void sendStateToAllClients(int clientState, int userId) { |
| sendStateToClients(clientState, mGlobalClients); |
| sendStateToClients(clientState, userId); |
| } |
| |
| private void sendStateToClients(int clientState, int userId) { |
| sendStateToClients(clientState, getUserState(userId).mUserClients); |
| } |
| |
| private void sendStateToClients(int clientState, |
| RemoteCallbackList<IAccessibilityManagerClient> clients) { |
| clients.broadcast(ignoreRemoteException( |
| client -> client.setState(clientState))); |
| } |
| |
| private void scheduleNotifyClientsOfServicesStateChangeLocked( |
| AccessibilityUserState userState) { |
| updateRecommendedUiTimeoutLocked(userState); |
| mMainHandler.sendMessage(obtainMessage( |
| AccessibilityManagerService::sendServicesStateChanged, |
| this, userState.mUserClients, getRecommendedTimeoutMillisLocked(userState))); |
| } |
| |
| private void sendServicesStateChanged( |
| RemoteCallbackList<IAccessibilityManagerClient> userClients, long uiTimeout) { |
| notifyClientsOfServicesStateChange(mGlobalClients, uiTimeout); |
| notifyClientsOfServicesStateChange(userClients, uiTimeout); |
| } |
| |
| private void notifyClientsOfServicesStateChange( |
| RemoteCallbackList<IAccessibilityManagerClient> clients, long uiTimeout) { |
| clients.broadcast(ignoreRemoteException( |
| client -> client.notifyServicesStateChanged(uiTimeout))); |
| } |
| |
| private void scheduleUpdateInputFilter(AccessibilityUserState userState) { |
| mMainHandler.sendMessage(obtainMessage( |
| AccessibilityManagerService::updateInputFilter, this, userState)); |
| } |
| |
| private void scheduleUpdateFingerprintGestureHandling(AccessibilityUserState userState) { |
| mMainHandler.sendMessage(obtainMessage( |
| AccessibilityManagerService::updateFingerprintGestureHandling, |
| this, userState)); |
| } |
| |
| private void updateInputFilter(AccessibilityUserState userState) { |
| if (mUiAutomationManager.suppressingAccessibilityServicesLocked()) return; |
| |
| boolean setInputFilter = false; |
| AccessibilityInputFilter inputFilter = null; |
| synchronized (mLock) { |
| int flags = 0; |
| if (userState.isDisplayMagnificationEnabledLocked()) { |
| flags |= AccessibilityInputFilter.FLAG_FEATURE_SCREEN_MAGNIFIER; |
| } |
| if (userState.isShortcutMagnificationEnabledLocked()) { |
| 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.isHandlingAccessibilityEventsLocked() |
| && userState.isTouchExplorationEnabledLocked()) { |
| flags |= AccessibilityInputFilter.FLAG_FEATURE_TOUCH_EXPLORATION; |
| if (userState.isServiceHandlesDoubleTapEnabledLocked()) { |
| flags |= AccessibilityInputFilter.FLAG_SERVICE_HANDLES_DOUBLE_TAP; |
| } |
| if (userState.isMultiFingerGesturesEnabledLocked()) { |
| flags |= AccessibilityInputFilter.FLAG_REQUEST_MULTI_FINGER_GESTURES; |
| } |
| if (userState.isTwoFingerPassthroughEnabledLocked()) { |
| flags |= AccessibilityInputFilter.FLAG_REQUEST_2_FINGER_PASSTHROUGH; |
| } |
| } |
| if (userState.isFilterKeyEventsEnabledLocked()) { |
| flags |= AccessibilityInputFilter.FLAG_FEATURE_FILTER_KEY_EVENTS; |
| } |
| if (userState.isAutoclickEnabledLocked()) { |
| flags |= AccessibilityInputFilter.FLAG_FEATURE_AUTOCLICK; |
| } |
| if (userState.isPerformGesturesEnabledLocked()) { |
| 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 AccessibilityServiceConnection service) { |
| synchronized (mLock) { |
| String label = service.getServiceInfo().getResolveInfo() |
| .loadLabel(mContext.getPackageManager()).toString(); |
| |
| final AccessibilityUserState userState = getCurrentUserStateLocked(); |
| if (userState.isTouchExplorationEnabledLocked()) { |
| 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. |
| userState.mTouchExplorationGrantedServices.add(service.mComponentName); |
| persistComponentNamesToSettingLocked( |
| Settings.Secure.TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES, |
| userState.mTouchExplorationGrantedServices, userState.mUserId); |
| // Enable touch exploration. |
| userState.setTouchExplorationEnabledLocked(true); |
| final long identity = Binder.clearCallingIdentity(); |
| try { |
| Settings.Secure.putIntForUser(mContext.getContentResolver(), |
| Settings.Secure.TOUCH_EXPLORATION_ENABLED, 1, |
| userState.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.SYSTEM_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(AccessibilityUserState userState) { |
| // TODO: Remove this hack |
| mInitialized = true; |
| updateLegacyCapabilitiesLocked(userState); |
| updateServicesLocked(userState); |
| updateWindowsForAccessibilityCallbackLocked(userState); |
| updateFilterKeyEventsLocked(userState); |
| updateTouchExplorationLocked(userState); |
| updatePerformGesturesLocked(userState); |
| updateMagnificationLocked(userState); |
| scheduleUpdateFingerprintGestureHandling(userState); |
| scheduleUpdateInputFilter(userState); |
| updateRelevantEventsLocked(userState); |
| scheduleUpdateClientsIfNeededLocked(userState); |
| updateAccessibilityShortcutKeyTargetsLocked(userState); |
| updateAccessibilityButtonTargetsLocked(userState); |
| } |
| |
| private void updateWindowsForAccessibilityCallbackLocked(AccessibilityUserState 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. |
| |
| boolean observingWindows = mUiAutomationManager.canRetrieveInteractiveWindowsLocked(); |
| List<AccessibilityServiceConnection> boundServices = userState.mBoundServices; |
| final int boundServiceCount = boundServices.size(); |
| for (int i = 0; !observingWindows && (i < boundServiceCount); i++) { |
| AccessibilityServiceConnection boundService = boundServices.get(i); |
| if (boundService.canRetrieveInteractiveWindowsLocked()) { |
| userState.setAccessibilityFocusOnlyInActiveWindow(false); |
| observingWindows = true; |
| } |
| } |
| userState.setAccessibilityFocusOnlyInActiveWindow(true); |
| |
| // Gets all valid displays and start tracking windows of each display if there is at least |
| // one bound service that can retrieve window content. |
| final ArrayList<Display> displays = getValidDisplayList(); |
| for (int i = 0; i < displays.size(); i++) { |
| final Display display = displays.get(i); |
| if (display != null) { |
| if (observingWindows) { |
| mA11yWindowManager.startTrackingWindows(display.getDisplayId()); |
| } else { |
| mA11yWindowManager.stopTrackingWindows(display.getDisplayId()); |
| } |
| } |
| } |
| } |
| |
| private void updateLegacyCapabilitiesLocked(AccessibilityUserState 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(AccessibilityUserState userState) { |
| final int serviceCount = userState.mBoundServices.size(); |
| for (int i = 0; i < serviceCount; i++) { |
| AccessibilityServiceConnection service = userState.mBoundServices.get(i); |
| if ((service.getCapabilities() |
| & AccessibilityServiceInfo.CAPABILITY_CAN_PERFORM_GESTURES) != 0) { |
| userState.setPerformGesturesEnabledLocked(true); |
| return; |
| } |
| } |
| userState.setPerformGesturesEnabledLocked(false); |
| } |
| |
| private void updateFilterKeyEventsLocked(AccessibilityUserState userState) { |
| final int serviceCount = userState.mBoundServices.size(); |
| for (int i = 0; i < serviceCount; i++) { |
| AccessibilityServiceConnection service = userState.mBoundServices.get(i); |
| if (service.mRequestFilterKeyEvents |
| && (service.getCapabilities() |
| & AccessibilityServiceInfo |
| .CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS) != 0) { |
| userState.setFilterKeyEventsEnabledLocked(true); |
| return; |
| } |
| } |
| userState.setFilterKeyEventsEnabledLocked(false); |
| } |
| |
| private boolean readConfigurationForUserStateLocked(AccessibilityUserState userState) { |
| boolean somethingChanged = readInstalledAccessibilityServiceLocked(userState); |
| somethingChanged |= readInstalledAccessibilityShortcutLocked(userState); |
| somethingChanged |= readEnabledAccessibilityServicesLocked(userState); |
| somethingChanged |= readTouchExplorationGrantedAccessibilityServicesLocked(userState); |
| somethingChanged |= readTouchExplorationEnabledSettingLocked(userState); |
| somethingChanged |= readHighTextContrastEnabledSettingLocked(userState); |
| somethingChanged |= readMagnificationEnabledSettingsLocked(userState); |
| somethingChanged |= readAutoclickEnabledSettingLocked(userState); |
| somethingChanged |= readAccessibilityShortcutKeySettingLocked(userState); |
| somethingChanged |= readAccessibilityButtonTargetsLocked(userState); |
| somethingChanged |= readAccessibilityButtonTargetComponentLocked(userState); |
| somethingChanged |= readUserRecommendedUiTimeoutSettingsLocked(userState); |
| return somethingChanged; |
| } |
| |
| private void updateAccessibilityEnabledSettingLocked(AccessibilityUserState userState) { |
| final long identity = Binder.clearCallingIdentity(); |
| final boolean isA11yEnabled = mUiAutomationManager.isUiAutomationRunningLocked() |
| || userState.isHandlingAccessibilityEventsLocked(); |
| try { |
| Settings.Secure.putIntForUser(mContext.getContentResolver(), |
| Settings.Secure.ACCESSIBILITY_ENABLED, |
| (isA11yEnabled) ? 1 : 0, |
| userState.mUserId); |
| } finally { |
| Binder.restoreCallingIdentity(identity); |
| } |
| } |
| |
| private boolean readTouchExplorationEnabledSettingLocked(AccessibilityUserState userState) { |
| final boolean touchExplorationEnabled = Settings.Secure.getIntForUser( |
| mContext.getContentResolver(), |
| Settings.Secure.TOUCH_EXPLORATION_ENABLED, 0, userState.mUserId) == 1; |
| if (touchExplorationEnabled != userState.isTouchExplorationEnabledLocked()) { |
| userState.setTouchExplorationEnabledLocked(touchExplorationEnabled); |
| return true; |
| } |
| return false; |
| } |
| |
| private boolean readMagnificationEnabledSettingsLocked(AccessibilityUserState userState) { |
| final boolean displayMagnificationEnabled = Settings.Secure.getIntForUser( |
| mContext.getContentResolver(), |
| Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED, |
| 0, userState.mUserId) == 1; |
| if ((displayMagnificationEnabled != userState.isDisplayMagnificationEnabledLocked())) { |
| userState.setDisplayMagnificationEnabledLocked(displayMagnificationEnabled); |
| return true; |
| } |
| return false; |
| } |
| |
| private boolean readAutoclickEnabledSettingLocked(AccessibilityUserState userState) { |
| final boolean autoclickEnabled = Settings.Secure.getIntForUser( |
| mContext.getContentResolver(), |
| Settings.Secure.ACCESSIBILITY_AUTOCLICK_ENABLED, |
| 0, userState.mUserId) == 1; |
| if (autoclickEnabled != userState.isAutoclickEnabledLocked()) { |
| userState.setAutoclickEnabledLocked(autoclickEnabled); |
| return true; |
| } |
| return false; |
| } |
| |
| private boolean readHighTextContrastEnabledSettingLocked(AccessibilityUserState userState) { |
| final boolean highTextContrastEnabled = Settings.Secure.getIntForUser( |
| mContext.getContentResolver(), |
| Settings.Secure.ACCESSIBILITY_HIGH_TEXT_CONTRAST_ENABLED, 0, |
| userState.mUserId) == 1; |
| if (highTextContrastEnabled != userState.isTextHighContrastEnabledLocked()) { |
| userState.setTextHighContrastEnabledLocked(highTextContrastEnabled); |
| return true; |
| } |
| return false; |
| } |
| |
| private void updateTouchExplorationLocked(AccessibilityUserState userState) { |
| boolean touchExplorationEnabled = mUiAutomationManager.isTouchExplorationEnabledLocked(); |
| boolean serviceHandlesDoubleTapEnabled = false; |
| boolean requestMultiFingerGestures = false; |
| boolean requestTwoFingerPassthrough = false; |
| final int serviceCount = userState.mBoundServices.size(); |
| for (int i = 0; i < serviceCount; i++) { |
| AccessibilityServiceConnection service = userState.mBoundServices.get(i); |
| if (canRequestAndRequestsTouchExplorationLocked(service, userState)) { |
| touchExplorationEnabled = true; |
| serviceHandlesDoubleTapEnabled = service.isServiceHandlesDoubleTapEnabled(); |
| requestMultiFingerGestures = service.isMultiFingerGesturesEnabled(); |
| requestTwoFingerPassthrough = service.isTwoFingerPassthroughEnabled(); |
| break; |
| } |
| } |
| if (touchExplorationEnabled != userState.isTouchExplorationEnabledLocked()) { |
| userState.setTouchExplorationEnabledLocked(touchExplorationEnabled); |
| final long identity = Binder.clearCallingIdentity(); |
| try { |
| Settings.Secure.putIntForUser(mContext.getContentResolver(), |
| Settings.Secure.TOUCH_EXPLORATION_ENABLED, touchExplorationEnabled ? 1 : 0, |
| userState.mUserId); |
| } finally { |
| Binder.restoreCallingIdentity(identity); |
| } |
| } |
| userState.setServiceHandlesDoubleTapLocked(serviceHandlesDoubleTapEnabled); |
| userState.setMultiFingerGesturesLocked(requestMultiFingerGestures); |
| userState.setTwoFingerPassthroughLocked(requestTwoFingerPassthrough); |
| } |
| |
| private boolean readAccessibilityShortcutKeySettingLocked(AccessibilityUserState userState) { |
| final String settingValue = Settings.Secure.getStringForUser(mContext.getContentResolver(), |
| Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, userState.mUserId); |
| final Set<String> targetsFromSetting = new ArraySet<>(); |
| readColonDelimitedStringToSet(settingValue, targetsFromSetting, false, str -> str); |
| // Fall back to device's default a11y service, only when setting is never updated. |
| if (settingValue == null) { |
| final String defaultService = mContext.getString( |
| R.string.config_defaultAccessibilityService); |
| if (!TextUtils.isEmpty(defaultService)) { |
| targetsFromSetting.add(defaultService); |
| } |
| } |
| |
| final Set<String> currentTargets = |
| userState.getShortcutTargetsLocked(ACCESSIBILITY_SHORTCUT_KEY); |
| if (targetsFromSetting.equals(currentTargets)) { |
| return false; |
| } |
| currentTargets.clear(); |
| currentTargets.addAll(targetsFromSetting); |
| scheduleNotifyClientsOfServicesStateChangeLocked(userState); |
| return true; |
| } |
| |
| private boolean readAccessibilityButtonTargetsLocked(AccessibilityUserState userState) { |
| final Set<String> targetsFromSetting = new ArraySet<>(); |
| readColonDelimitedSettingToSet(Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, |
| userState.mUserId, targetsFromSetting, str -> str); |
| |
| final Set<String> currentTargets = |
| userState.getShortcutTargetsLocked(ACCESSIBILITY_BUTTON); |
| if (targetsFromSetting.equals(currentTargets)) { |
| return false; |
| } |
| currentTargets.clear(); |
| currentTargets.addAll(targetsFromSetting); |
| scheduleNotifyClientsOfServicesStateChangeLocked(userState); |
| return true; |
| } |
| |
| private boolean readAccessibilityButtonTargetComponentLocked(AccessibilityUserState userState) { |
| final String componentId = Settings.Secure.getStringForUser(mContext.getContentResolver(), |
| Settings.Secure.ACCESSIBILITY_BUTTON_TARGET_COMPONENT, userState.mUserId); |
| if (TextUtils.isEmpty(componentId)) { |
| if (userState.getTargetAssignedToAccessibilityButton() == null) { |
| return false; |
| } |
| userState.setTargetAssignedToAccessibilityButton(null); |
| return true; |
| } |
| if (componentId.equals(userState.getTargetAssignedToAccessibilityButton())) { |
| return false; |
| } |
| userState.setTargetAssignedToAccessibilityButton(componentId); |
| return true; |
| } |
| |
| private boolean readUserRecommendedUiTimeoutSettingsLocked(AccessibilityUserState userState) { |
| final int nonInteractiveUiTimeout = Settings.Secure.getIntForUser( |
| mContext.getContentResolver(), |
| Settings.Secure.ACCESSIBILITY_NON_INTERACTIVE_UI_TIMEOUT_MS, 0, |
| userState.mUserId); |
| final int interactiveUiTimeout = Settings.Secure.getIntForUser( |
| mContext.getContentResolver(), |
| Settings.Secure.ACCESSIBILITY_INTERACTIVE_UI_TIMEOUT_MS, 0, |
| userState.mUserId); |
| if (nonInteractiveUiTimeout != userState.getUserNonInteractiveUiTimeoutLocked() |
| || interactiveUiTimeout != userState.getUserInteractiveUiTimeoutLocked()) { |
| userState.setUserNonInteractiveUiTimeoutLocked(nonInteractiveUiTimeout); |
| userState.setUserInteractiveUiTimeoutLocked(interactiveUiTimeout); |
| scheduleNotifyClientsOfServicesStateChangeLocked(userState); |
| return true; |
| } |
| return false; |
| } |
| |
| /** |
| * Check if the target that will be enabled by the accessibility shortcut key is installed. |
| * If it isn't, remove it from the list and associated setting so a side loaded service can't |
| * spoof the package name of the default service. |
| */ |
| private void updateAccessibilityShortcutKeyTargetsLocked(AccessibilityUserState userState) { |
| final Set<String> currentTargets = |
| userState.getShortcutTargetsLocked(ACCESSIBILITY_SHORTCUT_KEY); |
| final int lastSize = currentTargets.size(); |
| if (lastSize == 0) { |
| return; |
| } |
| currentTargets.removeIf( |
| name -> !userState.isShortcutTargetInstalledLocked(name)); |
| if (lastSize == currentTargets.size()) { |
| return; |
| } |
| |
| // Update setting key with new value. |
| persistColonDelimitedSetToSettingLocked( |
| Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, |
| userState.mUserId, currentTargets, str -> str); |
| scheduleNotifyClientsOfServicesStateChangeLocked(userState); |
| } |
| |
| private boolean canRequestAndRequestsTouchExplorationLocked( |
| AccessibilityServiceConnection service, AccessibilityUserState userState) { |
| // Service not ready or cannot request the feature - well nothing to do. |
| if (!service.canReceiveEventsLocked() || !service.mRequestTouchExplorationMode) { |
| return false; |
| } |
| if (service.getServiceInfo().getResolveInfo().serviceInfo.applicationInfo.targetSdkVersion |
| <= Build.VERSION_CODES.JELLY_BEAN_MR1) { |
| // Up to JB-MR1 we had a white list with services that can enable touch |
| // exploration. When a service is first started we show a dialog to the |
| // use to get a permission to white list the service. |
| if (userState.mTouchExplorationGrantedServices.contains(service.mComponentName)) { |
| return true; |
| } else if (mEnableTouchExplorationDialog == null |
| || !mEnableTouchExplorationDialog.isShowing()) { |
| mMainHandler.sendMessage(obtainMessage( |
| AccessibilityManagerService::showEnableTouchExplorationDialog, |
| this, service)); |
| } |
| } 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.getCapabilities() |
| & AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION) != 0) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| private void updateMagnificationLocked(AccessibilityUserState userState) { |
| if (userState.mUserId != mCurrentUserId) { |
| return; |
| } |
| |
| if (mMagnificationController != null) { |
| mMagnificationController.setUserId(userState.mUserId); |
| } |
| |
| if (mUiAutomationManager.suppressingAccessibilityServicesLocked() |
| && mMagnificationController != null) { |
| mMagnificationController.unregisterAll(); |
| return; |
| } |
| |
| // Get all valid displays and register them if global magnification is enabled. |
| // We would skip overlay display because it uses overlay window to simulate secondary |
| // displays in one display. It's not a real display and there's no input events for it. |
| final ArrayList<Display> displays = getValidDisplayList(); |
| if (userState.isDisplayMagnificationEnabledLocked() |
| || userState.isShortcutMagnificationEnabledLocked()) { |
| for (int i = 0; i < displays.size(); i++) { |
| final Display display = displays.get(i); |
| getMagnificationController().register(display.getDisplayId()); |
| } |
| return; |
| } |
| |
| // Register if display has listening magnification services. |
| for (int i = 0; i < displays.size(); i++) { |
| final Display display = displays.get(i); |
| final int displayId = display.getDisplayId(); |
| if (userHasListeningMagnificationServicesLocked(userState, displayId)) { |
| getMagnificationController().register(displayId); |
| } else if (mMagnificationController != null) { |
| mMagnificationController.unregister(displayId); |
| } |
| } |
| } |
| |
| /** |
| * Returns whether the specified user has any services that are capable of |
| * controlling magnification. |
| */ |
| private boolean userHasMagnificationServicesLocked(AccessibilityUserState userState) { |
| final List<AccessibilityServiceConnection> services = userState.mBoundServices; |
| for (int i = 0, count = services.size(); i < count; i++) { |
| final AccessibilityServiceConnection 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(AccessibilityUserState userState, |
| int displayId) { |
| final List<AccessibilityServiceConnection> services = userState.mBoundServices; |
| for (int i = 0, count = services.size(); i < count; i++) { |
| final AccessibilityServiceConnection service = services.get(i); |
| if (mSecurityPolicy.canControlMagnification(service) |
| && service.isMagnificationCallbackEnabled(displayId)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| private void updateFingerprintGestureHandling(AccessibilityUserState userState) { |
| final List<AccessibilityServiceConnection> 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, mContext.getResources(), mLock); |
| break; |
| } |
| } |
| } |
| } |
| } |
| if (mFingerprintGestureDispatcher != null) { |
| mFingerprintGestureDispatcher.updateClientList(services); |
| } |
| } |
| |
| /** |
| * 1) Update accessibility button availability to accessibility services. |
| * 2) Check if the target that will be enabled by the accessibility button is installed. |
| * If it isn't, remove it from the list and associated setting so a side loaded service can't |
| * spoof the package name of the default service. |
| */ |
| private void updateAccessibilityButtonTargetsLocked(AccessibilityUserState userState) { |
| // Update accessibility button availability. |
| for (int i = userState.mBoundServices.size() - 1; i >= 0; i--) { |
| final AccessibilityServiceConnection service = userState.mBoundServices.get(i); |
| if (service.mRequestAccessibilityButton) { |
| service.notifyAccessibilityButtonAvailabilityChangedLocked( |
| service.isAccessibilityButtonAvailableLocked(userState)); |
| } |
| } |
| |
| final Set<String> currentTargets = |
| userState.getShortcutTargetsLocked(ACCESSIBILITY_BUTTON); |
| final int lastSize = currentTargets.size(); |
| if (lastSize == 0) { |
| return; |
| } |
| currentTargets.removeIf( |
| name -> !userState.isShortcutTargetInstalledLocked(name)); |
| if (lastSize == currentTargets.size()) { |
| return; |
| } |
| |
| // Update setting key with new value. |
| persistColonDelimitedSetToSettingLocked(Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, |
| userState.mUserId, currentTargets, str -> str); |
| scheduleNotifyClientsOfServicesStateChangeLocked(userState); |
| } |
| |
| /** |
| * 1) Check if the service assigned to accessibility button target sdk version > Q. |
| * If it isn't, remove it from the list and associated setting. |
| * (It happens when an accessibility service package is downgraded.) |
| * 2) For a service targeting sdk version > Q and requesting a11y button, it should be in the |
| * enabled list if's assigned to a11y button. |
| * (It happens when an accessibility service package is same graded, and updated requesting |
| * a11y button flag) |
| * 3) Check if an enabled service targeting sdk version > Q and requesting a11y button is |
| * assigned to a shortcut. If it isn't, assigns it to the accessibility button. |
| * (It happens when an enabled accessibility service package is upgraded.) |
| * |
| * @param packageName The package name to check, or {@code null} to check all services. |
| */ |
| private void migrateAccessibilityButtonSettingsIfNecessaryLocked( |
| AccessibilityUserState userState, @Nullable String packageName) { |
| final Set<String> buttonTargets = |
| userState.getShortcutTargetsLocked(ACCESSIBILITY_BUTTON); |
| int lastSize = buttonTargets.size(); |
| buttonTargets.removeIf(name -> { |
| if (packageName != null && name != null && !name.contains(packageName)) { |
| return false; |
| } |
| final ComponentName componentName = ComponentName.unflattenFromString(name); |
| if (componentName == null) { |
| return false; |
| } |
| final AccessibilityServiceInfo serviceInfo = |
| userState.getInstalledServiceInfoLocked(componentName); |
| if (serviceInfo == null) { |
| return false; |
| } |
| if (serviceInfo.getResolveInfo().serviceInfo.applicationInfo |
| .targetSdkVersion <= Build.VERSION_CODES.Q) { |
| // A11y services targeting sdk version <= Q should not be in the list. |
| Slog.v(LOG_TAG, "Legacy service " + componentName |
| + " should not in the button"); |
| return true; |
| } |
| final boolean requestA11yButton = (serviceInfo.flags |
| & AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0; |
| if (requestA11yButton && !userState.mEnabledServices.contains(componentName)) { |
| // An a11y service targeting sdk version > Q and request A11y button and is assigned |
| // to a11y btn should be in the enabled list. |
| Slog.v(LOG_TAG, "Service requesting a11y button and be assigned to the button" |
| + componentName + " should be enabled state"); |
| return true; |
| } |
| return false; |
| }); |
| boolean changed = (lastSize != buttonTargets.size()); |
| lastSize = buttonTargets.size(); |
| |
| final Set<String> shortcutKeyTargets = |
| userState.getShortcutTargetsLocked(ACCESSIBILITY_SHORTCUT_KEY); |
| userState.mEnabledServices.forEach(componentName -> { |
| if (packageName != null && componentName != null |
| && !packageName.equals(componentName.getPackageName())) { |
| return; |
| } |
| final AccessibilityServiceInfo serviceInfo = |
| userState.getInstalledServiceInfoLocked(componentName); |
| if (serviceInfo == null) { |
| return; |
| } |
| final boolean requestA11yButton = (serviceInfo.flags |
| & AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0; |
| if (!(serviceInfo.getResolveInfo().serviceInfo.applicationInfo |
| .targetSdkVersion > Build.VERSION_CODES.Q && requestA11yButton)) { |
| return; |
| } |
| final String serviceName = componentName.flattenToString(); |
| if (TextUtils.isEmpty(serviceName)) { |
| return; |
| } |
| if (doesShortcutTargetsStringContain(buttonTargets, serviceName) |
| || doesShortcutTargetsStringContain(shortcutKeyTargets, serviceName)) { |
| return; |
| } |
| // For enabled a11y services targeting sdk version > Q and requesting a11y button should |
| // be assigned to a shortcut. |
| Slog.v(LOG_TAG, "A enabled service requesting a11y button " + componentName |
| + " should be assign to the button or shortcut."); |
| buttonTargets.add(serviceName); |
| }); |
| changed |= (lastSize != buttonTargets.size()); |
| if (!changed) { |
| return; |
| } |
| |
| // Update setting key with new value. |
| persistColonDelimitedSetToSettingLocked(Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, |
| userState.mUserId, buttonTargets, str -> str); |
| scheduleNotifyClientsOfServicesStateChangeLocked(userState); |
| } |
| |
| /** |
| * Remove the shortcut target for the unbound service which is requesting accessibility button |
| * and targeting sdk > Q from the accessibility button and shortcut. |
| * |
| * @param userState The accessibility user state. |
| * @param service The unbound service. |
| */ |
| private void removeShortcutTargetForUnboundServiceLocked(AccessibilityUserState userState, |
| AccessibilityServiceConnection service) { |
| if (!service.mRequestAccessibilityButton |
| || service.getServiceInfo().getResolveInfo().serviceInfo.applicationInfo |
| .targetSdkVersion <= Build.VERSION_CODES.Q) { |
| return; |
| } |
| final ComponentName serviceName = service.getComponentName(); |
| if (userState.removeShortcutTargetLocked(ACCESSIBILITY_SHORTCUT_KEY, serviceName)) { |
| final Set<String> currentTargets = userState.getShortcutTargetsLocked( |
| ACCESSIBILITY_SHORTCUT_KEY); |
| persistColonDelimitedSetToSettingLocked( |
| Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, |
| userState.mUserId, currentTargets, str -> str); |
| } |
| if (userState.removeShortcutTargetLocked(ACCESSIBILITY_BUTTON, serviceName)) { |
| final Set<String> currentTargets = userState.getShortcutTargetsLocked( |
| ACCESSIBILITY_BUTTON); |
| persistColonDelimitedSetToSettingLocked(Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, |
| userState.mUserId, currentTargets, str -> str); |
| } |
| } |
| |
| private void updateRecommendedUiTimeoutLocked(AccessibilityUserState userState) { |
| int newNonInteractiveUiTimeout = userState.getUserNonInteractiveUiTimeoutLocked(); |
| int newInteractiveUiTimeout = userState.getUserInteractiveUiTimeoutLocked(); |
| // read from a11y services if user does not specify value |
| if (newNonInteractiveUiTimeout == 0 || newInteractiveUiTimeout == 0) { |
| int serviceNonInteractiveUiTimeout = 0; |
| int serviceInteractiveUiTimeout = 0; |
| final List<AccessibilityServiceConnection> services = userState.mBoundServices; |
| for (int i = 0; i < services.size(); i++) { |
| int timeout = services.get(i).getServiceInfo().getInteractiveUiTimeoutMillis(); |
| if (serviceInteractiveUiTimeout < timeout) { |
| serviceInteractiveUiTimeout = timeout; |
| } |
| timeout = services.get(i).getServiceInfo().getNonInteractiveUiTimeoutMillis(); |
| if (serviceNonInteractiveUiTimeout < timeout) { |
| serviceNonInteractiveUiTimeout = timeout; |
| } |
| } |
| if (newNonInteractiveUiTimeout == 0) { |
| newNonInteractiveUiTimeout = serviceNonInteractiveUiTimeout; |
| } |
| if (newInteractiveUiTimeout == 0) { |
| newInteractiveUiTimeout = serviceInteractiveUiTimeout; |
| } |
| } |
| userState.setNonInteractiveUiTimeoutLocked(newNonInteractiveUiTimeout); |
| userState.setInteractiveUiTimeoutLocked(newInteractiveUiTimeout); |
| } |
| |
| @GuardedBy("mLock") |
| @Override |
| public MagnificationSpec getCompatibleMagnificationSpecLocked(int windowId) { |
| IBinder windowToken = mA11yWindowManager.getWindowTokenForUserAndWindowIdLocked( |
| mCurrentUserId, windowId); |
| if (windowToken != null) { |
| return mWindowManagerService.getCompatibleMagnificationSpecForWindow( |
| windowToken); |
| } |
| return null; |
| } |
| |
| @Override |
| public KeyEventDispatcher getKeyEventDispatcher() { |
| if (mKeyEventDispatcher == null) { |
| mKeyEventDispatcher = new KeyEventDispatcher( |
| mMainHandler, MainHandler.MSG_SEND_KEY_EVENT_TO_INPUT_FILTER, mLock, |
| mPowerManager); |
| } |
| return mKeyEventDispatcher; |
| } |
| |
| @Override |
| public PendingIntent getPendingIntentActivity(Context context, int requestCode, Intent intent, |
| int flags) { |
| return PendingIntent.getActivity(context, requestCode, intent, flags); |
| } |
| |
| /** |
| * AIDL-exposed method to be called when the accessibility shortcut key is enabled. Requires |
| * permission to write secure settings, since someone with that permission can enable |
| * accessibility services themselves. |
| * |
| * @param targetName The flattened {@link ComponentName} string or the class name of a system |
| * class implementing a supported accessibility feature, or {@code null} if there's no |
| * specified target. |
| */ |
| @Override |
| public void performAccessibilityShortcut(String targetName) { |
| if ((UserHandle.getAppId(Binder.getCallingUid()) != Process.SYSTEM_UID) |
| && (mContext.checkCallingPermission(Manifest.permission.MANAGE_ACCESSIBILITY) |
| != PackageManager.PERMISSION_GRANTED)) { |
| throw new SecurityException( |
| "performAccessibilityShortcut requires the MANAGE_ACCESSIBILITY permission"); |
| } |
| mMainHandler.sendMessage(obtainMessage( |
| AccessibilityManagerService::performAccessibilityShortcutInternal, this, |
| Display.DEFAULT_DISPLAY, ACCESSIBILITY_SHORTCUT_KEY, targetName)); |
| } |
| |
| /** |
| * Perform the accessibility shortcut action. |
| * |
| * @param shortcutType The shortcut type. |
| * @param displayId The display id of the accessibility button. |
| * @param targetName The flattened {@link ComponentName} string or the class name of a system |
| * class implementing a supported accessibility feature, or {@code null} if there's no |
| * specified target. |
| */ |
| private void performAccessibilityShortcutInternal(int displayId, |
| @ShortcutType int shortcutType, @Nullable String targetName) { |
| final List<String> shortcutTargets = getAccessibilityShortcutTargetsInternal(shortcutType); |
| if (shortcutTargets.isEmpty()) { |
| Slog.d(LOG_TAG, "No target to perform shortcut, shortcutType=" + shortcutType); |
| return; |
| } |
| // In case the caller specified a target name |
| if (targetName != null && !doesShortcutTargetsStringContain(shortcutTargets, targetName)) { |
| Slog.v(LOG_TAG, "Perform shortcut failed, invalid target name:" + targetName); |
| targetName = null; |
| } |
| if (targetName == null) { |
| // In case there are many targets assigned to the given shortcut. |
| if (shortcutTargets.size() > 1) { |
| showAccessibilityTargetsSelection(displayId, shortcutType); |
| return; |
| } |
| targetName = shortcutTargets.get(0); |
| } |
| // In case user assigned magnification to the given shortcut. |
| if (targetName.equals(MAGNIFICATION_CONTROLLER_NAME)) { |
| final boolean enabled = !getMagnificationController().isMagnifying(displayId); |
| logAccessibilityShortcutActivated(MAGNIFICATION_COMPONENT_NAME, shortcutType, enabled); |
| sendAccessibilityButtonToInputFilter(displayId); |
| return; |
| } |
| final ComponentName targetComponentName = ComponentName.unflattenFromString(targetName); |
| if (targetComponentName == null) { |
| Slog.d(LOG_TAG, "Perform shortcut failed, invalid target name:" + targetName); |
| return; |
| } |
| // In case user assigned an accessibility framework feature to the given shortcut. |
| if (performAccessibilityFrameworkFeature(targetComponentName, shortcutType)) { |
| return; |
| } |
| // In case user assigned an accessibility shortcut target to the given shortcut. |
| if (performAccessibilityShortcutTargetActivity(displayId, targetComponentName)) { |
| logAccessibilityShortcutActivated(targetComponentName, shortcutType); |
| return; |
| } |
| // in case user assigned an accessibility service to the given shortcut. |
| if (performAccessibilityShortcutTargetService( |
| displayId, shortcutType, targetComponentName)) { |
| return; |
| } |
| } |
| |
| private boolean performAccessibilityFrameworkFeature(ComponentName assignedTarget, |
| @ShortcutType int shortcutType) { |
| final Map<ComponentName, ToggleableFrameworkFeatureInfo> frameworkFeatureMap = |
| AccessibilityShortcutController.getFrameworkShortcutFeaturesMap(); |
| if (!frameworkFeatureMap.containsKey(assignedTarget)) { |
| return false; |
| } |
| // Toggle the requested framework feature |
| final ToggleableFrameworkFeatureInfo featureInfo = frameworkFeatureMap.get(assignedTarget); |
| final SettingStringHelper setting = new SettingStringHelper(mContext.getContentResolver(), |
| featureInfo.getSettingKey(), mCurrentUserId); |
| // Assuming that the default state will be to have the feature off |
| if (!TextUtils.equals(featureInfo.getSettingOnValue(), setting.read())) { |
| logAccessibilityShortcutActivated(assignedTarget, shortcutType, /* serviceEnabled= */ |
| true); |
| setting.write(featureInfo.getSettingOnValue()); |
| } else { |
| logAccessibilityShortcutActivated(assignedTarget, shortcutType, /* serviceEnabled= */ |
| false); |
| setting.write(featureInfo.getSettingOffValue()); |
| } |
| return true; |
| } |
| |
| private boolean performAccessibilityShortcutTargetActivity(int displayId, |
| ComponentName assignedTarget) { |
| synchronized (mLock) { |
| final AccessibilityUserState userState = getCurrentUserStateLocked(); |
| for (int i = 0; i < userState.mInstalledShortcuts.size(); i++) { |
| final AccessibilityShortcutInfo shortcutInfo = userState.mInstalledShortcuts.get(i); |
| if (!shortcutInfo.getComponentName().equals(assignedTarget)) { |
| continue; |
| } |
| launchShortcutTargetActivity(displayId, assignedTarget); |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Perform accessibility service shortcut action. |
| * |
| * 1) For {@link AccessibilityManager#ACCESSIBILITY_BUTTON} type and services targeting sdk |
| * version <= Q: callbacks to accessibility service if service is bounded and requests |
| * accessibility button. |
| * 2) For {@link AccessibilityManager#ACCESSIBILITY_SHORTCUT_KEY} type and service targeting sdk |
| * version <= Q: turns on / off the accessibility service. |
| * 3) For {@link AccessibilityManager#ACCESSIBILITY_SHORTCUT_KEY} type and service targeting sdk |
| * version > Q and request accessibility button: turn on the accessibility service if it's |
| * not in the enabled state. |
| * (It'll happen when a service is disabled and assigned to shortcut then upgraded.) |
| * 4) For services targeting sdk version > Q: |
| * a) Turns on / off the accessibility service, if service does not request accessibility |
| * button. |
| * b) Callbacks to accessibility service if service is bounded and requests accessibility |
| * button. |
| */ |
| private boolean performAccessibilityShortcutTargetService(int displayId, |
| @ShortcutType int shortcutType, ComponentName assignedTarget) { |
| synchronized (mLock) { |
| final AccessibilityUserState userState = getCurrentUserStateLocked(); |
| final AccessibilityServiceInfo installedServiceInfo = |
| userState.getInstalledServiceInfoLocked(assignedTarget); |
| if (installedServiceInfo == null) { |
| Slog.d(LOG_TAG, "Perform shortcut failed, invalid component name:" |
| + assignedTarget); |
| return false; |
| } |
| |
| final AccessibilityServiceConnection serviceConnection = |
| userState.getServiceConnectionLocked(assignedTarget); |
| final int targetSdk = installedServiceInfo.getResolveInfo() |
| .serviceInfo.applicationInfo.targetSdkVersion; |
| final boolean requestA11yButton = (installedServiceInfo.flags |
| & AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0; |
| // Turns on / off the accessibility service |
| if ((targetSdk <= Build.VERSION_CODES.Q && shortcutType == ACCESSIBILITY_SHORTCUT_KEY) |
| || (targetSdk > Build.VERSION_CODES.Q && !requestA11yButton)) { |
| if (serviceConnection == null) { |
| logAccessibilityShortcutActivated(assignedTarget, |
| shortcutType, /* serviceEnabled= */ true); |
| enableAccessibilityServiceLocked(assignedTarget, mCurrentUserId); |
| |
| } else { |
| logAccessibilityShortcutActivated(assignedTarget, |
| shortcutType, /* serviceEnabled= */ false); |
| disableAccessibilityServiceLocked(assignedTarget, mCurrentUserId); |
| } |
| return true; |
| } |
| if (shortcutType == ACCESSIBILITY_SHORTCUT_KEY && targetSdk > Build.VERSION_CODES.Q |
| && requestA11yButton) { |
| if (!userState.getEnabledServicesLocked().contains(assignedTarget)) { |
| enableAccessibilityServiceLocked(assignedTarget, mCurrentUserId); |
| return true; |
| } |
| } |
| // Callbacks to a11y service if it's bounded and requests a11y button. |
| if (serviceConnection == null |
| || !userState.mBoundServices.contains(serviceConnection) |
| || !serviceConnection.mRequestAccessibilityButton) { |
| Slog.d(LOG_TAG, "Perform shortcut failed, service is not ready:" |
| + assignedTarget); |
| return false; |
| } |
| // ServiceConnection means service enabled. |
| logAccessibilityShortcutActivated(assignedTarget, shortcutType, /* serviceEnabled= */ |
| true); |
| serviceConnection.notifyAccessibilityButtonClickedLocked(displayId); |
| return true; |
| } |
| } |
| |
| @Override |
| public List<String> getAccessibilityShortcutTargets(@ShortcutType int shortcutType) { |
| if (mContext.checkCallingOrSelfPermission(Manifest.permission.MANAGE_ACCESSIBILITY) |
| != PackageManager.PERMISSION_GRANTED) { |
| throw new SecurityException( |
| "getAccessibilityShortcutService requires the MANAGE_ACCESSIBILITY permission"); |
| } |
| return getAccessibilityShortcutTargetsInternal(shortcutType); |
| } |
| |
| private List<String> getAccessibilityShortcutTargetsInternal(@ShortcutType int shortcutType) { |
| synchronized (mLock) { |
| final AccessibilityUserState userState = getCurrentUserStateLocked(); |
| final ArrayList<String> shortcutTargets = new ArrayList<>( |
| userState.getShortcutTargetsLocked(shortcutType)); |
| if (shortcutType != ACCESSIBILITY_BUTTON) { |
| return shortcutTargets; |
| } |
| // Adds legacy a11y services requesting a11y button into the list. |
| for (int i = userState.mBoundServices.size() - 1; i >= 0; i--) { |
| final AccessibilityServiceConnection service = userState.mBoundServices.get(i); |
| if (!service.mRequestAccessibilityButton |
| || service.getServiceInfo().getResolveInfo().serviceInfo.applicationInfo |
| .targetSdkVersion > Build.VERSION_CODES.Q) { |
| continue; |
| } |
| final String serviceName = service.getComponentName().flattenToString(); |
| if (!TextUtils.isEmpty(serviceName)) { |
| shortcutTargets.add(serviceName); |
| } |
| } |
| return shortcutTargets; |
| } |
| } |
| |
| /** |
| * Enables accessibility service specified by {@param componentName} for the {@param userId}. |
| */ |
| private void enableAccessibilityServiceLocked(ComponentName componentName, int userId) { |
| mTempComponentNameSet.clear(); |
| readComponentNamesFromSettingLocked(Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, |
| userId, mTempComponentNameSet); |
| mTempComponentNameSet.add(componentName); |
| persistComponentNamesToSettingLocked(Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, |
| mTempComponentNameSet, userId); |
| |
| AccessibilityUserState 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) { |
| mTempComponentNameSet.clear(); |
| readComponentNamesFromSettingLocked(Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, |
| userId, mTempComponentNameSet); |
| mTempComponentNameSet.remove(componentName); |
| persistComponentNamesToSettingLocked(Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, |
| mTempComponentNameSet, userId); |
| |
| AccessibilityUserState userState = getUserStateLocked(userId); |
| if (userState.mEnabledServices.remove(componentName)) { |
| onUserStateChangedLocked(userState); |
| } |
| } |
| |
| @Override |
| public void sendAccessibilityEventForCurrentUserLocked(AccessibilityEvent event) { |
| sendAccessibilityEventLocked(event, mCurrentUserId); |
| } |
| |
| private void sendAccessibilityEventLocked(AccessibilityEvent event, int userId) { |
| // Resync to avoid calling out with the lock held |
| event.setEventTime(SystemClock.uptimeMillis()); |
| mMainHandler.sendMessage(obtainMessage( |
| AccessibilityManagerService::sendAccessibilityEvent, |
| this, event, userId)); |
| } |
| |
| /** |
| * 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); |
| } |
| |
| /** |
| * AIDL-exposed method. System only. |
| * Gets accessibility window id from window token. |
| * |
| * @param windowToken Window token to get accessibility window id. |
| * @return Accessibility window id for the window token. Returns -1 if no such token is |
| * registered. |
| */ |
| @Override |
| public int getAccessibilityWindowId(@Nullable IBinder windowToken) { |
| synchronized (mLock) { |
| if (UserHandle.getAppId(Binder.getCallingUid()) != Process.SYSTEM_UID) { |
| throw new SecurityException("Only SYSTEM can call getAccessibilityWindowId"); |
| } |
| |
| return mA11yWindowManager.findWindowIdLocked(mCurrentUserId, windowToken); |
| } |
| } |
| |
| /** |
| * Get the recommended timeout of interactive controls and non-interactive controls. |
| * |
| * @return A long for pair of {@code int}s. First integer for interactive one, and second |
| * integer for non-interactive one. |
| */ |
| @Override |
| public long getRecommendedTimeoutMillis() { |
| synchronized(mLock) { |
| final AccessibilityUserState userState = getCurrentUserStateLocked(); |
| return getRecommendedTimeoutMillisLocked(userState); |
| } |
| } |
| |
| private long getRecommendedTimeoutMillisLocked(AccessibilityUserState userState) { |
| return IntPair.of(userState.getInteractiveUiTimeoutLocked(), |
| userState.getNonInteractiveUiTimeoutLocked()); |
| } |
| |
| @Override |
| public void setWindowMagnificationConnection( |
| IWindowMagnificationConnection connection) throws RemoteException { |
| mSecurityPolicy.enforceCallingOrSelfPermission( |
| android.Manifest.permission.STATUS_BAR_SERVICE); |
| |
| getWindowMagnificationMgr().setConnection(connection); |
| } |
| |
| WindowMagnificationManager getWindowMagnificationMgr() { |
| synchronized (mLock) { |
| if (mWindowMagnificationMgr == null) { |
| mWindowMagnificationMgr = new WindowMagnificationManager(); |
| } |
| return mWindowMagnificationMgr; |
| } |
| } |
| |
| @Override |
| public void associateEmbeddedHierarchy(@NonNull IBinder host, @NonNull IBinder embedded) { |
| synchronized (mLock) { |
| mA11yWindowManager.associateEmbeddedHierarchyLocked(host, embedded); |
| } |
| } |
| |
| @Override |
| public void disassociateEmbeddedHierarchy(@NonNull IBinder token) { |
| synchronized (mLock) { |
| mA11yWindowManager.disassociateEmbeddedHierarchyLocked(token); |
| } |
| } |
| |
| @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(); |
| pw.append("currentUserId=").append(String.valueOf(mCurrentUserId)); |
| pw.println(); |
| final int userCount = mUserStates.size(); |
| for (int i = 0; i < userCount; i++) { |
| mUserStates.valueAt(i).dump(fd, pw, args); |
| } |
| if (mUiAutomationManager.isUiAutomationRunningLocked()) { |
| mUiAutomationManager.dumpUiAutomationService(fd, pw, args); |
| pw.println(); |
| } |
| mA11yWindowManager.dump(fd, pw, args); |
| } |
| } |
| |
| //TODO remove after refactoring KeyEventDispatcherTest |
| final class MainHandler extends Handler { |
| public static final int MSG_SEND_KEY_EVENT_TO_INPUT_FILTER = 8; |
| |
| public MainHandler(Looper looper) { |
| super(looper); |
| } |
| |
| @Override |
| public void handleMessage(Message msg) { |
| if (msg.what == 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(); |
| } |
| } |
| } |
| |
| @Override |
| public MagnificationController getMagnificationController() { |
| synchronized (mLock) { |
| if (mMagnificationController == null) { |
| mMagnificationController = new MagnificationController(mContext, this, mLock); |
| mMagnificationController.setUserId(mCurrentUserId); |
| } |
| return mMagnificationController; |
| } |
| } |
| |
| @Override |
| public void onClientChangeLocked(boolean serviceInfoChanged) { |
| AccessibilityUserState userState = getUserStateLocked(mCurrentUserId); |
| onUserStateChangedLocked(userState); |
| if (serviceInfoChanged) { |
| scheduleNotifyClientsOfServicesStateChangeLocked(userState); |
| } |
| } |
| |
| @Override |
| public void onShellCommand(FileDescriptor in, FileDescriptor out, |
| FileDescriptor err, String[] args, ShellCallback callback, |
| ResultReceiver resultReceiver) { |
| new AccessibilityShellCommand(this, mSystemActionPerformer).exec(this, in, out, err, args, |
| callback, resultReceiver); |
| } |
| |
| private final class InteractionBridge { |
| private final ComponentName COMPONENT_NAME = |
| new ComponentName("com.android.server.accessibility", "InteractionBridge"); |
| |
| private final Display mDefaultDisplay; |
| private final int mConnectionId; |
| private final AccessibilityInteractionClient mClient; |
| |
| public InteractionBridge() { |
| final AccessibilityServiceInfo info = new AccessibilityServiceInfo(); |
| info.setCapabilities(AccessibilityServiceInfo.CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT); |
| info.flags |= AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS; |
| info.flags |= AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS; |
| final AccessibilityUserState userState; |
| synchronized (mLock) { |
| userState = getCurrentUserStateLocked(); |
| } |
| AccessibilityServiceConnection service = new AccessibilityServiceConnection( |
| userState, mContext, |
| COMPONENT_NAME, info, sIdCounter++, mMainHandler, mLock, mSecurityPolicy, |
| AccessibilityManagerService.this, mWindowManagerService, |
| getSystemActionPerformer(), mA11yWindowManager, mActivityTaskManagerService) { |
| @Override |
| public boolean supportsFlagForNotImportantViews(AccessibilityServiceInfo info) { |
| return true; |
| } |
| }; |
| |
| 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); |
| } |
| |
| /** |
| * 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 action 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 = mA11yWindowManager.getFocusedWindowId( |
| AccessibilityNodeInfo.FOCUS_ACCESSIBILITY); |
| if (focusedWindowId == AccessibilityWindowInfo.UNDEFINED_WINDOW_ID) { |
| return null; |
| } |
| } |
| return getAccessibilityFocusNotLocked(focusedWindowId); |
| } |
| |
| private AccessibilityNodeInfo getAccessibilityFocusNotLocked(int windowId) { |
| return mClient.findFocus(mConnectionId, |
| windowId, AccessibilityNodeInfo.ROOT_NODE_ID, |
| AccessibilityNodeInfo.FOCUS_ACCESSIBILITY); |
| } |
| } |
| |
| /** |
| * Gets all currently valid logical displays. |
| * |
| * @return An array list containing all valid logical displays. |
| */ |
| public ArrayList<Display> getValidDisplayList() { |
| return mA11yDisplayListener.getValidDisplayList(); |
| } |
| |
| /** |
| * A Utility class to handle display state. |
| */ |
| public class AccessibilityDisplayListener implements DisplayManager.DisplayListener { |
| private final DisplayManager mDisplayManager; |
| private final ArrayList<Display> mDisplaysList = new ArrayList<>(); |
| private int mSystemUiUid = 0; |
| |
| AccessibilityDisplayListener(Context context, MainHandler handler) { |
| mDisplayManager = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE); |
| mDisplayManager.registerDisplayListener(this, handler); |
| initializeDisplayList(); |
| |
| final PackageManagerInternal pm = |
| LocalServices.getService(PackageManagerInternal.class); |
| if (pm != null) { |
| mSystemUiUid = pm.getPackageUid(pm.getSystemUiServiceComponent().getPackageName(), |
| PackageManager.MATCH_SYSTEM_ONLY, mCurrentUserId); |
| } |
| } |
| |
| ArrayList<Display> getValidDisplayList() { |
| synchronized (mLock) { |
| return mDisplaysList; |
| } |
| } |
| |
| private void initializeDisplayList() { |
| final Display[] displays = mDisplayManager.getDisplays(); |
| synchronized (mLock) { |
| mDisplaysList.clear(); |
| for (int i = 0; i < displays.length; i++) { |
| // Exclude overlay virtual displays. The display list is for A11yInputFilter |
| // to create event handler per display. The events should be handled by the |
| // display which is overlaid by it. |
| final Display display = displays[i]; |
| if (isValidDisplay(display)) { |
| mDisplaysList.add(display); |
| } |
| } |
| } |
| } |
| |
| @Override |
| public void onDisplayAdded(int displayId) { |
| final Display display = mDisplayManager.getDisplay(displayId); |
| if (!isValidDisplay(display)) { |
| return; |
| } |
| |
| synchronized (mLock) { |
| mDisplaysList.add(display); |
| if (mInputFilter != null) { |
| mInputFilter.onDisplayChanged(); |
| } |
| AccessibilityUserState userState = getCurrentUserStateLocked(); |
| if (displayId != Display.DEFAULT_DISPLAY) { |
| final List<AccessibilityServiceConnection> services = userState.mBoundServices; |
| for (int i = 0; i < services.size(); i++) { |
| AccessibilityServiceConnection boundClient = services.get(i); |
| boundClient.onDisplayAdded(displayId); |
| } |
| } |
| updateMagnificationLocked(userState); |
| updateWindowsForAccessibilityCallbackLocked(userState); |
| } |
| } |
| |
| @Override |
| public void onDisplayRemoved(int displayId) { |
| synchronized (mLock) { |
| if (!removeDisplayFromList(displayId)) { |
| return; |
| } |
| if (mInputFilter != null) { |
| mInputFilter.onDisplayChanged(); |
| } |
| AccessibilityUserState userState = getCurrentUserStateLocked(); |
| if (displayId != Display.DEFAULT_DISPLAY) { |
| final List<AccessibilityServiceConnection> services = userState.mBoundServices; |
| for (int i = 0; i < services.size(); i++) { |
| AccessibilityServiceConnection boundClient = services.get(i); |
| boundClient.onDisplayRemoved(displayId); |
| } |
| } |
| } |
| if (mMagnificationController != null) { |
| mMagnificationController.onDisplayRemoved(displayId); |
| } |
| mA11yWindowManager.stopTrackingWindows(displayId); |
| } |
| |
| @GuardedBy("mLock") |
| private boolean removeDisplayFromList(int displayId) { |
| for (int i = 0; i < mDisplaysList.size(); i++) { |
| if (mDisplaysList.get(i).getDisplayId() == displayId) { |
| mDisplaysList.remove(i); |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| @Override |
| public void onDisplayChanged(int displayId) { |
| /* do nothing */ |
| } |
| |
| private boolean isValidDisplay(@Nullable Display display) { |
| if (display == null || display.getType() == Display.TYPE_OVERLAY) { |
| return false; |
| } |
| // Private virtual displays are created by the ap and is not allowed to access by other |
| // aps. We assume we could ignore them. |
| // The exceptional case is for bubbles. Because the bubbles use the activityView, and |
| // the virtual display of the activityView is private, so if the owner UID of the |
| // private virtual display is the one of system ui which creates the virtual display of |
| // bubbles, then this private virtual display should track the windows. |
| if (display.getType() == Display.TYPE_VIRTUAL |
| && (display.getFlags() & Display.FLAG_PRIVATE) != 0 |
| && display.getOwnerUid() != mSystemUiUid) { |
| return false; |
| } |
| return true; |
| } |
| } |
| |
| /** Represents an {@link AccessibilityManager} */ |
| class Client { |
| final IAccessibilityManagerClient mCallback; |
| final String[] mPackageNames; |
| int mLastSentRelevantEventTypes; |
| |
| private Client(IAccessibilityManagerClient callback, int clientUid, |
| AccessibilityUserState userState) { |
| mCallback = callback; |
| mPackageNames = mPackageManager.getPackagesForUid(clientUid); |
| synchronized (mLock) { |
| mLastSentRelevantEventTypes = computeRelevantEventTypesLocked(userState, this); |
| } |
| } |
| } |
| |
| 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 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 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 mShowImeWithHardKeyboardUri = Settings.Secure.getUriFor( |
| Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD); |
| |
| 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); |
| |
| private final Uri mAccessibilityButtonTargetsUri = Settings.Secure.getUriFor( |
| Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS); |
| |
| private final Uri mUserNonInteractiveUiTimeoutUri = Settings.Secure.getUriFor( |
| Settings.Secure.ACCESSIBILITY_NON_INTERACTIVE_UI_TIMEOUT_MS); |
| |
| private final Uri mUserInteractiveUiTimeoutUri = Settings.Secure.getUriFor( |
| Settings.Secure.ACCESSIBILITY_INTERACTIVE_UI_TIMEOUT_MS); |
| |
| 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(mAutoclickEnabledUri, |
| false, this, UserHandle.USER_ALL); |
| contentResolver.registerContentObserver(mEnabledAccessibilityServicesUri, |
| false, this, UserHandle.USER_ALL); |
| contentResolver.registerContentObserver( |
| mTouchExplorationGrantedAccessibilityServicesUri, |
| false, this, UserHandle.USER_ALL); |
| contentResolver.registerContentObserver( |
| mHighTextContrastUri, false, this, UserHandle.USER_ALL); |
| contentResolver.registerContentObserver( |
| mAccessibilitySoftKeyboardModeUri, false, this, UserHandle.USER_ALL); |
| contentResolver.registerContentObserver( |
| mShowImeWithHardKeyboardUri, false, this, UserHandle.USER_ALL); |
| contentResolver.registerContentObserver( |
| mAccessibilityShortcutServiceIdUri, false, this, UserHandle.USER_ALL); |
| contentResolver.registerContentObserver( |
| mAccessibilityButtonComponentIdUri, false, this, UserHandle.USER_ALL); |
| contentResolver.registerContentObserver( |
| mAccessibilityButtonTargetsUri, false, this, UserHandle.USER_ALL); |
| contentResolver.registerContentObserver( |
| mUserNonInteractiveUiTimeoutUri, false, this, UserHandle.USER_ALL); |
| contentResolver.registerContentObserver( |
| mUserInteractiveUiTimeoutUri, 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. |
| AccessibilityUserState userState = getCurrentUserStateLocked(); |
| |
| if (mTouchExplorationEnabledUri.equals(uri)) { |
| if (readTouchExplorationEnabledSettingLocked(userState)) { |
| onUserStateChangedLocked(userState); |
| } |
| } else if (mDisplayMagnificationEnabledUri.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)) { |
| userState.updateCrashedServicesIfNeededLocked(); |
| onUserStateChangedLocked(userState); |
| } |
| } else if (mTouchExplorationGrantedAccessibilityServicesUri.equals(uri)) { |
| if (readTouchExplorationGrantedAccessibilityServicesLocked(userState)) { |
| onUserStateChangedLocked(userState); |
| } |
| } else if (mHighTextContrastUri.equals(uri)) { |
| if (readHighTextContrastEnabledSettingLocked(userState)) { |
| onUserStateChangedLocked(userState); |
| } |
| } else if (mAccessibilitySoftKeyboardModeUri.equals(uri) |
| || mShowImeWithHardKeyboardUri.equals(uri)) { |
| userState.reconcileSoftKeyboardModeWithSettingsLocked(); |
| } else if (mAccessibilityShortcutServiceIdUri.equals(uri)) { |
| if (readAccessibilityShortcutKeySettingLocked(userState)) { |
| onUserStateChangedLocked(userState); |
| } |
| } else if (mAccessibilityButtonComponentIdUri.equals(uri)) { |
| if (readAccessibilityButtonTargetComponentLocked(userState)) { |
| onUserStateChangedLocked(userState); |
| } |
| } else if (mAccessibilityButtonTargetsUri.equals(uri)) { |
| if (readAccessibilityButtonTargetsLocked(userState)) { |
| onUserStateChangedLocked(userState); |
| } |
| } else if (mUserNonInteractiveUiTimeoutUri.equals(uri) |
| || mUserInteractiveUiTimeoutUri.equals(uri)) { |
| readUserRecommendedUiTimeoutSettingsLocked(userState); |
| } |
| } |
| } |
| } |
| |
| @Override |
| public void setGestureDetectionPassthroughRegion(int displayId, Region region) { |
| mMainHandler.sendMessage( |
| obtainMessage( |
| AccessibilityManagerService::setGestureDetectionPassthroughRegionInternal, |
| this, |
| displayId, |
| region)); |
| } |
| |
| @Override |
| public void setTouchExplorationPassthroughRegion(int displayId, Region region) { |
| mMainHandler.sendMessage( |
| obtainMessage( |
| AccessibilityManagerService::setTouchExplorationPassthroughRegionInternal, |
| this, |
| displayId, |
| region)); |
| } |
| |
| private void setTouchExplorationPassthroughRegionInternal(int displayId, Region region) { |
| synchronized (mLock) { |
| if (mHasInputFilter && mInputFilter != null) { |
| mInputFilter.setTouchExplorationPassthroughRegion(displayId, region); |
| } |
| } |
| } |
| |
| private void setGestureDetectionPassthroughRegionInternal(int displayId, Region region) { |
| synchronized (mLock) { |
| if (mHasInputFilter && mInputFilter != null) { |
| mInputFilter.setGestureDetectionPassthroughRegion(displayId, region); |
| } |
| } |
| } |
| } |