Merge "Adding an opt-in mechanism for gesture detection in AccessibilityService."
This commit is contained in:
committed by
Android (Google) Code Review
commit
e30df7744c
@@ -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 \
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
@@ -298,6 +293,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.
|
||||
*/
|
||||
@@ -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);
|
||||
|
||||
@@ -223,6 +223,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.
|
||||
*/
|
||||
@@ -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,12 +385,24 @@ 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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -177,8 +177,8 @@ public class UiTestAutomationBridge {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGesture(int gestureId) {
|
||||
/* do nothing */
|
||||
public boolean onGesture(int gestureId) {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -149,6 +149,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.
|
||||
*
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -3599,5 +3599,6 @@
|
||||
<public type="attr" name="parentActivityName" />
|
||||
|
||||
<public type="attr" name="importantForAccessibility"/>
|
||||
<public type="attr" name="canHandleGestures"/>
|
||||
|
||||
</resources>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -152,7 +152,7 @@ public class TouchExplorer {
|
||||
*
|
||||
* @param gestureId The gesture id.
|
||||
*/
|
||||
public void onGesture(int gestureId);
|
||||
public boolean onGesture(int gestureId);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user