diff options
13 files changed, 402 insertions, 133 deletions
@@ -61,6 +61,7 @@ LOCAL_SRC_FILES := $(filter-out \ LOCAL_SRC_FILES += \ core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl \ core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl \ + core/java/android/accessibilityservice/IAccessibilityServiceClientCallback.aidl \ core/java/android/accounts/IAccountManager.aidl \ core/java/android/accounts/IAccountManagerResponse.aidl \ core/java/android/accounts/IAccountAuthenticator.aidl \ diff --git a/api/current.txt b/api/current.txt index 8dbb577..5c53ab7 100644 --- a/api/current.txt +++ b/api/current.txt @@ -294,6 +294,7 @@ package android { field public static final int cacheColorHint = 16843009; // 0x1010101 field public static final int calendarViewShown = 16843596; // 0x101034c field public static final int calendarViewStyle = 16843613; // 0x101035d + field public static final int canHandleGestures = 16843699; // 0x10103b3 field public static final int canRetrieveWindowContent = 16843653; // 0x1010385 field public static final int candidatesTextStyleSpans = 16843312; // 0x1010230 field public static final deprecated int capitalize = 16843113; // 0x1010169 @@ -1997,7 +1998,7 @@ package android.accessibilityservice { method public final android.accessibilityservice.AccessibilityServiceInfo getServiceInfo(); method public abstract void onAccessibilityEvent(android.view.accessibility.AccessibilityEvent); method public final android.os.IBinder onBind(android.content.Intent); - method protected void onGesture(int); + method protected boolean onGesture(int); method public abstract void onInterrupt(); method protected void onServiceConnected(); method public final boolean performGlobalAction(int); @@ -2020,6 +2021,8 @@ package android.accessibilityservice { 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 GESTURE_TWO_FINGER_LONG_PRESS = 20; // 0x14 + field public static final int GESTURE_TWO_FINGER_TAP = 19; // 0x13 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 @@ -2033,6 +2036,7 @@ package android.accessibilityservice { method public int describeContents(); method public static java.lang.String feedbackTypeToString(int); method public static java.lang.String flagToString(int); + method public boolean getCanHandleGestures(); method public boolean getCanRetrieveWindowContent(); method public deprecated java.lang.String getDescription(); method public java.lang.String getId(); diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java index 4e340c0..c858e3c 100644 --- a/core/java/android/accessibilityservice/AccessibilityService.java +++ b/core/java/android/accessibilityservice/AccessibilityService.java @@ -19,22 +19,17 @@ package android.accessibilityservice; import android.app.Service; import android.content.Context; import android.content.Intent; -import android.content.res.Configuration; import android.os.IBinder; import android.os.Looper; import android.os.Message; import android.os.RemoteException; -import android.util.LocaleUtil; import android.util.Log; -import android.view.View; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityInteractionClient; import android.view.accessibility.AccessibilityNodeInfo; import com.android.internal.os.HandlerCaller; -import java.util.Locale; - /** * An accessibility service runs in the background and receives callbacks by the system * when {@link AccessibilityEvent}s are fired. Such events denote some state transition @@ -299,6 +294,16 @@ public abstract class AccessibilityService extends Service { public static final int GESTURE_SWIPE_DOWN_AND_RIGHT = 18; /** + * The user has performed a two finger tap gesture on the touch screen. + */ + public static final int GESTURE_TWO_FINGER_TAP = 19; + + /** + * The user has performed a two finger long press gesture on the touch screen. + */ + public static final int GESTURE_TWO_FINGER_LONG_PRESS = 20; + + /** * The {@link Intent} that must be declared as handled by the service. */ public static final String SERVICE_INTERFACE = @@ -342,8 +347,6 @@ public abstract class AccessibilityService extends Service { */ public static final int GLOBAL_ACTION_NOTIFICATIONS = 4; - private static final int UNDEFINED = -1; - private static final String LOG_TAG = "AccessibilityService"; interface Callbacks { @@ -351,15 +354,13 @@ public abstract class AccessibilityService extends Service { public void onInterrupt(); public void onServiceConnected(); public void onSetConnectionId(int connectionId); - public void onGesture(int gestureId); + public boolean onGesture(int gestureId); } private int mConnectionId; private AccessibilityServiceInfo mInfo; - private int mLayoutDirection; - /** * Callback for {@link android.view.accessibility.AccessibilityEvent}s. * @@ -386,95 +387,43 @@ public abstract class AccessibilityService extends Service { /** * Called by the system when the user performs a specific gesture on the - * touch screen. + * touch screen. If the gesture is not handled in this callback the system + * may provide default handing. Therefore, one should return true from this + * function if overriding of default behavior is desired. + * + * <strong>Note:</strong> To receive gestures an accessibility service + * must declare that it can handle such by specifying the + * <code><{@link android.R.styleable#AccessibilityService_canHandleGestures + * canHandleGestures}></code> attribute. * * @param gestureId The unique id of the performed gesture. * + * @return Whether the gesture was handled. + * * @see #GESTURE_SWIPE_UP - * @see #GESTURE_SWIPE_DOWN - * @see #GESTURE_SWIPE_LEFT - * @see #GESTURE_SWIPE_RIGHT + * @see #GESTURE_SWIPE_UP_AND_LEFT * @see #GESTURE_SWIPE_UP_AND_DOWN + * @see #GESTURE_SWIPE_UP_AND_RIGHT + * @see #GESTURE_SWIPE_DOWN + * @see #GESTURE_SWIPE_DOWN_AND_LEFT * @see #GESTURE_SWIPE_DOWN_AND_UP + * @see #GESTURE_SWIPE_DOWN_AND_RIGHT + * @see #GESTURE_SWIPE_LEFT + * @see #GESTURE_SWIPE_LEFT_AND_UP * @see #GESTURE_SWIPE_LEFT_AND_RIGHT + * @see #GESTURE_SWIPE_LEFT_AND_DOWN + * @see #GESTURE_SWIPE_RIGHT + * @see #GESTURE_SWIPE_RIGHT_AND_UP * @see #GESTURE_SWIPE_RIGHT_AND_LEFT + * @see #GESTURE_SWIPE_RIGHT_AND_DOWN * @see #GESTURE_CLOCKWISE_CIRCLE * @see #GESTURE_COUNTER_CLOCKWISE_CIRCLE + * @see #GESTURE_TWO_FINGER_TAP + * @see #GESTURE_TWO_FINGER_LONG_PRESS */ - protected void onGesture(int gestureId) { + protected boolean 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) { - throw new IllegalStateException("AccessibilityService not connected." - + " Did you receive a call of onServiceConnected()?"); - } - AccessibilityNodeInfo root = getRootInActiveWindow(); - if (root == null) { - return; - } - - AccessibilityNodeInfo current = root.findFocus(AccessibilityNodeInfo.FOCUS_ACCESSIBILITY); - if (current == null) { - current = root; - } - - // Local actions. - AccessibilityNodeInfo next = null; - switch (gestureId) { - case GESTURE_SWIPE_UP: { - next = current.focusSearch(View.ACCESSIBILITY_FOCUS_OUT); - } break; - case GESTURE_SWIPE_DOWN: { - next = current.focusSearch(View.ACCESSIBILITY_FOCUS_IN); - } break; - case GESTURE_SWIPE_LEFT: { - if (mLayoutDirection == View.LAYOUT_DIRECTION_LTR) { - next = current.focusSearch(View.ACCESSIBILITY_FOCUS_BACKWARD); - } else { // LAYOUT_DIRECTION_RTL - next = current.focusSearch(View.ACCESSIBILITY_FOCUS_FORWARD); - } - } break; - case GESTURE_SWIPE_RIGHT: { - if (mLayoutDirection == View.LAYOUT_DIRECTION_LTR) { - next = current.focusSearch(View.ACCESSIBILITY_FOCUS_FORWARD); - } else { // LAYOUT_DIRECTION_RTL - next = current.focusSearch(View.ACCESSIBILITY_FOCUS_BACKWARD); - } - } break; - case GESTURE_SWIPE_UP_AND_DOWN: { - next = current.focusSearch(View.ACCESSIBILITY_FOCUS_UP); - } break; - case GESTURE_SWIPE_DOWN_AND_UP: { - next = current.focusSearch(View.ACCESSIBILITY_FOCUS_DOWN); - } break; - case GESTURE_SWIPE_LEFT_AND_RIGHT: { - next = current.focusSearch(View.ACCESSIBILITY_FOCUS_LEFT); - } break; - case GESTURE_SWIPE_RIGHT_AND_LEFT: { - next = current.focusSearch(View.ACCESSIBILITY_FOCUS_RIGHT); - } break; - } - if (next != null && !next.equals(current)) { - next.performAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS); - } + return false; } /** @@ -484,10 +433,7 @@ public abstract class AccessibilityService extends Service { * @return The root node if this service can retrieve window content. */ public AccessibilityNodeInfo getRootInActiveWindow() { - return AccessibilityInteractionClient.getInstance() - .findAccessibilityNodeInfoByAccessibilityId(mConnectionId, - AccessibilityNodeInfo.ACTIVE_WINDOW_ID, AccessibilityNodeInfo.ROOT_NODE_ID, - AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS); + return AccessibilityInteractionClient.getInstance().getRootInActiveWindow(mConnectionId); } /** @@ -509,7 +455,7 @@ public abstract class AccessibilityService extends Service { AccessibilityInteractionClient.getInstance().getConnection(mConnectionId); if (connection != null) { try { - return connection.perfromGlobalAction(action); + return connection.performGlobalAction(action); } catch (RemoteException re) { Log.w(LOG_TAG, "Error while calling performGlobalAction", re); } @@ -572,18 +518,6 @@ public abstract class AccessibilityService extends Service { } } - @Override - public void onCreate() { - Locale locale = getResources().getConfiguration().locale; - mLayoutDirection = LocaleUtil.getLayoutDirectionFromLocale(locale); - } - - @Override - public void onConfigurationChanged(Configuration configuration) { - super.onConfigurationChanged(configuration); - mLayoutDirection = LocaleUtil.getLayoutDirectionFromLocale(configuration.locale); - } - /** * Implement to return the implementation of the internal accessibility * service interface. @@ -612,8 +546,8 @@ public abstract class AccessibilityService extends Service { } @Override - public void onGesture(int gestureId) { - AccessibilityService.this.onGesture(gestureId); + public boolean onGesture(int gestureId) { + return AccessibilityService.this.onGesture(gestureId); } }); } @@ -658,8 +592,10 @@ public abstract class AccessibilityService extends Service { mCaller.sendMessage(message); } - public void onGesture(int gestureId) { - Message message = mCaller.obtainMessageI(DO_ON_GESTURE, gestureId); + public void onGesture(int gestureId, IAccessibilityServiceClientCallback callback, + int interactionId) { + Message message = mCaller.obtainMessageIIO(DO_ON_GESTURE, gestureId, interactionId, + callback); mCaller.sendMessage(message); } @@ -692,7 +628,15 @@ public abstract class AccessibilityService extends Service { return; case DO_ON_GESTURE : final int gestureId = message.arg1; - mCallback.onGesture(gestureId); + final int interactionId = message.arg2; + IAccessibilityServiceClientCallback callback = + (IAccessibilityServiceClientCallback) message.obj; + final boolean handled = mCallback.onGesture(gestureId); + try { + callback.setGestureResult(gestureId, handled, interactionId); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error calling back with the gesture resut.", re); + } return; default : Log.w(LOG_TAG, "Unknown message type " + message.what); diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java index e77ed9a..7e6786b 100644 --- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java +++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java @@ -224,6 +224,11 @@ public class AccessibilityServiceInfo implements Parcelable { private boolean mCanRetrieveWindowContent; /** + * Flag whether this accessibility service can handle gestures. + */ + private boolean mCanHandleGestures; + + /** * Resource id of the description of the accessibility service. */ private int mDescriptionResId; @@ -303,6 +308,8 @@ public class AccessibilityServiceInfo implements Parcelable { mCanRetrieveWindowContent = asAttributes.getBoolean( com.android.internal.R.styleable.AccessibilityService_canRetrieveWindowContent, false); + mCanHandleGestures = asAttributes.getBoolean( + com.android.internal.R.styleable.AccessibilityService_canHandleGestures, false); TypedValue peekedValue = asAttributes.peekValue( com.android.internal.R.styleable.AccessibilityService_description); if (peekedValue != null) { @@ -378,13 +385,25 @@ public class AccessibilityServiceInfo implements Parcelable { * <strong>Statically set from * {@link AccessibilityService#SERVICE_META_DATA meta-data}.</strong> * </p> - * @return True window content can be retrieved. + * @return True if window content can be retrieved. */ public boolean getCanRetrieveWindowContent() { return mCanRetrieveWindowContent; } /** + * Whether this service can handle gestures. + * <p> + * <strong>Statically set from + * {@link AccessibilityService#SERVICE_META_DATA meta-data}.</strong> + * </p> + * @return True if the service can handle gestures. + */ + public boolean getCanHandleGestures() { + return mCanHandleGestures; + } + + /** * Gets the non-localized description of the accessibility service. * <p> * <strong>Statically set from diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl index 588728c..0257aa4 100644 --- a/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl +++ b/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl @@ -16,6 +16,7 @@ package android.accessibilityservice; +import android.accessibilityservice.IAccessibilityServiceClientCallback; import android.accessibilityservice.IAccessibilityServiceConnection; import android.view.accessibility.AccessibilityEvent; @@ -32,5 +33,5 @@ import android.view.accessibility.AccessibilityEvent; void onInterrupt(); - void onGesture(int gestureId); + void onGesture(int gesture, in IAccessibilityServiceClientCallback callback, int interactionId); } diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceClientCallback.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceClientCallback.aidl new file mode 100644 index 0000000..9061398 --- /dev/null +++ b/core/java/android/accessibilityservice/IAccessibilityServiceClientCallback.aidl @@ -0,0 +1,30 @@ +/* +** 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 android.accessibilityservice; + +import android.accessibilityservice.IAccessibilityServiceConnection; +import android.view.accessibility.AccessibilityEvent; + +/** + * Callback for IAccessibilityServiceClient. + * + * @hide + */ + oneway interface IAccessibilityServiceClientCallback { + + void setGestureResult(int gestureId, boolean handled, int interactionId); +} diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl index 1bd5387..6e85665 100644 --- a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl +++ b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl @@ -167,5 +167,5 @@ interface IAccessibilityServiceConnection { * @param action The action to perform. * @return Whether the action was performed. */ - boolean perfromGlobalAction(int action); + boolean performGlobalAction(int action); } diff --git a/core/java/android/accessibilityservice/UiTestAutomationBridge.java b/core/java/android/accessibilityservice/UiTestAutomationBridge.java index c840bd6..1697df0 100644 --- a/core/java/android/accessibilityservice/UiTestAutomationBridge.java +++ b/core/java/android/accessibilityservice/UiTestAutomationBridge.java @@ -177,8 +177,8 @@ public class UiTestAutomationBridge { } @Override - public void onGesture(int gestureId) { - /* do nothing */ + public boolean onGesture(int gestureId) { + return false; } }); diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java index 35f0d9d..f73faf3 100644 --- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java +++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java @@ -84,7 +84,7 @@ public final class AccessibilityInteractionClient private final Object mInstanceLock = new Object(); - private int mInteractionId = -1; + private volatile int mInteractionId = -1; private AccessibilityNodeInfo mFindAccessibilityNodeInfoResult; @@ -150,6 +150,18 @@ public final class AccessibilityInteractionClient } /** + * Gets the root {@link AccessibilityNodeInfo} in the currently active window. + * + * @param connectionId The id of a connection for interacting with the system. + * @return The root {@link AccessibilityNodeInfo} if found, null otherwise. + */ + public AccessibilityNodeInfo getRootInActiveWindow(int connectionId) { + return findAccessibilityNodeInfoByAccessibilityId(connectionId, + AccessibilityNodeInfo.ACTIVE_WINDOW_ID, AccessibilityNodeInfo.ROOT_NODE_ID, + AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS); + } + + /** * Finds an {@link AccessibilityNodeInfo} by accessibility id. * * @param connectionId The id of a connection for interacting with the system. diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 6f489d4..aa47993 100755 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -2470,6 +2470,8 @@ <!-- Flag whether the accessibility service wants to be able to retrieve the active window content. This setting cannot be changed at runtime. --> <attr name="canRetrieveWindowContent" format="boolean" /> + <!-- Flag whether the accessibility service can handle gesrures and wants such. --> + <attr name="canHandleGestures" format="boolean" /> <!-- Short description of the accessibility serivce purpose or behavior.--> <attr name="description" /> </declare-styleable> diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index 03ba08c..3f8036b 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -3599,5 +3599,6 @@ <public type="attr" name="parentActivityName" /> <public type="attr" name="importantForAccessibility"/> + <public type="attr" name="canHandleGestures"/> </resources> diff --git a/services/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/java/com/android/server/accessibility/AccessibilityManagerService.java index 6675816..7361062 100644 --- a/services/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -23,6 +23,7 @@ import android.Manifest; import android.accessibilityservice.AccessibilityService; import android.accessibilityservice.AccessibilityServiceInfo; import android.accessibilityservice.IAccessibilityServiceClient; +import android.accessibilityservice.IAccessibilityServiceClientCallback; import android.accessibilityservice.IAccessibilityServiceConnection; import android.app.PendingIntent; import android.content.BroadcastReceiver; @@ -42,6 +43,7 @@ import android.os.Binder; import android.os.Build; import android.os.Handler; import android.os.IBinder; +import android.os.Looper; import android.os.Message; import android.os.RemoteException; import android.os.ServiceManager; @@ -55,7 +57,9 @@ import android.view.IWindow; import android.view.InputDevice; import android.view.KeyCharacterMap; import android.view.KeyEvent; +import android.view.View; 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; @@ -64,6 +68,8 @@ import android.view.accessibility.IAccessibilityManager; import android.view.accessibility.IAccessibilityManagerClient; import com.android.internal.content.PackageMonitor; +import com.android.internal.os.HandlerCaller; +import com.android.internal.os.HandlerCaller.Callback; import com.android.server.accessibility.TouchExplorer.GestureListener; import com.android.server.wm.WindowManagerService; @@ -78,6 +84,7 @@ import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; /** * This class is instantiated by the system as a system level service and can be @@ -97,10 +104,12 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub private static final String FUNCTION_REGISTER_UI_TEST_AUTOMATION_SERVICE = "registerUiTestAutomationService"; - private static int sIdCounter = 0; - private static final int OWN_PROCESS_ID = android.os.Process.myPid(); + private static final int UNDEFINED = -1; + + private static int sIdCounter = 0; + private static int sNextWindowId; final Context mContext; @@ -145,6 +154,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub private Service mUiAutomationService; + private GestureHandler mGestureHandler; + + private int mDefaultGestureHandlingHelperServiceId = UNDEFINED; + /** * Handler for delayed event dispatch. */ @@ -471,26 +484,60 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } @Override - public void onGesture(int gestureId) { + public boolean onGesture(int gestureId) { + // Lazily instantiate the gesture handler. + if (mGestureHandler == null) { + mGestureHandler = new GestureHandler(); + } synchronized (mLock) { - final boolean dispatched = notifyGestureLocked(gestureId, false); - if (!dispatched) { - notifyGestureLocked(gestureId, true); + boolean handled = notifyGestureLocked(gestureId, false); + if (!handled) { + handled = notifyGestureLocked(gestureId, true); + } + if (!handled) { + mGestureHandler.scheduleHandleGestureDefault(gestureId); } + return handled; + } + } + + private Service getDefaultGestureHandlingHelperService() { + // Since querying of screen content is done through the + // AccessibilityInteractionClient which talks to an + // IAccessibilityServiceConnection implementation we create a proxy + // Service when necessary to enable interaction with the remote + // view tree. Note that this service is just a stateless proxy + // that does not get any events or interrupts. + if (mDefaultGestureHandlingHelperServiceId == UNDEFINED) { + ComponentName name = new ComponentName("android", + "DefaultGestureHandlingHelperService"); + AccessibilityServiceInfo info = new AccessibilityServiceInfo(); + Service service = new Service(name, info, true); + mDefaultGestureHandlingHelperServiceId = service.mId; + AccessibilityInteractionClient.getInstance().addConnection( + mDefaultGestureHandlingHelperServiceId, service); + return service; + } else { + return (Service) AccessibilityInteractionClient.getInstance() + .getConnection(mDefaultGestureHandlingHelperServiceId); } } private boolean notifyGestureLocked(int gestureId, boolean isDefault) { - final int serviceCount = mServices.size(); - for (int i = 0; i < serviceCount; i++) { + // 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 + // gestrues to avoid user frustration when different + // bahiour is observed from different combinations of + // enabled accessibility services. + for (int i = mServices.size() - 1; i >= 0; i--) { Service service = mServices.get(i); - if (service.mIsDefault == isDefault) { - try { - service.mServiceInterface.onGesture(gestureId); - return true; - } catch (RemoteException re) { - Slog.e(LOG_TAG, "Error dispatching gesture."); - } + if (service.mCanHandleGestures && service.mIsDefault == isDefault) { + mGestureHandler.scheduleHandleGesture(gestureId, service.mServiceInterface); + return true; } } return false; @@ -935,6 +982,210 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } } + class GestureHandler extends IAccessibilityServiceClientCallback.Stub + implements Runnable, Callback { + + private static final String THREAD_NAME = "AccessibilityGestureHandler"; + + private static final long TIMEOUT_INTERACTION_MILLIS = 5000; + + private static final int MSG_HANDLE_GESTURE = 1; + + private static final int MSG_HANDLE_GESTURE_DEFAULT = 2; + + private final AtomicInteger mInteractionCounter = new AtomicInteger(); + + private final Object mGestureLock = new Object(); + + private HandlerCaller mHandlerCaller; + + private volatile int mInteractionId = -1; + + private volatile boolean mGestureResult; + + public GestureHandler() { + synchronized (mGestureLock) { + Thread worker = new Thread(this, THREAD_NAME); + worker.start(); + try { + mGestureLock.wait(); + } catch (InterruptedException ie) { + /* ignore */ + } + } + } + + @Override + public void run() { + Looper.prepare(); + synchronized (mGestureLock) { + mHandlerCaller = new HandlerCaller(mContext, Looper.myLooper(), this); + mGestureLock.notifyAll(); + } + Looper.loop(); + } + + @Override + public void setGestureResult(int gestureId, boolean handled, int interactionId) { + synchronized (mGestureLock) { + if (interactionId > mInteractionId) { + mGestureResult = handled; + mInteractionId = interactionId; + } + mGestureLock.notifyAll(); + } + } + + @Override + public void executeMessage(Message message) { + final int type = message.what; + switch (type) { + case MSG_HANDLE_GESTURE: { + IAccessibilityServiceClient service = + (IAccessibilityServiceClient) message.obj; + final int gestureId = message.arg1; + final int interactionId = message.arg1; + + try { + service.onGesture(gestureId, this, interactionId); + } catch (RemoteException re) { + Slog.e(LOG_TAG, "Error dispatching a gesture to a client.", re); + return; + } + + long waitTimeMillis = 0; + final long startTimeMillis = SystemClock.uptimeMillis(); + synchronized (mGestureLock) { + while (true) { + try { + // Did we get the expected callback? + if (mInteractionId == interactionId) { + break; + } + // Did we get an obsolete callback? + if (mInteractionId > interactionId) { + break; + } + // Did we time out? + final long elapsedTimeMillis = + SystemClock.uptimeMillis() - startTimeMillis; + waitTimeMillis = TIMEOUT_INTERACTION_MILLIS - elapsedTimeMillis; + if (waitTimeMillis <= 0) { + break; + } + mGestureLock.wait(waitTimeMillis); + } catch (InterruptedException ie) { + /* ignore */ + } + } + handleGestureIfNeededAndResetLocked(gestureId); + } + } break; + case MSG_HANDLE_GESTURE_DEFAULT: { + final int gestureId = message.arg1; + handleGestureDefault(gestureId); + } break; + default: { + throw new IllegalArgumentException("Unknown message type: " + type); + } + } + } + + private void handleGestureIfNeededAndResetLocked(int gestureId) { + if (!mGestureResult) { + handleGestureDefault(gestureId); + } + mGestureResult = false; + mInteractionId = -1; + } + + public void scheduleHandleGesture(int gestureId, IAccessibilityServiceClient service) { + final int interactionId = mInteractionCounter.incrementAndGet(); + mHandlerCaller.obtainMessageIIO(MSG_HANDLE_GESTURE, gestureId, interactionId, + service).sendToTarget(); + } + + public void scheduleHandleGestureDefault(int gestureId) { + final int interactionId = mInteractionCounter.incrementAndGet(); + mHandlerCaller.obtainMessageI(MSG_HANDLE_GESTURE_DEFAULT, gestureId).sendToTarget(); + } + + private void handleGestureDefault(int gestureId) { + Service service = getDefaultGestureHandlingHelperService(); + + // Global actions. + switch (gestureId) { + case AccessibilityService.GESTURE_SWIPE_DOWN_AND_LEFT: { + service.performGlobalAction(AccessibilityService.GLOBAL_ACTION_BACK); + } return; + case AccessibilityService.GESTURE_SWIPE_DOWN_AND_RIGHT: { + service.performGlobalAction(AccessibilityService.GLOBAL_ACTION_HOME); + } return; + case AccessibilityService.GESTURE_SWIPE_UP_AND_LEFT: { + service.performGlobalAction(AccessibilityService.GLOBAL_ACTION_RECENTS); + } return; + case AccessibilityService.GESTURE_SWIPE_UP_AND_RIGHT: { + service.performGlobalAction(AccessibilityService.GLOBAL_ACTION_NOTIFICATIONS); + } return; + } + + AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance(); + + AccessibilityNodeInfo root = client.getRootInActiveWindow(service.mId); + if (root == null) { + return; + } + + AccessibilityNodeInfo current = root.findFocus( + AccessibilityNodeInfo.FOCUS_ACCESSIBILITY); + if (current == null) { + current = root; + } + + // Local actions. + AccessibilityNodeInfo next = null; + switch (gestureId) { + case AccessibilityService.GESTURE_SWIPE_UP: { + next = current.focusSearch(View.ACCESSIBILITY_FOCUS_OUT); + } break; + case AccessibilityService.GESTURE_SWIPE_DOWN: { + next = current.focusSearch(View.ACCESSIBILITY_FOCUS_IN); + } break; + case AccessibilityService.GESTURE_SWIPE_LEFT: { + // TODO: Implement the RTL support. +// if (mLayoutDirection == View.LAYOUT_DIRECTION_LTR) { + next = current.focusSearch(View.ACCESSIBILITY_FOCUS_BACKWARD); +// } else { // LAYOUT_DIRECTION_RTL +// next = current.focusSearch(View.ACCESSIBILITY_FOCUS_FORWARD); +// } + } break; + case AccessibilityService.GESTURE_SWIPE_RIGHT: { + // TODO: Implement the RTL support. +// if (mLayoutDirection == View.LAYOUT_DIRECTION_LTR) { + next = current.focusSearch(View.ACCESSIBILITY_FOCUS_FORWARD); +// } else { // LAYOUT_DIRECTION_RTL +// next = current.focusSearch(View.ACCESSIBILITY_FOCUS_BACKWARD); +// } + } break; + case AccessibilityService.GESTURE_SWIPE_UP_AND_DOWN: { + next = current.focusSearch(View.ACCESSIBILITY_FOCUS_UP); + } break; + case AccessibilityService.GESTURE_SWIPE_DOWN_AND_UP: { + next = current.focusSearch(View.ACCESSIBILITY_FOCUS_DOWN); + } break; + case AccessibilityService.GESTURE_SWIPE_LEFT_AND_RIGHT: { + next = current.focusSearch(View.ACCESSIBILITY_FOCUS_LEFT); + } break; + case AccessibilityService.GESTURE_SWIPE_RIGHT_AND_LEFT: { + next = current.focusSearch(View.ACCESSIBILITY_FOCUS_RIGHT); + } break; + } + if (next != null && !next.equals(current)) { + next.performAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS); + } + } + } + /** * This class represents an accessibility service. It stores all per service * data required for the service management, provides API for starting/stopping the @@ -971,6 +1222,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub boolean mCanRetrieveScreenContent; + boolean mCanHandleGestures; + boolean mIsAutomation; final Rect mTempBounds = new Rect(); @@ -987,6 +1240,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub mIsAutomation = isAutomation; if (!isAutomation) { mCanRetrieveScreenContent = accessibilityServiceInfo.getCanRetrieveWindowContent(); + mCanHandleGestures = accessibilityServiceInfo.getCanHandleGestures(); mIntent = new Intent().setComponent(mComponentName); mIntent.putExtra(Intent.EXTRA_CLIENT_LABEL, com.android.internal.R.string.accessibility_binding_label); @@ -995,6 +1249,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } else { mCanRetrieveScreenContent = true; mIncludeNotImportantViews = true; + mCanHandleGestures = true; } setDynamicallyConfigurableProperties(accessibilityServiceInfo); } @@ -1323,7 +1578,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub return true; } - public boolean perfromGlobalAction(int action) { + public boolean performGlobalAction(int action) { switch (action) { case AccessibilityService.GLOBAL_ACTION_BACK: { sendDownAndUpKeyEvents(KeyEvent.KEYCODE_BACK); diff --git a/services/java/com/android/server/accessibility/TouchExplorer.java b/services/java/com/android/server/accessibility/TouchExplorer.java index 5d01c77..39012e6 100644 --- a/services/java/com/android/server/accessibility/TouchExplorer.java +++ b/services/java/com/android/server/accessibility/TouchExplorer.java @@ -152,7 +152,7 @@ public class TouchExplorer { * * @param gestureId The gesture id. */ - public void onGesture(int gestureId); + public boolean onGesture(int gestureId); } /** |