diff options
-rw-r--r-- | api/current.txt | 20 | ||||
-rw-r--r-- | core/java/android/accessibilityservice/AccessibilityService.java | 111 | ||||
-rw-r--r-- | core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl | 8 | ||||
-rw-r--r-- | core/java/android/view/AccessibilityInteractionController.java | 9 | ||||
-rw-r--r-- | core/java/android/view/View.java | 18 | ||||
-rw-r--r-- | core/java/android/view/ViewGroup.java | 30 | ||||
-rw-r--r-- | core/java/android/view/ViewRootImpl.java | 8 | ||||
-rw-r--r-- | core/java/android/view/accessibility/AccessibilityNodeInfo.java | 23 | ||||
-rw-r--r-- | core/res/res/raw/accessibility_gestures.bin | bin | 9261 -> 13005 bytes | |||
-rw-r--r-- | services/java/com/android/server/accessibility/AccessibilityManagerService.java | 53 |
10 files changed, 235 insertions, 45 deletions
diff --git a/api/current.txt b/api/current.txt index fef1c83..d8387c1 100644 --- a/api/current.txt +++ b/api/current.txt @@ -1999,17 +1999,30 @@ package android.accessibilityservice { method protected void onGesture(int); method public abstract void onInterrupt(); method protected void onServiceConnected(); + method public final boolean performGlobalAction(int); method public final void setServiceInfo(android.accessibilityservice.AccessibilityServiceInfo); field public static final int GESTURE_CLOCKWISE_CIRCLE = 9; // 0x9 field public static final int GESTURE_COUNTER_CLOCKWISE_CIRCLE = 10; // 0xa field public static final int GESTURE_SWIPE_DOWN = 2; // 0x2 + field public static final int GESTURE_SWIPE_DOWN_AND_LEFT = 17; // 0x11 + field public static final int GESTURE_SWIPE_DOWN_AND_RIGHT = 18; // 0x12 field public static final int GESTURE_SWIPE_DOWN_AND_UP = 8; // 0x8 field public static final int GESTURE_SWIPE_LEFT = 3; // 0x3 + field public static final int GESTURE_SWIPE_LEFT_AND_DOWN = 12; // 0xc field public static final int GESTURE_SWIPE_LEFT_AND_RIGHT = 5; // 0x5 + field public static final int GESTURE_SWIPE_LEFT_AND_UP = 11; // 0xb field public static final int GESTURE_SWIPE_RIGHT = 4; // 0x4 + field public static final int GESTURE_SWIPE_RIGHT_AND_DOWN = 14; // 0xe field public static final int GESTURE_SWIPE_RIGHT_AND_LEFT = 6; // 0x6 + field public static final int GESTURE_SWIPE_RIGHT_AND_UP = 13; // 0xd field public static final int GESTURE_SWIPE_UP = 1; // 0x1 field public static final int GESTURE_SWIPE_UP_AND_DOWN = 7; // 0x7 + field public static final int GESTURE_SWIPE_UP_AND_LEFT = 15; // 0xf + field public static final int GESTURE_SWIPE_UP_AND_RIGHT = 16; // 0x10 + field public static final int GLOBAL_ACTION_BACK = 1; // 0x1 + field public static final int GLOBAL_ACTION_HOME = 2; // 0x2 + field public static final int GLOBAL_ACTION_NOTIFICATIONS = 4; // 0x4 + field public static final int GLOBAL_ACTION_RECENTS = 3; // 0x3 field public static final java.lang.String SERVICE_INTERFACE = "android.accessibilityservice.AccessibilityService"; field public static final java.lang.String SERVICE_META_DATA = "android.accessibilityservice"; } @@ -25006,12 +25019,13 @@ package android.view.accessibility { method public void setSource(android.view.View, int); method public void setText(java.lang.CharSequence); method public void writeToParcel(android.os.Parcel, int); - field public static final int ACTION_ACCESSIBILITY_FOCUS = 16; // 0x10 - field public static final int ACTION_CLEAR_ACCESSIBILITY_FOCUS = 32; // 0x20 + field public static final int ACTION_ACCESSIBILITY_FOCUS = 64; // 0x40 + field public static final int ACTION_CLEAR_ACCESSIBILITY_FOCUS = 128; // 0x80 field public static final int ACTION_CLEAR_FOCUS = 2; // 0x2 field public static final int ACTION_CLEAR_SELECTION = 8; // 0x8 - field public static final int ACTION_CLICK = 64; // 0x40 + field public static final int ACTION_CLICK = 16; // 0x10 field public static final int ACTION_FOCUS = 1; // 0x1 + field public static final int ACTION_LONG_CLICK = 32; // 0x20 field public static final int ACTION_SELECT = 4; // 0x4 field public static final android.os.Parcelable.Creator CREATOR; field public static final int FOCUS_ACCESSIBILITY = 2; // 0x2 diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java index 3da35d3..eed8aa6 100644 --- a/core/java/android/accessibilityservice/AccessibilityService.java +++ b/core/java/android/accessibilityservice/AccessibilityService.java @@ -259,13 +259,51 @@ public abstract class AccessibilityService extends Service { public static final int GESTURE_COUNTER_CLOCKWISE_CIRCLE = 10; /** + * The user has performed a left and up gesture on the touch screen. + */ + public static final int GESTURE_SWIPE_LEFT_AND_UP = 11; + + /** + * The user has performed a left and down gesture on the touch screen. + */ + public static final int GESTURE_SWIPE_LEFT_AND_DOWN = 12; + + /** + * The user has performed a right and up gesture on the touch screen. + */ + public static final int GESTURE_SWIPE_RIGHT_AND_UP = 13; + + /** + * The user has performed a right and down gesture on the touch screen. + */ + public static final int GESTURE_SWIPE_RIGHT_AND_DOWN = 14; + + /** + * The user has performed an up and left gesture on the touch screen. + */ + public static final int GESTURE_SWIPE_UP_AND_LEFT = 15; + + /** + * The user has performed an up and right gesture on the touch screen. + */ + public static final int GESTURE_SWIPE_UP_AND_RIGHT = 16; + + /** + * The user has performed an down and left gesture on the touch screen. + */ + public static final int GESTURE_SWIPE_DOWN_AND_LEFT = 17; + + /** + * The user has performed an down and right gesture on the touch screen. + */ + public static final int GESTURE_SWIPE_DOWN_AND_RIGHT = 18; + + /** * The {@link Intent} that must be declared as handled by the service. */ public static final String SERVICE_INTERFACE = "android.accessibilityservice.AccessibilityService"; - private static final int UNDEFINED = -1; - /** * Name under which an AccessibilityService component publishes information * about itself. This meta-data must reference an XML resource containing an @@ -284,6 +322,28 @@ public abstract class AccessibilityService extends Service { */ public static final String SERVICE_META_DATA = "android.accessibilityservice"; + /** + * Action to go back. + */ + public static final int GLOBAL_ACTION_BACK = 1; + + /** + * Action to go home. + */ + public static final int GLOBAL_ACTION_HOME = 2; + + /** + * Action to open the recents. + */ + public static final int GLOBAL_ACTION_RECENTS = 3; + + /** + * Action to open the notifications. + */ + public static final int GLOBAL_ACTION_NOTIFICATIONS = 4; + + private static final int UNDEFINED = -1; + private static final String LOG_TAG = "AccessibilityService"; interface Callbacks { @@ -344,6 +404,22 @@ public abstract class AccessibilityService extends Service { protected void onGesture(int gestureId) { // TODO: Describe the default gesture processing in the javaDoc once it is finalized. + // Global actions. + switch (gestureId) { + case GESTURE_SWIPE_DOWN_AND_LEFT: { + performGlobalAction(GLOBAL_ACTION_BACK); + } return; + case GESTURE_SWIPE_DOWN_AND_RIGHT: { + performGlobalAction(GLOBAL_ACTION_HOME); + } return; + case GESTURE_SWIPE_UP_AND_LEFT: { + performGlobalAction(GLOBAL_ACTION_RECENTS); + } return; + case GESTURE_SWIPE_UP_AND_RIGHT: { + performGlobalAction(GLOBAL_ACTION_NOTIFICATIONS); + } return; + } + // Cache the id to avoid locking final int connectionId = mConnectionId; if (connectionId == UNDEFINED) { @@ -357,10 +433,12 @@ public abstract class AccessibilityService extends Service { if (root == null) { return; } - AccessibilityNodeInfo current = root.findFocus(View.FOCUS_ACCESSIBILITY); + AccessibilityNodeInfo current = root.findFocus(AccessibilityNodeInfo.FOCUS_ACCESSIBILITY); if (current == null) { current = root; } + + // Local actions. AccessibilityNodeInfo next = null; switch (gestureId) { case GESTURE_SWIPE_UP: { @@ -402,6 +480,33 @@ public abstract class AccessibilityService extends Service { } /** + * Performs a global action. Such an action can be performed + * at any moment regardless of the current application or user + * location in that application. For example going back, going + * home, opening recents, etc. + * + * @param action The action to perform. + * @return Whether the action was successfully performed. + * + * @see #GLOBAL_ACTION_BACK + * @see #GLOBAL_ACTION_HOME + * @see #GLOBAL_ACTION_NOTIFICATIONS + * @see #GLOBAL_ACTION_RECENTS + */ + public final boolean performGlobalAction(int action) { + IAccessibilityServiceConnection connection = + AccessibilityInteractionClient.getInstance().getConnection(mConnectionId); + if (connection != null) { + try { + return connection.perfromGlobalAction(action); + } catch (RemoteException re) { + Log.w(LOG_TAG, "Error while calling performGlobalAction", re); + } + } + return false; + } + + /** * Gets the an {@link AccessibilityServiceInfo} describing this * {@link AccessibilityService}. This method is useful if one wants * to change some of the dynamically configurable properties at diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl index 30da9db..1bd5387 100644 --- a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl +++ b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl @@ -160,4 +160,12 @@ interface IAccessibilityServiceConnection { * @return The associated accessibility service info. */ AccessibilityServiceInfo getServiceInfo(); + + /** + * Performs a global action, such as going home, going back, etc. + * + * @param action The action to perform. + * @return Whether the action was performed. + */ + boolean perfromGlobalAction(int action); } diff --git a/core/java/android/view/AccessibilityInteractionController.java b/core/java/android/view/AccessibilityInteractionController.java index 6c1a6bf..54c62ee 100644 --- a/core/java/android/view/AccessibilityInteractionController.java +++ b/core/java/android/view/AccessibilityInteractionController.java @@ -52,13 +52,16 @@ final class AccessibilityInteractionController { private ArrayList<AccessibilityNodeInfo> mTempAccessibilityNodeInfoList = new ArrayList<AccessibilityNodeInfo>(); - private final Handler mHandler = new PrivateHandler(); + private final Handler mHandler; private final ViewRootImpl mViewRootImpl; private final AccessibilityNodePrefetcher mPrefetcher; public AccessibilityInteractionController(ViewRootImpl viewRootImpl) { + // mView is never null - the caller has already checked. + Looper looper = viewRootImpl.mView.mContext.getMainLooper(); + mHandler = new PrivateHandler(looper); mViewRootImpl = viewRootImpl; mPrefetcher = new AccessibilityNodePrefetcher(); } @@ -846,8 +849,8 @@ final class AccessibilityInteractionController { private final static int MSG_FIND_FOCUS = 5; private final static int MSG_FOCUS_SEARCH = 6; - public PrivateHandler() { - super(Looper.getMainLooper()); + public PrivateHandler(Looper looper) { + super(looper); } @Override diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 0ded5f9..2fea8ec 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -6359,16 +6359,14 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal public boolean performAccessibilityAction(int action) { switch (action) { case AccessibilityNodeInfo.ACTION_CLICK: { - final long now = SystemClock.uptimeMillis(); - // Send down. - MotionEvent event = MotionEvent.obtain(now, now, MotionEvent.ACTION_DOWN, - getWidth() / 2, getHeight() / 2, 0); - onTouchEvent(event); - // Send up. - event.setAction(MotionEvent.ACTION_UP); - onTouchEvent(event); - // Clean up. - event.recycle(); + if (isClickable()) { + performClick(); + } + } break; + case AccessibilityNodeInfo.ACTION_LONG_CLICK: { + if (isLongClickable()) { + performLongClick(); + } } break; case AccessibilityNodeInfo.ACTION_FOCUS: { if (!hasFocus()) { diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index 6371963..91e945b 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -5792,11 +5792,13 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager throw new IllegalStateException("Instance already recycled."); } clear(); - if (sPoolSize < MAX_POOL_SIZE) { - mNext = sPool; - mIsPooled = true; - sPool = this; - sPoolSize++; + synchronized (sPoolLock) { + if (sPoolSize < MAX_POOL_SIZE) { + mNext = sPool; + mIsPooled = true; + sPool = this; + sPoolSize++; + } } } @@ -5889,11 +5891,13 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager throw new IllegalStateException("Instance already recycled."); } clear(); - if (sPoolSize < MAX_POOL_SIZE) { - mNext = sPool; - mIsPooled = true; - sPool = this; - sPoolSize++; + synchronized (sPoolLock) { + if (sPoolSize < MAX_POOL_SIZE) { + mNext = sPool; + mIsPooled = true; + sPool = this; + sPoolSize++; + } } } @@ -5943,9 +5947,9 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager if (widthDiference != 0) { return -widthDiference; } - // Return nondeterministically one of them since we do - // not want to ignore any views. - return 1; + // Just break the tie somehow. The accessibliity ids are unique + // and stable, hence this is deterministic tie breaking. + return mView.getAccessibilityViewId() - another.mView.getAccessibilityViewId(); } private void init(ViewGroup root, View view) { diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 1472993..3d40b2f 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -2665,6 +2665,7 @@ public final class ViewRootImpl implements ViewParent, private final static int MSG_PROCESS_INPUT_EVENTS = 19; private final static int MSG_DISPATCH_SCREEN_STATE = 20; private final static int MSG_INVALIDATE_DISPLAY_LIST = 21; + private final static int MSG_CLEAR_ACCESSIBILITY_FOCUS_HOST = 22; final class ViewRootHandler extends Handler { @Override @@ -2712,6 +2713,8 @@ public final class ViewRootImpl implements ViewParent, return "MSG_DISPATCH_SCREEN_STATE"; case MSG_INVALIDATE_DISPLAY_LIST: return "MSG_INVALIDATE_DISPLAY_LIST"; + case MSG_CLEAR_ACCESSIBILITY_FOCUS_HOST: + return "MSG_CLEAR_ACCESSIBILITY_FOCUS_HOST"; } return super.getMessageName(message); } @@ -2921,6 +2924,9 @@ public final class ViewRootImpl implements ViewParent, case MSG_INVALIDATE_DISPLAY_LIST: { invalidateDisplayLists(); } break; + case MSG_CLEAR_ACCESSIBILITY_FOCUS_HOST: { + setAccessibilityFocusedHost(null); + } break; } } } @@ -5066,7 +5072,7 @@ public final class ViewRootImpl implements ViewParent, } } else { ensureNoConnection(); - setAccessibilityFocusedHost(null); + mHandler.obtainMessage(MSG_CLEAR_ACCESSIBILITY_FOCUS_HOST).sendToTarget(); } } diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java index 1071c65..c5f2062 100644 --- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java +++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java @@ -84,7 +84,7 @@ public class AccessibilityNodeInfo implements Parcelable { /** * Action that gives input focus to the node. */ - public static final int ACTION_FOCUS = 0x00000001; + public static final int ACTION_FOCUS = 0x00000001; /** * Action that clears input focus of the node. @@ -102,19 +102,24 @@ public class AccessibilityNodeInfo implements Parcelable { public static final int ACTION_CLEAR_SELECTION = 0x00000008; /** - * Action that gives accessibility focus to the node. + * Action that clicks on the node info. */ - public static final int ACTION_ACCESSIBILITY_FOCUS = 0x00000010; + public static final int ACTION_CLICK = 0x00000010; /** - * Action that clears accessibility focus of the node. + * Action that clicks on the node. + */ + public static final int ACTION_LONG_CLICK = 0x00000020; + + /** + * Action that gives accessibility focus to the node. */ - public static final int ACTION_CLEAR_ACCESSIBILITY_FOCUS = 0x00000020; + public static final int ACTION_ACCESSIBILITY_FOCUS = 0x00000040; /** - * Action that clicks on the node info./AccessibilityNodeInfoCache.java + * Action that clears accessibility focus of the node. */ - public static final int ACTION_CLICK = 0x00000040; + public static final int ACTION_CLEAR_ACCESSIBILITY_FOCUS = 0x00000080; /** * The input focus. @@ -278,9 +283,9 @@ public class AccessibilityNodeInfo implements Parcelable { (root != null) ? root.getAccessibilityViewId() : UNDEFINED; mSourceNodeId = makeNodeId(rootAccessibilityViewId, virtualDescendantId); } - + /** - * Find the view that has the input focus. The search starts from + * Find the view that has the specified focus type. The search starts from * the view represented by this node info. * * @param focus The focus to find. One of {@link #FOCUS_INPUT} or diff --git a/core/res/res/raw/accessibility_gestures.bin b/core/res/res/raw/accessibility_gestures.bin Binary files differindex 1f95e56..f7e6615 100644 --- a/core/res/res/raw/accessibility_gestures.bin +++ b/core/res/res/raw/accessibility_gestures.bin diff --git a/services/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/java/com/android/server/accessibility/AccessibilityManagerService.java index ed2a6c0..6675816 100644 --- a/services/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -36,6 +36,7 @@ import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.database.ContentObserver; import android.graphics.Rect; +import android.hardware.input.InputManager; import android.net.Uri; import android.os.Binder; import android.os.Build; @@ -44,12 +45,16 @@ import android.os.IBinder; 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.text.TextUtils.SimpleStringSplitter; import android.util.Slog; import android.util.SparseArray; import android.view.IWindow; +import android.view.InputDevice; +import android.view.KeyCharacterMap; +import android.view.KeyEvent; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityNodeInfo; @@ -1301,11 +1306,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } } } - final int flags = (mIncludeNotImportantViews) ? - AccessibilityNodeInfo.INCLUDE_NOT_IMPORTANT_VIEWS : 0; - final int interrogatingPid = Binder.getCallingPid(); final long identityToken = Binder.clearCallingIdentity(); try { + final int flags = (mIncludeNotImportantViews) ? + AccessibilityNodeInfo.INCLUDE_NOT_IMPORTANT_VIEWS : 0; + final int interrogatingPid = Binder.getCallingPid(); connection.performAccessibilityAction(accessibilityNodeId, action, interactionId, callback, flags, interrogatingPid, interrogatingTid); } catch (RemoteException re) { @@ -1318,6 +1323,24 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub return true; } + public boolean perfromGlobalAction(int action) { + 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: { + sendDownAndUpKeyEvents(KeyEvent.KEYCODE_APP_SWITCH); + } return true; + case AccessibilityService.GLOBAL_ACTION_NOTIFICATIONS: { + // TODO: Implement when 6346026 is fixed. + } return true; + } + return false; + } + public void onServiceDisconnected(ComponentName componentName) { /* do nothing - #binderDied takes care */ } @@ -1358,6 +1381,30 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } } + 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 IAccessibilityInteractionConnection getConnectionLocked(int windowId) { if (DEBUG) { Slog.i(LOG_TAG, "Trying to get interaction connection to windowId: " + windowId); |