summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Android.mk1
-rw-r--r--api/current.txt6
-rw-r--r--core/java/android/accessibilityservice/AccessibilityService.java162
-rw-r--r--core/java/android/accessibilityservice/AccessibilityServiceInfo.java21
-rw-r--r--core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl3
-rw-r--r--core/java/android/accessibilityservice/IAccessibilityServiceClientCallback.aidl30
-rw-r--r--core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl2
-rw-r--r--core/java/android/accessibilityservice/UiTestAutomationBridge.java4
-rw-r--r--core/java/android/view/accessibility/AccessibilityInteractionClient.java14
-rwxr-xr-xcore/res/res/values/attrs.xml2
-rw-r--r--core/res/res/values/public.xml1
-rw-r--r--services/java/com/android/server/accessibility/AccessibilityManagerService.java287
-rw-r--r--services/java/com/android/server/accessibility/TouchExplorer.java2
13 files changed, 402 insertions, 133 deletions
diff --git a/Android.mk b/Android.mk
index 3b2d32d..a98b1c2 100644
--- a/Android.mk
+++ b/Android.mk
@@ -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>&lt;{@link android.R.styleable#AccessibilityService_canHandleGestures
+ * canHandleGestures}&gt;</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);
}
/**