diff options
Diffstat (limited to 'services/java/com/android/server/accessibility')
6 files changed, 0 insertions, 6844 deletions
diff --git a/services/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/java/com/android/server/accessibility/AccessibilityInputFilter.java deleted file mode 100644 index 9e893da..0000000 --- a/services/java/com/android/server/accessibility/AccessibilityInputFilter.java +++ /dev/null @@ -1,404 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.accessibility; - -import android.content.Context; -import android.os.PowerManager; -import android.util.Pools.SimplePool; -import android.util.Slog; -import android.view.Choreographer; -import android.view.Display; -import android.view.InputDevice; -import android.view.InputEvent; -import android.view.InputFilter; -import android.view.KeyEvent; -import android.view.MotionEvent; -import android.view.WindowManagerPolicy; -import android.view.accessibility.AccessibilityEvent; - -/** - * This class is an input filter for implementing accessibility features such - * as display magnification and explore by touch. - * - * NOTE: This class has to be created and poked only from the main thread. - */ -class AccessibilityInputFilter extends InputFilter implements EventStreamTransformation { - - private static final String TAG = AccessibilityInputFilter.class.getSimpleName(); - - private static final boolean DEBUG = false; - - /** - * Flag for enabling the screen magnification feature. - * - * @see #setEnabledFeatures(int) - */ - static final int FLAG_FEATURE_SCREEN_MAGNIFIER = 0x00000001; - - /** - * Flag for enabling the touch exploration feature. - * - * @see #setEnabledFeatures(int) - */ - static final int FLAG_FEATURE_TOUCH_EXPLORATION = 0x00000002; - - /** - * Flag for enabling the filtering key events feature. - * - * @see #setEnabledFeatures(int) - */ - static final int FLAG_FEATURE_FILTER_KEY_EVENTS = 0x00000004; - - private final Runnable mProcessBatchedEventsRunnable = new Runnable() { - @Override - public void run() { - final long frameTimeNanos = mChoreographer.getFrameTimeNanos(); - if (DEBUG) { - Slog.i(TAG, "Begin batch processing for frame: " + frameTimeNanos); - } - processBatchedEvents(frameTimeNanos); - if (DEBUG) { - Slog.i(TAG, "End batch processing."); - } - if (mEventQueue != null) { - scheduleProcessBatchedEvents(); - } - } - }; - - private final Context mContext; - - private final PowerManager mPm; - - private final AccessibilityManagerService mAms; - - private final Choreographer mChoreographer; - - private int mCurrentTouchDeviceId; - - private boolean mInstalled; - - private int mEnabledFeatures; - - private TouchExplorer mTouchExplorer; - - private ScreenMagnifier mScreenMagnifier; - - private EventStreamTransformation mEventHandler; - - private MotionEventHolder mEventQueue; - - private boolean mMotionEventSequenceStarted; - - private boolean mHoverEventSequenceStarted; - - private boolean mKeyEventSequenceStarted; - - private boolean mFilterKeyEvents; - - AccessibilityInputFilter(Context context, AccessibilityManagerService service) { - super(context.getMainLooper()); - mContext = context; - mAms = service; - mPm = (PowerManager) context.getSystemService(Context.POWER_SERVICE); - mChoreographer = Choreographer.getInstance(); - } - - @Override - public void onInstalled() { - if (DEBUG) { - Slog.d(TAG, "Accessibility input filter installed."); - } - mInstalled = true; - disableFeatures(); - enableFeatures(); - super.onInstalled(); - } - - @Override - public void onUninstalled() { - if (DEBUG) { - Slog.d(TAG, "Accessibility input filter uninstalled."); - } - mInstalled = false; - disableFeatures(); - super.onUninstalled(); - } - - @Override - public void onInputEvent(InputEvent event, int policyFlags) { - if (DEBUG) { - Slog.d(TAG, "Received event: " + event + ", policyFlags=0x" - + Integer.toHexString(policyFlags)); - } - if (event instanceof MotionEvent - && event.isFromSource(InputDevice.SOURCE_TOUCHSCREEN)) { - MotionEvent motionEvent = (MotionEvent) event; - onMotionEvent(motionEvent, policyFlags); - } else if (event instanceof KeyEvent - && event.isFromSource(InputDevice.SOURCE_KEYBOARD)) { - KeyEvent keyEvent = (KeyEvent) event; - onKeyEvent(keyEvent, policyFlags); - } else { - super.onInputEvent(event, policyFlags); - } - } - - private void onMotionEvent(MotionEvent event, int policyFlags) { - if (mEventHandler == null) { - super.onInputEvent(event, policyFlags); - return; - } - if ((policyFlags & WindowManagerPolicy.FLAG_PASS_TO_USER) == 0) { - mMotionEventSequenceStarted = false; - mHoverEventSequenceStarted = false; - mEventHandler.clear(); - super.onInputEvent(event, policyFlags); - return; - } - final int deviceId = event.getDeviceId(); - if (mCurrentTouchDeviceId != deviceId) { - mCurrentTouchDeviceId = deviceId; - mMotionEventSequenceStarted = false; - mHoverEventSequenceStarted = false; - mEventHandler.clear(); - } - if (mCurrentTouchDeviceId < 0) { - super.onInputEvent(event, policyFlags); - return; - } - // We do not handle scroll events. - if (event.getActionMasked() == MotionEvent.ACTION_SCROLL) { - super.onInputEvent(event, policyFlags); - return; - } - // Wait for a down touch event to start processing. - if (event.isTouchEvent()) { - if (!mMotionEventSequenceStarted) { - if (event.getActionMasked() != MotionEvent.ACTION_DOWN) { - return; - } - mMotionEventSequenceStarted = true; - } - } else { - // Wait for an enter hover event to start processing. - if (!mHoverEventSequenceStarted) { - if (event.getActionMasked() != MotionEvent.ACTION_HOVER_ENTER) { - return; - } - mHoverEventSequenceStarted = true; - } - } - batchMotionEvent((MotionEvent) event, policyFlags); - } - - private void onKeyEvent(KeyEvent event, int policyFlags) { - if (!mFilterKeyEvents) { - super.onInputEvent(event, policyFlags); - return; - } - if ((policyFlags & WindowManagerPolicy.FLAG_PASS_TO_USER) == 0) { - mKeyEventSequenceStarted = false; - super.onInputEvent(event, policyFlags); - return; - } - // Wait for a down key event to start processing. - if (!mKeyEventSequenceStarted) { - if (event.getAction() != KeyEvent.ACTION_DOWN) { - return; - } - mKeyEventSequenceStarted = true; - } - mAms.notifyKeyEvent(event, policyFlags); - } - - private void scheduleProcessBatchedEvents() { - mChoreographer.postCallback(Choreographer.CALLBACK_INPUT, - mProcessBatchedEventsRunnable, null); - } - - private void batchMotionEvent(MotionEvent event, int policyFlags) { - if (DEBUG) { - Slog.i(TAG, "Batching event: " + event + ", policyFlags: " + policyFlags); - } - if (mEventQueue == null) { - mEventQueue = MotionEventHolder.obtain(event, policyFlags); - scheduleProcessBatchedEvents(); - return; - } - if (mEventQueue.event.addBatch(event)) { - return; - } - MotionEventHolder holder = MotionEventHolder.obtain(event, policyFlags); - holder.next = mEventQueue; - mEventQueue.previous = holder; - mEventQueue = holder; - } - - private void processBatchedEvents(long frameNanos) { - MotionEventHolder current = mEventQueue; - while (current.next != null) { - current = current.next; - } - while (true) { - if (current == null) { - mEventQueue = null; - break; - } - if (current.event.getEventTimeNano() >= frameNanos) { - // Finished with this choreographer frame. Do the rest on the next one. - current.next = null; - break; - } - handleMotionEvent(current.event, current.policyFlags); - MotionEventHolder prior = current; - current = current.previous; - prior.recycle(); - } - } - - private void handleMotionEvent(MotionEvent event, int policyFlags) { - if (DEBUG) { - Slog.i(TAG, "Handling batched event: " + event + ", policyFlags: " + policyFlags); - } - // Since we do batch processing it is possible that by the time the - // next batch is processed the event handle had been set to null. - if (mEventHandler != null) { - mPm.userActivity(event.getEventTime(), false); - MotionEvent transformedEvent = MotionEvent.obtain(event); - mEventHandler.onMotionEvent(transformedEvent, event, policyFlags); - transformedEvent.recycle(); - } - } - - @Override - public void onMotionEvent(MotionEvent transformedEvent, MotionEvent rawEvent, - int policyFlags) { - sendInputEvent(transformedEvent, policyFlags); - } - - @Override - public void onAccessibilityEvent(AccessibilityEvent event) { - // TODO Implement this to inject the accessibility event - // into the accessibility manager service similarly - // to how this is done for input events. - } - - @Override - public void setNext(EventStreamTransformation sink) { - /* do nothing */ - } - - @Override - public void clear() { - /* do nothing */ - } - - void setEnabledFeatures(int enabledFeatures) { - if (mEnabledFeatures == enabledFeatures) { - return; - } - if (mInstalled) { - disableFeatures(); - } - mEnabledFeatures = enabledFeatures; - if (mInstalled) { - enableFeatures(); - } - } - - void notifyAccessibilityEvent(AccessibilityEvent event) { - if (mEventHandler != null) { - mEventHandler.onAccessibilityEvent(event); - } - } - - private void enableFeatures() { - mMotionEventSequenceStarted = false; - mHoverEventSequenceStarted = false; - if ((mEnabledFeatures & FLAG_FEATURE_SCREEN_MAGNIFIER) != 0) { - mEventHandler = mScreenMagnifier = new ScreenMagnifier(mContext, - Display.DEFAULT_DISPLAY, mAms); - mEventHandler.setNext(this); - } - if ((mEnabledFeatures & FLAG_FEATURE_TOUCH_EXPLORATION) != 0) { - mTouchExplorer = new TouchExplorer(mContext, mAms); - mTouchExplorer.setNext(this); - if (mEventHandler != null) { - mEventHandler.setNext(mTouchExplorer); - } else { - mEventHandler = mTouchExplorer; - } - } - if ((mEnabledFeatures & FLAG_FEATURE_FILTER_KEY_EVENTS) != 0) { - mFilterKeyEvents = true; - } - } - - void disableFeatures() { - if (mTouchExplorer != null) { - mTouchExplorer.clear(); - mTouchExplorer.onDestroy(); - mTouchExplorer = null; - } - if (mScreenMagnifier != null) { - mScreenMagnifier.clear(); - mScreenMagnifier.onDestroy(); - mScreenMagnifier = null; - } - mEventHandler = null; - mKeyEventSequenceStarted = false; - mMotionEventSequenceStarted = false; - mHoverEventSequenceStarted = false; - mFilterKeyEvents = false; - } - - @Override - public void onDestroy() { - /* ignore */ - } - - private static class MotionEventHolder { - private static final int MAX_POOL_SIZE = 32; - private static final SimplePool<MotionEventHolder> sPool = - new SimplePool<MotionEventHolder>(MAX_POOL_SIZE); - - public int policyFlags; - public MotionEvent event; - public MotionEventHolder next; - public MotionEventHolder previous; - - public static MotionEventHolder obtain(MotionEvent event, int policyFlags) { - MotionEventHolder holder = sPool.acquire(); - if (holder == null) { - holder = new MotionEventHolder(); - } - holder.event = MotionEvent.obtain(event); - holder.policyFlags = policyFlags; - return holder; - } - - public void recycle() { - event.recycle(); - event = null; - policyFlags = 0; - next = null; - previous = null; - sPool.release(this); - } - } -} diff --git a/services/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/java/com/android/server/accessibility/AccessibilityManagerService.java deleted file mode 100644 index ccac0d3..0000000 --- a/services/java/com/android/server/accessibility/AccessibilityManagerService.java +++ /dev/null @@ -1,3131 +0,0 @@ -/* - ** Copyright 2009, The Android Open Source Project - ** - ** Licensed under the Apache License, Version 2.0 (the "License"); - ** you may not use this file except in compliance with the License. - ** You may obtain a copy of the License at - ** - ** http://www.apache.org/licenses/LICENSE-2.0 - ** - ** Unless required by applicable law or agreed to in writing, software - ** distributed under the License is distributed on an "AS IS" BASIS, - ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ** See the License for the specific language governing permissions and - ** limitations under the License. - */ - -package com.android.server.accessibility; - -import static android.accessibilityservice.AccessibilityServiceInfo.DEFAULT; - -import android.Manifest; -import android.accessibilityservice.AccessibilityService; -import android.accessibilityservice.AccessibilityServiceInfo; -import android.accessibilityservice.IAccessibilityServiceClient; -import android.accessibilityservice.IAccessibilityServiceConnection; -import android.app.AlertDialog; -import android.app.PendingIntent; -import android.app.StatusBarManager; -import android.content.BroadcastReceiver; -import android.content.ComponentName; -import android.content.ContentResolver; -import android.content.Context; -import android.content.DialogInterface; -import android.content.DialogInterface.OnClickListener; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.ServiceConnection; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; -import android.content.pm.ServiceInfo; -import android.database.ContentObserver; -import android.graphics.Point; -import android.graphics.Rect; -import android.hardware.display.DisplayManager; -import android.hardware.input.InputManager; -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.Process; -import android.os.RemoteCallbackList; -import android.os.RemoteException; -import android.os.ServiceManager; -import android.os.SystemClock; -import android.os.UserHandle; -import android.os.UserManager; -import android.provider.Settings; -import android.text.TextUtils; -import android.text.TextUtils.SimpleStringSplitter; -import android.util.Pools.Pool; -import android.util.Pools.SimplePool; -import android.util.Slog; -import android.util.SparseArray; -import android.view.Display; -import android.view.IWindow; -import android.view.IWindowManager; -import android.view.InputDevice; -import android.view.InputEventConsistencyVerifier; -import android.view.KeyCharacterMap; -import android.view.KeyEvent; -import android.view.MagnificationSpec; -import android.view.WindowManager; -import android.view.WindowManagerPolicy; -import android.view.accessibility.AccessibilityEvent; -import android.view.accessibility.AccessibilityInteractionClient; -import android.view.accessibility.AccessibilityManager; -import android.view.accessibility.AccessibilityNodeInfo; -import android.view.accessibility.IAccessibilityInteractionConnection; -import android.view.accessibility.IAccessibilityInteractionConnectionCallback; -import android.view.accessibility.IAccessibilityManager; -import android.view.accessibility.IAccessibilityManagerClient; - -import com.android.internal.R; -import com.android.internal.content.PackageMonitor; -import com.android.internal.statusbar.IStatusBarService; - -import org.xmlpull.v1.XmlPullParserException; - -import java.io.FileDescriptor; -import java.io.IOException; -import java.io.PrintWriter; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.CopyOnWriteArrayList; - -/** - * 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. - * - * @hide - */ -public class AccessibilityManagerService extends IAccessibilityManager.Stub { - - private static final boolean DEBUG = false; - - private static final String LOG_TAG = "AccessibilityManagerService"; - - // TODO: This is arbitrary. When there is time implement this by watching - // when that accessibility services are bound. - private static final int WAIT_FOR_USER_STATE_FULLY_INITIALIZED_MILLIS = 3000; - - private static final String FUNCTION_REGISTER_UI_TEST_AUTOMATION_SERVICE = - "registerUiTestAutomationService"; - - private static final String TEMPORARY_ENABLE_ACCESSIBILITY_UNTIL_KEYGUARD_REMOVED = - "temporaryEnableAccessibilityStateUntilKeyguardRemoved"; - - private static final ComponentName sFakeAccessibilityServiceComponentName = - new ComponentName("foo.bar", "FakeService"); - - private static final String FUNCTION_DUMP = "dump"; - - private static final char COMPONENT_NAME_SEPARATOR = ':'; - - private static final int OWN_PROCESS_ID = android.os.Process.myPid(); - - private static final int MAX_POOL_SIZE = 10; - - private static int sIdCounter = 0; - - private static int sNextWindowId; - - private final Context mContext; - - private final Object mLock = new Object(); - - private final Pool<PendingEvent> mPendingEventPool = - new SimplePool<PendingEvent>(MAX_POOL_SIZE); - - private final SimpleStringSplitter mStringColonSplitter = - new SimpleStringSplitter(COMPONENT_NAME_SEPARATOR); - - private final List<AccessibilityServiceInfo> mEnabledServicesForFeedbackTempList = - new ArrayList<AccessibilityServiceInfo>(); - - private final Rect mTempRect = new Rect(); - - private final Point mTempPoint = new Point(); - - private final Display mDefaultDisplay; - - private final PackageManager mPackageManager; - - private final IWindowManager mWindowManagerService; - - private final SecurityPolicy mSecurityPolicy; - - private final MainHandler mMainHandler; - - private Service mQueryBridge; - - private AlertDialog mEnableTouchExplorationDialog; - - private AccessibilityInputFilter mInputFilter; - - private boolean mHasInputFilter; - - private final Set<ComponentName> mTempComponentNameSet = new HashSet<ComponentName>(); - - private final List<AccessibilityServiceInfo> mTempAccessibilityServiceInfoList = - new ArrayList<AccessibilityServiceInfo>(); - - private final RemoteCallbackList<IAccessibilityManagerClient> mGlobalClients = - new RemoteCallbackList<IAccessibilityManagerClient>(); - - private final SparseArray<AccessibilityConnectionWrapper> mGlobalInteractionConnections = - new SparseArray<AccessibilityConnectionWrapper>(); - - private final SparseArray<IBinder> mGlobalWindowTokens = new SparseArray<IBinder>(); - - private final SparseArray<UserState> mUserStates = new SparseArray<UserState>(); - - private int mCurrentUserId = UserHandle.USER_OWNER; - - //TODO: Remove this hack - private boolean mInitialized; - - private UserState getCurrentUserStateLocked() { - return getUserStateLocked(mCurrentUserId); - } - - private UserState getUserStateLocked(int userId) { - UserState state = mUserStates.get(userId); - if (state == null) { - state = new UserState(userId); - mUserStates.put(userId, state); - } - return state; - } - - /** - * Creates a new instance. - * - * @param context A {@link Context} instance. - */ - public AccessibilityManagerService(Context context) { - mContext = context; - mPackageManager = mContext.getPackageManager(); - mWindowManagerService = (IWindowManager) ServiceManager.getService(Context.WINDOW_SERVICE); - mSecurityPolicy = new SecurityPolicy(); - mMainHandler = new MainHandler(mContext.getMainLooper()); - //TODO: (multi-display) We need to support multiple displays. - DisplayManager displayManager = (DisplayManager) - mContext.getSystemService(Context.DISPLAY_SERVICE); - mDefaultDisplay = displayManager.getDisplay(Display.DEFAULT_DISPLAY); - registerBroadcastReceivers(); - new AccessibilityContentObserver(mMainHandler).register( - context.getContentResolver()); - } - - private void registerBroadcastReceivers() { - PackageMonitor monitor = new PackageMonitor() { - @Override - public void onSomePackagesChanged() { - synchronized (mLock) { - if (getChangingUserId() != mCurrentUserId) { - return; - } - // We will update when the automation service dies. - UserState userState = getCurrentUserStateLocked(); - // We have to reload the installed services since some services may - // have different attributes, resolve info (does not support equals), - // etc. Remove them then to force reload. Do it even if automation is - // running since when it goes away, we will have to reload as well. - userState.mInstalledServices.clear(); - if (userState.mUiAutomationService == null) { - if (readConfigurationForUserStateLocked(userState)) { - onUserStateChangedLocked(userState); - } - } - } - } - - @Override - public void onPackageRemoved(String packageName, int uid) { - synchronized (mLock) { - final int userId = getChangingUserId(); - if (userId != mCurrentUserId) { - return; - } - UserState userState = getUserStateLocked(userId); - Iterator<ComponentName> it = userState.mEnabledServices.iterator(); - while (it.hasNext()) { - ComponentName comp = it.next(); - String compPkg = comp.getPackageName(); - if (compPkg.equals(packageName)) { - it.remove(); - // Update the enabled services setting. - persistComponentNamesToSettingLocked( - Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, - userState.mEnabledServices, userId); - // Update the touch exploration granted services setting. - userState.mTouchExplorationGrantedServices.remove(comp); - persistComponentNamesToSettingLocked( - Settings.Secure. - TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES, - userState.mTouchExplorationGrantedServices, userId); - // We will update when the automation service dies. - if (userState.mUiAutomationService == null) { - onUserStateChangedLocked(userState); - } - return; - } - } - } - } - - @Override - public boolean onHandleForceStop(Intent intent, String[] packages, - int uid, boolean doit) { - synchronized (mLock) { - final int userId = getChangingUserId(); - if (userId != mCurrentUserId) { - return false; - } - UserState userState = getUserStateLocked(userId); - Iterator<ComponentName> it = userState.mEnabledServices.iterator(); - while (it.hasNext()) { - ComponentName comp = it.next(); - String compPkg = comp.getPackageName(); - for (String pkg : packages) { - if (compPkg.equals(pkg)) { - if (!doit) { - return true; - } - it.remove(); - persistComponentNamesToSettingLocked( - Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, - userState.mEnabledServices, userId); - // We will update when the automation service dies. - if (userState.mUiAutomationService == null) { - 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_REMOVED); - intentFilter.addAction(Intent.ACTION_USER_PRESENT); - - 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_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. - UserState userState = getCurrentUserStateLocked(); - if (userState.mUiAutomationService == null) { - if (readConfigurationForUserStateLocked(userState)) { - onUserStateChangedLocked(userState); - } - } - } - } - }, UserHandle.ALL, intentFilter, null, null); - } - - public int addClient(IAccessibilityManagerClient client, int userId) { - synchronized (mLock) { - final int resolvedUserId = mSecurityPolicy - .resolveCallingUserIdEnforcingPermissionsLocked(userId); - // If the client is from a process that runs across users such as - // the system UI or the system we add it to the global state that - // is shared across users. - UserState userState = getUserStateLocked(resolvedUserId); - if (mSecurityPolicy.isCallerInteractingAcrossUsers(userId)) { - mGlobalClients.register(client); - if (DEBUG) { - Slog.i(LOG_TAG, "Added global client for pid:" + Binder.getCallingPid()); - } - return userState.getClientState(); - } else { - userState.mClients.register(client); - // If this client is not for the current user we do not - // return a state since it is not for the foreground user. - // We will send the state to the client on a user switch. - if (DEBUG) { - Slog.i(LOG_TAG, "Added user client for pid:" + Binder.getCallingPid() - + " and userId:" + mCurrentUserId); - } - return (resolvedUserId == mCurrentUserId) ? userState.getClientState() : 0; - } - } - } - - public boolean sendAccessibilityEvent(AccessibilityEvent event, int userId) { - synchronized (mLock) { - final int resolvedUserId = mSecurityPolicy - .resolveCallingUserIdEnforcingPermissionsLocked(userId); - // This method does nothing for a background user. - if (resolvedUserId != mCurrentUserId) { - return true; // yes, recycle the event - } - if (mSecurityPolicy.canDispatchAccessibilityEvent(event)) { - mSecurityPolicy.updateEventSourceLocked(event); - mMainHandler.obtainMessage(MainHandler.MSG_UPDATE_ACTIVE_WINDOW, - event.getWindowId(), event.getEventType()).sendToTarget(); - notifyAccessibilityServicesDelayedLocked(event, false); - notifyAccessibilityServicesDelayedLocked(event, true); - } - if (mHasInputFilter && mInputFilter != null) { - mMainHandler.obtainMessage(MainHandler.MSG_SEND_ACCESSIBILITY_EVENT_TO_INPUT_FILTER, - AccessibilityEvent.obtain(event)).sendToTarget(); - } - event.recycle(); - getUserStateLocked(resolvedUserId).mHandledFeedbackTypes = 0; - } - return (OWN_PROCESS_ID != Binder.getCallingPid()); - } - - public List<AccessibilityServiceInfo> getInstalledAccessibilityServiceList(int userId) { - synchronized (mLock) { - final int resolvedUserId = mSecurityPolicy - .resolveCallingUserIdEnforcingPermissionsLocked(userId); - // The automation service is a fake one and should not be reported - // to clients as being installed - it really is not. - UserState userState = getUserStateLocked(resolvedUserId); - if (userState.mUiAutomationService != null) { - List<AccessibilityServiceInfo> installedServices = - new ArrayList<AccessibilityServiceInfo>(); - installedServices.addAll(userState.mInstalledServices); - installedServices.remove(userState.mUiAutomationService); - return installedServices; - } - return userState.mInstalledServices; - } - } - - public List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList(int feedbackType, - int userId) { - List<AccessibilityServiceInfo> result = null; - synchronized (mLock) { - final int resolvedUserId = mSecurityPolicy - .resolveCallingUserIdEnforcingPermissionsLocked(userId); - - // The automation service is a fake one and should not be reported - // to clients as being enabled. The automation service is always the - // only active one, if it exists. - UserState userState = getUserStateLocked(resolvedUserId); - if (userState.mUiAutomationService != null) { - return Collections.emptyList(); - } - - result = mEnabledServicesForFeedbackTempList; - result.clear(); - List<Service> services = userState.mBoundServices; - while (feedbackType != 0) { - final int feedbackTypeBit = (1 << Integer.numberOfTrailingZeros(feedbackType)); - feedbackType &= ~feedbackTypeBit; - final int serviceCount = services.size(); - for (int i = 0; i < serviceCount; i++) { - Service service = services.get(i); - if ((service.mFeedbackType & feedbackTypeBit) != 0) { - result.add(service.mAccessibilityServiceInfo); - } - } - } - } - return result; - } - - public void interrupt(int userId) { - CopyOnWriteArrayList<Service> services; - synchronized (mLock) { - final int resolvedUserId = mSecurityPolicy - .resolveCallingUserIdEnforcingPermissionsLocked(userId); - // This method does nothing for a background user. - if (resolvedUserId != mCurrentUserId) { - return; - } - services = getUserStateLocked(resolvedUserId).mBoundServices; - } - for (int i = 0, count = services.size(); i < count; i++) { - Service service = services.get(i); - try { - service.mServiceInterface.onInterrupt(); - } catch (RemoteException re) { - Slog.e(LOG_TAG, "Error during sending interrupt request to " - + service.mService, re); - } - } - } - - public int addAccessibilityInteractionConnection(IWindow windowToken, - IAccessibilityInteractionConnection connection, int userId) throws RemoteException { - synchronized (mLock) { - final int resolvedUserId = mSecurityPolicy - .resolveCallingUserIdEnforcingPermissionsLocked(userId); - final int windowId = sNextWindowId++; - // If the window is from a process that runs across users such as - // the system UI or the system we add it to the global state that - // is shared across users. - if (mSecurityPolicy.isCallerInteractingAcrossUsers(userId)) { - AccessibilityConnectionWrapper wrapper = new AccessibilityConnectionWrapper( - windowId, connection, UserHandle.USER_ALL); - wrapper.linkToDeath(); - mGlobalInteractionConnections.put(windowId, wrapper); - mGlobalWindowTokens.put(windowId, windowToken.asBinder()); - if (DEBUG) { - Slog.i(LOG_TAG, "Added global connection for pid:" + Binder.getCallingPid() - + " with windowId: " + windowId); - } - } else { - AccessibilityConnectionWrapper wrapper = new AccessibilityConnectionWrapper( - windowId, connection, resolvedUserId); - wrapper.linkToDeath(); - UserState userState = getUserStateLocked(resolvedUserId); - userState.mInteractionConnections.put(windowId, wrapper); - userState.mWindowTokens.put(windowId, windowToken.asBinder()); - if (DEBUG) { - Slog.i(LOG_TAG, "Added user connection for pid:" + Binder.getCallingPid() - + " with windowId: " + windowId + " and userId:" + mCurrentUserId); - } - } - if (DEBUG) { - Slog.i(LOG_TAG, "Adding interaction connection to windowId: " + windowId); - } - return windowId; - } - } - - public void removeAccessibilityInteractionConnection(IWindow window) { - synchronized (mLock) { - mSecurityPolicy.resolveCallingUserIdEnforcingPermissionsLocked( - UserHandle.getCallingUserId()); - IBinder token = window.asBinder(); - final int removedWindowId = removeAccessibilityInteractionConnectionInternalLocked( - token, mGlobalWindowTokens, mGlobalInteractionConnections); - if (removedWindowId >= 0) { - if (DEBUG) { - Slog.i(LOG_TAG, "Removed global connection for pid:" + Binder.getCallingPid() - + " with windowId: " + removedWindowId); - } - return; - } - final int userCount = mUserStates.size(); - for (int i = 0; i < userCount; i++) { - UserState userState = mUserStates.valueAt(i); - final int removedWindowIdForUser = - removeAccessibilityInteractionConnectionInternalLocked( - token, userState.mWindowTokens, userState.mInteractionConnections); - if (removedWindowIdForUser >= 0) { - if (DEBUG) { - Slog.i(LOG_TAG, "Removed user connection for pid:" + Binder.getCallingPid() - + " with windowId: " + removedWindowIdForUser + " and userId:" - + mUserStates.keyAt(i)); - } - return; - } - } - } - } - - private int removeAccessibilityInteractionConnectionInternalLocked(IBinder windowToken, - SparseArray<IBinder> windowTokens, - SparseArray<AccessibilityConnectionWrapper> interactionConnections) { - final int count = windowTokens.size(); - for (int i = 0; i < count; i++) { - if (windowTokens.valueAt(i) == windowToken) { - final int windowId = windowTokens.keyAt(i); - windowTokens.removeAt(i); - AccessibilityConnectionWrapper wrapper = interactionConnections.get(windowId); - wrapper.unlinkToDeath(); - interactionConnections.remove(windowId); - return windowId; - } - } - return -1; - } - - public void registerUiTestAutomationService(IBinder owner, - IAccessibilityServiceClient serviceClient, - AccessibilityServiceInfo accessibilityServiceInfo) { - mSecurityPolicy.enforceCallingPermission(Manifest.permission.RETRIEVE_WINDOW_CONTENT, - FUNCTION_REGISTER_UI_TEST_AUTOMATION_SERVICE); - - accessibilityServiceInfo.setComponentName(sFakeAccessibilityServiceComponentName); - - synchronized (mLock) { - UserState userState = getCurrentUserStateLocked(); - - if (userState.mUiAutomationService != null) { - throw new IllegalStateException("UiAutomationService " + serviceClient - + "already registered!"); - } - - try { - owner.linkToDeath(userState.mUiAutomationSerivceOnwerDeathRecipient, 0); - } catch (RemoteException re) { - Slog.e(LOG_TAG, "Couldn't register for the death of a" - + " UiTestAutomationService!", re); - return; - } - - userState.mUiAutomationServiceOwner = owner; - userState.mUiAutomationServiceClient = serviceClient; - - // Set the temporary state. - userState.mIsAccessibilityEnabled = true; - userState.mIsTouchExplorationEnabled = false; - userState.mIsEnhancedWebAccessibilityEnabled = false; - userState.mIsDisplayMagnificationEnabled = false; - userState.mInstalledServices.add(accessibilityServiceInfo); - userState.mEnabledServices.clear(); - userState.mEnabledServices.add(sFakeAccessibilityServiceComponentName); - userState.mTouchExplorationGrantedServices.add(sFakeAccessibilityServiceComponentName); - - // Use the new state instead of settings. - onUserStateChangedLocked(userState); - } - } - - public void unregisterUiTestAutomationService(IAccessibilityServiceClient serviceClient) { - synchronized (mLock) { - UserState userState = getCurrentUserStateLocked(); - // Automation service is not bound, so pretend it died to perform clean up. - if (userState.mUiAutomationService != null - && serviceClient != null - && userState.mUiAutomationService != null - && userState.mUiAutomationService.mServiceInterface != null - && userState.mUiAutomationService.mServiceInterface.asBinder() - == serviceClient.asBinder()) { - userState.mUiAutomationService.binderDied(); - } else { - throw new IllegalStateException("UiAutomationService " + serviceClient - + " not registered!"); - } - } - } - - public void temporaryEnableAccessibilityStateUntilKeyguardRemoved( - ComponentName service, boolean touchExplorationEnabled) { - mSecurityPolicy.enforceCallingPermission( - Manifest.permission.TEMPORARY_ENABLE_ACCESSIBILITY, - TEMPORARY_ENABLE_ACCESSIBILITY_UNTIL_KEYGUARD_REMOVED); - try { - if (!mWindowManagerService.isKeyguardLocked()) { - return; - } - } catch (RemoteException re) { - return; - } - synchronized (mLock) { - // Set the temporary state. - UserState userState = getCurrentUserStateLocked(); - - // This is a nop if UI automation is enabled. - if (userState.mUiAutomationService != null) { - return; - } - - userState.mIsAccessibilityEnabled = true; - userState.mIsTouchExplorationEnabled = touchExplorationEnabled; - userState.mIsEnhancedWebAccessibilityEnabled = false; - userState.mIsDisplayMagnificationEnabled = false; - userState.mEnabledServices.clear(); - userState.mEnabledServices.add(service); - userState.mBindingServices.clear(); - userState.mTouchExplorationGrantedServices.clear(); - userState.mTouchExplorationGrantedServices.add(service); - - // User the current state instead settings. - onUserStateChangedLocked(userState); - } - } - - boolean onGesture(int gestureId) { - synchronized (mLock) { - boolean handled = notifyGestureLocked(gestureId, false); - if (!handled) { - handled = notifyGestureLocked(gestureId, true); - } - return handled; - } - } - - boolean notifyKeyEvent(KeyEvent event, int policyFlags) { - synchronized (mLock) { - KeyEvent localClone = KeyEvent.obtain(event); - boolean handled = notifyKeyEventLocked(localClone, policyFlags, false); - if (!handled) { - handled = notifyKeyEventLocked(localClone, policyFlags, true); - } - return handled; - } - } - - /** - * Gets the bounds of the accessibility focus in the active window. - * - * @param outBounds The output to which to write the focus bounds. - * @return Whether accessibility focus was found and the bounds are populated. - */ - // TODO: (multi-display) Make sure this works for multiple displays. - boolean getAccessibilityFocusBoundsInActiveWindow(Rect outBounds) { - // Instead of keeping track of accessibility focus events per - // window to be able to find the focus in the active window, - // we take a stateless approach and look it up. This is fine - // since we do this only when the user clicks/long presses. - Service service = getQueryBridge(); - final int connectionId = service.mId; - AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance(); - client.addConnection(connectionId, service); - try { - AccessibilityNodeInfo root = AccessibilityInteractionClient.getInstance() - .getRootInActiveWindow(connectionId); - if (root == null) { - return false; - } - AccessibilityNodeInfo focus = root.findFocus( - AccessibilityNodeInfo.FOCUS_ACCESSIBILITY); - if (focus == null) { - return false; - } - focus.getBoundsInScreen(outBounds); - - MagnificationSpec spec = service.getCompatibleMagnificationSpec(focus.getWindowId()); - if (spec != null && !spec.isNop()) { - outBounds.offset((int) -spec.offsetX, (int) -spec.offsetY); - outBounds.scale(1 / spec.scale); - } - - // Clip to the window rectangle. - Rect windowBounds = mTempRect; - getActiveWindowBounds(windowBounds); - outBounds.intersect(windowBounds); - // Clip to the screen rectangle. - mDefaultDisplay.getRealSize(mTempPoint); - outBounds.intersect(0, 0, mTempPoint.x, mTempPoint.y); - - return true; - } finally { - client.removeConnection(connectionId); - } - } - - /** - * Gets the bounds of the active window. - * - * @param outBounds The output to which to write the bounds. - */ - boolean getActiveWindowBounds(Rect outBounds) { - IBinder token; - synchronized (mLock) { - final int windowId = mSecurityPolicy.mActiveWindowId; - token = mGlobalWindowTokens.get(windowId); - if (token == null) { - token = getCurrentUserStateLocked().mWindowTokens.get(windowId); - } - } - try { - mWindowManagerService.getWindowFrame(token, outBounds); - if (!outBounds.isEmpty()) { - return true; - } - } catch (RemoteException re) { - /* ignore */ - } - return false; - } - - int getActiveWindowId() { - return mSecurityPolicy.mActiveWindowId; - } - - void onTouchInteractionStart() { - mSecurityPolicy.onTouchInteractionStart(); - } - - void onTouchInteractionEnd() { - mSecurityPolicy.onTouchInteractionEnd(); - } - - void onMagnificationStateChanged() { - notifyClearAccessibilityNodeInfoCacheLocked(); - } - - private void switchUser(int userId) { - synchronized (mLock) { - if (mCurrentUserId == userId && mInitialized) { - return; - } - - // Disconnect from services for the old user. - UserState oldUserState = getUserStateLocked(mCurrentUserId); - oldUserState.onSwitchToAnotherUser(); - - // Disable the local managers for the old user. - if (oldUserState.mClients.getRegisteredCallbackCount() > 0) { - mMainHandler.obtainMessage(MainHandler.MSG_SEND_CLEARED_STATE_TO_CLIENTS_FOR_USER, - oldUserState.mUserId, 0).sendToTarget(); - } - - // Announce user changes only if more that one exist. - UserManager userManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE); - final boolean announceNewUser = userManager.getUsers().size() > 1; - - // The user changed. - mCurrentUserId = userId; - - UserState userState = getCurrentUserStateLocked(); - if (userState.mUiAutomationService != null) { - // Switching users disables the UI automation service. - userState.mUiAutomationService.binderDied(); - } - - readConfigurationForUserStateLocked(userState); - // Even if reading did not yield change, we have to update - // the state since the context in which the current user - // state was used has changed since it was inactive. - onUserStateChangedLocked(userState); - - if (announceNewUser) { - // Schedule announcement of the current user if needed. - mMainHandler.sendEmptyMessageDelayed(MainHandler.MSG_ANNOUNCE_NEW_USER_IF_NEEDED, - WAIT_FOR_USER_STATE_FULLY_INITIALIZED_MILLIS); - } - } - } - - private void removeUser(int userId) { - synchronized (mLock) { - mUserStates.remove(userId); - } - } - - private Service getQueryBridge() { - if (mQueryBridge == null) { - AccessibilityServiceInfo info = new AccessibilityServiceInfo(); - info.setCapabilities(AccessibilityServiceInfo.CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT); - mQueryBridge = new Service(UserHandle.USER_NULL, - sFakeAccessibilityServiceComponentName, info); - } - return mQueryBridge; - } - - private boolean notifyGestureLocked(int gestureId, boolean isDefault) { - // TODO: Now we are giving the gestures to the last enabled - // service that can handle them which is the last one - // in our list since we write the last enabled as the - // last record in the enabled services setting. Ideally, - // the user should make the call which service handles - // gestures. However, only one service should handle - // gestures to avoid user frustration when different - // behavior is observed from different combinations of - // enabled accessibility services. - UserState state = getCurrentUserStateLocked(); - for (int i = state.mBoundServices.size() - 1; i >= 0; i--) { - Service service = state.mBoundServices.get(i); - if (service.mRequestTouchExplorationMode && service.mIsDefault == isDefault) { - service.notifyGesture(gestureId); - return true; - } - } - return false; - } - - private boolean notifyKeyEventLocked(KeyEvent event, int policyFlags, boolean isDefault) { - // TODO: Now we are giving the key events to the last enabled - // service that can handle them Ideally, the user should - // make the call which service handles key events. However, - // only one service should handle key events to avoid user - // frustration when different behavior is observed from - // different combinations of enabled accessibility services. - UserState state = getCurrentUserStateLocked(); - for (int i = state.mBoundServices.size() - 1; i >= 0; i--) { - Service service = state.mBoundServices.get(i); - // Key events are handled only by services that declared - // this capability and requested to filter key events. - if (!service.mRequestFilterKeyEvents || - (service.mAccessibilityServiceInfo.getCapabilities() & AccessibilityServiceInfo - .CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS) == 0) { - continue; - } - if (service.mIsDefault == isDefault) { - service.notifyKeyEvent(event, policyFlags); - return true; - } - } - return false; - } - - private void notifyClearAccessibilityNodeInfoCacheLocked() { - UserState state = getCurrentUserStateLocked(); - for (int i = state.mBoundServices.size() - 1; i >= 0; i--) { - Service service = state.mBoundServices.get(i); - service.notifyClearAccessibilityNodeInfoCache(); - } - } - - /** - * Removes an AccessibilityInteractionConnection. - * - * @param windowId The id of the window to which the connection is targeted. - * @param userId The id of the user owning the connection. UserHandle.USER_ALL - * if global. - */ - private void removeAccessibilityInteractionConnectionLocked(int windowId, int userId) { - if (userId == UserHandle.USER_ALL) { - mGlobalWindowTokens.remove(windowId); - mGlobalInteractionConnections.remove(windowId); - } else { - UserState userState = getCurrentUserStateLocked(); - userState.mWindowTokens.remove(windowId); - userState.mInteractionConnections.remove(windowId); - } - if (DEBUG) { - Slog.i(LOG_TAG, "Removing interaction connection to windowId: " + windowId); - } - } - - private boolean readInstalledAccessibilityServiceLocked(UserState userState) { - mTempAccessibilityServiceInfoList.clear(); - - List<ResolveInfo> installedServices = mPackageManager.queryIntentServicesAsUser( - new Intent(AccessibilityService.SERVICE_INTERFACE), - PackageManager.GET_SERVICES | PackageManager.GET_META_DATA, - mCurrentUserId); - - for (int i = 0, count = installedServices.size(); i < count; i++) { - ResolveInfo resolveInfo = installedServices.get(i); - ServiceInfo serviceInfo = resolveInfo.serviceInfo; - if (!android.Manifest.permission.BIND_ACCESSIBILITY_SERVICE.equals( - serviceInfo.permission)) { - Slog.w(LOG_TAG, "Skipping accessibilty service " + new ComponentName( - serviceInfo.packageName, serviceInfo.name).flattenToShortString() - + ": it does not require the permission " - + android.Manifest.permission.BIND_ACCESSIBILITY_SERVICE); - continue; - } - AccessibilityServiceInfo accessibilityServiceInfo; - try { - accessibilityServiceInfo = new AccessibilityServiceInfo(resolveInfo, mContext); - mTempAccessibilityServiceInfoList.add(accessibilityServiceInfo); - } catch (XmlPullParserException xppe) { - Slog.e(LOG_TAG, "Error while initializing AccessibilityServiceInfo", xppe); - } catch (IOException ioe) { - Slog.e(LOG_TAG, "Error while initializing AccessibilityServiceInfo", ioe); - } - } - - if (!mTempAccessibilityServiceInfoList.equals(userState.mInstalledServices)) { - userState.mInstalledServices.clear(); - userState.mInstalledServices.addAll(mTempAccessibilityServiceInfoList); - mTempAccessibilityServiceInfoList.clear(); - return true; - } - - mTempAccessibilityServiceInfoList.clear(); - return false; - } - - private boolean readEnabledAccessibilityServicesLocked(UserState userState) { - mTempComponentNameSet.clear(); - readComponentNamesFromSettingLocked(Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, - userState.mUserId, mTempComponentNameSet); - if (!mTempComponentNameSet.equals(userState.mEnabledServices)) { - userState.mEnabledServices.clear(); - userState.mEnabledServices.addAll(mTempComponentNameSet); - mTempComponentNameSet.clear(); - return true; - } - mTempComponentNameSet.clear(); - return false; - } - - private boolean readTouchExplorationGrantedAccessibilityServicesLocked( - UserState userState) { - mTempComponentNameSet.clear(); - readComponentNamesFromSettingLocked( - Settings.Secure.TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES, - userState.mUserId, mTempComponentNameSet); - if (!mTempComponentNameSet.equals(userState.mTouchExplorationGrantedServices)) { - userState.mTouchExplorationGrantedServices.clear(); - userState.mTouchExplorationGrantedServices.addAll(mTempComponentNameSet); - mTempComponentNameSet.clear(); - return true; - } - mTempComponentNameSet.clear(); - return false; - } - - /** - * Performs {@link AccessibilityService}s delayed notification. The delay is configurable - * and denotes the period after the last event before notifying the service. - * - * @param event The event. - * @param isDefault True to notify default listeners, not default services. - */ - private void notifyAccessibilityServicesDelayedLocked(AccessibilityEvent event, - boolean isDefault) { - try { - UserState state = getCurrentUserStateLocked(); - for (int i = 0, count = state.mBoundServices.size(); i < count; i++) { - Service service = state.mBoundServices.get(i); - - if (service.mIsDefault == isDefault) { - if (canDispathEventLocked(service, event, state.mHandledFeedbackTypes)) { - state.mHandledFeedbackTypes |= service.mFeedbackType; - 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. - return; - } - } - - private void addServiceLocked(Service service, UserState userState) { - try { - service.linkToOwnDeathLocked(); - userState.mBoundServices.add(service); - userState.mComponentNameToServiceMap.put(service.mComponentName, service); - } catch (RemoteException re) { - /* do nothing */ - } - } - - /** - * Removes a service. - * - * @param service The service. - * @return True if the service was removed, false otherwise. - */ - private void removeServiceLocked(Service service, UserState userState) { - userState.mBoundServices.remove(service); - userState.mComponentNameToServiceMap.remove(service.mComponentName); - service.unlinkToOwnDeathLocked(); - } - - /** - * Determines if given event can be dispatched to a service based on the package of the - * event source and already notified services for that event type. Specifically, a - * service is notified if it is interested in events from the package and no other service - * providing the same feedback type has been notified. Exception are services the - * provide generic feedback (feedback type left as a safety net for unforeseen feedback - * types) which are always notified. - * - * @param service The potential receiver. - * @param event The event. - * @param handledFeedbackTypes The feedback types for which services have been notified. - * @return True if the listener should be notified, false otherwise. - */ - private boolean canDispathEventLocked(Service service, AccessibilityEvent event, - int handledFeedbackTypes) { - - if (!service.canReceiveEventsLocked()) { - return false; - } - - if (!event.isImportantForAccessibility() - && (service.mFetchFlags - & AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS) == 0) { - return false; - } - - int eventType = event.getEventType(); - if ((service.mEventTypes & eventType) != eventType) { - return false; - } - - Set<String> packageNames = service.mPackageNames; - CharSequence packageName = event.getPackageName(); - - if (packageNames.isEmpty() || packageNames.contains(packageName)) { - int feedbackType = service.mFeedbackType; - if ((handledFeedbackTypes & feedbackType) != feedbackType - || feedbackType == AccessibilityServiceInfo.FEEDBACK_GENERIC) { - return true; - } - } - - return false; - } - - private void unbindAllServicesLocked(UserState userState) { - List<Service> services = userState.mBoundServices; - for (int i = 0, count = services.size(); i < count; i++) { - Service service = services.get(i); - if (service.unbindLocked()) { - i--; - count--; - } - } - } - - /** - * Populates a set with the {@link ComponentName}s stored in a colon - * separated value setting for a given user. - * - * @param settingName The setting to parse. - * @param userId The user id. - * @param outComponentNames The output component names. - */ - private void readComponentNamesFromSettingLocked(String settingName, int userId, - Set<ComponentName> outComponentNames) { - String settingValue = Settings.Secure.getStringForUser(mContext.getContentResolver(), - settingName, userId); - outComponentNames.clear(); - if (settingValue != null) { - TextUtils.SimpleStringSplitter splitter = mStringColonSplitter; - splitter.setString(settingValue); - while (splitter.hasNext()) { - String str = splitter.next(); - if (str == null || str.length() <= 0) { - continue; - } - ComponentName enabledService = ComponentName.unflattenFromString(str); - if (enabledService != null) { - outComponentNames.add(enabledService); - } - } - } - } - - /** - * Persists the component names in the specified setting in a - * colon separated fashion. - * - * @param settingName The setting name. - * @param componentNames The component names. - */ - private void persistComponentNamesToSettingLocked(String settingName, - Set<ComponentName> componentNames, int userId) { - StringBuilder builder = new StringBuilder(); - for (ComponentName componentName : componentNames) { - if (builder.length() > 0) { - builder.append(COMPONENT_NAME_SEPARATOR); - } - builder.append(componentName.flattenToShortString()); - } - Settings.Secure.putStringForUser(mContext.getContentResolver(), - settingName, builder.toString(), userId); - } - - private void manageServicesLocked(UserState userState) { - Map<ComponentName, Service> componentNameToServiceMap = - userState.mComponentNameToServiceMap; - boolean isEnabled = userState.mIsAccessibilityEnabled; - - for (int i = 0, count = userState.mInstalledServices.size(); i < count; i++) { - AccessibilityServiceInfo installedService = userState.mInstalledServices.get(i); - ComponentName componentName = ComponentName.unflattenFromString( - installedService.getId()); - Service service = componentNameToServiceMap.get(componentName); - - if (isEnabled) { - // Wait for the binding if it is in process. - if (userState.mBindingServices.contains(componentName)) { - continue; - } - if (userState.mEnabledServices.contains(componentName)) { - if (service == null) { - service = new Service(userState.mUserId, componentName, installedService); - } else if (userState.mBoundServices.contains(service)) { - continue; - } - service.bindLocked(); - } else { - if (service != null) { - service.unbindLocked(); - } - } - } else { - if (service != null) { - service.unbindLocked(); - } else { - userState.mBindingServices.remove(componentName); - } - } - } - - // No enabled installed services => disable accessibility to avoid - // sending accessibility events with no recipient across processes. - if (isEnabled && userState.mEnabledServices.isEmpty()) { - userState.mIsAccessibilityEnabled = false; - Settings.Secure.putIntForUser(mContext.getContentResolver(), - Settings.Secure.ACCESSIBILITY_ENABLED, 0, userState.mUserId); - } - } - - private void scheduleUpdateClientsIfNeededLocked(UserState userState) { - final int clientState = userState.getClientState(); - if (userState.mLastSentClientState != clientState - && (mGlobalClients.getRegisteredCallbackCount() > 0 - || userState.mClients.getRegisteredCallbackCount() > 0)) { - userState.mLastSentClientState = clientState; - mMainHandler.obtainMessage(MainHandler.MSG_SEND_STATE_TO_CLIENTS, - clientState, userState.mUserId) .sendToTarget(); - } - } - - private void scheduleUpdateInputFilter(UserState userState) { - mMainHandler.obtainMessage(MainHandler.MSG_UPDATE_INPUT_FILTER, userState).sendToTarget(); - } - - private void updateInputFilter(UserState userState) { - boolean setInputFilter = false; - AccessibilityInputFilter inputFilter = null; - synchronized (mLock) { - int flags = 0; - if (userState.mIsDisplayMagnificationEnabled) { - flags |= AccessibilityInputFilter.FLAG_FEATURE_SCREEN_MAGNIFIER; - } - // Touch exploration without accessibility makes no sense. - if (userState.mIsAccessibilityEnabled && userState.mIsTouchExplorationEnabled) { - flags |= AccessibilityInputFilter.FLAG_FEATURE_TOUCH_EXPLORATION; - } - if (userState.mIsFilterKeyEventsEnabled) { - flags |= AccessibilityInputFilter.FLAG_FEATURE_FILTER_KEY_EVENTS; - } - if (flags != 0) { - if (!mHasInputFilter) { - mHasInputFilter = true; - if (mInputFilter == null) { - mInputFilter = new AccessibilityInputFilter(mContext, - AccessibilityManagerService.this); - } - inputFilter = mInputFilter; - setInputFilter = true; - } - mInputFilter.setEnabledFeatures(flags); - } else { - if (mHasInputFilter) { - mHasInputFilter = false; - mInputFilter.disableFeatures(); - inputFilter = null; - setInputFilter = true; - } - } - } - if (setInputFilter) { - try { - mWindowManagerService.setInputFilter(inputFilter); - } catch (RemoteException re) { - /* ignore */ - } - } - } - - private void showEnableTouchExplorationDialog(final Service service) { - synchronized (mLock) { - String label = service.mResolveInfo.loadLabel( - mContext.getPackageManager()).toString(); - - final UserState state = getCurrentUserStateLocked(); - if (state.mIsTouchExplorationEnabled) { - return; - } - if (mEnableTouchExplorationDialog != null - && mEnableTouchExplorationDialog.isShowing()) { - return; - } - mEnableTouchExplorationDialog = new AlertDialog.Builder(mContext) - .setIconAttribute(android.R.attr.alertDialogIcon) - .setPositiveButton(android.R.string.ok, new OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - // The user allowed the service to toggle touch exploration. - state.mTouchExplorationGrantedServices.add(service.mComponentName); - persistComponentNamesToSettingLocked( - Settings.Secure.TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES, - state.mTouchExplorationGrantedServices, state.mUserId); - // Enable touch exploration. - UserState userState = getUserStateLocked(service.mUserId); - userState.mIsTouchExplorationEnabled = true; - Settings.Secure.putIntForUser(mContext.getContentResolver(), - Settings.Secure.TOUCH_EXPLORATION_ENABLED, 1, - service.mUserId); - onUserStateChangedLocked(userState); - } - }) - .setNegativeButton(android.R.string.cancel, new OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - dialog.dismiss(); - } - }) - .setTitle(R.string.enable_explore_by_touch_warning_title) - .setMessage(mContext.getString( - R.string.enable_explore_by_touch_warning_message, label)) - .create(); - mEnableTouchExplorationDialog.getWindow().setType( - WindowManager.LayoutParams.TYPE_SYSTEM_ALERT); - mEnableTouchExplorationDialog.getWindow().getAttributes().privateFlags - |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS; - mEnableTouchExplorationDialog.setCanceledOnTouchOutside(true); - mEnableTouchExplorationDialog.show(); - } - } - - private void onUserStateChangedLocked(UserState userState) { - // TODO: Remove this hack - mInitialized = true; - updateLegacyCapabilities(userState); - updateServicesLocked(userState); - updateFilterKeyEventsLocked(userState); - updateTouchExplorationLocked(userState); - updateEnhancedWebAccessibilityLocked(userState); - scheduleUpdateInputFilter(userState); - scheduleUpdateClientsIfNeededLocked(userState); - } - - private void updateLegacyCapabilities(UserState userState) { - // Up to JB-MR1 we had a white list with services that can enable touch - // exploration. When a service is first started we show a dialog to the - // use to get a permission to white list the service. - final int installedServiceCount = userState.mInstalledServices.size(); - for (int i = 0; i < installedServiceCount; i++) { - AccessibilityServiceInfo serviceInfo = userState.mInstalledServices.get(i); - ResolveInfo resolveInfo = serviceInfo.getResolveInfo(); - if ((serviceInfo.getCapabilities() - & AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION) == 0 - && resolveInfo.serviceInfo.applicationInfo.targetSdkVersion - <= Build.VERSION_CODES.JELLY_BEAN_MR1) { - ComponentName componentName = new ComponentName( - resolveInfo.serviceInfo.packageName, resolveInfo.serviceInfo.name); - if (userState.mTouchExplorationGrantedServices.contains(componentName)) { - serviceInfo.setCapabilities(serviceInfo.getCapabilities() - | AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION); - } - } - } - } - - private void updateFilterKeyEventsLocked(UserState userState) { - final int serviceCount = userState.mBoundServices.size(); - for (int i = 0; i < serviceCount; i++) { - Service service = userState.mBoundServices.get(i); - if (service.mRequestFilterKeyEvents - && (service.mAccessibilityServiceInfo.getCapabilities() - & AccessibilityServiceInfo - .CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS) != 0) { - userState.mIsFilterKeyEventsEnabled = true; - return; - } - } - userState.mIsFilterKeyEventsEnabled = false; - } - - private void updateServicesLocked(UserState userState) { - if (userState.mIsAccessibilityEnabled) { - manageServicesLocked(userState); - } else { - unbindAllServicesLocked(userState); - } - } - - private boolean readConfigurationForUserStateLocked(UserState userState) { - boolean somthingChanged = false; - somthingChanged |= readAccessibilityEnabledSettingLocked(userState); - somthingChanged |= readInstalledAccessibilityServiceLocked(userState); - somthingChanged |= readEnabledAccessibilityServicesLocked(userState); - somthingChanged |= readTouchExplorationGrantedAccessibilityServicesLocked(userState); - somthingChanged |= readTouchExplorationEnabledSettingLocked(userState); - somthingChanged |= readEnhancedWebAccessibilityEnabledChangedLocked(userState); - somthingChanged |= readDisplayMagnificationEnabledSettingLocked(userState); - return somthingChanged; - } - - private boolean readAccessibilityEnabledSettingLocked(UserState userState) { - final boolean accessibilityEnabled = Settings.Secure.getIntForUser( - mContext.getContentResolver(), - Settings.Secure.ACCESSIBILITY_ENABLED, 0, userState.mUserId) == 1; - if (accessibilityEnabled != userState.mIsAccessibilityEnabled) { - userState.mIsAccessibilityEnabled = accessibilityEnabled; - return true; - } - return false; - } - - private boolean readTouchExplorationEnabledSettingLocked(UserState userState) { - final boolean touchExplorationEnabled = Settings.Secure.getIntForUser( - mContext.getContentResolver(), - Settings.Secure.TOUCH_EXPLORATION_ENABLED, 0, userState.mUserId) == 1; - if (touchExplorationEnabled != userState.mIsTouchExplorationEnabled) { - userState.mIsTouchExplorationEnabled = touchExplorationEnabled; - return true; - } - return false; - } - - private boolean readDisplayMagnificationEnabledSettingLocked(UserState userState) { - final boolean displayMagnificationEnabled = Settings.Secure.getIntForUser( - mContext.getContentResolver(), - Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED, - 0, userState.mUserId) == 1; - if (displayMagnificationEnabled != userState.mIsDisplayMagnificationEnabled) { - userState.mIsDisplayMagnificationEnabled = displayMagnificationEnabled; - return true; - } - return false; - } - - private boolean readEnhancedWebAccessibilityEnabledChangedLocked(UserState userState) { - final boolean enhancedWeAccessibilityEnabled = Settings.Secure.getIntForUser( - mContext.getContentResolver(), Settings.Secure.ACCESSIBILITY_SCRIPT_INJECTION, - 0, userState.mUserId) == 1; - if (enhancedWeAccessibilityEnabled != userState.mIsEnhancedWebAccessibilityEnabled) { - userState.mIsEnhancedWebAccessibilityEnabled = enhancedWeAccessibilityEnabled; - return true; - } - return false; - } - - private void updateTouchExplorationLocked(UserState userState) { - boolean enabled = false; - final int serviceCount = userState.mBoundServices.size(); - for (int i = 0; i < serviceCount; i++) { - Service service = userState.mBoundServices.get(i); - if (canRequestAndRequestsTouchExplorationLocked(service)) { - enabled = true; - break; - } - } - if (enabled != userState.mIsTouchExplorationEnabled) { - userState.mIsTouchExplorationEnabled = enabled; - Settings.Secure.putIntForUser(mContext.getContentResolver(), - Settings.Secure.TOUCH_EXPLORATION_ENABLED, enabled ? 1 : 0, - userState.mUserId); - } - try { - mWindowManagerService.setTouchExplorationEnabled(enabled); - } catch (RemoteException e) { - e.printStackTrace(); - } - } - - private boolean canRequestAndRequestsTouchExplorationLocked(Service service) { - // Service not ready or cannot request the feature - well nothing to do. - if (!service.canReceiveEventsLocked() || !service.mRequestTouchExplorationMode) { - return false; - } - // UI test automation service can always enable it. - if (service.mIsAutomation) { - return true; - } - if (service.mResolveInfo.serviceInfo.applicationInfo.targetSdkVersion - <= Build.VERSION_CODES.JELLY_BEAN_MR1) { - // Up to JB-MR1 we had a white list with services that can enable touch - // exploration. When a service is first started we show a dialog to the - // use to get a permission to white list the service. - UserState userState = getUserStateLocked(service.mUserId); - if (userState.mTouchExplorationGrantedServices.contains(service.mComponentName)) { - return true; - } else if (mEnableTouchExplorationDialog == null - || !mEnableTouchExplorationDialog.isShowing()) { - mMainHandler.obtainMessage( - MainHandler.MSG_SHOW_ENABLED_TOUCH_EXPLORATION_DIALOG, - service).sendToTarget(); - } - } else { - // Starting in JB-MR2 we request an accessibility service to declare - // certain capabilities in its meta-data to allow it to enable the - // corresponding features. - if ((service.mAccessibilityServiceInfo.getCapabilities() - & AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION) != 0) { - return true; - } - } - return false; - } - - private void updateEnhancedWebAccessibilityLocked(UserState userState) { - boolean enabled = false; - final int serviceCount = userState.mBoundServices.size(); - for (int i = 0; i < serviceCount; i++) { - Service service = userState.mBoundServices.get(i); - if (canRequestAndRequestsEnhancedWebAccessibilityLocked(service)) { - enabled = true; - break; - } - } - if (enabled != userState.mIsEnhancedWebAccessibilityEnabled) { - userState.mIsEnhancedWebAccessibilityEnabled = enabled; - Settings.Secure.putIntForUser(mContext.getContentResolver(), - Settings.Secure.ACCESSIBILITY_SCRIPT_INJECTION, enabled ? 1 : 0, - userState.mUserId); - } - } - - private boolean canRequestAndRequestsEnhancedWebAccessibilityLocked(Service service) { - if (!service.canReceiveEventsLocked() || !service.mRequestEnhancedWebAccessibility ) { - return false; - } - if (service.mIsAutomation || (service.mAccessibilityServiceInfo.getCapabilities() - & AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY) != 0) { - return true; - } - return false; - } - - @Override - public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) { - mSecurityPolicy.enforceCallingPermission(Manifest.permission.DUMP, FUNCTION_DUMP); - synchronized (mLock) { - pw.println("ACCESSIBILITY MANAGER (dumpsys accessibility)"); - pw.println(); - final int userCount = mUserStates.size(); - for (int i = 0; i < userCount; i++) { - UserState userState = mUserStates.valueAt(i); - pw.append("User state[attributes:{id=" + userState.mUserId); - pw.append(", currentUser=" + (userState.mUserId == mCurrentUserId)); - pw.append(", accessibilityEnabled=" + userState.mIsAccessibilityEnabled); - pw.append(", touchExplorationEnabled=" + userState.mIsTouchExplorationEnabled); - pw.append(", displayMagnificationEnabled=" - + userState.mIsDisplayMagnificationEnabled); - if (userState.mUiAutomationService != null) { - pw.append(", "); - userState.mUiAutomationService.dump(fd, pw, args); - pw.println(); - } - pw.append("}"); - pw.println(); - pw.append(" services:{"); - final int serviceCount = userState.mBoundServices.size(); - for (int j = 0; j < serviceCount; j++) { - if (j > 0) { - pw.append(", "); - pw.println(); - pw.append(" "); - } - Service service = userState.mBoundServices.get(j); - service.dump(fd, pw, args); - } - pw.println("}]"); - pw.println(); - } - } - } - - private class AccessibilityConnectionWrapper implements DeathRecipient { - private final int mWindowId; - private final int mUserId; - private final IAccessibilityInteractionConnection mConnection; - - public AccessibilityConnectionWrapper(int windowId, - IAccessibilityInteractionConnection connection, int userId) { - mWindowId = windowId; - mUserId = userId; - mConnection = connection; - } - - public void linkToDeath() throws RemoteException { - mConnection.asBinder().linkToDeath(this, 0); - } - - public void unlinkToDeath() { - mConnection.asBinder().unlinkToDeath(this, 0); - } - - @Override - public void binderDied() { - unlinkToDeath(); - synchronized (mLock) { - removeAccessibilityInteractionConnectionLocked(mWindowId, mUserId); - } - } - } - - private final class MainHandler extends Handler { - public static final int MSG_SEND_ACCESSIBILITY_EVENT_TO_INPUT_FILTER = 1; - public static final int MSG_SEND_STATE_TO_CLIENTS = 2; - public static final int MSG_SEND_CLEARED_STATE_TO_CLIENTS_FOR_USER = 3; - public static final int MSG_UPDATE_ACTIVE_WINDOW = 4; - public static final int MSG_ANNOUNCE_NEW_USER_IF_NEEDED = 5; - public static final int MSG_UPDATE_INPUT_FILTER = 6; - public static final int MSG_SHOW_ENABLED_TOUCH_EXPLORATION_DIALOG = 7; - public static final int MSG_SEND_KEY_EVENT_TO_INPUT_FILTER = 8; - - public MainHandler(Looper looper) { - super(looper); - } - - @Override - public void handleMessage(Message msg) { - final int type = msg.what; - switch (type) { - case MSG_SEND_ACCESSIBILITY_EVENT_TO_INPUT_FILTER: { - AccessibilityEvent event = (AccessibilityEvent) msg.obj; - synchronized (mLock) { - if (mHasInputFilter && mInputFilter != null) { - mInputFilter.notifyAccessibilityEvent(event); - } - } - event.recycle(); - } break; - case MSG_SEND_KEY_EVENT_TO_INPUT_FILTER: { - KeyEvent event = (KeyEvent) msg.obj; - final int policyFlags = msg.arg1; - synchronized (mLock) { - if (mHasInputFilter && mInputFilter != null) { - mInputFilter.sendInputEvent(event, policyFlags); - } - } - event.recycle(); - } break; - case MSG_SEND_STATE_TO_CLIENTS: { - final int clientState = msg.arg1; - final int userId = msg.arg2; - sendStateToClients(clientState, mGlobalClients); - sendStateToClientsForUser(clientState, userId); - } break; - case MSG_SEND_CLEARED_STATE_TO_CLIENTS_FOR_USER: { - final int userId = msg.arg1; - sendStateToClientsForUser(0, userId); - } break; - case MSG_UPDATE_ACTIVE_WINDOW: { - final int windowId = msg.arg1; - final int eventType = msg.arg2; - mSecurityPolicy.updateActiveWindow(windowId, eventType); - } break; - case MSG_ANNOUNCE_NEW_USER_IF_NEEDED: { - announceNewUserIfNeeded(); - } break; - case MSG_UPDATE_INPUT_FILTER: { - UserState userState = (UserState) msg.obj; - updateInputFilter(userState); - } break; - case MSG_SHOW_ENABLED_TOUCH_EXPLORATION_DIALOG: { - Service service = (Service) msg.obj; - showEnableTouchExplorationDialog(service); - } break; - } - } - - private void announceNewUserIfNeeded() { - synchronized (mLock) { - UserState userState = getCurrentUserStateLocked(); - if (userState.mIsAccessibilityEnabled) { - 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); - event.setWindowId(mSecurityPolicy.getRetrievalAllowingWindowLocked()); - sendAccessibilityEvent(event, mCurrentUserId); - } - } - } - - private void sendStateToClientsForUser(int clientState, int userId) { - final UserState userState; - synchronized (mLock) { - userState = getUserStateLocked(userId); - } - sendStateToClients(clientState, userState.mClients); - } - - private void sendStateToClients(int clientState, - RemoteCallbackList<IAccessibilityManagerClient> clients) { - try { - final int userClientCount = clients.beginBroadcast(); - for (int i = 0; i < userClientCount; i++) { - IAccessibilityManagerClient client = clients.getBroadcastItem(i); - try { - client.setState(clientState); - } catch (RemoteException re) { - /* ignore */ - } - } - } finally { - clients.finishBroadcast(); - } - } - } - - private PendingEvent obtainPendingEventLocked(KeyEvent event, int policyFlags, int sequence) { - PendingEvent pendingEvent = mPendingEventPool.acquire(); - if (pendingEvent == null) { - pendingEvent = new PendingEvent(); - } - pendingEvent.event = event; - pendingEvent.policyFlags = policyFlags; - pendingEvent.sequence = sequence; - return pendingEvent; - } - - private void recyclePendingEventLocked(PendingEvent pendingEvent) { - pendingEvent.clear(); - mPendingEventPool.release(pendingEvent); - } - - /** - * This class represents an accessibility service. It stores all per service - * data required for the service management, provides API for starting/stopping the - * service and is responsible for adding/removing the service in the data structures - * for service management. The class also exposes configuration interface that is - * passed to the service it represents as soon it is bound. It also serves as the - * connection for the service. - */ - class Service extends IAccessibilityServiceConnection.Stub - implements ServiceConnection, DeathRecipient {; - - final int mUserId; - - int mId = 0; - - AccessibilityServiceInfo mAccessibilityServiceInfo; - - IBinder mService; - - IAccessibilityServiceClient mServiceInterface; - - int mEventTypes; - - int mFeedbackType; - - Set<String> mPackageNames = new HashSet<String>(); - - boolean mIsDefault; - - boolean mRequestTouchExplorationMode; - - boolean mRequestEnhancedWebAccessibility; - - boolean mRequestFilterKeyEvents; - - int mFetchFlags; - - long mNotificationTimeout; - - ComponentName mComponentName; - - Intent mIntent; - - boolean mIsAutomation; - - final Rect mTempBounds = new Rect(); - - final ResolveInfo mResolveInfo; - - // the events pending events to be dispatched to this service - final SparseArray<AccessibilityEvent> mPendingEvents = - new SparseArray<AccessibilityEvent>(); - - final KeyEventDispatcher mKeyEventDispatcher = new KeyEventDispatcher(); - - boolean mWasConnectedAndDied; - - // Handler only for dispatching accessibility events since we use event - // types as message types allowing us to remove messages per event type. - public Handler mEventDispatchHandler = new Handler(mMainHandler.getLooper()) { - @Override - public void handleMessage(Message message) { - final int eventType = message.what; - notifyAccessibilityEventInternal(eventType); - } - }; - - // Handler for scheduling method invocations on the main thread. - public InvocationHandler mInvocationHandler = new InvocationHandler( - mMainHandler.getLooper()); - - public Service(int userId, ComponentName componentName, - AccessibilityServiceInfo accessibilityServiceInfo) { - mUserId = userId; - mResolveInfo = accessibilityServiceInfo.getResolveInfo(); - mId = sIdCounter++; - mComponentName = componentName; - mAccessibilityServiceInfo = accessibilityServiceInfo; - mIsAutomation = (sFakeAccessibilityServiceComponentName.equals(componentName)); - if (!mIsAutomation) { - mIntent = new Intent().setComponent(mComponentName); - mIntent.putExtra(Intent.EXTRA_CLIENT_LABEL, - com.android.internal.R.string.accessibility_binding_label); - mIntent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivity( - mContext, 0, new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS), 0)); - } - setDynamicallyConfigurableProperties(accessibilityServiceInfo); - } - - public void setDynamicallyConfigurableProperties(AccessibilityServiceInfo info) { - mEventTypes = info.eventTypes; - mFeedbackType = info.feedbackType; - String[] packageNames = info.packageNames; - if (packageNames != null) { - mPackageNames.addAll(Arrays.asList(packageNames)); - } - mNotificationTimeout = info.notificationTimeout; - mIsDefault = (info.flags & DEFAULT) != 0; - - if (mIsAutomation || info.getResolveInfo().serviceInfo.applicationInfo.targetSdkVersion - >= Build.VERSION_CODES.JELLY_BEAN) { - if ((info.flags & AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS) != 0) { - mFetchFlags |= AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS; - } else { - mFetchFlags &= ~AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS; - } - } - - if ((info.flags & AccessibilityServiceInfo.FLAG_REPORT_VIEW_IDS) != 0) { - mFetchFlags |= AccessibilityNodeInfo.FLAG_REPORT_VIEW_IDS; - } else { - mFetchFlags &= ~AccessibilityNodeInfo.FLAG_REPORT_VIEW_IDS; - } - - mRequestTouchExplorationMode = (info.flags - & AccessibilityServiceInfo.FLAG_REQUEST_TOUCH_EXPLORATION_MODE) != 0; - mRequestEnhancedWebAccessibility = (info.flags - & AccessibilityServiceInfo.FLAG_REQUEST_ENHANCED_WEB_ACCESSIBILITY) != 0; - mRequestFilterKeyEvents = (info.flags - & AccessibilityServiceInfo.FLAG_REQUEST_FILTER_KEY_EVENTS) != 0; - } - - /** - * Binds to the accessibility service. - * - * @return True if binding is successful. - */ - public boolean bindLocked() { - UserState userState = getUserStateLocked(mUserId); - if (!mIsAutomation) { - if (mService == null && mContext.bindServiceAsUser( - mIntent, this, Context.BIND_AUTO_CREATE, new UserHandle(mUserId))) { - userState.mBindingServices.add(mComponentName); - } - } else { - userState.mBindingServices.add(mComponentName); - mService = userState.mUiAutomationServiceClient.asBinder(); - onServiceConnected(mComponentName, mService); - userState.mUiAutomationService = this; - } - return false; - } - - /** - * Unbinds form the accessibility service and removes it from the data - * structures for service management. - * - * @return True if unbinding is successful. - */ - public boolean unbindLocked() { - if (mService == null) { - return false; - } - UserState userState = getUserStateLocked(mUserId); - mKeyEventDispatcher.flush(); - if (!mIsAutomation) { - mContext.unbindService(this); - } else { - userState.destroyUiAutomationService(); - } - removeServiceLocked(this, userState); - resetLocked(); - return true; - } - - public boolean canReceiveEventsLocked() { - return (mEventTypes != 0 && mFeedbackType != 0 && mService != null); - } - - @Override - public void setOnKeyEventResult(boolean handled, int sequence) { - mKeyEventDispatcher.setOnKeyEventResult(handled, sequence); - } - - @Override - public AccessibilityServiceInfo getServiceInfo() { - synchronized (mLock) { - return mAccessibilityServiceInfo; - } - } - - @Override - public void setServiceInfo(AccessibilityServiceInfo info) { - final long identity = Binder.clearCallingIdentity(); - try { - synchronized (mLock) { - // If the XML manifest had data to configure the service its info - // should be already set. In such a case update only the dynamically - // configurable properties. - AccessibilityServiceInfo oldInfo = mAccessibilityServiceInfo; - if (oldInfo != null) { - oldInfo.updateDynamicallyConfigurableProperties(info); - setDynamicallyConfigurableProperties(oldInfo); - } else { - setDynamicallyConfigurableProperties(info); - } - UserState userState = getUserStateLocked(mUserId); - onUserStateChangedLocked(userState); - } - } finally { - Binder.restoreCallingIdentity(identity); - } - } - - @Override - public void onServiceConnected(ComponentName componentName, IBinder service) { - synchronized (mLock) { - mService = service; - mServiceInterface = IAccessibilityServiceClient.Stub.asInterface(service); - UserState userState = getUserStateLocked(mUserId); - addServiceLocked(this, userState); - if (userState.mBindingServices.contains(mComponentName) || mWasConnectedAndDied) { - userState.mBindingServices.remove(mComponentName); - mWasConnectedAndDied = false; - try { - mServiceInterface.setConnection(this, mId); - onUserStateChangedLocked(userState); - } catch (RemoteException re) { - Slog.w(LOG_TAG, "Error while setting connection for service: " - + service, re); - binderDied(); - } - } else { - binderDied(); - } - } - } - - @Override - public boolean findAccessibilityNodeInfosByViewId(int accessibilityWindowId, - long accessibilityNodeId, String viewIdResName, int interactionId, - IAccessibilityInteractionConnectionCallback callback, long interrogatingTid) - throws RemoteException { - final int resolvedWindowId; - IAccessibilityInteractionConnection connection = null; - synchronized (mLock) { - final int resolvedUserId = mSecurityPolicy - .resolveCallingUserIdEnforcingPermissionsLocked( - UserHandle.getCallingUserId()); - if (resolvedUserId != mCurrentUserId) { - return false; - } - mSecurityPolicy.enforceCanRetrieveWindowContent(this); - final boolean permissionGranted = mSecurityPolicy.canRetrieveWindowContent(this); - if (!permissionGranted) { - return false; - } else { - resolvedWindowId = resolveAccessibilityWindowIdLocked(accessibilityWindowId); - connection = getConnectionLocked(resolvedWindowId); - if (connection == null) { - return false; - } - } - } - final int interrogatingPid = Binder.getCallingPid(); - final long identityToken = Binder.clearCallingIdentity(); - MagnificationSpec spec = getCompatibleMagnificationSpec(resolvedWindowId); - try { - connection.findAccessibilityNodeInfosByViewId(accessibilityNodeId, - viewIdResName, interactionId, callback, mFetchFlags, interrogatingPid, - interrogatingTid, spec); - return true; - } catch (RemoteException re) { - if (DEBUG) { - Slog.e(LOG_TAG, "Error findAccessibilityNodeInfoByViewId()."); - } - } finally { - Binder.restoreCallingIdentity(identityToken); - } - return false; - } - - @Override - public boolean findAccessibilityNodeInfosByText(int accessibilityWindowId, - long accessibilityNodeId, String text, int interactionId, - IAccessibilityInteractionConnectionCallback callback, long interrogatingTid) - throws RemoteException { - final int resolvedWindowId; - IAccessibilityInteractionConnection connection = null; - synchronized (mLock) { - final int resolvedUserId = mSecurityPolicy - .resolveCallingUserIdEnforcingPermissionsLocked( - UserHandle.getCallingUserId()); - if (resolvedUserId != mCurrentUserId) { - return false; - } - mSecurityPolicy.enforceCanRetrieveWindowContent(this); - resolvedWindowId = resolveAccessibilityWindowIdLocked(accessibilityWindowId); - final boolean permissionGranted = - mSecurityPolicy.canGetAccessibilityNodeInfoLocked(this, resolvedWindowId); - if (!permissionGranted) { - return false; - } else { - connection = getConnectionLocked(resolvedWindowId); - if (connection == null) { - return false; - } - } - } - final int interrogatingPid = Binder.getCallingPid(); - final long identityToken = Binder.clearCallingIdentity(); - MagnificationSpec spec = getCompatibleMagnificationSpec(resolvedWindowId); - try { - connection.findAccessibilityNodeInfosByText(accessibilityNodeId, text, - interactionId, callback, mFetchFlags, interrogatingPid, interrogatingTid, - spec); - return true; - } catch (RemoteException re) { - if (DEBUG) { - Slog.e(LOG_TAG, "Error calling findAccessibilityNodeInfosByText()"); - } - } finally { - Binder.restoreCallingIdentity(identityToken); - } - return false; - } - - @Override - public boolean findAccessibilityNodeInfoByAccessibilityId( - int accessibilityWindowId, long accessibilityNodeId, int interactionId, - IAccessibilityInteractionConnectionCallback callback, int flags, - long interrogatingTid) throws RemoteException { - final int resolvedWindowId; - IAccessibilityInteractionConnection connection = null; - synchronized (mLock) { - final int resolvedUserId = mSecurityPolicy - .resolveCallingUserIdEnforcingPermissionsLocked( - UserHandle.getCallingUserId()); - if (resolvedUserId != mCurrentUserId) { - return false; - } - mSecurityPolicy.enforceCanRetrieveWindowContent(this); - resolvedWindowId = resolveAccessibilityWindowIdLocked(accessibilityWindowId); - final boolean permissionGranted = - mSecurityPolicy.canGetAccessibilityNodeInfoLocked(this, resolvedWindowId); - if (!permissionGranted) { - return false; - } else { - connection = getConnectionLocked(resolvedWindowId); - if (connection == null) { - return false; - } - } - } - final int interrogatingPid = Binder.getCallingPid(); - final long identityToken = Binder.clearCallingIdentity(); - MagnificationSpec spec = getCompatibleMagnificationSpec(resolvedWindowId); - try { - connection.findAccessibilityNodeInfoByAccessibilityId(accessibilityNodeId, - interactionId, callback, mFetchFlags | flags, interrogatingPid, - interrogatingTid, spec); - return true; - } catch (RemoteException re) { - if (DEBUG) { - Slog.e(LOG_TAG, "Error calling findAccessibilityNodeInfoByAccessibilityId()"); - } - } finally { - Binder.restoreCallingIdentity(identityToken); - } - return false; - } - - @Override - public boolean findFocus(int accessibilityWindowId, long accessibilityNodeId, - int focusType, int interactionId, - IAccessibilityInteractionConnectionCallback callback, long interrogatingTid) - throws RemoteException { - final int resolvedWindowId; - IAccessibilityInteractionConnection connection = null; - synchronized (mLock) { - final int resolvedUserId = mSecurityPolicy - .resolveCallingUserIdEnforcingPermissionsLocked( - UserHandle.getCallingUserId()); - if (resolvedUserId != mCurrentUserId) { - return false; - } - mSecurityPolicy.enforceCanRetrieveWindowContent(this); - resolvedWindowId = resolveAccessibilityWindowIdLocked(accessibilityWindowId); - final boolean permissionGranted = - mSecurityPolicy.canGetAccessibilityNodeInfoLocked(this, resolvedWindowId); - if (!permissionGranted) { - return false; - } else { - connection = getConnectionLocked(resolvedWindowId); - if (connection == null) { - return false; - } - } - } - final int interrogatingPid = Binder.getCallingPid(); - final long identityToken = Binder.clearCallingIdentity(); - MagnificationSpec spec = getCompatibleMagnificationSpec(resolvedWindowId); - try { - connection.findFocus(accessibilityNodeId, focusType, interactionId, callback, - mFetchFlags, interrogatingPid, interrogatingTid, spec); - return true; - } catch (RemoteException re) { - if (DEBUG) { - Slog.e(LOG_TAG, "Error calling findAccessibilityFocus()"); - } - } finally { - Binder.restoreCallingIdentity(identityToken); - } - return false; - } - - @Override - public boolean focusSearch(int accessibilityWindowId, long accessibilityNodeId, - int direction, int interactionId, - IAccessibilityInteractionConnectionCallback callback, long interrogatingTid) - throws RemoteException { - final int resolvedWindowId; - IAccessibilityInteractionConnection connection = null; - synchronized (mLock) { - final int resolvedUserId = mSecurityPolicy - .resolveCallingUserIdEnforcingPermissionsLocked( - UserHandle.getCallingUserId()); - if (resolvedUserId != mCurrentUserId) { - return false; - } - mSecurityPolicy.enforceCanRetrieveWindowContent(this); - resolvedWindowId = resolveAccessibilityWindowIdLocked(accessibilityWindowId); - final boolean permissionGranted = - mSecurityPolicy.canGetAccessibilityNodeInfoLocked(this, resolvedWindowId); - if (!permissionGranted) { - return false; - } else { - connection = getConnectionLocked(resolvedWindowId); - if (connection == null) { - return false; - } - } - } - final int interrogatingPid = Binder.getCallingPid(); - final long identityToken = Binder.clearCallingIdentity(); - MagnificationSpec spec = getCompatibleMagnificationSpec(resolvedWindowId); - try { - connection.focusSearch(accessibilityNodeId, direction, interactionId, callback, - mFetchFlags, interrogatingPid, interrogatingTid, spec); - return true; - } catch (RemoteException re) { - if (DEBUG) { - Slog.e(LOG_TAG, "Error calling accessibilityFocusSearch()"); - } - } finally { - Binder.restoreCallingIdentity(identityToken); - } - return false; - } - - @Override - public boolean performAccessibilityAction(int accessibilityWindowId, - long accessibilityNodeId, int action, Bundle arguments, int interactionId, - IAccessibilityInteractionConnectionCallback callback, long interrogatingTid) - throws RemoteException { - final int resolvedWindowId; - IAccessibilityInteractionConnection connection = null; - synchronized (mLock) { - final int resolvedUserId = mSecurityPolicy - .resolveCallingUserIdEnforcingPermissionsLocked( - UserHandle.getCallingUserId()); - if (resolvedUserId != mCurrentUserId) { - return false; - } - mSecurityPolicy.enforceCanRetrieveWindowContent(this); - resolvedWindowId = resolveAccessibilityWindowIdLocked(accessibilityWindowId); - final boolean permissionGranted = mSecurityPolicy.canPerformActionLocked(this, - resolvedWindowId, action, arguments); - if (!permissionGranted) { - return false; - } else { - connection = getConnectionLocked(resolvedWindowId); - if (connection == null) { - return false; - } - } - } - final int interrogatingPid = Binder.getCallingPid(); - final long identityToken = Binder.clearCallingIdentity(); - try { - connection.performAccessibilityAction(accessibilityNodeId, action, arguments, - interactionId, callback, mFetchFlags, interrogatingPid, interrogatingTid); - } catch (RemoteException re) { - if (DEBUG) { - Slog.e(LOG_TAG, "Error calling performAccessibilityAction()"); - } - } finally { - Binder.restoreCallingIdentity(identityToken); - } - return true; - } - - public boolean performGlobalAction(int action) { - synchronized (mLock) { - final int resolvedUserId = mSecurityPolicy - .resolveCallingUserIdEnforcingPermissionsLocked( - UserHandle.getCallingUserId()); - if (resolvedUserId != mCurrentUserId) { - return false; - } - } - final long identity = Binder.clearCallingIdentity(); - try { - switch (action) { - case AccessibilityService.GLOBAL_ACTION_BACK: { - sendDownAndUpKeyEvents(KeyEvent.KEYCODE_BACK); - } return true; - case AccessibilityService.GLOBAL_ACTION_HOME: { - sendDownAndUpKeyEvents(KeyEvent.KEYCODE_HOME); - } return true; - case AccessibilityService.GLOBAL_ACTION_RECENTS: { - openRecents(); - } return true; - case AccessibilityService.GLOBAL_ACTION_NOTIFICATIONS: { - expandNotifications(); - } return true; - case AccessibilityService.GLOBAL_ACTION_QUICK_SETTINGS: { - expandQuickSettings(); - } return true; - } - return false; - } finally { - Binder.restoreCallingIdentity(identity); - } - } - - @Override - public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) { - mSecurityPolicy.enforceCallingPermission(Manifest.permission.DUMP, FUNCTION_DUMP); - synchronized (mLock) { - pw.append("Service[label=" + mAccessibilityServiceInfo.getResolveInfo() - .loadLabel(mContext.getPackageManager())); - pw.append(", feedbackType" - + AccessibilityServiceInfo.feedbackTypeToString(mFeedbackType)); - pw.append(", capabilities=" + mAccessibilityServiceInfo.getCapabilities()); - pw.append(", eventTypes=" - + AccessibilityEvent.eventTypeToString(mEventTypes)); - pw.append(", notificationTimeout=" + mNotificationTimeout); - pw.append("]"); - } - } - - @Override - public void onServiceDisconnected(ComponentName componentName) { - /* do nothing - #binderDied takes care */ - } - - public void linkToOwnDeathLocked() throws RemoteException { - mService.linkToDeath(this, 0); - } - - public void unlinkToOwnDeathLocked() { - mService.unlinkToDeath(this, 0); - } - - public void resetLocked() { - try { - // Clear the proxy in the other process so this - // IAccessibilityServiceConnection can be garbage collected. - mServiceInterface.setConnection(null, mId); - } catch (RemoteException re) { - /* ignore */ - } - mService = null; - mServiceInterface = null; - } - - public boolean isConnectedLocked() { - return (mService != null); - } - - public void binderDied() { - synchronized (mLock) { - // It is possible that this service's package was force stopped during - // whose handling the death recipient is unlinked and still get a call - // on binderDied since the call was made before we unlink but was - // waiting on the lock we held during the force stop handling. - if (!isConnectedLocked()) { - return; - } - mWasConnectedAndDied = true; - mKeyEventDispatcher.flush(); - UserState userState = getUserStateLocked(mUserId); - // The death recipient is unregistered in removeServiceLocked - removeServiceLocked(this, userState); - resetLocked(); - if (mIsAutomation) { - // We no longer have an automation service, so restore - // the state based on values in the settings database. - userState.mInstalledServices.remove(mAccessibilityServiceInfo); - userState.mEnabledServices.remove(mComponentName); - userState.destroyUiAutomationService(); - } - } - } - - /** - * Performs a notification for an {@link AccessibilityEvent}. - * - * @param event The event. - */ - public void notifyAccessibilityEvent(AccessibilityEvent event) { - synchronized (mLock) { - final int eventType = event.getEventType(); - // Make a copy since during dispatch it is possible the event to - // be modified to remove its source if the receiving service does - // not have permission to access the window content. - AccessibilityEvent newEvent = AccessibilityEvent.obtain(event); - AccessibilityEvent oldEvent = mPendingEvents.get(eventType); - mPendingEvents.put(eventType, newEvent); - - final int what = eventType; - if (oldEvent != null) { - mEventDispatchHandler.removeMessages(what); - oldEvent.recycle(); - } - - Message message = mEventDispatchHandler.obtainMessage(what); - mEventDispatchHandler.sendMessageDelayed(message, mNotificationTimeout); - } - } - - /** - * Notifies an accessibility service client for a scheduled event given the event type. - * - * @param eventType The type of the event to dispatch. - */ - private void notifyAccessibilityEventInternal(int eventType) { - IAccessibilityServiceClient listener; - AccessibilityEvent event; - - synchronized (mLock) { - listener = mServiceInterface; - - // If the service died/was disabled while the message for dispatching - // the accessibility event was propagating the listener may be null. - if (listener == null) { - return; - } - - event = mPendingEvents.get(eventType); - - // Check for null here because there is a concurrent scenario in which this - // happens: 1) A binder thread calls notifyAccessibilityServiceDelayedLocked - // which posts a message for dispatching an event. 2) The message is pulled - // from the queue by the handler on the service thread and the latter is - // just about to acquire the lock and call this method. 3) Now another binder - // thread acquires the lock calling notifyAccessibilityServiceDelayedLocked - // so the service thread waits for the lock; 4) The binder thread replaces - // the event with a more recent one (assume the same event type) and posts a - // dispatch request releasing the lock. 5) Now the main thread is unblocked and - // dispatches the event which is removed from the pending ones. 6) And ... now - // the service thread handles the last message posted by the last binder call - // but the event is already dispatched and hence looking it up in the pending - // ones yields null. This check is much simpler that keeping count for each - // event type of each service to catch such a scenario since only one message - // is processed at a time. - if (event == null) { - return; - } - - mPendingEvents.remove(eventType); - if (mSecurityPolicy.canRetrieveWindowContent(this)) { - event.setConnectionId(mId); - } else { - event.setSource(null); - } - event.setSealed(true); - } - - try { - listener.onAccessibilityEvent(event); - if (DEBUG) { - Slog.i(LOG_TAG, "Event " + event + " sent to " + listener); - } - } catch (RemoteException re) { - Slog.e(LOG_TAG, "Error during sending " + event + " to " + listener, re); - } finally { - event.recycle(); - } - } - - public void notifyGesture(int gestureId) { - mInvocationHandler.obtainMessage(InvocationHandler.MSG_ON_GESTURE, - gestureId, 0).sendToTarget(); - } - - public void notifyKeyEvent(KeyEvent event, int policyFlags) { - mInvocationHandler.obtainMessage(InvocationHandler.MSG_ON_KEY_EVENT, - policyFlags, 0, event).sendToTarget(); - } - - public void notifyClearAccessibilityNodeInfoCache() { - mInvocationHandler.sendEmptyMessage( - InvocationHandler.MSG_CLEAR_ACCESSIBILITY_NODE_INFO_CACHE); - } - - private void notifyGestureInternal(int gestureId) { - IAccessibilityServiceClient listener = mServiceInterface; - if (listener != null) { - try { - listener.onGesture(gestureId); - } catch (RemoteException re) { - Slog.e(LOG_TAG, "Error during sending gesture " + gestureId - + " to " + mService, re); - } - } - } - - private void notifyKeyEventInternal(KeyEvent event, int policyFlags) { - mKeyEventDispatcher.notifyKeyEvent(event, policyFlags); - } - - private void notifyClearAccessibilityNodeInfoCacheInternal() { - IAccessibilityServiceClient listener = mServiceInterface; - if (listener != null) { - try { - listener.clearAccessibilityNodeInfoCache(); - } catch (RemoteException re) { - Slog.e(LOG_TAG, "Error during requesting accessibility info cache" - + " to be cleared.", re); - } - } - } - - private void sendDownAndUpKeyEvents(int keyCode) { - final long token = Binder.clearCallingIdentity(); - - // Inject down. - final long downTime = SystemClock.uptimeMillis(); - KeyEvent down = KeyEvent.obtain(downTime, downTime, KeyEvent.ACTION_DOWN, keyCode, 0, 0, - KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_FROM_SYSTEM, - InputDevice.SOURCE_KEYBOARD, null); - InputManager.getInstance().injectInputEvent(down, - InputManager.INJECT_INPUT_EVENT_MODE_ASYNC); - down.recycle(); - - // Inject up. - final long upTime = SystemClock.uptimeMillis(); - KeyEvent up = KeyEvent.obtain(downTime, upTime, KeyEvent.ACTION_UP, keyCode, 0, 0, - KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_FROM_SYSTEM, - InputDevice.SOURCE_KEYBOARD, null); - InputManager.getInstance().injectInputEvent(up, - InputManager.INJECT_INPUT_EVENT_MODE_ASYNC); - up.recycle(); - - Binder.restoreCallingIdentity(token); - } - - private void expandNotifications() { - final long token = Binder.clearCallingIdentity(); - - StatusBarManager statusBarManager = (StatusBarManager) mContext.getSystemService( - android.app.Service.STATUS_BAR_SERVICE); - statusBarManager.expandNotificationsPanel(); - - Binder.restoreCallingIdentity(token); - } - - private void expandQuickSettings() { - final long token = Binder.clearCallingIdentity(); - - StatusBarManager statusBarManager = (StatusBarManager) mContext.getSystemService( - android.app.Service.STATUS_BAR_SERVICE); - statusBarManager.expandSettingsPanel(); - - Binder.restoreCallingIdentity(token); - } - - private void openRecents() { - final long token = Binder.clearCallingIdentity(); - - IStatusBarService statusBarService = IStatusBarService.Stub.asInterface( - ServiceManager.getService("statusbar")); - try { - statusBarService.toggleRecentApps(); - } catch (RemoteException e) { - Slog.e(LOG_TAG, "Error toggling recent apps."); - } - - Binder.restoreCallingIdentity(token); - } - - private IAccessibilityInteractionConnection getConnectionLocked(int windowId) { - if (DEBUG) { - Slog.i(LOG_TAG, "Trying to get interaction connection to windowId: " + windowId); - } - AccessibilityConnectionWrapper wrapper = mGlobalInteractionConnections.get(windowId); - if (wrapper == null) { - wrapper = getCurrentUserStateLocked().mInteractionConnections.get(windowId); - } - if (wrapper != null && wrapper.mConnection != null) { - return wrapper.mConnection; - } - if (DEBUG) { - Slog.e(LOG_TAG, "No interaction connection to window: " + windowId); - } - return null; - } - - private int resolveAccessibilityWindowIdLocked(int accessibilityWindowId) { - if (accessibilityWindowId == AccessibilityNodeInfo.ACTIVE_WINDOW_ID) { - return mSecurityPolicy.mActiveWindowId; - } - return accessibilityWindowId; - } - - private MagnificationSpec getCompatibleMagnificationSpec(int windowId) { - try { - IBinder windowToken = mGlobalWindowTokens.get(windowId); - if (windowToken == null) { - windowToken = getCurrentUserStateLocked().mWindowTokens.get(windowId); - } - if (windowToken != null) { - return mWindowManagerService.getCompatibleMagnificationSpecForWindow( - windowToken); - } - } catch (RemoteException re) { - /* ignore */ - } - return null; - } - - private final class InvocationHandler extends Handler { - - public static final int MSG_ON_GESTURE = 1; - public static final int MSG_ON_KEY_EVENT = 2; - public static final int MSG_CLEAR_ACCESSIBILITY_NODE_INFO_CACHE = 3; - public static final int MSG_ON_KEY_EVENT_TIMEOUT = 4; - - public InvocationHandler(Looper looper) { - super(looper, null, true); - } - - @Override - public void handleMessage(Message message) { - final int type = message.what; - switch (type) { - case MSG_ON_GESTURE: { - final int gestureId = message.arg1; - notifyGestureInternal(gestureId); - } break; - case MSG_ON_KEY_EVENT: { - KeyEvent event = (KeyEvent) message.obj; - final int policyFlags = message.arg1; - notifyKeyEventInternal(event, policyFlags); - } break; - case MSG_CLEAR_ACCESSIBILITY_NODE_INFO_CACHE: { - notifyClearAccessibilityNodeInfoCacheInternal(); - } break; - case MSG_ON_KEY_EVENT_TIMEOUT: { - PendingEvent eventState = (PendingEvent) message.obj; - setOnKeyEventResult(false, eventState.sequence); - } break; - default: { - throw new IllegalArgumentException("Unknown message: " + type); - } - } - } - } - - private final class KeyEventDispatcher { - - private static final long ON_KEY_EVENT_TIMEOUT_MILLIS = 500; - - private PendingEvent mPendingEvents; - - private final InputEventConsistencyVerifier mSentEventsVerifier = - InputEventConsistencyVerifier.isInstrumentationEnabled() - ? new InputEventConsistencyVerifier( - this, 0, KeyEventDispatcher.class.getSimpleName()) : null; - - public void notifyKeyEvent(KeyEvent event, int policyFlags) { - final PendingEvent pendingEvent; - - synchronized (mLock) { - pendingEvent = addPendingEventLocked(event, policyFlags); - } - - Message message = mInvocationHandler.obtainMessage( - InvocationHandler.MSG_ON_KEY_EVENT_TIMEOUT, pendingEvent); - mInvocationHandler.sendMessageDelayed(message, ON_KEY_EVENT_TIMEOUT_MILLIS); - - try { - // Accessibility services are exclusively not in the system - // process, therefore no need to clone the motion event to - // prevent tampering. It will be cloned in the IPC call. - mServiceInterface.onKeyEvent(pendingEvent.event, pendingEvent.sequence); - } catch (RemoteException re) { - setOnKeyEventResult(false, pendingEvent.sequence); - } - } - - public void setOnKeyEventResult(boolean handled, int sequence) { - synchronized (mLock) { - PendingEvent pendingEvent = removePendingEventLocked(sequence); - if (pendingEvent != null) { - mInvocationHandler.removeMessages( - InvocationHandler.MSG_ON_KEY_EVENT_TIMEOUT, - pendingEvent); - pendingEvent.handled = handled; - finishPendingEventLocked(pendingEvent); - } - } - } - - public void flush() { - synchronized (mLock) { - cancelAllPendingEventsLocked(); - if (mSentEventsVerifier != null) { - mSentEventsVerifier.reset(); - } - } - } - - private PendingEvent addPendingEventLocked(KeyEvent event, int policyFlags) { - final int sequence = event.getSequenceNumber(); - PendingEvent pendingEvent = obtainPendingEventLocked(event, policyFlags, sequence); - pendingEvent.next = mPendingEvents; - mPendingEvents = pendingEvent; - return pendingEvent; - } - - private PendingEvent removePendingEventLocked(int sequence) { - PendingEvent previous = null; - PendingEvent current = mPendingEvents; - - while (current != null) { - if (current.sequence == sequence) { - if (previous != null) { - previous.next = current.next; - } else { - mPendingEvents = current.next; - } - current.next = null; - return current; - } - previous = current; - current = current.next; - } - return null; - } - - private void finishPendingEventLocked(PendingEvent pendingEvent) { - if (!pendingEvent.handled) { - sendKeyEventToInputFilter(pendingEvent.event, pendingEvent.policyFlags); - } - // Nullify the event since we do not want it to be - // recycled yet. It will be sent to the input filter. - pendingEvent.event = null; - recyclePendingEventLocked(pendingEvent); - } - - private void sendKeyEventToInputFilter(KeyEvent event, int policyFlags) { - if (DEBUG) { - Slog.i(LOG_TAG, "Injecting event: " + event); - } - if (mSentEventsVerifier != null) { - mSentEventsVerifier.onKeyEvent(event, 0); - } - policyFlags |= WindowManagerPolicy.FLAG_PASS_TO_USER; - mMainHandler.obtainMessage(MainHandler.MSG_SEND_KEY_EVENT_TO_INPUT_FILTER, - policyFlags, 0, event).sendToTarget(); - } - - private void cancelAllPendingEventsLocked() { - while (mPendingEvents != null) { - PendingEvent pendingEvent = removePendingEventLocked(mPendingEvents.sequence); - pendingEvent.handled = false; - mInvocationHandler.removeMessages(InvocationHandler.MSG_ON_KEY_EVENT_TIMEOUT, - pendingEvent); - finishPendingEventLocked(pendingEvent); - } - } - } - } - - private static final class PendingEvent { - PendingEvent next; - - KeyEvent event; - int policyFlags; - int sequence; - boolean handled; - - public void clear() { - if (event != null) { - event.recycle(); - event = null; - } - next = null; - policyFlags = 0; - sequence = 0; - handled = false; - } - } - - final class SecurityPolicy { - private static final int VALID_ACTIONS = - AccessibilityNodeInfo.ACTION_CLICK - | AccessibilityNodeInfo.ACTION_LONG_CLICK - | AccessibilityNodeInfo.ACTION_FOCUS - | AccessibilityNodeInfo.ACTION_CLEAR_FOCUS - | AccessibilityNodeInfo.ACTION_SELECT - | AccessibilityNodeInfo.ACTION_CLEAR_SELECTION - | AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS - | AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS - | AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY - | AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY - | AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT - | AccessibilityNodeInfo.ACTION_PREVIOUS_HTML_ELEMENT - | AccessibilityNodeInfo.ACTION_SCROLL_FORWARD - | AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD - | AccessibilityNodeInfo.ACTION_COPY - | AccessibilityNodeInfo.ACTION_PASTE - | AccessibilityNodeInfo.ACTION_CUT - | AccessibilityNodeInfo.ACTION_SET_SELECTION - | AccessibilityNodeInfo.ACTION_EXPAND - | AccessibilityNodeInfo.ACTION_COLLAPSE - | AccessibilityNodeInfo.ACTION_DISMISS; - - private static final int RETRIEVAL_ALLOWING_EVENT_TYPES = - AccessibilityEvent.TYPE_VIEW_CLICKED - | AccessibilityEvent.TYPE_VIEW_FOCUSED - | AccessibilityEvent.TYPE_VIEW_HOVER_ENTER - | AccessibilityEvent.TYPE_VIEW_HOVER_EXIT - | AccessibilityEvent.TYPE_VIEW_LONG_CLICKED - | AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED - | AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED - | AccessibilityEvent.TYPE_VIEW_SELECTED - | AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED - | AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED - | AccessibilityEvent.TYPE_VIEW_SCROLLED - | AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED - | AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED; - - private int mActiveWindowId; - private boolean mTouchInteractionInProgress; - - private boolean canDispatchAccessibilityEvent(AccessibilityEvent event) { - final int eventType = event.getEventType(); - switch (eventType) { - // All events that are for changes in a global window - // state should *always* be dispatched. - case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED: - case AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED: - // All events generated by the user touching the - // screen should *always* be dispatched. - case AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START: - case AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END: - case AccessibilityEvent.TYPE_GESTURE_DETECTION_START: - case AccessibilityEvent.TYPE_GESTURE_DETECTION_END: - case AccessibilityEvent.TYPE_TOUCH_INTERACTION_START: - case AccessibilityEvent.TYPE_TOUCH_INTERACTION_END: - // These will change the active window, so dispatch. - case AccessibilityEvent.TYPE_VIEW_HOVER_ENTER: - case AccessibilityEvent.TYPE_VIEW_HOVER_EXIT: { - return true; - } - // All events for changes in window content should be - // dispatched *only* if this window is the active one. - default: - return event.getWindowId() == mActiveWindowId; - } - } - - public void updateEventSourceLocked(AccessibilityEvent event) { - if ((event.getEventType() & RETRIEVAL_ALLOWING_EVENT_TYPES) == 0) { - event.setSource(null); - } - } - - public void updateActiveWindow(int windowId, int eventType) { - // The active window is either the window that has input focus or - // the window that the user is currently touching. If the user is - // touching a window that does not have input focus as soon as the - // the user stops touching that window the focused window becomes - // the active one. - switch (eventType) { - case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED: { - if (getFocusedWindowId() == windowId) { - mActiveWindowId = windowId; - } - } break; - case AccessibilityEvent.TYPE_VIEW_HOVER_ENTER: { - // Do not allow delayed hover events to confuse us - // which the active window is. - if (mTouchInteractionInProgress) { - mActiveWindowId = windowId; - } - } break; - } - } - - public void onTouchInteractionStart() { - mTouchInteractionInProgress = true; - } - - public void onTouchInteractionEnd() { - mTouchInteractionInProgress = false; - // We want to set the active window to be current immediately - // after the user has stopped touching the screen since if the - // user types with the IME he should get a feedback for the - // letter typed in the text view which is in the input focused - // window. Note that we always deliver hover accessibility events - // (they are a result of user touching the screen) so change of - // the active window before all hover accessibility events from - // the touched window are delivered is fine. - mActiveWindowId = getFocusedWindowId(); - } - - public int getRetrievalAllowingWindowLocked() { - return mActiveWindowId; - } - - public boolean canGetAccessibilityNodeInfoLocked(Service service, int windowId) { - return canRetrieveWindowContent(service) && isRetrievalAllowingWindow(windowId); - } - - public boolean canPerformActionLocked(Service service, int windowId, int action, - Bundle arguments) { - return canRetrieveWindowContent(service) - && isRetrievalAllowingWindow(windowId) - && isActionPermitted(action); - } - - public boolean canRetrieveWindowContent(Service service) { - return (service.mAccessibilityServiceInfo.getCapabilities() - & AccessibilityServiceInfo.CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT) != 0; - } - - public void enforceCanRetrieveWindowContent(Service service) throws RemoteException { - // This happens due to incorrect registration so make it apparent. - if (!canRetrieveWindowContent(service)) { - Slog.e(LOG_TAG, "Accessibility serivce " + service.mComponentName + " does not " + - "declare android:canRetrieveWindowContent."); - throw new RemoteException(); - } - } - - public int resolveCallingUserIdEnforcingPermissionsLocked(int userId) { - final int callingUid = Binder.getCallingUid(); - if (callingUid == 0 - || callingUid == Process.SYSTEM_UID - || callingUid == Process.SHELL_UID) { - return mCurrentUserId; - } - final int callingUserId = UserHandle.getUserId(callingUid); - if (callingUserId == userId) { - return userId; - } - if (!hasPermission(Manifest.permission.INTERACT_ACROSS_USERS) - && !hasPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL)) { - throw new SecurityException("Call from user " + callingUserId + " as user " - + userId + " without permission INTERACT_ACROSS_USERS or " - + "INTERACT_ACROSS_USERS_FULL not allowed."); - } - if (userId == UserHandle.USER_CURRENT - || userId == UserHandle.USER_CURRENT_OR_SELF) { - return mCurrentUserId; - } - throw new IllegalArgumentException("Calling user can be changed to only " - + "UserHandle.USER_CURRENT or UserHandle.USER_CURRENT_OR_SELF."); - } - - public boolean isCallerInteractingAcrossUsers(int userId) { - final int callingUid = Binder.getCallingUid(); - return (Binder.getCallingPid() == android.os.Process.myPid() - || callingUid == Process.SHELL_UID - || userId == UserHandle.USER_CURRENT - || userId == UserHandle.USER_CURRENT_OR_SELF); - } - - private boolean isRetrievalAllowingWindow(int windowId) { - return (mActiveWindowId == windowId); - } - - private boolean isActionPermitted(int action) { - return (VALID_ACTIONS & action) != 0; - } - - private void enforceCallingPermission(String permission, String function) { - if (OWN_PROCESS_ID == Binder.getCallingPid()) { - return; - } - if (!hasPermission(permission)) { - throw new SecurityException("You do not have " + permission - + " required to call " + function + " from pid=" - + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()); - } - } - - private boolean hasPermission(String permission) { - return mContext.checkCallingPermission(permission) == PackageManager.PERMISSION_GRANTED; - } - - private int getFocusedWindowId() { - try { - // We call this only on window focus change or after touch - // exploration gesture end and the shown windows are not that - // many, so the linear look up is just fine. - IBinder token = mWindowManagerService.getFocusedWindowToken(); - if (token != null) { - synchronized (mLock) { - int windowId = getFocusedWindowIdLocked(token, mGlobalWindowTokens); - if (windowId < 0) { - windowId = getFocusedWindowIdLocked(token, - getCurrentUserStateLocked().mWindowTokens); - } - return windowId; - } - } - } catch (RemoteException re) { - /* ignore */ - } - return -1; - } - - private int getFocusedWindowIdLocked(IBinder token, SparseArray<IBinder> windows) { - final int windowCount = windows.size(); - for (int i = 0; i < windowCount; i++) { - if (windows.valueAt(i) == token) { - return windows.keyAt(i); - } - } - return -1; - } - } - - private class UserState { - public final int mUserId; - - // Non-transient state. - - public final RemoteCallbackList<IAccessibilityManagerClient> mClients = - new RemoteCallbackList<IAccessibilityManagerClient>(); - - public final SparseArray<AccessibilityConnectionWrapper> mInteractionConnections = - new SparseArray<AccessibilityConnectionWrapper>(); - - public final SparseArray<IBinder> mWindowTokens = new SparseArray<IBinder>(); - - // Transient state. - - public final CopyOnWriteArrayList<Service> mBoundServices = - new CopyOnWriteArrayList<Service>(); - - public final Map<ComponentName, Service> mComponentNameToServiceMap = - new HashMap<ComponentName, Service>(); - - public final List<AccessibilityServiceInfo> mInstalledServices = - new ArrayList<AccessibilityServiceInfo>(); - - public final Set<ComponentName> mBindingServices = new HashSet<ComponentName>(); - - public final Set<ComponentName> mEnabledServices = new HashSet<ComponentName>(); - - public final Set<ComponentName> mTouchExplorationGrantedServices = - new HashSet<ComponentName>(); - - public int mHandledFeedbackTypes = 0; - - public int mLastSentClientState = -1; - - public boolean mIsAccessibilityEnabled; - public boolean mIsTouchExplorationEnabled; - public boolean mIsEnhancedWebAccessibilityEnabled; - public boolean mIsDisplayMagnificationEnabled; - public boolean mIsFilterKeyEventsEnabled; - - private Service mUiAutomationService; - private IAccessibilityServiceClient mUiAutomationServiceClient; - - private IBinder mUiAutomationServiceOwner; - private final DeathRecipient mUiAutomationSerivceOnwerDeathRecipient = - new DeathRecipient() { - @Override - public void binderDied() { - mUiAutomationServiceOwner.unlinkToDeath( - mUiAutomationSerivceOnwerDeathRecipient, 0); - mUiAutomationServiceOwner = null; - if (mUiAutomationService != null) { - mUiAutomationService.binderDied(); - } - } - }; - - public UserState(int userId) { - mUserId = userId; - } - - public int getClientState() { - int clientState = 0; - if (mIsAccessibilityEnabled) { - clientState |= AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED; - } - // Touch exploration relies on enabled accessibility. - if (mIsAccessibilityEnabled && mIsTouchExplorationEnabled) { - clientState |= AccessibilityManager.STATE_FLAG_TOUCH_EXPLORATION_ENABLED; - } - return clientState; - } - - public void onSwitchToAnotherUser() { - // Clear UI test automation state. - if (mUiAutomationService != null) { - mUiAutomationService.binderDied(); - } - - // Unbind all services. - unbindAllServicesLocked(this); - - // Clear service management state. - mBoundServices.clear(); - mBindingServices.clear(); - - // Clear event management state. - mHandledFeedbackTypes = 0; - mLastSentClientState = -1; - - // Clear state persisted in settings. - mEnabledServices.clear(); - mTouchExplorationGrantedServices.clear(); - mIsAccessibilityEnabled = false; - mIsTouchExplorationEnabled = false; - mIsEnhancedWebAccessibilityEnabled = false; - mIsDisplayMagnificationEnabled = false; - } - - public void destroyUiAutomationService() { - mUiAutomationService = null; - mUiAutomationServiceClient = null; - if (mUiAutomationServiceOwner != null) { - mUiAutomationServiceOwner.unlinkToDeath( - mUiAutomationSerivceOnwerDeathRecipient, 0); - mUiAutomationServiceOwner = null; - } - } - } - - private final class AccessibilityContentObserver extends ContentObserver { - - private final Uri mAccessibilityEnabledUri = Settings.Secure.getUriFor( - Settings.Secure.ACCESSIBILITY_ENABLED); - - 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 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 mEnhancedWebAccessibilityUri = Settings.Secure - .getUriFor(Settings.Secure.ACCESSIBILITY_SCRIPT_INJECTION); - - public AccessibilityContentObserver(Handler handler) { - super(handler); - } - - public void register(ContentResolver contentResolver) { - contentResolver.registerContentObserver(mAccessibilityEnabledUri, - false, this, UserHandle.USER_ALL); - contentResolver.registerContentObserver(mTouchExplorationEnabledUri, - false, this, UserHandle.USER_ALL); - contentResolver.registerContentObserver(mDisplayMagnificationEnabledUri, - false, this, UserHandle.USER_ALL); - contentResolver.registerContentObserver(mEnabledAccessibilityServicesUri, - false, this, UserHandle.USER_ALL); - contentResolver.registerContentObserver( - mTouchExplorationGrantedAccessibilityServicesUri, - false, this, UserHandle.USER_ALL); - contentResolver.registerContentObserver(mEnhancedWebAccessibilityUri, - false, this, UserHandle.USER_ALL); - } - - @Override - public void onChange(boolean selfChange, Uri uri) { - if (mAccessibilityEnabledUri.equals(uri)) { - synchronized (mLock) { - // We will update when the automation service dies. - UserState userState = getCurrentUserStateLocked(); - if (userState.mUiAutomationService == null) { - if (readAccessibilityEnabledSettingLocked(userState)) { - onUserStateChangedLocked(userState); - } - } - } - } else if (mTouchExplorationEnabledUri.equals(uri)) { - synchronized (mLock) { - // We will update when the automation service dies. - UserState userState = getCurrentUserStateLocked(); - if (userState.mUiAutomationService == null) { - if (readTouchExplorationEnabledSettingLocked(userState)) { - onUserStateChangedLocked(userState); - } - } - } - } else if (mDisplayMagnificationEnabledUri.equals(uri)) { - synchronized (mLock) { - // We will update when the automation service dies. - UserState userState = getCurrentUserStateLocked(); - if (userState.mUiAutomationService == null) { - if (readDisplayMagnificationEnabledSettingLocked(userState)) { - onUserStateChangedLocked(userState); - } - } - } - } else if (mEnabledAccessibilityServicesUri.equals(uri)) { - synchronized (mLock) { - // We will update when the automation service dies. - UserState userState = getCurrentUserStateLocked(); - if (userState.mUiAutomationService == null) { - if (readEnabledAccessibilityServicesLocked(userState)) { - onUserStateChangedLocked(userState); - } - } - } - } else if (mTouchExplorationGrantedAccessibilityServicesUri.equals(uri)) { - synchronized (mLock) { - // We will update when the automation service dies. - UserState userState = getCurrentUserStateLocked(); - if (userState.mUiAutomationService == null) { - if (readTouchExplorationGrantedAccessibilityServicesLocked(userState)) { - onUserStateChangedLocked(userState); - } - } - } - } else if (mEnhancedWebAccessibilityUri.equals(uri)) { - synchronized (mLock) { - // We will update when the automation service dies. - UserState userState = getCurrentUserStateLocked(); - if (userState.mUiAutomationService == null) { - if (readEnhancedWebAccessibilityEnabledChangedLocked(userState)) { - onUserStateChangedLocked(userState); - } - } - } - } - } - } -} diff --git a/services/java/com/android/server/accessibility/EventStreamTransformation.java b/services/java/com/android/server/accessibility/EventStreamTransformation.java deleted file mode 100644 index 8c93e7b..0000000 --- a/services/java/com/android/server/accessibility/EventStreamTransformation.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - ** Copyright 2012, The Android Open Source Project - ** - ** Licensed under the Apache License, Version 2.0 (the "License"); - ** you may not use this file except in compliance with the License. - ** You may obtain a copy of the License at - ** - ** http://www.apache.org/licenses/LICENSE-2.0 - ** - ** Unless required by applicable law or agreed to in writing, software - ** distributed under the License is distributed on an "AS IS" BASIS, - ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ** See the License for the specific language governing permissions and - ** limitations under the License. - */ - -package com.android.server.accessibility; - -import android.view.KeyEvent; -import android.view.MotionEvent; -import android.view.accessibility.AccessibilityEvent; - -/** - * Interface for classes that can handle and potentially transform a stream of - * motion and accessibility events. Instances implementing this interface are - * ordered in a sequence to implement a transformation chain. An instance may - * consume, modify, and generate events. It is responsible to deliver the - * output events to the next transformation in the sequence set via - * {@link #setNext(EventStreamTransformation)}. - * - * Note that since instances implementing this interface are transformations - * of the event stream, an instance should work against the event stream - * potentially modified by previous ones. Hence, the order of transformations - * is important. - * - * It is a responsibility of each handler that decides to react to an event - * sequence and prevent any subsequent ones from performing an action to send - * the appropriate cancel event given it has delegated a part of the events - * that belong to the current gesture. This will ensure that subsequent - * transformations will not be left in an inconsistent state and the applications - * see a consistent event stream. - * - * For example, to cancel a {@link KeyEvent} the handler has to emit an event - * with action {@link KeyEvent#ACTION_UP} with the additional flag - * {@link KeyEvent#FLAG_CANCELED}. To cancel a {@link MotionEvent} the handler - * has to send an event with action {@link MotionEvent#ACTION_CANCEL}. - * - * It is a responsibility of each handler that received a cancel event to clear its - * internal state and to propagate the event to the next one to enable subsequent - * transformations to clear their internal state. - * - * It is a responsibility for each transformation to start handling events only - * after an event that designates the start of a well-formed event sequence. - * For example, if it received a down motion event followed by a cancel motion - * event, it should not handle subsequent move and up events until it gets a down. - */ -interface EventStreamTransformation { - - /** - * Receives a motion event. Passed are the event transformed by previous - * transformations and the raw event to which no transformations have - * been applied. - * - * @param event The transformed motion event. - * @param rawEvent The raw motion event. - * @param policyFlags Policy flags for the event. - */ - public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags); - - /** - * Receives an accessibility event. - * - * @param event The accessibility event. - */ - public void onAccessibilityEvent(AccessibilityEvent event); - - /** - * Sets the next transformation. - * - * @param next The next transformation. - */ - public void setNext(EventStreamTransformation next); - - /** - * Clears the internal state of this transformation. - */ - public void clear(); - - /** - * Destroys this transformation. - */ - public void onDestroy(); -} diff --git a/services/java/com/android/server/accessibility/GestureUtils.java b/services/java/com/android/server/accessibility/GestureUtils.java deleted file mode 100644 index b68b09f..0000000 --- a/services/java/com/android/server/accessibility/GestureUtils.java +++ /dev/null @@ -1,102 +0,0 @@ -package com.android.server.accessibility; - -import android.util.MathUtils; -import android.view.MotionEvent; - -/** - * Some helper functions for gesture detection. - */ -final class GestureUtils { - - private GestureUtils() { - /* cannot be instantiated */ - } - - public static boolean isTap(MotionEvent down, MotionEvent up, int tapTimeSlop, - int tapDistanceSlop, int actionIndex) { - return eventsWithinTimeAndDistanceSlop(down, up, tapTimeSlop, tapDistanceSlop, actionIndex); - } - - public static boolean isMultiTap(MotionEvent firstUp, MotionEvent secondUp, - int multiTapTimeSlop, int multiTapDistanceSlop, int actionIndex) { - return eventsWithinTimeAndDistanceSlop(firstUp, secondUp, multiTapTimeSlop, - multiTapDistanceSlop, actionIndex); - } - - private static boolean eventsWithinTimeAndDistanceSlop(MotionEvent first, MotionEvent second, - int timeout, int distance, int actionIndex) { - if (isTimedOut(first, second, timeout)) { - return false; - } - final double deltaMove = computeDistance(first, second, actionIndex); - if (deltaMove >= distance) { - return false; - } - return true; - } - - public static double computeDistance(MotionEvent first, MotionEvent second, int pointerIndex) { - return MathUtils.dist(first.getX(pointerIndex), first.getY(pointerIndex), - second.getX(pointerIndex), second.getY(pointerIndex)); - } - - public static boolean isTimedOut(MotionEvent firstUp, MotionEvent secondUp, int timeout) { - final long deltaTime = secondUp.getEventTime() - firstUp.getEventTime(); - return (deltaTime >= timeout); - } - - public static boolean isSamePointerContext(MotionEvent first, MotionEvent second) { - return (first.getPointerIdBits() == second.getPointerIdBits() - && first.getPointerId(first.getActionIndex()) - == second.getPointerId(second.getActionIndex())); - } - - /** - * Determines whether a two pointer gesture is a dragging one. - * - * @param event The event with the pointer data. - * @return True if the gesture is a dragging one. - */ - public static boolean isDraggingGesture(float firstPtrDownX, float firstPtrDownY, - float secondPtrDownX, float secondPtrDownY, float firstPtrX, float firstPtrY, - float secondPtrX, float secondPtrY, float maxDraggingAngleCos) { - - // Check if the pointers are moving in the same direction. - final float firstDeltaX = firstPtrX - firstPtrDownX; - final float firstDeltaY = firstPtrY - firstPtrDownY; - - if (firstDeltaX == 0 && firstDeltaY == 0) { - return true; - } - - final float firstMagnitude = - (float) Math.sqrt(firstDeltaX * firstDeltaX + firstDeltaY * firstDeltaY); - final float firstXNormalized = - (firstMagnitude > 0) ? firstDeltaX / firstMagnitude : firstDeltaX; - final float firstYNormalized = - (firstMagnitude > 0) ? firstDeltaY / firstMagnitude : firstDeltaY; - - final float secondDeltaX = secondPtrX - secondPtrDownX; - final float secondDeltaY = secondPtrY - secondPtrDownY; - - if (secondDeltaX == 0 && secondDeltaY == 0) { - return true; - } - - final float secondMagnitude = - (float) Math.sqrt(secondDeltaX * secondDeltaX + secondDeltaY * secondDeltaY); - final float secondXNormalized = - (secondMagnitude > 0) ? secondDeltaX / secondMagnitude : secondDeltaX; - final float secondYNormalized = - (secondMagnitude > 0) ? secondDeltaY / secondMagnitude : secondDeltaY; - - final float angleCos = - firstXNormalized * secondXNormalized + firstYNormalized * secondYNormalized; - - if (angleCos < maxDraggingAngleCos) { - return false; - } - - return true; - } -} diff --git a/services/java/com/android/server/accessibility/ScreenMagnifier.java b/services/java/com/android/server/accessibility/ScreenMagnifier.java deleted file mode 100644 index 5f12cf4..0000000 --- a/services/java/com/android/server/accessibility/ScreenMagnifier.java +++ /dev/null @@ -1,1177 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.accessibility; - -import android.animation.ObjectAnimator; -import android.animation.TypeEvaluator; -import android.animation.ValueAnimator; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.graphics.Rect; -import android.graphics.Region; -import android.os.AsyncTask; -import android.os.Binder; -import android.os.Handler; -import android.os.Message; -import android.os.RemoteException; -import android.os.ServiceManager; -import android.os.SystemClock; -import android.provider.Settings; -import android.text.TextUtils; -import android.util.Property; -import android.util.Slog; -import android.view.GestureDetector; -import android.view.GestureDetector.SimpleOnGestureListener; -import android.view.IMagnificationCallbacks; -import android.view.IWindowManager; -import android.view.MagnificationSpec; -import android.view.MotionEvent; -import android.view.MotionEvent.PointerCoords; -import android.view.MotionEvent.PointerProperties; -import android.view.ScaleGestureDetector; -import android.view.ScaleGestureDetector.OnScaleGestureListener; -import android.view.View; -import android.view.ViewConfiguration; -import android.view.accessibility.AccessibilityEvent; -import android.view.animation.DecelerateInterpolator; - -import com.android.internal.os.SomeArgs; - -import java.util.Locale; - -/** - * This class handles the screen magnification when accessibility is enabled. - * The behavior is as follows: - * - * 1. Triple tap toggles permanent screen magnification which is magnifying - * the area around the location of the triple tap. One can think of the - * location of the triple tap as the center of the magnified viewport. - * For example, a triple tap when not magnified would magnify the screen - * and leave it in a magnified state. A triple tapping when magnified would - * clear magnification and leave the screen in a not magnified state. - * - * 2. Triple tap and hold would magnify the screen if not magnified and enable - * viewport dragging mode until the finger goes up. One can think of this - * mode as a way to move the magnified viewport since the area around the - * moving finger will be magnified to fit the screen. For example, if the - * screen was not magnified and the user triple taps and holds the screen - * would magnify and the viewport will follow the user's finger. When the - * finger goes up the screen will zoom out. If the same user interaction - * is performed when the screen is magnified, the viewport movement will - * be the same but when the finger goes up the screen will stay magnified. - * In other words, the initial magnified state is sticky. - * - * 3. Pinching with any number of additional fingers when viewport dragging - * is enabled, i.e. the user triple tapped and holds, would adjust the - * magnification scale which will become the current default magnification - * scale. The next time the user magnifies the same magnification scale - * would be used. - * - * 4. When in a permanent magnified state the user can use two or more fingers - * to pan the viewport. Note that in this mode the content is panned as - * opposed to the viewport dragging mode in which the viewport is moved. - * - * 5. When in a permanent magnified state the user can use two or more - * fingers to change the magnification scale which will become the current - * default magnification scale. The next time the user magnifies the same - * magnification scale would be used. - * - * 6. The magnification scale will be persisted in settings and in the cloud. - */ -public final class ScreenMagnifier extends IMagnificationCallbacks.Stub - implements EventStreamTransformation { - - private static final String LOG_TAG = ScreenMagnifier.class.getSimpleName(); - - private static final boolean DEBUG_STATE_TRANSITIONS = false; - private static final boolean DEBUG_DETECTING = false; - private static final boolean DEBUG_SET_MAGNIFICATION_SPEC = false; - private static final boolean DEBUG_PANNING = false; - private static final boolean DEBUG_SCALING = false; - private static final boolean DEBUG_MAGNIFICATION_CONTROLLER = false; - - private static final int STATE_DELEGATING = 1; - private static final int STATE_DETECTING = 2; - private static final int STATE_VIEWPORT_DRAGGING = 3; - private static final int STATE_MAGNIFIED_INTERACTION = 4; - - private static final float DEFAULT_MAGNIFICATION_SCALE = 2.0f; - private static final int MULTI_TAP_TIME_SLOP_ADJUSTMENT = 50; - - private static final int MESSAGE_ON_MAGNIFIED_BOUNDS_CHANGED = 1; - private static final int MESSAGE_ON_RECTANGLE_ON_SCREEN_REQUESTED = 2; - private static final int MESSAGE_ON_USER_CONTEXT_CHANGED = 3; - private static final int MESSAGE_ON_ROTATION_CHANGED = 4; - - private static final int DEFAULT_SCREEN_MAGNIFICATION_AUTO_UPDATE = 1; - - private static final int MY_PID = android.os.Process.myPid(); - - private final Rect mTempRect = new Rect(); - private final Rect mTempRect1 = new Rect(); - - private final Context mContext; - private final IWindowManager mWindowManager; - private final MagnificationController mMagnificationController; - private final ScreenStateObserver mScreenStateObserver; - - private final DetectingStateHandler mDetectingStateHandler; - private final MagnifiedContentInteractonStateHandler mMagnifiedContentInteractonStateHandler; - private final StateViewportDraggingHandler mStateViewportDraggingHandler; - - private final AccessibilityManagerService mAms; - - private final int mTapTimeSlop = ViewConfiguration.getTapTimeout(); - private final int mMultiTapTimeSlop = - ViewConfiguration.getDoubleTapTimeout() - MULTI_TAP_TIME_SLOP_ADJUSTMENT; - private final int mTapDistanceSlop; - private final int mMultiTapDistanceSlop; - - private final long mLongAnimationDuration; - - private final Region mMagnifiedBounds = new Region(); - - private EventStreamTransformation mNext; - - private int mCurrentState; - private int mPreviousState; - private boolean mTranslationEnabledBeforePan; - - private PointerCoords[] mTempPointerCoords; - private PointerProperties[] mTempPointerProperties; - - private long mDelegatingStateDownTime; - - private boolean mUpdateMagnificationSpecOnNextBoundsChange; - - private final Handler mHandler = new Handler() { - @Override - public void handleMessage(Message message) { - switch (message.what) { - case MESSAGE_ON_MAGNIFIED_BOUNDS_CHANGED: { - Region bounds = (Region) message.obj; - handleOnMagnifiedBoundsChanged(bounds); - bounds.recycle(); - } break; - case MESSAGE_ON_RECTANGLE_ON_SCREEN_REQUESTED: { - SomeArgs args = (SomeArgs) message.obj; - final int left = args.argi1; - final int top = args.argi2; - final int right = args.argi3; - final int bottom = args.argi4; - handleOnRectangleOnScreenRequested(left, top, right, bottom); - args.recycle(); - } break; - case MESSAGE_ON_USER_CONTEXT_CHANGED: { - handleOnUserContextChanged(); - } break; - case MESSAGE_ON_ROTATION_CHANGED: { - final int rotation = message.arg1; - handleOnRotationChanged(rotation); - } break; - } - } - }; - - public ScreenMagnifier(Context context, int displayId, AccessibilityManagerService service) { - mContext = context; - mWindowManager = IWindowManager.Stub.asInterface( - ServiceManager.getService("window")); - mAms = service; - - mLongAnimationDuration = context.getResources().getInteger( - com.android.internal.R.integer.config_longAnimTime); - mTapDistanceSlop = ViewConfiguration.get(context).getScaledTouchSlop(); - mMultiTapDistanceSlop = ViewConfiguration.get(context).getScaledDoubleTapSlop(); - - mDetectingStateHandler = new DetectingStateHandler(); - mStateViewportDraggingHandler = new StateViewportDraggingHandler(); - mMagnifiedContentInteractonStateHandler = new MagnifiedContentInteractonStateHandler( - context); - - mMagnificationController = new MagnificationController(mLongAnimationDuration); - mScreenStateObserver = new ScreenStateObserver(context, mMagnificationController); - - try { - mWindowManager.setMagnificationCallbacks(this); - } catch (RemoteException re) { - /* ignore */ - } - - transitionToState(STATE_DETECTING); - } - - @Override - public void onMagnifedBoundsChanged(Region bounds) { - Region newBounds = Region.obtain(bounds); - mHandler.obtainMessage(MESSAGE_ON_MAGNIFIED_BOUNDS_CHANGED, newBounds).sendToTarget(); - if (MY_PID != Binder.getCallingPid()) { - bounds.recycle(); - } - } - - private void handleOnMagnifiedBoundsChanged(Region bounds) { - // If there was a rotation we have to update the center of the magnified - // region since the old offset X/Y may be out of its acceptable range for - // the new display width and height. - if (mUpdateMagnificationSpecOnNextBoundsChange) { - mUpdateMagnificationSpecOnNextBoundsChange = false; - MagnificationSpec spec = mMagnificationController.getMagnificationSpec(); - Rect magnifiedFrame = mTempRect; - mMagnifiedBounds.getBounds(magnifiedFrame); - final float scale = spec.scale; - final float centerX = (-spec.offsetX + magnifiedFrame.width() / 2) / scale; - final float centerY = (-spec.offsetY + magnifiedFrame.height() / 2) / scale; - mMagnificationController.setScaleAndMagnifiedRegionCenter(scale, centerX, - centerY, false); - } - mMagnifiedBounds.set(bounds); - mAms.onMagnificationStateChanged(); - } - - @Override - public void onRectangleOnScreenRequested(int left, int top, int right, int bottom) { - SomeArgs args = SomeArgs.obtain(); - args.argi1 = left; - args.argi2 = top; - args.argi3 = right; - args.argi4 = bottom; - mHandler.obtainMessage(MESSAGE_ON_RECTANGLE_ON_SCREEN_REQUESTED, args).sendToTarget(); - } - - private void handleOnRectangleOnScreenRequested(int left, int top, int right, int bottom) { - Rect magnifiedFrame = mTempRect; - mMagnifiedBounds.getBounds(magnifiedFrame); - if (!magnifiedFrame.intersects(left, top, right, bottom)) { - return; - } - Rect magnifFrameInScreenCoords = mTempRect1; - getMagnifiedFrameInContentCoords(magnifFrameInScreenCoords); - final float scrollX; - final float scrollY; - if (right - left > magnifFrameInScreenCoords.width()) { - final int direction = TextUtils.getLayoutDirectionFromLocale(Locale.getDefault()); - if (direction == View.LAYOUT_DIRECTION_LTR) { - scrollX = left - magnifFrameInScreenCoords.left; - } else { - scrollX = right - magnifFrameInScreenCoords.right; - } - } else if (left < magnifFrameInScreenCoords.left) { - scrollX = left - magnifFrameInScreenCoords.left; - } else if (right > magnifFrameInScreenCoords.right) { - scrollX = right - magnifFrameInScreenCoords.right; - } else { - scrollX = 0; - } - if (bottom - top > magnifFrameInScreenCoords.height()) { - scrollY = top - magnifFrameInScreenCoords.top; - } else if (top < magnifFrameInScreenCoords.top) { - scrollY = top - magnifFrameInScreenCoords.top; - } else if (bottom > magnifFrameInScreenCoords.bottom) { - scrollY = bottom - magnifFrameInScreenCoords.bottom; - } else { - scrollY = 0; - } - final float scale = mMagnificationController.getScale(); - mMagnificationController.offsetMagnifiedRegionCenter(scrollX * scale, scrollY * scale); - } - - @Override - public void onRotationChanged(int rotation) { - mHandler.obtainMessage(MESSAGE_ON_ROTATION_CHANGED, rotation, 0).sendToTarget(); - } - - private void handleOnRotationChanged(int rotation) { - resetMagnificationIfNeeded(); - if (mMagnificationController.isMagnifying()) { - mUpdateMagnificationSpecOnNextBoundsChange = true; - } - } - - @Override - public void onUserContextChanged() { - mHandler.sendEmptyMessage(MESSAGE_ON_USER_CONTEXT_CHANGED); - } - - private void handleOnUserContextChanged() { - resetMagnificationIfNeeded(); - } - - private void getMagnifiedFrameInContentCoords(Rect rect) { - MagnificationSpec spec = mMagnificationController.getMagnificationSpec(); - mMagnifiedBounds.getBounds(rect); - rect.offset((int) -spec.offsetX, (int) -spec.offsetY); - rect.scale(1.0f / spec.scale); - } - - private void resetMagnificationIfNeeded() { - if (mMagnificationController.isMagnifying() - && isScreenMagnificationAutoUpdateEnabled(mContext)) { - mMagnificationController.reset(true); - } - } - - @Override - public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, - int policyFlags) { - mMagnifiedContentInteractonStateHandler.onMotionEvent(event); - switch (mCurrentState) { - case STATE_DELEGATING: { - handleMotionEventStateDelegating(event, rawEvent, policyFlags); - } break; - case STATE_DETECTING: { - mDetectingStateHandler.onMotionEvent(event, rawEvent, policyFlags); - } break; - case STATE_VIEWPORT_DRAGGING: { - mStateViewportDraggingHandler.onMotionEvent(event, policyFlags); - } break; - case STATE_MAGNIFIED_INTERACTION: { - // mMagnifiedContentInteractonStateHandler handles events only - // if this is the current state since it uses ScaleGestureDetecotr - // and a GestureDetector which need well formed event stream. - } break; - default: { - throw new IllegalStateException("Unknown state: " + mCurrentState); - } - } - } - - @Override - public void onAccessibilityEvent(AccessibilityEvent event) { - if (mNext != null) { - mNext.onAccessibilityEvent(event); - } - } - - @Override - public void setNext(EventStreamTransformation next) { - mNext = next; - } - - @Override - public void clear() { - mCurrentState = STATE_DETECTING; - mDetectingStateHandler.clear(); - mStateViewportDraggingHandler.clear(); - mMagnifiedContentInteractonStateHandler.clear(); - if (mNext != null) { - mNext.clear(); - } - } - - @Override - public void onDestroy() { - mScreenStateObserver.destroy(); - try { - mWindowManager.setMagnificationCallbacks(null); - } catch (RemoteException re) { - /* ignore */ - } - } - - private void handleMotionEventStateDelegating(MotionEvent event, - MotionEvent rawEvent, int policyFlags) { - switch (event.getActionMasked()) { - case MotionEvent.ACTION_DOWN: { - mDelegatingStateDownTime = event.getDownTime(); - } break; - case MotionEvent.ACTION_UP: { - if (mDetectingStateHandler.mDelayedEventQueue == null) { - transitionToState(STATE_DETECTING); - } - } break; - } - if (mNext != null) { - // If the event is within the magnified portion of the screen we have - // to change its location to be where the user thinks he is poking the - // UI which may have been magnified and panned. - final float eventX = event.getX(); - final float eventY = event.getY(); - if (mMagnificationController.isMagnifying() - && mMagnifiedBounds.contains((int) eventX, (int) eventY)) { - final float scale = mMagnificationController.getScale(); - final float scaledOffsetX = mMagnificationController.getOffsetX(); - final float scaledOffsetY = mMagnificationController.getOffsetY(); - final int pointerCount = event.getPointerCount(); - PointerCoords[] coords = getTempPointerCoordsWithMinSize(pointerCount); - PointerProperties[] properties = getTempPointerPropertiesWithMinSize(pointerCount); - for (int i = 0; i < pointerCount; i++) { - event.getPointerCoords(i, coords[i]); - coords[i].x = (coords[i].x - scaledOffsetX) / scale; - coords[i].y = (coords[i].y - scaledOffsetY) / scale; - event.getPointerProperties(i, properties[i]); - } - event = MotionEvent.obtain(event.getDownTime(), - event.getEventTime(), event.getAction(), pointerCount, properties, - coords, 0, 0, 1.0f, 1.0f, event.getDeviceId(), 0, event.getSource(), - event.getFlags()); - } - // We cache some events to see if the user wants to trigger magnification. - // If no magnification is triggered we inject these events with adjusted - // time and down time to prevent subsequent transformations being confused - // by stale events. After the cached events, which always have a down, are - // injected we need to also update the down time of all subsequent non cached - // events. All delegated events cached and non-cached are delivered here. - event.setDownTime(mDelegatingStateDownTime); - mNext.onMotionEvent(event, rawEvent, policyFlags); - } - } - - private PointerCoords[] getTempPointerCoordsWithMinSize(int size) { - final int oldSize = (mTempPointerCoords != null) ? mTempPointerCoords.length : 0; - if (oldSize < size) { - PointerCoords[] oldTempPointerCoords = mTempPointerCoords; - mTempPointerCoords = new PointerCoords[size]; - if (oldTempPointerCoords != null) { - System.arraycopy(oldTempPointerCoords, 0, mTempPointerCoords, 0, oldSize); - } - } - for (int i = oldSize; i < size; i++) { - mTempPointerCoords[i] = new PointerCoords(); - } - return mTempPointerCoords; - } - - private PointerProperties[] getTempPointerPropertiesWithMinSize(int size) { - final int oldSize = (mTempPointerProperties != null) ? mTempPointerProperties.length : 0; - if (oldSize < size) { - PointerProperties[] oldTempPointerProperties = mTempPointerProperties; - mTempPointerProperties = new PointerProperties[size]; - if (oldTempPointerProperties != null) { - System.arraycopy(oldTempPointerProperties, 0, mTempPointerProperties, 0, oldSize); - } - } - for (int i = oldSize; i < size; i++) { - mTempPointerProperties[i] = new PointerProperties(); - } - return mTempPointerProperties; - } - - private void transitionToState(int state) { - if (DEBUG_STATE_TRANSITIONS) { - switch (state) { - case STATE_DELEGATING: { - Slog.i(LOG_TAG, "mCurrentState: STATE_DELEGATING"); - } break; - case STATE_DETECTING: { - Slog.i(LOG_TAG, "mCurrentState: STATE_DETECTING"); - } break; - case STATE_VIEWPORT_DRAGGING: { - Slog.i(LOG_TAG, "mCurrentState: STATE_VIEWPORT_DRAGGING"); - } break; - case STATE_MAGNIFIED_INTERACTION: { - Slog.i(LOG_TAG, "mCurrentState: STATE_MAGNIFIED_INTERACTION"); - } break; - default: { - throw new IllegalArgumentException("Unknown state: " + state); - } - } - } - mPreviousState = mCurrentState; - mCurrentState = state; - } - - private final class MagnifiedContentInteractonStateHandler - extends SimpleOnGestureListener implements OnScaleGestureListener { - private static final float MIN_SCALE = 1.3f; - private static final float MAX_SCALE = 5.0f; - - private static final float SCALING_THRESHOLD = 0.3f; - - private final ScaleGestureDetector mScaleGestureDetector; - private final GestureDetector mGestureDetector; - - private float mInitialScaleFactor = -1; - private boolean mScaling; - - public MagnifiedContentInteractonStateHandler(Context context) { - mScaleGestureDetector = new ScaleGestureDetector(context, this); - mScaleGestureDetector.setQuickScaleEnabled(false); - mGestureDetector = new GestureDetector(context, this); - } - - public void onMotionEvent(MotionEvent event) { - mScaleGestureDetector.onTouchEvent(event); - mGestureDetector.onTouchEvent(event); - if (mCurrentState != STATE_MAGNIFIED_INTERACTION) { - return; - } - if (event.getActionMasked() == MotionEvent.ACTION_UP) { - clear(); - final float scale = Math.min(Math.max(mMagnificationController.getScale(), - MIN_SCALE), MAX_SCALE); - if (scale != getPersistedScale()) { - persistScale(scale); - } - if (mPreviousState == STATE_VIEWPORT_DRAGGING) { - transitionToState(STATE_VIEWPORT_DRAGGING); - } else { - transitionToState(STATE_DETECTING); - } - } - } - - @Override - public boolean onScroll(MotionEvent first, MotionEvent second, float distanceX, - float distanceY) { - if (mCurrentState != STATE_MAGNIFIED_INTERACTION) { - return true; - } - if (DEBUG_PANNING) { - Slog.i(LOG_TAG, "Panned content by scrollX: " + distanceX - + " scrollY: " + distanceY); - } - mMagnificationController.offsetMagnifiedRegionCenter(distanceX, distanceY); - return true; - } - - @Override - public boolean onScale(ScaleGestureDetector detector) { - if (!mScaling) { - if (mInitialScaleFactor < 0) { - mInitialScaleFactor = detector.getScaleFactor(); - } else { - final float deltaScale = detector.getScaleFactor() - mInitialScaleFactor; - if (Math.abs(deltaScale) > SCALING_THRESHOLD) { - mScaling = true; - return true; - } - } - return false; - } - final float newScale = mMagnificationController.getScale() - * detector.getScaleFactor(); - final float normalizedNewScale = Math.min(Math.max(newScale, MIN_SCALE), MAX_SCALE); - if (DEBUG_SCALING) { - Slog.i(LOG_TAG, "normalizedNewScale: " + normalizedNewScale); - } - mMagnificationController.setScale(normalizedNewScale, detector.getFocusX(), - detector.getFocusY(), false); - return true; - } - - @Override - public boolean onScaleBegin(ScaleGestureDetector detector) { - return (mCurrentState == STATE_MAGNIFIED_INTERACTION); - } - - @Override - public void onScaleEnd(ScaleGestureDetector detector) { - clear(); - } - - private void clear() { - mInitialScaleFactor = -1; - mScaling = false; - } - } - - private final class StateViewportDraggingHandler { - private boolean mLastMoveOutsideMagnifiedRegion; - - private void onMotionEvent(MotionEvent event, int policyFlags) { - final int action = event.getActionMasked(); - switch (action) { - case MotionEvent.ACTION_DOWN: { - throw new IllegalArgumentException("Unexpected event type: ACTION_DOWN"); - } - case MotionEvent.ACTION_POINTER_DOWN: { - clear(); - transitionToState(STATE_MAGNIFIED_INTERACTION); - } break; - case MotionEvent.ACTION_MOVE: { - if (event.getPointerCount() != 1) { - throw new IllegalStateException("Should have one pointer down."); - } - final float eventX = event.getX(); - final float eventY = event.getY(); - if (mMagnifiedBounds.contains((int) eventX, (int) eventY)) { - if (mLastMoveOutsideMagnifiedRegion) { - mLastMoveOutsideMagnifiedRegion = false; - mMagnificationController.setMagnifiedRegionCenter(eventX, - eventY, true); - } else { - mMagnificationController.setMagnifiedRegionCenter(eventX, - eventY, false); - } - } else { - mLastMoveOutsideMagnifiedRegion = true; - } - } break; - case MotionEvent.ACTION_UP: { - if (!mTranslationEnabledBeforePan) { - mMagnificationController.reset(true); - } - clear(); - transitionToState(STATE_DETECTING); - } break; - case MotionEvent.ACTION_POINTER_UP: { - throw new IllegalArgumentException("Unexpected event type: ACTION_POINTER_UP"); - } - } - } - - public void clear() { - mLastMoveOutsideMagnifiedRegion = false; - } - } - - private final class DetectingStateHandler { - - private static final int MESSAGE_ON_ACTION_TAP_AND_HOLD = 1; - - private static final int MESSAGE_TRANSITION_TO_DELEGATING_STATE = 2; - - private static final int ACTION_TAP_COUNT = 3; - - private MotionEventInfo mDelayedEventQueue; - - private MotionEvent mLastDownEvent; - private MotionEvent mLastTapUpEvent; - private int mTapCount; - - private final Handler mHandler = new Handler() { - @Override - public void handleMessage(Message message) { - final int type = message.what; - switch (type) { - case MESSAGE_ON_ACTION_TAP_AND_HOLD: { - MotionEvent event = (MotionEvent) message.obj; - final int policyFlags = message.arg1; - onActionTapAndHold(event, policyFlags); - } break; - case MESSAGE_TRANSITION_TO_DELEGATING_STATE: { - transitionToState(STATE_DELEGATING); - sendDelayedMotionEvents(); - clear(); - } break; - default: { - throw new IllegalArgumentException("Unknown message type: " + type); - } - } - } - }; - - public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) { - cacheDelayedMotionEvent(event, rawEvent, policyFlags); - final int action = event.getActionMasked(); - switch (action) { - case MotionEvent.ACTION_DOWN: { - mHandler.removeMessages(MESSAGE_TRANSITION_TO_DELEGATING_STATE); - if (!mMagnifiedBounds.contains((int) event.getX(), - (int) event.getY())) { - transitionToDelegatingStateAndClear(); - return; - } - if (mTapCount == ACTION_TAP_COUNT - 1 && mLastDownEvent != null - && GestureUtils.isMultiTap(mLastDownEvent, event, - mMultiTapTimeSlop, mMultiTapDistanceSlop, 0)) { - Message message = mHandler.obtainMessage(MESSAGE_ON_ACTION_TAP_AND_HOLD, - policyFlags, 0, event); - mHandler.sendMessageDelayed(message, - ViewConfiguration.getLongPressTimeout()); - } else if (mTapCount < ACTION_TAP_COUNT) { - Message message = mHandler.obtainMessage( - MESSAGE_TRANSITION_TO_DELEGATING_STATE); - mHandler.sendMessageDelayed(message, mMultiTapTimeSlop); - } - clearLastDownEvent(); - mLastDownEvent = MotionEvent.obtain(event); - } break; - case MotionEvent.ACTION_POINTER_DOWN: { - if (mMagnificationController.isMagnifying()) { - transitionToState(STATE_MAGNIFIED_INTERACTION); - clear(); - } else { - transitionToDelegatingStateAndClear(); - } - } break; - case MotionEvent.ACTION_MOVE: { - if (mLastDownEvent != null && mTapCount < ACTION_TAP_COUNT - 1) { - final double distance = GestureUtils.computeDistance(mLastDownEvent, - event, 0); - if (Math.abs(distance) > mTapDistanceSlop) { - transitionToDelegatingStateAndClear(); - } - } - } break; - case MotionEvent.ACTION_UP: { - if (mLastDownEvent == null) { - return; - } - mHandler.removeMessages(MESSAGE_ON_ACTION_TAP_AND_HOLD); - if (!mMagnifiedBounds.contains((int) event.getX(), (int) event.getY())) { - transitionToDelegatingStateAndClear(); - return; - } - if (!GestureUtils.isTap(mLastDownEvent, event, mTapTimeSlop, - mTapDistanceSlop, 0)) { - transitionToDelegatingStateAndClear(); - return; - } - if (mLastTapUpEvent != null && !GestureUtils.isMultiTap(mLastTapUpEvent, - event, mMultiTapTimeSlop, mMultiTapDistanceSlop, 0)) { - transitionToDelegatingStateAndClear(); - return; - } - mTapCount++; - if (DEBUG_DETECTING) { - Slog.i(LOG_TAG, "Tap count:" + mTapCount); - } - if (mTapCount == ACTION_TAP_COUNT) { - clear(); - onActionTap(event, policyFlags); - return; - } - clearLastTapUpEvent(); - mLastTapUpEvent = MotionEvent.obtain(event); - } break; - case MotionEvent.ACTION_POINTER_UP: { - /* do nothing */ - } break; - } - } - - public void clear() { - mHandler.removeMessages(MESSAGE_ON_ACTION_TAP_AND_HOLD); - mHandler.removeMessages(MESSAGE_TRANSITION_TO_DELEGATING_STATE); - clearTapDetectionState(); - clearDelayedMotionEvents(); - } - - private void clearTapDetectionState() { - mTapCount = 0; - clearLastTapUpEvent(); - clearLastDownEvent(); - } - - private void clearLastTapUpEvent() { - if (mLastTapUpEvent != null) { - mLastTapUpEvent.recycle(); - mLastTapUpEvent = null; - } - } - - private void clearLastDownEvent() { - if (mLastDownEvent != null) { - mLastDownEvent.recycle(); - mLastDownEvent = null; - } - } - - private void cacheDelayedMotionEvent(MotionEvent event, MotionEvent rawEvent, - int policyFlags) { - MotionEventInfo info = MotionEventInfo.obtain(event, rawEvent, - policyFlags); - if (mDelayedEventQueue == null) { - mDelayedEventQueue = info; - } else { - MotionEventInfo tail = mDelayedEventQueue; - while (tail.mNext != null) { - tail = tail.mNext; - } - tail.mNext = info; - } - } - - private void sendDelayedMotionEvents() { - while (mDelayedEventQueue != null) { - MotionEventInfo info = mDelayedEventQueue; - mDelayedEventQueue = info.mNext; - final long offset = SystemClock.uptimeMillis() - info.mCachedTimeMillis; - MotionEvent event = obtainEventWithOffsetTimeAndDownTime(info.mEvent, offset); - MotionEvent rawEvent = obtainEventWithOffsetTimeAndDownTime(info.mRawEvent, offset); - ScreenMagnifier.this.onMotionEvent(event, rawEvent, info.mPolicyFlags); - event.recycle(); - rawEvent.recycle(); - info.recycle(); - } - } - - private MotionEvent obtainEventWithOffsetTimeAndDownTime(MotionEvent event, long offset) { - final int pointerCount = event.getPointerCount(); - PointerCoords[] coords = getTempPointerCoordsWithMinSize(pointerCount); - PointerProperties[] properties = getTempPointerPropertiesWithMinSize(pointerCount); - for (int i = 0; i < pointerCount; i++) { - event.getPointerCoords(i, coords[i]); - event.getPointerProperties(i, properties[i]); - } - final long downTime = event.getDownTime() + offset; - final long eventTime = event.getEventTime() + offset; - return MotionEvent.obtain(downTime, eventTime, - event.getAction(), pointerCount, properties, coords, - event.getMetaState(), event.getButtonState(), - 1.0f, 1.0f, event.getDeviceId(), event.getEdgeFlags(), - event.getSource(), event.getFlags()); - } - - private void clearDelayedMotionEvents() { - while (mDelayedEventQueue != null) { - MotionEventInfo info = mDelayedEventQueue; - mDelayedEventQueue = info.mNext; - info.recycle(); - } - } - - private void transitionToDelegatingStateAndClear() { - transitionToState(STATE_DELEGATING); - sendDelayedMotionEvents(); - clear(); - } - - private void onActionTap(MotionEvent up, int policyFlags) { - if (DEBUG_DETECTING) { - Slog.i(LOG_TAG, "onActionTap()"); - } - if (!mMagnificationController.isMagnifying()) { - mMagnificationController.setScaleAndMagnifiedRegionCenter(getPersistedScale(), - up.getX(), up.getY(), true); - } else { - mMagnificationController.reset(true); - } - } - - private void onActionTapAndHold(MotionEvent down, int policyFlags) { - if (DEBUG_DETECTING) { - Slog.i(LOG_TAG, "onActionTapAndHold()"); - } - clear(); - mTranslationEnabledBeforePan = mMagnificationController.isMagnifying(); - mMagnificationController.setScaleAndMagnifiedRegionCenter(getPersistedScale(), - down.getX(), down.getY(), true); - transitionToState(STATE_VIEWPORT_DRAGGING); - } - } - - private void persistScale(final float scale) { - new AsyncTask<Void, Void, Void>() { - @Override - protected Void doInBackground(Void... params) { - Settings.Secure.putFloat(mContext.getContentResolver(), - Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, scale); - return null; - } - }.execute(); - } - - private float getPersistedScale() { - return Settings.Secure.getFloat(mContext.getContentResolver(), - Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, - DEFAULT_MAGNIFICATION_SCALE); - } - - private static boolean isScreenMagnificationAutoUpdateEnabled(Context context) { - return (Settings.Secure.getInt(context.getContentResolver(), - Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_AUTO_UPDATE, - DEFAULT_SCREEN_MAGNIFICATION_AUTO_UPDATE) == 1); - } - - private static final class MotionEventInfo { - - private static final int MAX_POOL_SIZE = 10; - - private static final Object sLock = new Object(); - private static MotionEventInfo sPool; - private static int sPoolSize; - - private MotionEventInfo mNext; - private boolean mInPool; - - public MotionEvent mEvent; - public MotionEvent mRawEvent; - public int mPolicyFlags; - public long mCachedTimeMillis; - - public static MotionEventInfo obtain(MotionEvent event, MotionEvent rawEvent, - int policyFlags) { - synchronized (sLock) { - MotionEventInfo info; - if (sPoolSize > 0) { - sPoolSize--; - info = sPool; - sPool = info.mNext; - info.mNext = null; - info.mInPool = false; - } else { - info = new MotionEventInfo(); - } - info.initialize(event, rawEvent, policyFlags); - return info; - } - } - - private void initialize(MotionEvent event, MotionEvent rawEvent, - int policyFlags) { - mEvent = MotionEvent.obtain(event); - mRawEvent = MotionEvent.obtain(rawEvent); - mPolicyFlags = policyFlags; - mCachedTimeMillis = SystemClock.uptimeMillis(); - } - - public void recycle() { - synchronized (sLock) { - if (mInPool) { - throw new IllegalStateException("Already recycled."); - } - clear(); - if (sPoolSize < MAX_POOL_SIZE) { - sPoolSize++; - mNext = sPool; - sPool = this; - mInPool = true; - } - } - } - - private void clear() { - mEvent.recycle(); - mEvent = null; - mRawEvent.recycle(); - mRawEvent = null; - mPolicyFlags = 0; - mCachedTimeMillis = 0; - } - } - - private final class MagnificationController { - - private static final String PROPERTY_NAME_MAGNIFICATION_SPEC = - "magnificationSpec"; - - private final MagnificationSpec mSentMagnificationSpec = MagnificationSpec.obtain(); - - private final MagnificationSpec mCurrentMagnificationSpec = MagnificationSpec.obtain(); - - private final Rect mTempRect = new Rect(); - - private final ValueAnimator mTransformationAnimator; - - public MagnificationController(long animationDuration) { - Property<MagnificationController, MagnificationSpec> property = - Property.of(MagnificationController.class, MagnificationSpec.class, - PROPERTY_NAME_MAGNIFICATION_SPEC); - TypeEvaluator<MagnificationSpec> evaluator = new TypeEvaluator<MagnificationSpec>() { - private final MagnificationSpec mTempTransformationSpec = - MagnificationSpec.obtain(); - @Override - public MagnificationSpec evaluate(float fraction, MagnificationSpec fromSpec, - MagnificationSpec toSpec) { - MagnificationSpec result = mTempTransformationSpec; - result.scale = fromSpec.scale - + (toSpec.scale - fromSpec.scale) * fraction; - result.offsetX = fromSpec.offsetX + (toSpec.offsetX - fromSpec.offsetX) - * fraction; - result.offsetY = fromSpec.offsetY + (toSpec.offsetY - fromSpec.offsetY) - * fraction; - return result; - } - }; - mTransformationAnimator = ObjectAnimator.ofObject(this, property, - evaluator, mSentMagnificationSpec, mCurrentMagnificationSpec); - mTransformationAnimator.setDuration((long) (animationDuration)); - mTransformationAnimator.setInterpolator(new DecelerateInterpolator(2.5f)); - } - - public boolean isMagnifying() { - return mCurrentMagnificationSpec.scale > 1.0f; - } - - public void reset(boolean animate) { - if (mTransformationAnimator.isRunning()) { - mTransformationAnimator.cancel(); - } - mCurrentMagnificationSpec.clear(); - if (animate) { - animateMangificationSpec(mSentMagnificationSpec, - mCurrentMagnificationSpec); - } else { - setMagnificationSpec(mCurrentMagnificationSpec); - } - Rect bounds = mTempRect; - bounds.setEmpty(); - mAms.onMagnificationStateChanged(); - } - - public float getScale() { - return mCurrentMagnificationSpec.scale; - } - - public float getOffsetX() { - return mCurrentMagnificationSpec.offsetX; - } - - public float getOffsetY() { - return mCurrentMagnificationSpec.offsetY; - } - - public void setScale(float scale, float pivotX, float pivotY, boolean animate) { - Rect magnifiedFrame = mTempRect; - mMagnifiedBounds.getBounds(magnifiedFrame); - MagnificationSpec spec = mCurrentMagnificationSpec; - final float oldScale = spec.scale; - final float oldCenterX = (-spec.offsetX + magnifiedFrame.width() / 2) / oldScale; - final float oldCenterY = (-spec.offsetY + magnifiedFrame.height() / 2) / oldScale; - final float normPivotX = (-spec.offsetX + pivotX) / oldScale; - final float normPivotY = (-spec.offsetY + pivotY) / oldScale; - final float offsetX = (oldCenterX - normPivotX) * (oldScale / scale); - final float offsetY = (oldCenterY - normPivotY) * (oldScale / scale); - final float centerX = normPivotX + offsetX; - final float centerY = normPivotY + offsetY; - setScaleAndMagnifiedRegionCenter(scale, centerX, centerY, animate); - } - - public void setMagnifiedRegionCenter(float centerX, float centerY, boolean animate) { - setScaleAndMagnifiedRegionCenter(mCurrentMagnificationSpec.scale, centerX, centerY, - animate); - } - - public void offsetMagnifiedRegionCenter(float offsetX, float offsetY) { - final float nonNormOffsetX = mCurrentMagnificationSpec.offsetX - offsetX; - mCurrentMagnificationSpec.offsetX = Math.min(Math.max(nonNormOffsetX, - getMinOffsetX()), 0); - final float nonNormOffsetY = mCurrentMagnificationSpec.offsetY - offsetY; - mCurrentMagnificationSpec.offsetY = Math.min(Math.max(nonNormOffsetY, - getMinOffsetY()), 0); - setMagnificationSpec(mCurrentMagnificationSpec); - } - - public void setScaleAndMagnifiedRegionCenter(float scale, float centerX, float centerY, - boolean animate) { - if (Float.compare(mCurrentMagnificationSpec.scale, scale) == 0 - && Float.compare(mCurrentMagnificationSpec.offsetX, - centerX) == 0 - && Float.compare(mCurrentMagnificationSpec.offsetY, - centerY) == 0) { - return; - } - if (mTransformationAnimator.isRunning()) { - mTransformationAnimator.cancel(); - } - if (DEBUG_MAGNIFICATION_CONTROLLER) { - Slog.i(LOG_TAG, "scale: " + scale + " offsetX: " + centerX - + " offsetY: " + centerY); - } - updateMagnificationSpec(scale, centerX, centerY); - if (animate) { - animateMangificationSpec(mSentMagnificationSpec, - mCurrentMagnificationSpec); - } else { - setMagnificationSpec(mCurrentMagnificationSpec); - } - mAms.onMagnificationStateChanged(); - } - - public void updateMagnificationSpec(float scale, float magnifiedCenterX, - float magnifiedCenterY) { - Rect magnifiedFrame = mTempRect; - mMagnifiedBounds.getBounds(magnifiedFrame); - mCurrentMagnificationSpec.scale = scale; - final int viewportWidth = magnifiedFrame.width(); - final float nonNormOffsetX = viewportWidth / 2 - magnifiedCenterX * scale; - mCurrentMagnificationSpec.offsetX = Math.min(Math.max(nonNormOffsetX, - getMinOffsetX()), 0); - final int viewportHeight = magnifiedFrame.height(); - final float nonNormOffsetY = viewportHeight / 2 - magnifiedCenterY * scale; - mCurrentMagnificationSpec.offsetY = Math.min(Math.max(nonNormOffsetY, - getMinOffsetY()), 0); - } - - private float getMinOffsetX() { - Rect magnifiedFrame = mTempRect; - mMagnifiedBounds.getBounds(magnifiedFrame); - final float viewportWidth = magnifiedFrame.width(); - return viewportWidth - viewportWidth * mCurrentMagnificationSpec.scale; - } - - private float getMinOffsetY() { - Rect magnifiedFrame = mTempRect; - mMagnifiedBounds.getBounds(magnifiedFrame); - final float viewportHeight = magnifiedFrame.height(); - return viewportHeight - viewportHeight * mCurrentMagnificationSpec.scale; - } - - private void animateMangificationSpec(MagnificationSpec fromSpec, - MagnificationSpec toSpec) { - mTransformationAnimator.setObjectValues(fromSpec, toSpec); - mTransformationAnimator.start(); - } - - public MagnificationSpec getMagnificationSpec() { - return mSentMagnificationSpec; - } - - public void setMagnificationSpec(MagnificationSpec spec) { - if (DEBUG_SET_MAGNIFICATION_SPEC) { - Slog.i(LOG_TAG, "Sending: " + spec); - } - try { - mSentMagnificationSpec.scale = spec.scale; - mSentMagnificationSpec.offsetX = spec.offsetX; - mSentMagnificationSpec.offsetY = spec.offsetY; - mWindowManager.setMagnificationSpec( - MagnificationSpec.obtain(spec)); - } catch (RemoteException re) { - /* ignore */ - } - } - } - - private final class ScreenStateObserver extends BroadcastReceiver { - private static final int MESSAGE_ON_SCREEN_STATE_CHANGE = 1; - - private final Context mContext; - private final MagnificationController mMagnificationController; - - private final Handler mHandler = new Handler() { - @Override - public void handleMessage(Message message) { - switch (message.what) { - case MESSAGE_ON_SCREEN_STATE_CHANGE: { - String action = (String) message.obj; - handleOnScreenStateChange(action); - } break; - } - } - }; - - public ScreenStateObserver(Context context, - MagnificationController magnificationController) { - mContext = context; - mMagnificationController = magnificationController; - mContext.registerReceiver(this, new IntentFilter(Intent.ACTION_SCREEN_OFF)); - } - - public void destroy() { - mContext.unregisterReceiver(this); - } - - @Override - public void onReceive(Context context, Intent intent) { - mHandler.obtainMessage(MESSAGE_ON_SCREEN_STATE_CHANGE, - intent.getAction()).sendToTarget(); - } - - private void handleOnScreenStateChange(String action) { - if (mMagnificationController.isMagnifying() - && isScreenMagnificationAutoUpdateEnabled(mContext)) { - mMagnificationController.reset(false); - } - } - } -} diff --git a/services/java/com/android/server/accessibility/TouchExplorer.java b/services/java/com/android/server/accessibility/TouchExplorer.java deleted file mode 100644 index 43f12eb..0000000 --- a/services/java/com/android/server/accessibility/TouchExplorer.java +++ /dev/null @@ -1,1937 +0,0 @@ -/* - ** Copyright 2011, The Android Open Source Project - ** - ** Licensed under the Apache License, Version 2.0 (the "License"); - ** you may not use this file except in compliance with the License. - ** You may obtain a copy of the License at - ** - ** http://www.apache.org/licenses/LICENSE-2.0 - ** - ** Unless required by applicable law or agreed to in writing, software - ** distributed under the License is distributed on an "AS IS" BASIS, - ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ** See the License for the specific language governing permissions and - ** limitations under the License. - */ - -package com.android.server.accessibility; - -import android.content.Context; -import android.gesture.Gesture; -import android.gesture.GestureLibraries; -import android.gesture.GestureLibrary; -import android.gesture.GesturePoint; -import android.gesture.GestureStore; -import android.gesture.GestureStroke; -import android.gesture.Prediction; -import android.graphics.Rect; -import android.os.Handler; -import android.os.SystemClock; -import android.util.Slog; -import android.view.MotionEvent; -import android.view.MotionEvent.PointerCoords; -import android.view.MotionEvent.PointerProperties; -import android.view.VelocityTracker; -import android.view.ViewConfiguration; -import android.view.WindowManagerPolicy; -import android.view.accessibility.AccessibilityEvent; -import android.view.accessibility.AccessibilityManager; - -import com.android.internal.R; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -/** - * This class is a strategy for performing touch exploration. It - * transforms the motion event stream by modifying, adding, replacing, - * and consuming certain events. The interaction model is: - * - * <ol> - * <li>1. One finger moving slow around performs touch exploration.</li> - * <li>2. One finger moving fast around performs gestures.</li> - * <li>3. Two close fingers moving in the same direction perform a drag.</li> - * <li>4. Multi-finger gestures are delivered to view hierarchy.</li> - * <li>5. Two fingers moving in different directions are considered a multi-finger gesture.</li> - * <li>7. Double tapping clicks on the on the last touch explored location if it was in - * a window that does not take focus, otherwise the click is within the accessibility - * focused rectangle.</li> - * <li>7. Tapping and holding for a while performs a long press in a similar fashion - * as the click above.</li> - * <ol> - * - * @hide - */ -class TouchExplorer implements EventStreamTransformation { - - private static final boolean DEBUG = false; - - // Tag for logging received events. - private static final String LOG_TAG = "TouchExplorer"; - - // States this explorer can be in. - private static final int STATE_TOUCH_EXPLORING = 0x00000001; - private static final int STATE_DRAGGING = 0x00000002; - private static final int STATE_DELEGATING = 0x00000004; - private static final int STATE_GESTURE_DETECTING = 0x00000005; - - // The maximum of the cosine between the vectors of two moving - // pointers so they can be considered moving in the same direction. - private static final float MAX_DRAGGING_ANGLE_COS = 0.525321989f; // cos(pi/4) - - // Constant referring to the ids bits of all pointers. - private static final int ALL_POINTER_ID_BITS = 0xFFFFFFFF; - - // This constant captures the current implementation detail that - // pointer IDs are between 0 and 31 inclusive (subject to change). - // (See MAX_POINTER_ID in frameworks/base/include/ui/Input.h) - private static final int MAX_POINTER_COUNT = 32; - - // Invalid pointer ID. - private static final int INVALID_POINTER_ID = -1; - - // The velocity above which we detect gestures. - private static final int GESTURE_DETECTION_VELOCITY_DIP = 1000; - - // The minimal distance before we take the middle of the distance between - // the two dragging pointers as opposed to use the location of the primary one. - private static final int MIN_POINTER_DISTANCE_TO_USE_MIDDLE_LOCATION_DIP = 200; - - // The timeout after which we are no longer trying to detect a gesture. - private static final int EXIT_GESTURE_DETECTION_TIMEOUT = 2000; - - // Timeout before trying to decide what the user is trying to do. - private final int mDetermineUserIntentTimeout; - - // Timeout within which we try to detect a tap. - private final int mTapTimeout; - - // Timeout within which we try to detect a double tap. - private final int mDoubleTapTimeout; - - // Slop between the down and up tap to be a tap. - private final int mTouchSlop; - - // Slop between the first and second tap to be a double tap. - private final int mDoubleTapSlop; - - // The current state of the touch explorer. - private int mCurrentState = STATE_TOUCH_EXPLORING; - - // The ID of the pointer used for dragging. - private int mDraggingPointerId; - - // Handler for performing asynchronous operations. - private final Handler mHandler; - - // Command for delayed sending of a hover enter and move event. - private final SendHoverEnterAndMoveDelayed mSendHoverEnterAndMoveDelayed; - - // Command for delayed sending of a hover exit event. - private final SendHoverExitDelayed mSendHoverExitDelayed; - - // Command for delayed sending of touch exploration end events. - private final SendAccessibilityEventDelayed mSendTouchExplorationEndDelayed; - - // Command for delayed sending of touch interaction end events. - private final SendAccessibilityEventDelayed mSendTouchInteractionEndDelayed; - - // Command for delayed sending of a long press. - private final PerformLongPressDelayed mPerformLongPressDelayed; - - // Command for exiting gesture detection mode after a timeout. - private final ExitGestureDetectionModeDelayed mExitGestureDetectionModeDelayed; - - // Helper to detect and react to double tap in touch explore mode. - private final DoubleTapDetector mDoubleTapDetector; - - // The scaled minimal distance before we take the middle of the distance between - // the two dragging pointers as opposed to use the location of the primary one. - private final int mScaledMinPointerDistanceToUseMiddleLocation; - - // The scaled velocity above which we detect gestures. - private final int mScaledGestureDetectionVelocity; - - // The handler to which to delegate events. - private EventStreamTransformation mNext; - - // Helper to track gesture velocity. - private final VelocityTracker mVelocityTracker = VelocityTracker.obtain(); - - // Helper class to track received pointers. - private final ReceivedPointerTracker mReceivedPointerTracker; - - // Helper class to track injected pointers. - private final InjectedPointerTracker mInjectedPointerTracker; - - // Handle to the accessibility manager service. - private final AccessibilityManagerService mAms; - - // Temporary rectangle to avoid instantiation. - private final Rect mTempRect = new Rect(); - - // Context in which this explorer operates. - private final Context mContext; - - // The X of the previous event. - private float mPreviousX; - - // The Y of the previous event. - private float mPreviousY; - - // Buffer for storing points for gesture detection. - private final ArrayList<GesturePoint> mStrokeBuffer = new ArrayList<GesturePoint>(100); - - // The minimal delta between moves to add a gesture point. - private static final int TOUCH_TOLERANCE = 3; - - // The minimal score for accepting a predicted gesture. - private static final float MIN_PREDICTION_SCORE = 2.0f; - - // The library for gesture detection. - private GestureLibrary mGestureLibrary; - - // The long pressing pointer id if coordinate remapping is needed. - private int mLongPressingPointerId = -1; - - // The long pressing pointer X if coordinate remapping is needed. - private int mLongPressingPointerDeltaX; - - // The long pressing pointer Y if coordinate remapping is needed. - private int mLongPressingPointerDeltaY; - - // The id of the last touch explored window. - private int mLastTouchedWindowId; - - // Whether touch exploration is in progress. - private boolean mTouchExplorationInProgress; - - /** - * Creates a new instance. - * - * @param inputFilter The input filter associated with this explorer. - * @param context A context handle for accessing resources. - */ - public TouchExplorer(Context context, AccessibilityManagerService service) { - mContext = context; - mAms = service; - mReceivedPointerTracker = new ReceivedPointerTracker(); - mInjectedPointerTracker = new InjectedPointerTracker(); - mTapTimeout = ViewConfiguration.getTapTimeout(); - mDetermineUserIntentTimeout = ViewConfiguration.getDoubleTapTimeout(); - mDoubleTapTimeout = ViewConfiguration.getDoubleTapTimeout(); - mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); - mDoubleTapSlop = ViewConfiguration.get(context).getScaledDoubleTapSlop(); - mHandler = new Handler(context.getMainLooper()); - mPerformLongPressDelayed = new PerformLongPressDelayed(); - mExitGestureDetectionModeDelayed = new ExitGestureDetectionModeDelayed(); - mGestureLibrary = GestureLibraries.fromRawResource(context, R.raw.accessibility_gestures); - mGestureLibrary.setOrientationStyle(8); - mGestureLibrary.setSequenceType(GestureStore.SEQUENCE_SENSITIVE); - mGestureLibrary.load(); - mSendHoverEnterAndMoveDelayed = new SendHoverEnterAndMoveDelayed(); - mSendHoverExitDelayed = new SendHoverExitDelayed(); - mSendTouchExplorationEndDelayed = new SendAccessibilityEventDelayed( - AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END, - mDetermineUserIntentTimeout); - mSendTouchInteractionEndDelayed = new SendAccessibilityEventDelayed( - AccessibilityEvent.TYPE_TOUCH_INTERACTION_END, - mDetermineUserIntentTimeout); - mDoubleTapDetector = new DoubleTapDetector(); - final float density = context.getResources().getDisplayMetrics().density; - mScaledMinPointerDistanceToUseMiddleLocation = - (int) (MIN_POINTER_DISTANCE_TO_USE_MIDDLE_LOCATION_DIP * density); - mScaledGestureDetectionVelocity = (int) (GESTURE_DETECTION_VELOCITY_DIP * density); - } - - public void clear() { - // If we have not received an event then we are in initial - // state. Therefore, there is not need to clean anything. - MotionEvent event = mReceivedPointerTracker.getLastReceivedEvent(); - if (event != null) { - clear(mReceivedPointerTracker.getLastReceivedEvent(), WindowManagerPolicy.FLAG_TRUSTED); - } - } - - public void onDestroy() { - // TODO: Implement - } - - private void clear(MotionEvent event, int policyFlags) { - switch (mCurrentState) { - case STATE_TOUCH_EXPLORING: { - // If a touch exploration gesture is in progress send events for its end. - sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags); - } break; - case STATE_DRAGGING: { - mDraggingPointerId = INVALID_POINTER_ID; - // Send exit to all pointers that we have delivered. - sendUpForInjectedDownPointers(event, policyFlags); - } break; - case STATE_DELEGATING: { - // Send exit to all pointers that we have delivered. - sendUpForInjectedDownPointers(event, policyFlags); - } break; - case STATE_GESTURE_DETECTING: { - // Clear the current stroke. - mStrokeBuffer.clear(); - } break; - } - // Remove all pending callbacks. - mSendHoverEnterAndMoveDelayed.cancel(); - mSendHoverExitDelayed.cancel(); - mPerformLongPressDelayed.cancel(); - mExitGestureDetectionModeDelayed.cancel(); - mSendTouchExplorationEndDelayed.cancel(); - mSendTouchInteractionEndDelayed.cancel(); - // Reset the pointer trackers. - mReceivedPointerTracker.clear(); - mInjectedPointerTracker.clear(); - // Clear the double tap detector - mDoubleTapDetector.clear(); - // Go to initial state. - // Clear the long pressing pointer remap data. - mLongPressingPointerId = -1; - mLongPressingPointerDeltaX = 0; - mLongPressingPointerDeltaY = 0; - mCurrentState = STATE_TOUCH_EXPLORING; - if (mNext != null) { - mNext.clear(); - } - mTouchExplorationInProgress = false; - mAms.onTouchInteractionEnd(); - } - - @Override - public void setNext(EventStreamTransformation next) { - mNext = next; - } - - @Override - public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) { - if (DEBUG) { - Slog.d(LOG_TAG, "Received event: " + event + ", policyFlags=0x" - + Integer.toHexString(policyFlags)); - Slog.d(LOG_TAG, getStateSymbolicName(mCurrentState)); - } - - mReceivedPointerTracker.onMotionEvent(rawEvent); - - switch(mCurrentState) { - case STATE_TOUCH_EXPLORING: { - handleMotionEventStateTouchExploring(event, rawEvent, policyFlags); - } break; - case STATE_DRAGGING: { - handleMotionEventStateDragging(event, policyFlags); - } break; - case STATE_DELEGATING: { - handleMotionEventStateDelegating(event, policyFlags); - } break; - case STATE_GESTURE_DETECTING: { - handleMotionEventGestureDetecting(rawEvent, policyFlags); - } break; - default: - throw new IllegalStateException("Illegal state: " + mCurrentState); - } - } - - public void onAccessibilityEvent(AccessibilityEvent event) { - final int eventType = event.getEventType(); - - // The event for gesture end should be strictly after the - // last hover exit event. - if (mSendTouchExplorationEndDelayed.isPending() - && eventType == AccessibilityEvent.TYPE_VIEW_HOVER_EXIT) { - mSendTouchExplorationEndDelayed.cancel(); - sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END); - } - - // The event for touch interaction end should be strictly after the - // last hover exit and the touch exploration gesture end events. - if (mSendTouchInteractionEndDelayed.isPending() - && eventType == AccessibilityEvent.TYPE_VIEW_HOVER_EXIT) { - mSendTouchInteractionEndDelayed.cancel(); - sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_END); - } - - // If a new window opens or the accessibility focus moves we no longer - // want to click/long press on the last touch explored location. - switch (eventType) { - case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED: - case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED: { - if (mInjectedPointerTracker.mLastInjectedHoverEventForClick != null) { - mInjectedPointerTracker.mLastInjectedHoverEventForClick.recycle(); - mInjectedPointerTracker.mLastInjectedHoverEventForClick = null; - } - mLastTouchedWindowId = -1; - } break; - case AccessibilityEvent.TYPE_VIEW_HOVER_ENTER: - case AccessibilityEvent.TYPE_VIEW_HOVER_EXIT: { - mLastTouchedWindowId = event.getWindowId(); - } break; - } - if (mNext != null) { - mNext.onAccessibilityEvent(event); - } - } - - /** - * Handles a motion event in touch exploring state. - * - * @param event The event to be handled. - * @param rawEvent The raw (unmodified) motion event. - * @param policyFlags The policy flags associated with the event. - */ - private void handleMotionEventStateTouchExploring(MotionEvent event, MotionEvent rawEvent, - int policyFlags) { - ReceivedPointerTracker receivedTracker = mReceivedPointerTracker; - - mVelocityTracker.addMovement(rawEvent); - - mDoubleTapDetector.onMotionEvent(event, policyFlags); - - switch (event.getActionMasked()) { - case MotionEvent.ACTION_DOWN: { - mAms.onTouchInteractionStart(); - - // Pre-feed the motion events to the gesture detector since we - // have a distance slop before getting into gesture detection - // mode and not using the points within this slop significantly - // decreases the quality of gesture recognition. - handleMotionEventGestureDetecting(rawEvent, policyFlags); - sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_START); - - // If we still have not notified the user for the last - // touch, we figure out what to do. If were waiting - // we resent the delayed callback and wait again. - mSendHoverEnterAndMoveDelayed.cancel(); - mSendHoverExitDelayed.cancel(); - mPerformLongPressDelayed.cancel(); - - if (mSendTouchExplorationEndDelayed.isPending()) { - mSendTouchExplorationEndDelayed.forceSendAndRemove(); - } - - if (mSendTouchInteractionEndDelayed.isPending()) { - mSendTouchInteractionEndDelayed.forceSendAndRemove(); - } - - // If we have the first tap, schedule a long press and break - // since we do not want to schedule hover enter because - // the delayed callback will kick in before the long click. - // This would lead to a state transition resulting in long - // pressing the item below the double taped area which is - // not necessary where accessibility focus is. - if (mDoubleTapDetector.firstTapDetected()) { - // We got a tap now post a long press action. - mPerformLongPressDelayed.post(event, policyFlags); - break; - } - if (!mTouchExplorationInProgress) { - if (!mSendHoverEnterAndMoveDelayed.isPending()) { - // Deliver hover enter with a delay to have a chance - // to detect what the user is trying to do. - final int pointerId = receivedTracker.getPrimaryPointerId(); - final int pointerIdBits = (1 << pointerId); - mSendHoverEnterAndMoveDelayed.post(event, true, pointerIdBits, - policyFlags); - } else { - // Cache the event until we discern exploration from gesturing. - mSendHoverEnterAndMoveDelayed.addEvent(event); - } - } - } break; - case MotionEvent.ACTION_POINTER_DOWN: { - // Another finger down means that if we have not started to deliver - // hover events, we will not have to. The code for ACTION_MOVE will - // decide what we will actually do next. - mSendHoverEnterAndMoveDelayed.cancel(); - mSendHoverExitDelayed.cancel(); - mPerformLongPressDelayed.cancel(); - } break; - case MotionEvent.ACTION_MOVE: { - final int pointerId = receivedTracker.getPrimaryPointerId(); - final int pointerIndex = event.findPointerIndex(pointerId); - final int pointerIdBits = (1 << pointerId); - switch (event.getPointerCount()) { - case 1: { - // We have not started sending events since we try to - // figure out what the user is doing. - if (mSendHoverEnterAndMoveDelayed.isPending()) { - // Pre-feed the motion events to the gesture detector since we - // have a distance slop before getting into gesture detection - // mode and not using the points within this slop significantly - // decreases the quality of gesture recognition. - handleMotionEventGestureDetecting(rawEvent, policyFlags); - - // Cache the event until we discern exploration from gesturing. - mSendHoverEnterAndMoveDelayed.addEvent(event); - - // It is *important* to use the distance traveled by the pointers - // on the screen which may or may not be magnified. - final float deltaX = receivedTracker.getReceivedPointerDownX(pointerId) - - rawEvent.getX(pointerIndex); - final float deltaY = receivedTracker.getReceivedPointerDownY(pointerId) - - rawEvent.getY(pointerIndex); - final double moveDelta = Math.hypot(deltaX, deltaY); - // The user has moved enough for us to decide. - if (moveDelta > mDoubleTapSlop) { - // Check whether the user is performing a gesture. We - // detect gestures if the pointer is moving above a - // given velocity. - mVelocityTracker.computeCurrentVelocity(1000); - final float maxAbsVelocity = Math.max( - Math.abs(mVelocityTracker.getXVelocity(pointerId)), - Math.abs(mVelocityTracker.getYVelocity(pointerId))); - if (maxAbsVelocity > mScaledGestureDetectionVelocity) { - // We have to perform gesture detection, so - // clear the current state and try to detect. - mCurrentState = STATE_GESTURE_DETECTING; - mVelocityTracker.clear(); - mSendHoverEnterAndMoveDelayed.cancel(); - mSendHoverExitDelayed.cancel(); - mPerformLongPressDelayed.cancel(); - mExitGestureDetectionModeDelayed.post(); - // Send accessibility event to announce the start - // of gesture recognition. - sendAccessibilityEvent( - AccessibilityEvent.TYPE_GESTURE_DETECTION_START); - } else { - // We have just decided that the user is touch, - // exploring so start sending events. - mSendHoverEnterAndMoveDelayed.forceSendAndRemove(); - mSendHoverExitDelayed.cancel(); - mPerformLongPressDelayed.cancel(); - sendMotionEvent(event, MotionEvent.ACTION_HOVER_MOVE, - pointerIdBits, policyFlags); - } - break; - } - } else { - // Cancel the long press if pending and the user - // moved more than the slop. - if (mPerformLongPressDelayed.isPending()) { - final float deltaX = - receivedTracker.getReceivedPointerDownX(pointerId) - - rawEvent.getX(pointerIndex); - final float deltaY = - receivedTracker.getReceivedPointerDownY(pointerId) - - rawEvent.getY(pointerIndex); - final double moveDelta = Math.hypot(deltaX, deltaY); - // The user has moved enough for us to decide. - if (moveDelta > mTouchSlop) { - mPerformLongPressDelayed.cancel(); - } - } - if (mTouchExplorationInProgress) { - sendTouchExplorationGestureStartAndHoverEnterIfNeeded(policyFlags); - sendMotionEvent(event, MotionEvent.ACTION_HOVER_MOVE, pointerIdBits, - policyFlags); - } - } - } break; - case 2: { - // More than one pointer so the user is not touch exploring - // and now we have to decide whether to delegate or drag. - if (mSendHoverEnterAndMoveDelayed.isPending()) { - // We have not started sending events so cancel - // scheduled sending events. - mSendHoverEnterAndMoveDelayed.cancel(); - mSendHoverExitDelayed.cancel(); - mPerformLongPressDelayed.cancel(); - } else { - mPerformLongPressDelayed.cancel(); - if (mTouchExplorationInProgress) { - // If the user is touch exploring the second pointer may be - // performing a double tap to activate an item without need - // for the user to lift his exploring finger. - // It is *important* to use the distance traveled by the pointers - // on the screen which may or may not be magnified. - final float deltaX = receivedTracker.getReceivedPointerDownX( - pointerId) - rawEvent.getX(pointerIndex); - final float deltaY = receivedTracker.getReceivedPointerDownY( - pointerId) - rawEvent.getY(pointerIndex); - final double moveDelta = Math.hypot(deltaX, deltaY); - if (moveDelta < mDoubleTapSlop) { - break; - } - // We are sending events so send exit and gesture - // end since we transition to another state. - sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags); - } - } - - // We know that a new state transition is to happen and the - // new state will not be gesture recognition, so clear the - // stashed gesture strokes. - mStrokeBuffer.clear(); - - if (isDraggingGesture(event)) { - // Two pointers moving in the same direction within - // a given distance perform a drag. - mCurrentState = STATE_DRAGGING; - mDraggingPointerId = pointerId; - event.setEdgeFlags(receivedTracker.getLastReceivedDownEdgeFlags()); - sendMotionEvent(event, MotionEvent.ACTION_DOWN, pointerIdBits, - policyFlags); - } else { - // Two pointers moving arbitrary are delegated to the view hierarchy. - mCurrentState = STATE_DELEGATING; - sendDownForAllNotInjectedPointers(event, policyFlags); - } - mVelocityTracker.clear(); - } break; - default: { - // More than one pointer so the user is not touch exploring - // and now we have to decide whether to delegate or drag. - if (mSendHoverEnterAndMoveDelayed.isPending()) { - // We have not started sending events so cancel - // scheduled sending events. - mSendHoverEnterAndMoveDelayed.cancel(); - mSendHoverExitDelayed.cancel(); - mPerformLongPressDelayed.cancel(); - } else { - mPerformLongPressDelayed.cancel(); - // We are sending events so send exit and gesture - // end since we transition to another state. - sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags); - } - - // More than two pointers are delegated to the view hierarchy. - mCurrentState = STATE_DELEGATING; - sendDownForAllNotInjectedPointers(event, policyFlags); - mVelocityTracker.clear(); - } - } - } break; - case MotionEvent.ACTION_UP: { - mAms.onTouchInteractionEnd(); - // We know that we do not need the pre-fed gesture points are not - // needed anymore since the last pointer just went up. - mStrokeBuffer.clear(); - final int pointerId = event.getPointerId(event.getActionIndex()); - final int pointerIdBits = (1 << pointerId); - - mPerformLongPressDelayed.cancel(); - mVelocityTracker.clear(); - - if (mSendHoverEnterAndMoveDelayed.isPending()) { - // If we have not delivered the enter schedule an exit. - mSendHoverExitDelayed.post(event, pointerIdBits, policyFlags); - } else { - // The user is touch exploring so we send events for end. - sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags); - } - - if (!mSendTouchInteractionEndDelayed.isPending()) { - mSendTouchInteractionEndDelayed.post(); - } - - } break; - case MotionEvent.ACTION_CANCEL: { - clear(event, policyFlags); - } break; - } - } - - /** - * Handles a motion event in dragging state. - * - * @param event The event to be handled. - * @param policyFlags The policy flags associated with the event. - */ - private void handleMotionEventStateDragging(MotionEvent event, int policyFlags) { - final int pointerIdBits = (1 << mDraggingPointerId); - switch (event.getActionMasked()) { - case MotionEvent.ACTION_DOWN: { - throw new IllegalStateException("Dragging state can be reached only if two " - + "pointers are already down"); - } - case MotionEvent.ACTION_POINTER_DOWN: { - // We are in dragging state so we have two pointers and another one - // goes down => delegate the three pointers to the view hierarchy - mCurrentState = STATE_DELEGATING; - if (mDraggingPointerId != INVALID_POINTER_ID) { - sendMotionEvent(event, MotionEvent.ACTION_UP, pointerIdBits, policyFlags); - } - sendDownForAllNotInjectedPointers(event, policyFlags); - } break; - case MotionEvent.ACTION_MOVE: { - switch (event.getPointerCount()) { - case 1: { - // do nothing - } break; - case 2: { - if (isDraggingGesture(event)) { - final float firstPtrX = event.getX(0); - final float firstPtrY = event.getY(0); - final float secondPtrX = event.getX(1); - final float secondPtrY = event.getY(1); - - final float deltaX = firstPtrX - secondPtrX; - final float deltaY = firstPtrY - secondPtrY; - final double distance = Math.hypot(deltaX, deltaY); - - if (distance > mScaledMinPointerDistanceToUseMiddleLocation) { - event.setLocation(deltaX / 2, deltaY / 2); - } - - // If still dragging send a drag event. - sendMotionEvent(event, MotionEvent.ACTION_MOVE, pointerIdBits, - policyFlags); - } else { - // The two pointers are moving either in different directions or - // no close enough => delegate the gesture to the view hierarchy. - mCurrentState = STATE_DELEGATING; - // Send an event to the end of the drag gesture. - sendMotionEvent(event, MotionEvent.ACTION_UP, pointerIdBits, - policyFlags); - // Deliver all pointers to the view hierarchy. - sendDownForAllNotInjectedPointers(event, policyFlags); - } - } break; - default: { - mCurrentState = STATE_DELEGATING; - // Send an event to the end of the drag gesture. - sendMotionEvent(event, MotionEvent.ACTION_UP, pointerIdBits, - policyFlags); - // Deliver all pointers to the view hierarchy. - sendDownForAllNotInjectedPointers(event, policyFlags); - } - } - } break; - case MotionEvent.ACTION_POINTER_UP: { - final int pointerId = event.getPointerId(event.getActionIndex()); - if (pointerId == mDraggingPointerId) { - mDraggingPointerId = INVALID_POINTER_ID; - // Send an event to the end of the drag gesture. - sendMotionEvent(event, MotionEvent.ACTION_UP, pointerIdBits, policyFlags); - } - } break; - case MotionEvent.ACTION_UP: { - mAms.onTouchInteractionEnd(); - // Announce the end of a new touch interaction. - sendAccessibilityEvent( - AccessibilityEvent.TYPE_TOUCH_INTERACTION_END); - final int pointerId = event.getPointerId(event.getActionIndex()); - if (pointerId == mDraggingPointerId) { - mDraggingPointerId = INVALID_POINTER_ID; - // Send an event to the end of the drag gesture. - sendMotionEvent(event, MotionEvent.ACTION_UP, pointerIdBits, policyFlags); - } - mCurrentState = STATE_TOUCH_EXPLORING; - } break; - case MotionEvent.ACTION_CANCEL: { - clear(event, policyFlags); - } break; - } - } - - /** - * Handles a motion event in delegating state. - * - * @param event The event to be handled. - * @param policyFlags The policy flags associated with the event. - */ - private void handleMotionEventStateDelegating(MotionEvent event, int policyFlags) { - switch (event.getActionMasked()) { - case MotionEvent.ACTION_DOWN: { - throw new IllegalStateException("Delegating state can only be reached if " - + "there is at least one pointer down!"); - } - case MotionEvent.ACTION_UP: { - // Offset the event if we are doing a long press as the - // target is not necessarily under the user's finger. - if (mLongPressingPointerId >= 0) { - event = offsetEvent(event, - mLongPressingPointerDeltaX, - - mLongPressingPointerDeltaY); - // Clear the long press state. - mLongPressingPointerId = -1; - mLongPressingPointerDeltaX = 0; - mLongPressingPointerDeltaY = 0; - } - - // Deliver the event. - sendMotionEvent(event, event.getAction(), ALL_POINTER_ID_BITS, policyFlags); - - // Announce the end of a the touch interaction. - mAms.onTouchInteractionEnd(); - sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_END); - - mCurrentState = STATE_TOUCH_EXPLORING; - } break; - case MotionEvent.ACTION_CANCEL: { - clear(event, policyFlags); - } break; - default: { - // Deliver the event. - sendMotionEvent(event, event.getAction(), ALL_POINTER_ID_BITS, policyFlags); - } - } - } - - private void handleMotionEventGestureDetecting(MotionEvent event, int policyFlags) { - switch (event.getActionMasked()) { - case MotionEvent.ACTION_DOWN: { - final float x = event.getX(); - final float y = event.getY(); - mPreviousX = x; - mPreviousY = y; - mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime())); - } break; - case MotionEvent.ACTION_MOVE: { - final float x = event.getX(); - final float y = event.getY(); - final float dX = Math.abs(x - mPreviousX); - final float dY = Math.abs(y - mPreviousY); - if (dX >= TOUCH_TOLERANCE || dY >= TOUCH_TOLERANCE) { - mPreviousX = x; - mPreviousY = y; - mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime())); - } - } break; - case MotionEvent.ACTION_UP: { - mAms.onTouchInteractionEnd(); - // Announce the end of the gesture recognition. - sendAccessibilityEvent(AccessibilityEvent.TYPE_GESTURE_DETECTION_END); - // Announce the end of a the touch interaction. - sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_END); - - float x = event.getX(); - float y = event.getY(); - mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime())); - - Gesture gesture = new Gesture(); - gesture.addStroke(new GestureStroke(mStrokeBuffer)); - - ArrayList<Prediction> predictions = mGestureLibrary.recognize(gesture); - if (!predictions.isEmpty()) { - Prediction bestPrediction = predictions.get(0); - if (bestPrediction.score >= MIN_PREDICTION_SCORE) { - if (DEBUG) { - Slog.i(LOG_TAG, "gesture: " + bestPrediction.name + " score: " - + bestPrediction.score); - } - try { - final int gestureId = Integer.parseInt(bestPrediction.name); - mAms.onGesture(gestureId); - } catch (NumberFormatException nfe) { - Slog.w(LOG_TAG, "Non numeric gesture id:" + bestPrediction.name); - } - } - } - - mStrokeBuffer.clear(); - mExitGestureDetectionModeDelayed.cancel(); - mCurrentState = STATE_TOUCH_EXPLORING; - } break; - case MotionEvent.ACTION_CANCEL: { - clear(event, policyFlags); - } break; - } - } - - /** - * Sends an accessibility event of the given type. - * - * @param type The event type. - */ - private void sendAccessibilityEvent(int type) { - AccessibilityManager accessibilityManager = AccessibilityManager.getInstance(mContext); - if (accessibilityManager.isEnabled()) { - AccessibilityEvent event = AccessibilityEvent.obtain(type); - event.setWindowId(mAms.getActiveWindowId()); - accessibilityManager.sendAccessibilityEvent(event); - switch (type) { - case AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START: { - mTouchExplorationInProgress = true; - } break; - case AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END: { - mTouchExplorationInProgress = false; - } break; - } - } - } - - /** - * Sends down events to the view hierarchy for all pointers which are - * not already being delivered i.e. pointers that are not yet injected. - * - * @param prototype The prototype from which to create the injected events. - * @param policyFlags The policy flags associated with the event. - */ - private void sendDownForAllNotInjectedPointers(MotionEvent prototype, int policyFlags) { - InjectedPointerTracker injectedPointers = mInjectedPointerTracker; - - // Inject the injected pointers. - int pointerIdBits = 0; - final int pointerCount = prototype.getPointerCount(); - for (int i = 0; i < pointerCount; i++) { - final int pointerId = prototype.getPointerId(i); - // Do not send event for already delivered pointers. - if (!injectedPointers.isInjectedPointerDown(pointerId)) { - pointerIdBits |= (1 << pointerId); - final int action = computeInjectionAction(MotionEvent.ACTION_DOWN, i); - sendMotionEvent(prototype, action, pointerIdBits, policyFlags); - } - } - } - - /** - * Sends the exit events if needed. Such events are hover exit and touch explore - * gesture end. - * - * @param policyFlags The policy flags associated with the event. - */ - private void sendHoverExitAndTouchExplorationGestureEndIfNeeded(int policyFlags) { - MotionEvent event = mInjectedPointerTracker.getLastInjectedHoverEvent(); - if (event != null && event.getActionMasked() != MotionEvent.ACTION_HOVER_EXIT) { - final int pointerIdBits = event.getPointerIdBits(); - if (!mSendTouchExplorationEndDelayed.isPending()) { - mSendTouchExplorationEndDelayed.post(); - } - sendMotionEvent(event, MotionEvent.ACTION_HOVER_EXIT, pointerIdBits, policyFlags); - } - } - - /** - * Sends the enter events if needed. Such events are hover enter and touch explore - * gesture start. - * - * @param policyFlags The policy flags associated with the event. - */ - private void sendTouchExplorationGestureStartAndHoverEnterIfNeeded(int policyFlags) { - MotionEvent event = mInjectedPointerTracker.getLastInjectedHoverEvent(); - if (event != null && event.getActionMasked() == MotionEvent.ACTION_HOVER_EXIT) { - final int pointerIdBits = event.getPointerIdBits(); - sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START); - sendMotionEvent(event, MotionEvent.ACTION_HOVER_ENTER, pointerIdBits, policyFlags); - } - } - - /** - * Sends up events to the view hierarchy for all pointers which are - * already being delivered i.e. pointers that are injected. - * - * @param prototype The prototype from which to create the injected events. - * @param policyFlags The policy flags associated with the event. - */ - private void sendUpForInjectedDownPointers(MotionEvent prototype, int policyFlags) { - final InjectedPointerTracker injectedTracked = mInjectedPointerTracker; - int pointerIdBits = 0; - final int pointerCount = prototype.getPointerCount(); - for (int i = 0; i < pointerCount; i++) { - final int pointerId = prototype.getPointerId(i); - // Skip non injected down pointers. - if (!injectedTracked.isInjectedPointerDown(pointerId)) { - continue; - } - pointerIdBits |= (1 << pointerId); - final int action = computeInjectionAction(MotionEvent.ACTION_UP, i); - sendMotionEvent(prototype, action, pointerIdBits, policyFlags); - } - } - - /** - * Sends an up and down events. - * - * @param prototype The prototype from which to create the injected events. - * @param policyFlags The policy flags associated with the event. - */ - private void sendActionDownAndUp(MotionEvent prototype, int policyFlags) { - // Tap with the pointer that last explored. - final int pointerId = prototype.getPointerId(prototype.getActionIndex()); - final int pointerIdBits = (1 << pointerId); - sendMotionEvent(prototype, MotionEvent.ACTION_DOWN, pointerIdBits, policyFlags); - sendMotionEvent(prototype, MotionEvent.ACTION_UP, pointerIdBits, policyFlags); - } - - /** - * Sends an event. - * - * @param prototype The prototype from which to create the injected events. - * @param action The action of the event. - * @param pointerIdBits The bits of the pointers to send. - * @param policyFlags The policy flags associated with the event. - */ - private void sendMotionEvent(MotionEvent prototype, int action, int pointerIdBits, - int policyFlags) { - prototype.setAction(action); - - MotionEvent event = null; - if (pointerIdBits == ALL_POINTER_ID_BITS) { - event = prototype; - } else { - event = prototype.split(pointerIdBits); - } - if (action == MotionEvent.ACTION_DOWN) { - event.setDownTime(event.getEventTime()); - } else { - event.setDownTime(mInjectedPointerTracker.getLastInjectedDownEventTime()); - } - - // If the user is long pressing but the long pressing pointer - // was not exactly over the accessibility focused item we need - // to remap the location of that pointer so the user does not - // have to explicitly touch explore something to be able to - // long press it, or even worse to avoid the user long pressing - // on the wrong item since click and long press behave differently. - if (mLongPressingPointerId >= 0) { - event = offsetEvent(event, - mLongPressingPointerDeltaX, - - mLongPressingPointerDeltaY); - } - - if (DEBUG) { - Slog.d(LOG_TAG, "Injecting event: " + event + ", policyFlags=0x" - + Integer.toHexString(policyFlags)); - } - - // Make sure that the user will see the event. - policyFlags |= WindowManagerPolicy.FLAG_PASS_TO_USER; - if (mNext != null) { - // TODO: For now pass null for the raw event since the touch - // explorer is the last event transformation and it does - // not care about the raw event. - mNext.onMotionEvent(event, null, policyFlags); - } - - mInjectedPointerTracker.onMotionEvent(event); - - if (event != prototype) { - event.recycle(); - } - } - - /** - * Offsets all pointers in the given event by adding the specified X and Y - * offsets. - * - * @param event The event to offset. - * @param offsetX The X offset. - * @param offsetY The Y offset. - * @return An event with the offset pointers or the original event if both - * offsets are zero. - */ - private MotionEvent offsetEvent(MotionEvent event, int offsetX, int offsetY) { - if (offsetX == 0 && offsetY == 0) { - return event; - } - final int remappedIndex = event.findPointerIndex(mLongPressingPointerId); - final int pointerCount = event.getPointerCount(); - PointerProperties[] props = PointerProperties.createArray(pointerCount); - PointerCoords[] coords = PointerCoords.createArray(pointerCount); - for (int i = 0; i < pointerCount; i++) { - event.getPointerProperties(i, props[i]); - event.getPointerCoords(i, coords[i]); - if (i == remappedIndex) { - coords[i].x += offsetX; - coords[i].y += offsetY; - } - } - return MotionEvent.obtain(event.getDownTime(), - event.getEventTime(), event.getAction(), event.getPointerCount(), - props, coords, event.getMetaState(), event.getButtonState(), - 1.0f, 1.0f, event.getDeviceId(), event.getEdgeFlags(), - event.getSource(), event.getFlags()); - } - - /** - * Computes the action for an injected event based on a masked action - * and a pointer index. - * - * @param actionMasked The masked action. - * @param pointerIndex The index of the pointer which has changed. - * @return The action to be used for injection. - */ - private int computeInjectionAction(int actionMasked, int pointerIndex) { - switch (actionMasked) { - case MotionEvent.ACTION_DOWN: - case MotionEvent.ACTION_POINTER_DOWN: { - InjectedPointerTracker injectedTracker = mInjectedPointerTracker; - // Compute the action based on how many down pointers are injected. - if (injectedTracker.getInjectedPointerDownCount() == 0) { - return MotionEvent.ACTION_DOWN; - } else { - return (pointerIndex << MotionEvent.ACTION_POINTER_INDEX_SHIFT) - | MotionEvent.ACTION_POINTER_DOWN; - } - } - case MotionEvent.ACTION_POINTER_UP: { - InjectedPointerTracker injectedTracker = mInjectedPointerTracker; - // Compute the action based on how many down pointers are injected. - if (injectedTracker.getInjectedPointerDownCount() == 1) { - return MotionEvent.ACTION_UP; - } else { - return (pointerIndex << MotionEvent.ACTION_POINTER_INDEX_SHIFT) - | MotionEvent.ACTION_POINTER_UP; - } - } - default: - return actionMasked; - } - } - - private class DoubleTapDetector { - private MotionEvent mDownEvent; - private MotionEvent mFirstTapEvent; - - public void onMotionEvent(MotionEvent event, int policyFlags) { - final int actionIndex = event.getActionIndex(); - final int action = event.getActionMasked(); - switch (action) { - case MotionEvent.ACTION_DOWN: - case MotionEvent.ACTION_POINTER_DOWN: { - if (mFirstTapEvent != null - && !GestureUtils.isSamePointerContext(mFirstTapEvent, event)) { - clear(); - } - mDownEvent = MotionEvent.obtain(event); - } break; - case MotionEvent.ACTION_UP: - case MotionEvent.ACTION_POINTER_UP: { - if (mDownEvent == null) { - return; - } - if (!GestureUtils.isSamePointerContext(mDownEvent, event)) { - clear(); - return; - } - if (GestureUtils.isTap(mDownEvent, event, mTapTimeout, mTouchSlop, - actionIndex)) { - if (mFirstTapEvent == null || GestureUtils.isTimedOut(mFirstTapEvent, - event, mDoubleTapTimeout)) { - mFirstTapEvent = MotionEvent.obtain(event); - mDownEvent.recycle(); - mDownEvent = null; - return; - } - if (GestureUtils.isMultiTap(mFirstTapEvent, event, mDoubleTapTimeout, - mDoubleTapSlop, actionIndex)) { - onDoubleTap(event, policyFlags); - mFirstTapEvent.recycle(); - mFirstTapEvent = null; - mDownEvent.recycle(); - mDownEvent = null; - return; - } - mFirstTapEvent.recycle(); - mFirstTapEvent = null; - } else { - if (mFirstTapEvent != null) { - mFirstTapEvent.recycle(); - mFirstTapEvent = null; - } - } - mDownEvent.recycle(); - mDownEvent = null; - } break; - } - } - - public void onDoubleTap(MotionEvent secondTapUp, int policyFlags) { - // This should never be called when more than two pointers are down. - if (secondTapUp.getPointerCount() > 2) { - return; - } - - // Remove pending event deliveries. - mSendHoverEnterAndMoveDelayed.cancel(); - mSendHoverExitDelayed.cancel(); - mPerformLongPressDelayed.cancel(); - - if (mSendTouchExplorationEndDelayed.isPending()) { - mSendTouchExplorationEndDelayed.forceSendAndRemove(); - } - if (mSendTouchInteractionEndDelayed.isPending()) { - mSendTouchInteractionEndDelayed.forceSendAndRemove(); - } - - int clickLocationX; - int clickLocationY; - - final int pointerId = secondTapUp.getPointerId(secondTapUp.getActionIndex()); - final int pointerIndex = secondTapUp.findPointerIndex(pointerId); - - MotionEvent lastExploreEvent = - mInjectedPointerTracker.getLastInjectedHoverEventForClick(); - if (lastExploreEvent == null) { - // No last touch explored event but there is accessibility focus in - // the active window. We click in the middle of the focus bounds. - Rect focusBounds = mTempRect; - if (mAms.getAccessibilityFocusBoundsInActiveWindow(focusBounds)) { - clickLocationX = focusBounds.centerX(); - clickLocationY = focusBounds.centerY(); - } else { - // Out of luck - do nothing. - return; - } - } else { - // If the click is within the active window but not within the - // accessibility focus bounds we click in the focus center. - final int lastExplorePointerIndex = lastExploreEvent.getActionIndex(); - clickLocationX = (int) lastExploreEvent.getX(lastExplorePointerIndex); - clickLocationY = (int) lastExploreEvent.getY(lastExplorePointerIndex); - Rect activeWindowBounds = mTempRect; - if (mLastTouchedWindowId == mAms.getActiveWindowId()) { - mAms.getActiveWindowBounds(activeWindowBounds); - if (activeWindowBounds.contains(clickLocationX, clickLocationY)) { - Rect focusBounds = mTempRect; - if (mAms.getAccessibilityFocusBoundsInActiveWindow(focusBounds)) { - if (!focusBounds.contains(clickLocationX, clickLocationY)) { - clickLocationX = focusBounds.centerX(); - clickLocationY = focusBounds.centerY(); - } - } - } - } - } - - // Do the click. - PointerProperties[] properties = new PointerProperties[1]; - properties[0] = new PointerProperties(); - secondTapUp.getPointerProperties(pointerIndex, properties[0]); - PointerCoords[] coords = new PointerCoords[1]; - coords[0] = new PointerCoords(); - coords[0].x = clickLocationX; - coords[0].y = clickLocationY; - MotionEvent event = MotionEvent.obtain(secondTapUp.getDownTime(), - secondTapUp.getEventTime(), MotionEvent.ACTION_DOWN, 1, properties, - coords, 0, 0, 1.0f, 1.0f, secondTapUp.getDeviceId(), 0, - secondTapUp.getSource(), secondTapUp.getFlags()); - sendActionDownAndUp(event, policyFlags); - event.recycle(); - } - - public void clear() { - if (mDownEvent != null) { - mDownEvent.recycle(); - mDownEvent = null; - } - if (mFirstTapEvent != null) { - mFirstTapEvent.recycle(); - mFirstTapEvent = null; - } - } - - public boolean firstTapDetected() { - return mFirstTapEvent != null - && SystemClock.uptimeMillis() - mFirstTapEvent.getEventTime() < mDoubleTapTimeout; - } - } - - /** - * Determines whether a two pointer gesture is a dragging one. - * - * @param event The event with the pointer data. - * @return True if the gesture is a dragging one. - */ - private boolean isDraggingGesture(MotionEvent event) { - ReceivedPointerTracker receivedTracker = mReceivedPointerTracker; - - final float firstPtrX = event.getX(0); - final float firstPtrY = event.getY(0); - final float secondPtrX = event.getX(1); - final float secondPtrY = event.getY(1); - - final float firstPtrDownX = receivedTracker.getReceivedPointerDownX(0); - final float firstPtrDownY = receivedTracker.getReceivedPointerDownY(0); - final float secondPtrDownX = receivedTracker.getReceivedPointerDownX(1); - final float secondPtrDownY = receivedTracker.getReceivedPointerDownY(1); - - return GestureUtils.isDraggingGesture(firstPtrDownX, firstPtrDownY, secondPtrDownX, - secondPtrDownY, firstPtrX, firstPtrY, secondPtrX, secondPtrY, - MAX_DRAGGING_ANGLE_COS); - } - - /** - * Gets the symbolic name of a state. - * - * @param state A state. - * @return The state symbolic name. - */ - private static String getStateSymbolicName(int state) { - switch (state) { - case STATE_TOUCH_EXPLORING: - return "STATE_TOUCH_EXPLORING"; - case STATE_DRAGGING: - return "STATE_DRAGGING"; - case STATE_DELEGATING: - return "STATE_DELEGATING"; - case STATE_GESTURE_DETECTING: - return "STATE_GESTURE_DETECTING"; - default: - throw new IllegalArgumentException("Unknown state: " + state); - } - } - - /** - * Class for delayed exiting from gesture detecting mode. - */ - private final class ExitGestureDetectionModeDelayed implements Runnable { - - public void post() { - mHandler.postDelayed(this, EXIT_GESTURE_DETECTION_TIMEOUT); - } - - public void cancel() { - mHandler.removeCallbacks(this); - } - - @Override - public void run() { - // Announce the end of gesture recognition. - sendAccessibilityEvent(AccessibilityEvent.TYPE_GESTURE_DETECTION_END); - // Clearing puts is in touch exploration state with a finger already - // down, so announce the transition to exploration state. - sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START); - clear(); - } - } - - /** - * Class for delayed sending of long press. - */ - private final class PerformLongPressDelayed implements Runnable { - private MotionEvent mEvent; - private int mPolicyFlags; - - public void post(MotionEvent prototype, int policyFlags) { - mEvent = MotionEvent.obtain(prototype); - mPolicyFlags = policyFlags; - mHandler.postDelayed(this, ViewConfiguration.getLongPressTimeout()); - } - - public void cancel() { - if (mEvent != null) { - mHandler.removeCallbacks(this); - clear(); - } - } - - private boolean isPending() { - return mHandler.hasCallbacks(this); - } - - @Override - public void run() { - // Pointers should not be zero when running this command. - if (mReceivedPointerTracker.getLastReceivedEvent().getPointerCount() == 0) { - return; - } - - int clickLocationX; - int clickLocationY; - - final int pointerId = mEvent.getPointerId(mEvent.getActionIndex()); - final int pointerIndex = mEvent.findPointerIndex(pointerId); - - MotionEvent lastExploreEvent = - mInjectedPointerTracker.getLastInjectedHoverEventForClick(); - if (lastExploreEvent == null) { - // No last touch explored event but there is accessibility focus in - // the active window. We click in the middle of the focus bounds. - Rect focusBounds = mTempRect; - if (mAms.getAccessibilityFocusBoundsInActiveWindow(focusBounds)) { - clickLocationX = focusBounds.centerX(); - clickLocationY = focusBounds.centerY(); - } else { - // Out of luck - do nothing. - return; - } - } else { - // If the click is within the active window but not within the - // accessibility focus bounds we click in the focus center. - final int lastExplorePointerIndex = lastExploreEvent.getActionIndex(); - clickLocationX = (int) lastExploreEvent.getX(lastExplorePointerIndex); - clickLocationY = (int) lastExploreEvent.getY(lastExplorePointerIndex); - Rect activeWindowBounds = mTempRect; - if (mLastTouchedWindowId == mAms.getActiveWindowId()) { - mAms.getActiveWindowBounds(activeWindowBounds); - if (activeWindowBounds.contains(clickLocationX, clickLocationY)) { - Rect focusBounds = mTempRect; - if (mAms.getAccessibilityFocusBoundsInActiveWindow(focusBounds)) { - if (!focusBounds.contains(clickLocationX, clickLocationY)) { - clickLocationX = focusBounds.centerX(); - clickLocationY = focusBounds.centerY(); - } - } - } - } - } - - mLongPressingPointerId = pointerId; - mLongPressingPointerDeltaX = (int) mEvent.getX(pointerIndex) - clickLocationX; - mLongPressingPointerDeltaY = (int) mEvent.getY(pointerIndex) - clickLocationY; - - sendHoverExitAndTouchExplorationGestureEndIfNeeded(mPolicyFlags); - - mCurrentState = STATE_DELEGATING; - sendDownForAllNotInjectedPointers(mEvent, mPolicyFlags); - clear(); - } - - private void clear() { - mEvent.recycle(); - mEvent = null; - mPolicyFlags = 0; - } - } - - /** - * Class for delayed sending of hover enter and move events. - */ - class SendHoverEnterAndMoveDelayed implements Runnable { - private final String LOG_TAG_SEND_HOVER_DELAYED = "SendHoverEnterAndMoveDelayed"; - - private final List<MotionEvent> mEvents = new ArrayList<MotionEvent>(); - - private int mPointerIdBits; - private int mPolicyFlags; - - public void post(MotionEvent event, boolean touchExplorationInProgress, - int pointerIdBits, int policyFlags) { - cancel(); - addEvent(event); - mPointerIdBits = pointerIdBits; - mPolicyFlags = policyFlags; - mHandler.postDelayed(this, mDetermineUserIntentTimeout); - } - - public void addEvent(MotionEvent event) { - mEvents.add(MotionEvent.obtain(event)); - } - - public void cancel() { - if (isPending()) { - mHandler.removeCallbacks(this); - clear(); - } - } - - private boolean isPending() { - return mHandler.hasCallbacks(this); - } - - private void clear() { - mPointerIdBits = -1; - mPolicyFlags = 0; - final int eventCount = mEvents.size(); - for (int i = eventCount - 1; i >= 0; i--) { - mEvents.remove(i).recycle(); - } - } - - public void forceSendAndRemove() { - if (isPending()) { - run(); - cancel(); - } - } - - public void run() { - // Send an accessibility event to announce the touch exploration start. - sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START); - - if (!mEvents.isEmpty()) { - // Deliver a down event. - sendMotionEvent(mEvents.get(0), MotionEvent.ACTION_HOVER_ENTER, - mPointerIdBits, mPolicyFlags); - if (DEBUG) { - Slog.d(LOG_TAG_SEND_HOVER_DELAYED, - "Injecting motion event: ACTION_HOVER_ENTER"); - } - - // Deliver move events. - final int eventCount = mEvents.size(); - for (int i = 1; i < eventCount; i++) { - sendMotionEvent(mEvents.get(i), MotionEvent.ACTION_HOVER_MOVE, - mPointerIdBits, mPolicyFlags); - if (DEBUG) { - Slog.d(LOG_TAG_SEND_HOVER_DELAYED, - "Injecting motion event: ACTION_HOVER_MOVE"); - } - } - } - clear(); - } - } - - /** - * Class for delayed sending of hover exit events. - */ - class SendHoverExitDelayed implements Runnable { - private final String LOG_TAG_SEND_HOVER_DELAYED = "SendHoverExitDelayed"; - - private MotionEvent mPrototype; - private int mPointerIdBits; - private int mPolicyFlags; - - public void post(MotionEvent prototype, int pointerIdBits, int policyFlags) { - cancel(); - mPrototype = MotionEvent.obtain(prototype); - mPointerIdBits = pointerIdBits; - mPolicyFlags = policyFlags; - mHandler.postDelayed(this, mDetermineUserIntentTimeout); - } - - public void cancel() { - if (isPending()) { - mHandler.removeCallbacks(this); - clear(); - } - } - - private boolean isPending() { - return mHandler.hasCallbacks(this); - } - - private void clear() { - mPrototype.recycle(); - mPrototype = null; - mPointerIdBits = -1; - mPolicyFlags = 0; - } - - public void forceSendAndRemove() { - if (isPending()) { - run(); - cancel(); - } - } - - public void run() { - if (DEBUG) { - Slog.d(LOG_TAG_SEND_HOVER_DELAYED, "Injecting motion event:" - + " ACTION_HOVER_EXIT"); - } - sendMotionEvent(mPrototype, MotionEvent.ACTION_HOVER_EXIT, - mPointerIdBits, mPolicyFlags); - if (!mSendTouchExplorationEndDelayed.isPending()) { - mSendTouchExplorationEndDelayed.cancel(); - mSendTouchExplorationEndDelayed.post(); - } - if (mSendTouchInteractionEndDelayed.isPending()) { - mSendTouchInteractionEndDelayed.cancel(); - mSendTouchInteractionEndDelayed.post(); - } - clear(); - } - } - - private class SendAccessibilityEventDelayed implements Runnable { - private final int mEventType; - private final int mDelay; - - public SendAccessibilityEventDelayed(int eventType, int delay) { - mEventType = eventType; - mDelay = delay; - } - - public void cancel() { - mHandler.removeCallbacks(this); - } - - public void post() { - mHandler.postDelayed(this, mDelay); - } - - public boolean isPending() { - return mHandler.hasCallbacks(this); - } - - public void forceSendAndRemove() { - if (isPending()) { - run(); - cancel(); - } - } - - @Override - public void run() { - sendAccessibilityEvent(mEventType); - } - } - - @Override - public String toString() { - return LOG_TAG; - } - - class InjectedPointerTracker { - private static final String LOG_TAG_INJECTED_POINTER_TRACKER = "InjectedPointerTracker"; - - // Keep track of which pointers sent to the system are down. - private int mInjectedPointersDown; - - // The time of the last injected down. - private long mLastInjectedDownEventTime; - - // The last injected hover event. - private MotionEvent mLastInjectedHoverEvent; - - // The last injected hover event used for performing clicks. - private MotionEvent mLastInjectedHoverEventForClick; - - /** - * Processes an injected {@link MotionEvent} event. - * - * @param event The event to process. - */ - public void onMotionEvent(MotionEvent event) { - final int action = event.getActionMasked(); - switch (action) { - case MotionEvent.ACTION_DOWN: - case MotionEvent.ACTION_POINTER_DOWN: { - final int pointerId = event.getPointerId(event.getActionIndex()); - final int pointerFlag = (1 << pointerId); - mInjectedPointersDown |= pointerFlag; - mLastInjectedDownEventTime = event.getDownTime(); - } break; - case MotionEvent.ACTION_UP: - case MotionEvent.ACTION_POINTER_UP: { - final int pointerId = event.getPointerId(event.getActionIndex()); - final int pointerFlag = (1 << pointerId); - mInjectedPointersDown &= ~pointerFlag; - if (mInjectedPointersDown == 0) { - mLastInjectedDownEventTime = 0; - } - } break; - case MotionEvent.ACTION_HOVER_ENTER: - case MotionEvent.ACTION_HOVER_MOVE: - case MotionEvent.ACTION_HOVER_EXIT: { - if (mLastInjectedHoverEvent != null) { - mLastInjectedHoverEvent.recycle(); - } - mLastInjectedHoverEvent = MotionEvent.obtain(event); - if (mLastInjectedHoverEventForClick != null) { - mLastInjectedHoverEventForClick.recycle(); - } - mLastInjectedHoverEventForClick = MotionEvent.obtain(event); - } break; - } - if (DEBUG) { - Slog.i(LOG_TAG_INJECTED_POINTER_TRACKER, "Injected pointer:\n" + toString()); - } - } - - /** - * Clears the internals state. - */ - public void clear() { - mInjectedPointersDown = 0; - } - - /** - * @return The time of the last injected down event. - */ - public long getLastInjectedDownEventTime() { - return mLastInjectedDownEventTime; - } - - /** - * @return The number of down pointers injected to the view hierarchy. - */ - public int getInjectedPointerDownCount() { - return Integer.bitCount(mInjectedPointersDown); - } - - /** - * @return The bits of the injected pointers that are down. - */ - public int getInjectedPointersDown() { - return mInjectedPointersDown; - } - - /** - * Whether an injected pointer is down. - * - * @param pointerId The unique pointer id. - * @return True if the pointer is down. - */ - public boolean isInjectedPointerDown(int pointerId) { - final int pointerFlag = (1 << pointerId); - return (mInjectedPointersDown & pointerFlag) != 0; - } - - /** - * @return The the last injected hover event. - */ - public MotionEvent getLastInjectedHoverEvent() { - return mLastInjectedHoverEvent; - } - - /** - * @return The the last injected hover event. - */ - public MotionEvent getLastInjectedHoverEventForClick() { - return mLastInjectedHoverEventForClick; - } - - @Override - public String toString() { - StringBuilder builder = new StringBuilder(); - builder.append("========================="); - builder.append("\nDown pointers #"); - builder.append(Integer.bitCount(mInjectedPointersDown)); - builder.append(" [ "); - for (int i = 0; i < MAX_POINTER_COUNT; i++) { - if ((mInjectedPointersDown & i) != 0) { - builder.append(i); - builder.append(" "); - } - } - builder.append("]"); - builder.append("\n========================="); - return builder.toString(); - } - } - - class ReceivedPointerTracker { - private static final String LOG_TAG_RECEIVED_POINTER_TRACKER = "ReceivedPointerTracker"; - - // Keep track of where and when a pointer went down. - private final float[] mReceivedPointerDownX = new float[MAX_POINTER_COUNT]; - private final float[] mReceivedPointerDownY = new float[MAX_POINTER_COUNT]; - private final long[] mReceivedPointerDownTime = new long[MAX_POINTER_COUNT]; - - // Which pointers are down. - private int mReceivedPointersDown; - - // The edge flags of the last received down event. - private int mLastReceivedDownEdgeFlags; - - // Primary pointer which is either the first that went down - // or if it goes up the next one that most recently went down. - private int mPrimaryPointerId; - - // Keep track of the last up pointer data. - private long mLastReceivedUpPointerDownTime; - private float mLastReceivedUpPointerDownX; - private float mLastReceivedUpPointerDownY; - - private MotionEvent mLastReceivedEvent; - - /** - * Clears the internals state. - */ - public void clear() { - Arrays.fill(mReceivedPointerDownX, 0); - Arrays.fill(mReceivedPointerDownY, 0); - Arrays.fill(mReceivedPointerDownTime, 0); - mReceivedPointersDown = 0; - mPrimaryPointerId = 0; - mLastReceivedUpPointerDownTime = 0; - mLastReceivedUpPointerDownX = 0; - mLastReceivedUpPointerDownY = 0; - } - - /** - * Processes a received {@link MotionEvent} event. - * - * @param event The event to process. - */ - public void onMotionEvent(MotionEvent event) { - if (mLastReceivedEvent != null) { - mLastReceivedEvent.recycle(); - } - mLastReceivedEvent = MotionEvent.obtain(event); - - final int action = event.getActionMasked(); - switch (action) { - case MotionEvent.ACTION_DOWN: { - handleReceivedPointerDown(event.getActionIndex(), event); - } break; - case MotionEvent.ACTION_POINTER_DOWN: { - handleReceivedPointerDown(event.getActionIndex(), event); - } break; - case MotionEvent.ACTION_UP: { - handleReceivedPointerUp(event.getActionIndex(), event); - } break; - case MotionEvent.ACTION_POINTER_UP: { - handleReceivedPointerUp(event.getActionIndex(), event); - } break; - } - if (DEBUG) { - Slog.i(LOG_TAG_RECEIVED_POINTER_TRACKER, "Received pointer:\n" + toString()); - } - } - - /** - * @return The last received event. - */ - public MotionEvent getLastReceivedEvent() { - return mLastReceivedEvent; - } - - /** - * @return The number of received pointers that are down. - */ - public int getReceivedPointerDownCount() { - return Integer.bitCount(mReceivedPointersDown); - } - - /** - * Whether an received pointer is down. - * - * @param pointerId The unique pointer id. - * @return True if the pointer is down. - */ - public boolean isReceivedPointerDown(int pointerId) { - final int pointerFlag = (1 << pointerId); - return (mReceivedPointersDown & pointerFlag) != 0; - } - - /** - * @param pointerId The unique pointer id. - * @return The X coordinate where the pointer went down. - */ - public float getReceivedPointerDownX(int pointerId) { - return mReceivedPointerDownX[pointerId]; - } - - /** - * @param pointerId The unique pointer id. - * @return The Y coordinate where the pointer went down. - */ - public float getReceivedPointerDownY(int pointerId) { - return mReceivedPointerDownY[pointerId]; - } - - /** - * @param pointerId The unique pointer id. - * @return The time when the pointer went down. - */ - public long getReceivedPointerDownTime(int pointerId) { - return mReceivedPointerDownTime[pointerId]; - } - - /** - * @return The id of the primary pointer. - */ - public int getPrimaryPointerId() { - if (mPrimaryPointerId == INVALID_POINTER_ID) { - mPrimaryPointerId = findPrimaryPointerId(); - } - return mPrimaryPointerId; - } - - /** - * @return The time when the last up received pointer went down. - */ - public long getLastReceivedUpPointerDownTime() { - return mLastReceivedUpPointerDownTime; - } - - /** - * @return The down X of the last received pointer that went up. - */ - public float getLastReceivedUpPointerDownX() { - return mLastReceivedUpPointerDownX; - } - - /** - * @return The down Y of the last received pointer that went up. - */ - public float getLastReceivedUpPointerDownY() { - return mLastReceivedUpPointerDownY; - } - - /** - * @return The edge flags of the last received down event. - */ - public int getLastReceivedDownEdgeFlags() { - return mLastReceivedDownEdgeFlags; - } - - /** - * Handles a received pointer down event. - * - * @param pointerIndex The index of the pointer that has changed. - * @param event The event to be handled. - */ - private void handleReceivedPointerDown(int pointerIndex, MotionEvent event) { - final int pointerId = event.getPointerId(pointerIndex); - final int pointerFlag = (1 << pointerId); - - mLastReceivedUpPointerDownTime = 0; - mLastReceivedUpPointerDownX = 0; - mLastReceivedUpPointerDownX = 0; - - mLastReceivedDownEdgeFlags = event.getEdgeFlags(); - - mReceivedPointersDown |= pointerFlag; - mReceivedPointerDownX[pointerId] = event.getX(pointerIndex); - mReceivedPointerDownY[pointerId] = event.getY(pointerIndex); - mReceivedPointerDownTime[pointerId] = event.getEventTime(); - - mPrimaryPointerId = pointerId; - } - - /** - * Handles a received pointer up event. - * - * @param pointerIndex The index of the pointer that has changed. - * @param event The event to be handled. - */ - private void handleReceivedPointerUp(int pointerIndex, MotionEvent event) { - final int pointerId = event.getPointerId(pointerIndex); - final int pointerFlag = (1 << pointerId); - - mLastReceivedUpPointerDownTime = getReceivedPointerDownTime(pointerId); - mLastReceivedUpPointerDownX = mReceivedPointerDownX[pointerId]; - mLastReceivedUpPointerDownY = mReceivedPointerDownY[pointerId]; - - mReceivedPointersDown &= ~pointerFlag; - mReceivedPointerDownX[pointerId] = 0; - mReceivedPointerDownY[pointerId] = 0; - mReceivedPointerDownTime[pointerId] = 0; - - if (mPrimaryPointerId == pointerId) { - mPrimaryPointerId = INVALID_POINTER_ID; - } - } - - /** - * @return The primary pointer id. - */ - private int findPrimaryPointerId() { - int primaryPointerId = INVALID_POINTER_ID; - long minDownTime = Long.MAX_VALUE; - - // Find the pointer that went down first. - int pointerIdBits = mReceivedPointersDown; - while (pointerIdBits > 0) { - final int pointerId = Integer.numberOfTrailingZeros(pointerIdBits); - pointerIdBits &= ~(1 << pointerId); - final long downPointerTime = mReceivedPointerDownTime[pointerId]; - if (downPointerTime < minDownTime) { - minDownTime = downPointerTime; - primaryPointerId = pointerId; - } - } - return primaryPointerId; - } - - @Override - public String toString() { - StringBuilder builder = new StringBuilder(); - builder.append("========================="); - builder.append("\nDown pointers #"); - builder.append(getReceivedPointerDownCount()); - builder.append(" [ "); - for (int i = 0; i < MAX_POINTER_COUNT; i++) { - if (isReceivedPointerDown(i)) { - builder.append(i); - builder.append(" "); - } - } - builder.append("]"); - builder.append("\nPrimary pointer id [ "); - builder.append(getPrimaryPointerId()); - builder.append(" ]"); - builder.append("\n========================="); - return builder.toString(); - } - } -} |