Accessibility can capture fingerprint gestures
Bug: 27148522
Test: Unit tests for two new classes in this CL, CTS in
linked CL.
Change-Id: Icb5113e00b1f8724814263b3cc7f72fe4a6f0b41
diff --git a/Android.mk b/Android.mk
index e68b310..22323c5 100644
--- a/Android.mk
+++ b/Android.mk
@@ -182,6 +182,7 @@
core/java/android/hardware/display/IVirtualDisplayCallback.aidl \
core/java/android/hardware/fingerprint/IFingerprintService.aidl \
core/java/android/hardware/fingerprint/IFingerprintServiceLockoutResetCallback.aidl \
+ core/java/android/hardware/fingerprint/IFingerprintClientActiveCallback.aidl \
core/java/android/hardware/fingerprint/IFingerprintServiceReceiver.aidl \
core/java/android/hardware/hdmi/IHdmiControlCallback.aidl \
core/java/android/hardware/hdmi/IHdmiControlService.aidl \
diff --git a/api/current.txt b/api/current.txt
index 95e8a5d..4607ee9 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -349,6 +349,7 @@
field public static final int calendarTextColor = 16843931; // 0x101049b
field public static final int calendarViewShown = 16843596; // 0x101034c
field public static final int calendarViewStyle = 16843613; // 0x101035d
+ field public static final int canCaptureFingerprintGestures = 16844111; // 0x101054f
field public static final int canControlMagnification = 16844039; // 0x1010507
field public static final int canPerformGestures = 16844045; // 0x101050d
field public static final int canRecord = 16844060; // 0x101051c
@@ -2692,6 +2693,7 @@
method public final void disableSelf();
method public final boolean dispatchGesture(android.accessibilityservice.GestureDescription, android.accessibilityservice.AccessibilityService.GestureResultCallback, android.os.Handler);
method public android.view.accessibility.AccessibilityNodeInfo findFocus(int);
+ method public final android.accessibilityservice.FingerprintGestureController getFingerprintGestureController();
method public final android.accessibilityservice.AccessibilityService.MagnificationController getMagnificationController();
method public android.view.accessibility.AccessibilityNodeInfo getRootInActiveWindow();
method public final android.accessibilityservice.AccessibilityServiceInfo getServiceInfo();
@@ -2783,6 +2785,7 @@
method public java.lang.String getSettingsActivityName();
method public java.lang.String loadDescription(android.content.pm.PackageManager);
method public void writeToParcel(android.os.Parcel, int);
+ field public static final int CAPABILITY_CAN_CAPTURE_FINGERPRINT_GESTURES = 64; // 0x40
field public static final int CAPABILITY_CAN_CONTROL_MAGNIFICATION = 16; // 0x10
field public static final int CAPABILITY_CAN_PERFORM_GESTURES = 32; // 0x20
field public static final int CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY = 4; // 0x4
@@ -2798,6 +2801,7 @@
field public static final int FEEDBACK_HAPTIC = 2; // 0x2
field public static final int FEEDBACK_SPOKEN = 1; // 0x1
field public static final int FEEDBACK_VISUAL = 8; // 0x8
+ field public static final int FLAG_CAPTURE_FINGERPRINT_GESTURES = 512; // 0x200
field public static final int FLAG_ENABLE_ACCESSIBILITY_VOLUME = 128; // 0x80
field public static final int FLAG_INCLUDE_NOT_IMPORTANT_VIEWS = 2; // 0x2
field public static final int FLAG_REPORT_VIEW_IDS = 16; // 0x10
@@ -2812,6 +2816,22 @@
field public java.lang.String[] packageNames;
}
+ public final class FingerprintGestureController {
+ method public boolean isGestureDetectionAvailable();
+ method public void registerFingerprintGestureCallback(android.accessibilityservice.FingerprintGestureController.FingerprintGestureCallback, android.os.Handler);
+ method public void unregisterFingerprintGestureCallback(android.accessibilityservice.FingerprintGestureController.FingerprintGestureCallback);
+ field public static final int FINGERPRINT_GESTURE_SWIPE_DOWN = 8; // 0x8
+ field public static final int FINGERPRINT_GESTURE_SWIPE_LEFT = 2; // 0x2
+ field public static final int FINGERPRINT_GESTURE_SWIPE_RIGHT = 1; // 0x1
+ field public static final int FINGERPRINT_GESTURE_SWIPE_UP = 4; // 0x4
+ }
+
+ public static abstract class FingerprintGestureController.FingerprintGestureCallback {
+ ctor public FingerprintGestureController.FingerprintGestureCallback();
+ method public void onGesture(int);
+ method public void onGestureDetectionAvailabilityChanged(boolean);
+ }
+
public final class GestureDescription {
method public static long getMaxGestureDuration();
method public static int getMaxStrokeCount();
@@ -15247,11 +15267,11 @@
method public void unregisterDisplayListener(android.hardware.display.DisplayManager.DisplayListener);
field public static final java.lang.String DISPLAY_CATEGORY_PRESENTATION = "android.hardware.display.category.PRESENTATION";
field public static final int VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR = 16; // 0x10
+ field public static final int VIRTUAL_DISPLAY_FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD = 32; // 0x20
field public static final int VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY = 8; // 0x8
field public static final int VIRTUAL_DISPLAY_FLAG_PRESENTATION = 2; // 0x2
field public static final int VIRTUAL_DISPLAY_FLAG_PUBLIC = 1; // 0x1
field public static final int VIRTUAL_DISPLAY_FLAG_SECURE = 4; // 0x4
- field public static final int VIRTUAL_DISPLAY_FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD = 32; // 0x20
}
public static abstract interface DisplayManager.DisplayListener {
@@ -42619,11 +42639,11 @@
method public boolean isValid();
method public boolean isWideColorGamut();
field public static final int DEFAULT_DISPLAY = 0; // 0x0
+ field public static final int FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD = 32; // 0x20
field public static final int FLAG_PRESENTATION = 8; // 0x8
field public static final int FLAG_PRIVATE = 4; // 0x4
field public static final int FLAG_ROUND = 16; // 0x10
field public static final int FLAG_SECURE = 2; // 0x2
- field public static final int FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD = 32; // 0x20
field public static final int FLAG_SUPPORTS_PROTECTED_BUFFERS = 1; // 0x1
field public static final int INVALID_DISPLAY = -1; // 0xffffffff
field public static final int STATE_DOZE = 3; // 0x3
diff --git a/api/system-current.txt b/api/system-current.txt
index 4bd3838..7a8b7db 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -461,6 +461,7 @@
field public static final int calendarTextColor = 16843931; // 0x101049b
field public static final int calendarViewShown = 16843596; // 0x101034c
field public static final int calendarViewStyle = 16843613; // 0x101035d
+ field public static final int canCaptureFingerprintGestures = 16844111; // 0x101054f
field public static final int canControlMagnification = 16844039; // 0x1010507
field public static final int canPerformGestures = 16844045; // 0x101050d
field public static final int canRecord = 16844060; // 0x101051c
@@ -2811,6 +2812,7 @@
method public final void disableSelf();
method public final boolean dispatchGesture(android.accessibilityservice.GestureDescription, android.accessibilityservice.AccessibilityService.GestureResultCallback, android.os.Handler);
method public android.view.accessibility.AccessibilityNodeInfo findFocus(int);
+ method public final android.accessibilityservice.FingerprintGestureController getFingerprintGestureController();
method public final android.accessibilityservice.AccessibilityService.MagnificationController getMagnificationController();
method public android.view.accessibility.AccessibilityNodeInfo getRootInActiveWindow();
method public final android.accessibilityservice.AccessibilityServiceInfo getServiceInfo();
@@ -2902,6 +2904,7 @@
method public java.lang.String getSettingsActivityName();
method public java.lang.String loadDescription(android.content.pm.PackageManager);
method public void writeToParcel(android.os.Parcel, int);
+ field public static final int CAPABILITY_CAN_CAPTURE_FINGERPRINT_GESTURES = 64; // 0x40
field public static final int CAPABILITY_CAN_CONTROL_MAGNIFICATION = 16; // 0x10
field public static final int CAPABILITY_CAN_PERFORM_GESTURES = 32; // 0x20
field public static final int CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY = 4; // 0x4
@@ -2917,6 +2920,7 @@
field public static final int FEEDBACK_HAPTIC = 2; // 0x2
field public static final int FEEDBACK_SPOKEN = 1; // 0x1
field public static final int FEEDBACK_VISUAL = 8; // 0x8
+ field public static final int FLAG_CAPTURE_FINGERPRINT_GESTURES = 512; // 0x200
field public static final int FLAG_ENABLE_ACCESSIBILITY_VOLUME = 128; // 0x80
field public static final int FLAG_INCLUDE_NOT_IMPORTANT_VIEWS = 2; // 0x2
field public static final int FLAG_REPORT_VIEW_IDS = 16; // 0x10
@@ -2931,6 +2935,22 @@
field public java.lang.String[] packageNames;
}
+ public final class FingerprintGestureController {
+ method public boolean isGestureDetectionAvailable();
+ method public void registerFingerprintGestureCallback(android.accessibilityservice.FingerprintGestureController.FingerprintGestureCallback, android.os.Handler);
+ method public void unregisterFingerprintGestureCallback(android.accessibilityservice.FingerprintGestureController.FingerprintGestureCallback);
+ field public static final int FINGERPRINT_GESTURE_SWIPE_DOWN = 8; // 0x8
+ field public static final int FINGERPRINT_GESTURE_SWIPE_LEFT = 2; // 0x2
+ field public static final int FINGERPRINT_GESTURE_SWIPE_RIGHT = 1; // 0x1
+ field public static final int FINGERPRINT_GESTURE_SWIPE_UP = 4; // 0x4
+ }
+
+ public static abstract class FingerprintGestureController.FingerprintGestureCallback {
+ ctor public FingerprintGestureController.FingerprintGestureCallback();
+ method public void onGesture(int);
+ method public void onGestureDetectionAvailabilityChanged(boolean);
+ }
+
public final class GestureDescription {
method public static long getMaxGestureDuration();
method public static int getMaxStrokeCount();
@@ -15825,11 +15845,11 @@
method public void unregisterDisplayListener(android.hardware.display.DisplayManager.DisplayListener);
field public static final java.lang.String DISPLAY_CATEGORY_PRESENTATION = "android.hardware.display.category.PRESENTATION";
field public static final int VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR = 16; // 0x10
+ field public static final int VIRTUAL_DISPLAY_FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD = 32; // 0x20
field public static final int VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY = 8; // 0x8
field public static final int VIRTUAL_DISPLAY_FLAG_PRESENTATION = 2; // 0x2
field public static final int VIRTUAL_DISPLAY_FLAG_PUBLIC = 1; // 0x1
field public static final int VIRTUAL_DISPLAY_FLAG_SECURE = 4; // 0x4
- field public static final int VIRTUAL_DISPLAY_FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD = 32; // 0x20
}
public static abstract interface DisplayManager.DisplayListener {
@@ -46022,11 +46042,11 @@
method public boolean isValid();
method public boolean isWideColorGamut();
field public static final int DEFAULT_DISPLAY = 0; // 0x0
+ field public static final int FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD = 32; // 0x20
field public static final int FLAG_PRESENTATION = 8; // 0x8
field public static final int FLAG_PRIVATE = 4; // 0x4
field public static final int FLAG_ROUND = 16; // 0x10
field public static final int FLAG_SECURE = 2; // 0x2
- field public static final int FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD = 32; // 0x20
field public static final int FLAG_SUPPORTS_PROTECTED_BUFFERS = 1; // 0x1
field public static final int INVALID_DISPLAY = -1; // 0xffffffff
field public static final int STATE_DOZE = 3; // 0x3
diff --git a/api/test-current.txt b/api/test-current.txt
index 89b098a..2aa5fad 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -349,6 +349,7 @@
field public static final int calendarTextColor = 16843931; // 0x101049b
field public static final int calendarViewShown = 16843596; // 0x101034c
field public static final int calendarViewStyle = 16843613; // 0x101035d
+ field public static final int canCaptureFingerprintGestures = 16844111; // 0x101054f
field public static final int canControlMagnification = 16844039; // 0x1010507
field public static final int canPerformGestures = 16844045; // 0x101050d
field public static final int canRecord = 16844060; // 0x101051c
@@ -2692,6 +2693,7 @@
method public final void disableSelf();
method public final boolean dispatchGesture(android.accessibilityservice.GestureDescription, android.accessibilityservice.AccessibilityService.GestureResultCallback, android.os.Handler);
method public android.view.accessibility.AccessibilityNodeInfo findFocus(int);
+ method public final android.accessibilityservice.FingerprintGestureController getFingerprintGestureController();
method public final android.accessibilityservice.AccessibilityService.MagnificationController getMagnificationController();
method public android.view.accessibility.AccessibilityNodeInfo getRootInActiveWindow();
method public final android.accessibilityservice.AccessibilityServiceInfo getServiceInfo();
@@ -2783,6 +2785,7 @@
method public java.lang.String getSettingsActivityName();
method public java.lang.String loadDescription(android.content.pm.PackageManager);
method public void writeToParcel(android.os.Parcel, int);
+ field public static final int CAPABILITY_CAN_CAPTURE_FINGERPRINT_GESTURES = 64; // 0x40
field public static final int CAPABILITY_CAN_CONTROL_MAGNIFICATION = 16; // 0x10
field public static final int CAPABILITY_CAN_PERFORM_GESTURES = 32; // 0x20
field public static final int CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY = 4; // 0x4
@@ -2798,6 +2801,7 @@
field public static final int FEEDBACK_HAPTIC = 2; // 0x2
field public static final int FEEDBACK_SPOKEN = 1; // 0x1
field public static final int FEEDBACK_VISUAL = 8; // 0x8
+ field public static final int FLAG_CAPTURE_FINGERPRINT_GESTURES = 512; // 0x200
field public static final int FLAG_ENABLE_ACCESSIBILITY_VOLUME = 128; // 0x80
field public static final int FLAG_INCLUDE_NOT_IMPORTANT_VIEWS = 2; // 0x2
field public static final int FLAG_REPORT_VIEW_IDS = 16; // 0x10
@@ -2812,6 +2816,22 @@
field public java.lang.String[] packageNames;
}
+ public final class FingerprintGestureController {
+ method public boolean isGestureDetectionAvailable();
+ method public void registerFingerprintGestureCallback(android.accessibilityservice.FingerprintGestureController.FingerprintGestureCallback, android.os.Handler);
+ method public void unregisterFingerprintGestureCallback(android.accessibilityservice.FingerprintGestureController.FingerprintGestureCallback);
+ field public static final int FINGERPRINT_GESTURE_SWIPE_DOWN = 8; // 0x8
+ field public static final int FINGERPRINT_GESTURE_SWIPE_LEFT = 2; // 0x2
+ field public static final int FINGERPRINT_GESTURE_SWIPE_RIGHT = 1; // 0x1
+ field public static final int FINGERPRINT_GESTURE_SWIPE_UP = 4; // 0x4
+ }
+
+ public static abstract class FingerprintGestureController.FingerprintGestureCallback {
+ ctor public FingerprintGestureController.FingerprintGestureCallback();
+ method public void onGesture(int);
+ method public void onGestureDetectionAvailabilityChanged(boolean);
+ }
+
public final class GestureDescription {
method public static long getMaxGestureDuration();
method public static int getMaxStrokeCount();
@@ -15281,11 +15301,11 @@
method public void unregisterDisplayListener(android.hardware.display.DisplayManager.DisplayListener);
field public static final java.lang.String DISPLAY_CATEGORY_PRESENTATION = "android.hardware.display.category.PRESENTATION";
field public static final int VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR = 16; // 0x10
+ field public static final int VIRTUAL_DISPLAY_FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD = 32; // 0x20
field public static final int VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY = 8; // 0x8
field public static final int VIRTUAL_DISPLAY_FLAG_PRESENTATION = 2; // 0x2
field public static final int VIRTUAL_DISPLAY_FLAG_PUBLIC = 1; // 0x1
field public static final int VIRTUAL_DISPLAY_FLAG_SECURE = 4; // 0x4
- field public static final int VIRTUAL_DISPLAY_FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD = 32; // 0x20
}
public static abstract interface DisplayManager.DisplayListener {
@@ -42923,11 +42943,11 @@
method public boolean isValid();
method public boolean isWideColorGamut();
field public static final int DEFAULT_DISPLAY = 0; // 0x0
+ field public static final int FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD = 32; // 0x20
field public static final int FLAG_PRESENTATION = 8; // 0x8
field public static final int FLAG_PRIVATE = 4; // 0x4
field public static final int FLAG_ROUND = 16; // 0x10
field public static final int FLAG_SECURE = 2; // 0x2
- field public static final int FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD = 32; // 0x20
field public static final int FLAG_SUPPORTS_PROTECTED_BUFFERS = 1; // 0x1
field public static final int INVALID_DISPLAY = -1; // 0xffffffff
field public static final int STATE_DOZE = 3; // 0x3
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index 3e5cc54..a036b6a 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -20,11 +20,13 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ParceledListSlice;
import android.graphics.Region;
+import android.hardware.fingerprint.FingerprintManager;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
@@ -33,11 +35,9 @@
import android.provider.Settings;
import android.util.ArrayMap;
import android.util.Log;
-import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import android.view.KeyEvent;
-import android.view.MotionEvent;
import android.view.WindowManager;
import android.view.WindowManagerImpl;
import android.view.accessibility.AccessibilityEvent;
@@ -362,19 +362,22 @@
private static final String LOG_TAG = "AccessibilityService";
/**
+ * Interface used by IAccessibilityServiceWrapper to call the service from its main thread.
* @hide
*/
public interface Callbacks {
- public void onAccessibilityEvent(AccessibilityEvent event);
- public void onInterrupt();
- public void onServiceConnected();
- public void init(int connectionId, IBinder windowToken);
- public boolean onGesture(int gestureId);
- public boolean onKeyEvent(KeyEvent event);
- public void onMagnificationChanged(@NonNull Region region,
+ void onAccessibilityEvent(AccessibilityEvent event);
+ void onInterrupt();
+ void onServiceConnected();
+ void init(int connectionId, IBinder windowToken);
+ boolean onGesture(int gestureId);
+ boolean onKeyEvent(KeyEvent event);
+ void onMagnificationChanged(@NonNull Region region,
float scale, float centerX, float centerY);
- public void onSoftKeyboardShowModeChanged(int showMode);
- public void onPerformGestureResult(int sequence, boolean completedSuccessfully);
+ void onSoftKeyboardShowModeChanged(int showMode);
+ void onPerformGestureResult(int sequence, boolean completedSuccessfully);
+ void onFingerprintCapturingGesturesChanged(boolean active);
+ void onFingerprintGesture(int gesture);
}
/**
@@ -404,6 +407,8 @@
private final Object mLock = new Object();
+ private FingerprintGestureController mFingerprintGestureController;
+
/**
* Callback for {@link android.view.accessibility.AccessibilityEvent}s.
*
@@ -598,6 +603,32 @@
}
/**
+ * Get the controller for fingerprint gestures. This feature requires {@link
+ * AccessibilityServiceInfo#CAPABILITY_CAN_CAPTURE_FINGERPRINT_GESTURES}.
+ *
+ *<strong>Note: </strong> The service must be connected before this method is called.
+ *
+ * @return The controller for fingerprint gestures, or {@code null} if gestures are unavailable.
+ */
+ @RequiresPermission(android.Manifest.permission.USE_FINGERPRINT)
+ public final @Nullable FingerprintGestureController getFingerprintGestureController() {
+ if (mFingerprintGestureController == null) {
+ FingerprintManager fingerprintManager = getSystemService(FingerprintManager.class);
+ if ((fingerprintManager != null) && fingerprintManager.isHardwareDetected()) {
+ AccessibilityServiceInfo info = getServiceInfo();
+ int fingerprintCapabilityMask =
+ AccessibilityServiceInfo.CAPABILITY_CAN_CAPTURE_FINGERPRINT_GESTURES;
+ if ((info.getCapabilities() & fingerprintCapabilityMask) != 0) {
+ mFingerprintGestureController = new FingerprintGestureController(
+ AccessibilityInteractionClient.getInstance()
+ .getConnection(mConnectionId));
+ }
+ }
+ }
+ return mFingerprintGestureController;
+ }
+
+ /**
* Dispatch a gesture to the touch screen. Any gestures currently in progress, whether from
* the user, this service, or another service, will be cancelled.
* <p>
@@ -694,6 +725,22 @@
}
/**
+ * Callback for fingerprint gesture handling
+ * @param active If gesture detection is active
+ */
+ private void onFingerprintCapturingGesturesChanged(boolean active) {
+ getFingerprintGestureController().onGestureDetectionActiveChanged(active);
+ }
+
+ /**
+ * Callback for fingerprint gesture handling
+ * @param gesture The identifier for the gesture performed
+ */
+ private void onFingerprintGesture(int gesture) {
+ getFingerprintGestureController().onGesture(gesture);
+ }
+
+ /**
* Used to control and query the state of display magnification.
*/
public static final class MagnificationController {
@@ -1486,6 +1533,16 @@
public void onPerformGestureResult(int sequence, boolean completedSuccessfully) {
AccessibilityService.this.onPerformGestureResult(sequence, completedSuccessfully);
}
+
+ @Override
+ public void onFingerprintCapturingGesturesChanged(boolean active) {
+ AccessibilityService.this.onFingerprintCapturingGesturesChanged(active);
+ }
+
+ @Override
+ public void onFingerprintGesture(int gesture) {
+ AccessibilityService.this.onFingerprintGesture(gesture);
+ }
});
}
@@ -1506,6 +1563,8 @@
private static final int DO_ON_MAGNIFICATION_CHANGED = 7;
private static final int DO_ON_SOFT_KEYBOARD_SHOW_MODE_CHANGED = 8;
private static final int DO_GESTURE_COMPLETE = 9;
+ private static final int DO_ON_FINGERPRINT_ACTIVE_CHANGED = 10;
+ private static final int DO_ON_FINGERPRINT_GESTURE = 11;
private final HandlerCaller mCaller;
@@ -1577,6 +1636,15 @@
mCaller.sendMessage(message);
}
+ public void onFingerprintCapturingGesturesChanged(boolean active) {
+ mCaller.sendMessage(mCaller.obtainMessageI(
+ DO_ON_FINGERPRINT_ACTIVE_CHANGED, active ? 1 : 0));
+ }
+
+ public void onFingerprintGesture(int gesture) {
+ mCaller.sendMessage(mCaller.obtainMessageI(DO_ON_FINGERPRINT_GESTURE, gesture));
+ }
+
@Override
public void executeMessage(Message message) {
switch (message.what) {
@@ -1675,6 +1743,12 @@
final boolean successfully = message.arg2 == 1;
mCallback.onPerformGestureResult(message.arg1, successfully);
} return;
+ case DO_ON_FINGERPRINT_ACTIVE_CHANGED: {
+ mCallback.onFingerprintCapturingGesturesChanged(message.arg1 == 1);
+ } return;
+ case DO_ON_FINGERPRINT_GESTURE: {
+ mCallback.onFingerprintGesture(message.arg1);
+ } 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 b76aeb7..18e57cb 100644
--- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
+++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
@@ -25,6 +25,7 @@
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
+import android.hardware.fingerprint.FingerprintManager;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
@@ -116,34 +117,13 @@
*/
public static final int CAPABILITY_CAN_PERFORM_GESTURES = 0x00000020;
- private static final SparseArray<CapabilityInfo> sAvailableCapabilityInfos =
- new SparseArray<CapabilityInfo>();
- static {
- sAvailableCapabilityInfos.put(CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT,
- new CapabilityInfo(CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT,
- R.string.capability_title_canRetrieveWindowContent,
- R.string.capability_desc_canRetrieveWindowContent));
- sAvailableCapabilityInfos.put(CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION,
- new CapabilityInfo(CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION,
- R.string.capability_title_canRequestTouchExploration,
- R.string.capability_desc_canRequestTouchExploration));
- sAvailableCapabilityInfos.put(CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY,
- new CapabilityInfo(CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY,
- R.string.capability_title_canRequestEnhancedWebAccessibility,
- R.string.capability_desc_canRequestEnhancedWebAccessibility));
- sAvailableCapabilityInfos.put(CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS,
- new CapabilityInfo(CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS,
- R.string.capability_title_canRequestFilterKeyEvents,
- R.string.capability_desc_canRequestFilterKeyEvents));
- sAvailableCapabilityInfos.put(CAPABILITY_CAN_CONTROL_MAGNIFICATION,
- new CapabilityInfo(CAPABILITY_CAN_CONTROL_MAGNIFICATION,
- R.string.capability_title_canControlMagnification,
- R.string.capability_desc_canControlMagnification));
- sAvailableCapabilityInfos.put(CAPABILITY_CAN_PERFORM_GESTURES,
- new CapabilityInfo(CAPABILITY_CAN_PERFORM_GESTURES,
- R.string.capability_title_canPerformGestures,
- R.string.capability_desc_canPerformGestures));
- }
+ /**
+ * Capability: This accessibility service can capture gestures from the fingerprint sensor
+ * @see android.R.styleable#AccessibilityService_canCaptureFingerprintGestures
+ */
+ public static final int CAPABILITY_CAN_CAPTURE_FINGERPRINT_GESTURES = 0x00000040;
+
+ private static SparseArray<CapabilityInfo> sAvailableCapabilityInfos;
/**
* Denotes spoken feedback.
@@ -326,6 +306,12 @@
*/
public static final int FLAG_ENABLE_ACCESSIBILITY_VOLUME = 0x00000080;
+ /**
+ * This flag requests that all fingerprint gestures be sent to the accessibility service.
+ * It is handled in {@link FingerprintGestureController}
+ */
+ public static final int FLAG_CAPTURE_FINGERPRINT_GESTURES = 0x00000200;
+
/** {@hide} */
public static final int FLAG_FORCE_DIRECT_BOOT_AWARE = 0x00010000;
@@ -535,6 +521,10 @@
.AccessibilityService_canPerformGestures, false)) {
mCapabilities |= CAPABILITY_CAN_PERFORM_GESTURES;
}
+ if (asAttributes.getBoolean(com.android.internal.R.styleable
+ .AccessibilityService_canCaptureFingerprintGestures, false)) {
+ mCapabilities |= CAPABILITY_CAN_CAPTURE_FINGERPRINT_GESTURES;
+ }
TypedValue peekedValue = asAttributes.peekValue(
com.android.internal.R.styleable.AccessibilityService_description);
if (peekedValue != null) {
@@ -946,6 +936,8 @@
return "FLAG_RETRIEVE_INTERACTIVE_WINDOWS";
case FLAG_ENABLE_ACCESSIBILITY_VOLUME:
return "FLAG_ENABLE_ACCESSIBILITY_VOLUME";
+ case FLAG_CAPTURE_FINGERPRINT_GESTURES:
+ return "FLAG_CAPTURE_FINGERPRINT_GESTURES";
default:
return null;
}
@@ -973,6 +965,8 @@
return "CAPABILITY_CAN_CONTROL_MAGNIFICATION";
case CAPABILITY_CAN_PERFORM_GESTURES:
return "CAPABILITY_CAN_PERFORM_GESTURES";
+ case CAPABILITY_CAN_CAPTURE_FINGERPRINT_GESTURES:
+ return "CAPABILITY_CAN_CAPTURE_FINGERPRINT_GESTURES";
default:
return "UNKNOWN";
}
@@ -981,17 +975,29 @@
/**
* @hide
* @return The list of {@link CapabilityInfo} objects.
+ * @deprecated The version that takes a context works better.
*/
public List<CapabilityInfo> getCapabilityInfos() {
+ return getCapabilityInfos(null);
+ }
+
+ /**
+ * @hide
+ * @param context A valid context
+ * @return The list of {@link CapabilityInfo} objects.
+ */
+ public List<CapabilityInfo> getCapabilityInfos(Context context) {
if (mCapabilities == 0) {
return Collections.emptyList();
}
int capabilities = mCapabilities;
List<CapabilityInfo> capabilityInfos = new ArrayList<CapabilityInfo>();
+ SparseArray<CapabilityInfo> capabilityInfoSparseArray =
+ getCapabilityInfoSparseArray(context);
while (capabilities != 0) {
final int capabilityBit = 1 << Integer.numberOfTrailingZeros(capabilities);
capabilities &= ~capabilityBit;
- CapabilityInfo capabilityInfo = sAvailableCapabilityInfos.get(capabilityBit);
+ CapabilityInfo capabilityInfo = capabilityInfoSparseArray.get(capabilityBit);
if (capabilityInfo != null) {
capabilityInfos.add(capabilityInfo);
}
@@ -999,6 +1005,44 @@
return capabilityInfos;
}
+ private static SparseArray<CapabilityInfo> getCapabilityInfoSparseArray(Context context) {
+ if (sAvailableCapabilityInfos == null) {
+ sAvailableCapabilityInfos = new SparseArray<CapabilityInfo>();
+ sAvailableCapabilityInfos.put(CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT,
+ new CapabilityInfo(CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT,
+ R.string.capability_title_canRetrieveWindowContent,
+ R.string.capability_desc_canRetrieveWindowContent));
+ sAvailableCapabilityInfos.put(CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION,
+ new CapabilityInfo(CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION,
+ R.string.capability_title_canRequestTouchExploration,
+ R.string.capability_desc_canRequestTouchExploration));
+ sAvailableCapabilityInfos.put(CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY,
+ new CapabilityInfo(CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY,
+ R.string.capability_title_canRequestEnhancedWebAccessibility,
+ R.string.capability_desc_canRequestEnhancedWebAccessibility));
+ sAvailableCapabilityInfos.put(CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS,
+ new CapabilityInfo(CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS,
+ R.string.capability_title_canRequestFilterKeyEvents,
+ R.string.capability_desc_canRequestFilterKeyEvents));
+ sAvailableCapabilityInfos.put(CAPABILITY_CAN_CONTROL_MAGNIFICATION,
+ new CapabilityInfo(CAPABILITY_CAN_CONTROL_MAGNIFICATION,
+ R.string.capability_title_canControlMagnification,
+ R.string.capability_desc_canControlMagnification));
+ sAvailableCapabilityInfos.put(CAPABILITY_CAN_PERFORM_GESTURES,
+ new CapabilityInfo(CAPABILITY_CAN_PERFORM_GESTURES,
+ R.string.capability_title_canPerformGestures,
+ R.string.capability_desc_canPerformGestures));
+ if ((context == null)
+ || context.getSystemService(FingerprintManager.class).isHardwareDetected()) {
+ sAvailableCapabilityInfos.put(CAPABILITY_CAN_CAPTURE_FINGERPRINT_GESTURES,
+ new CapabilityInfo(CAPABILITY_CAN_CAPTURE_FINGERPRINT_GESTURES,
+ R.string.capability_title_canCaptureFingerprintGestures,
+ R.string.capability_desc_canCaptureFingerprintGestures));
+ }
+ }
+ return sAvailableCapabilityInfos;
+ }
+
/**
* @hide
*/
diff --git a/core/java/android/accessibilityservice/FingerprintGestureController.java b/core/java/android/accessibilityservice/FingerprintGestureController.java
new file mode 100644
index 0000000..e203c6d
--- /dev/null
+++ b/core/java/android/accessibilityservice/FingerprintGestureController.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2017 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.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+import android.util.Log;
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * An {@link AccessibilityService} can capture gestures performed on a device's fingerprint
+ * sensor, as long as the device has a sensor capable of detecting gestures.
+ * <p>
+ * This capability must be declared by the service as
+ * {@link AccessibilityServiceInfo#CAPABILITY_CAN_CAPTURE_FINGERPRINT_GESTURES}. It also requires
+ * the permission {@link android.Manifest.permission#USE_FINGERPRINT}.
+ * <p>
+ * Because capturing fingerprint gestures may have side effects, services with the capability only
+ * capture gestures when {@link AccessibilityServiceInfo#FLAG_CAPTURE_FINGERPRINT_GESTURES} is set.
+ * <p>
+ * <strong>Note: </strong>The fingerprint sensor is used for authentication in critical use cases,
+ * so services must carefully design their user's experience when performing gestures on the sensor.
+ * When the sensor is in use by an app, for example, when authenticating or enrolling a user,
+ * the sensor will not detect gestures. Services need to ensure that users understand when the
+ * sensor is in-use for authentication to prevent users from authenticating unintentionally when
+ * trying to interact with the service. They can use
+ * {@link FingerprintGestureCallback#onGestureDetectionAvailabilityChanged(boolean)} to learn when
+ * gesture detection becomes unavailable.
+ * <p>
+ * Multiple accessibility services may listen for fingerprint gestures simultaneously, so services
+ * should provide a way for the user to disable the use of this feature so multiple services don't
+ * conflict with each other.
+ * <p>
+ * {@see android.hardware.fingerprint.FingerprintManager#isHardwareDetected}
+ */
+public final class FingerprintGestureController {
+ /** Identifier for a swipe right on the fingerprint sensor */
+ public static final int FINGERPRINT_GESTURE_SWIPE_RIGHT = 0x00000001;
+
+ /** Identifier for a swipe left on the fingerprint sensor */
+ public static final int FINGERPRINT_GESTURE_SWIPE_LEFT = 0x00000002;
+
+ /** Identifier for a swipe up on the fingerprint sensor */
+ public static final int FINGERPRINT_GESTURE_SWIPE_UP = 0x00000004;
+
+ /** Identifier for a swipe down on the fingerprint sensor */
+ public static final int FINGERPRINT_GESTURE_SWIPE_DOWN = 0x00000008;
+
+ private static final String LOG_TAG = "FingerprintGestureController";
+ private final Object mLock = new Object();
+ private final IAccessibilityServiceConnection mAccessibilityServiceConnection;
+
+ private final ArrayMap<FingerprintGestureCallback, Handler> mCallbackHandlerMap =
+ new ArrayMap<>(1);
+
+ /**
+ * @param connection The connection to use for system interactions
+ * @hide
+ */
+ @VisibleForTesting
+ public FingerprintGestureController(IAccessibilityServiceConnection connection) {
+ mAccessibilityServiceConnection = connection;
+ }
+
+ /**
+ * Gets if the fingerprint sensor's gesture detection is available.
+ *
+ * @return {@code true} if the sensor's gesture detection is available. {@code false} if it is
+ * not currently detecting gestures (for example, if it is enrolling a finger).
+ */
+ public boolean isGestureDetectionAvailable() {
+ try {
+ return mAccessibilityServiceConnection.isFingerprintGestureDetectionAvailable();
+ } catch (RemoteException re) {
+ Log.w(LOG_TAG, "Failed to check if fingerprint gestures are active", re);
+ re.rethrowFromSystemServer();
+ return false;
+ }
+ }
+
+ /**
+ * Register a callback to be informed of fingerprint sensor gesture events.
+ *
+ * @param callback The listener to be added.
+ * @param handler The handler to use for the callback. If {@code null}, callbacks will happen
+ * on the service's main thread.
+ */
+ public void registerFingerprintGestureCallback(
+ @NonNull FingerprintGestureCallback callback, @Nullable Handler handler) {
+ synchronized (mLock) {
+ mCallbackHandlerMap.put(callback, handler);
+ }
+ }
+
+ /**
+ * Unregister a listener added with {@link #registerFingerprintGestureCallback}.
+ *
+ * @param callback The callback to remove. Removing a callback that was never added has no
+ * effect.
+ */
+ public void unregisterFingerprintGestureCallback(FingerprintGestureCallback callback) {
+ synchronized (mLock) {
+ mCallbackHandlerMap.remove(callback);
+ }
+ }
+
+ /**
+ * Called when gesture detection becomes active or inactive
+ * @hide
+ */
+ public void onGestureDetectionActiveChanged(boolean active) {
+ final ArrayMap<FingerprintGestureCallback, Handler> handlerMap;
+ synchronized (mLock) {
+ handlerMap = new ArrayMap<>(mCallbackHandlerMap);
+ }
+ int numListeners = handlerMap.size();
+ for (int i = 0; i < numListeners; i++) {
+ FingerprintGestureCallback callback = handlerMap.keyAt(i);
+ Handler handler = handlerMap.valueAt(i);
+ if (handler != null) {
+ handler.post(() -> callback.onGestureDetectionAvailabilityChanged(active));
+ } else {
+ callback.onGestureDetectionAvailabilityChanged(active);
+ }
+ }
+ }
+
+ /**
+ * Called when gesture is detected.
+ * @hide
+ */
+ public void onGesture(int gesture) {
+ final ArrayMap<FingerprintGestureCallback, Handler> handlerMap;
+ synchronized (mLock) {
+ handlerMap = new ArrayMap<>(mCallbackHandlerMap);
+ }
+ int numListeners = handlerMap.size();
+ for (int i = 0; i < numListeners; i++) {
+ FingerprintGestureCallback callback = handlerMap.keyAt(i);
+ Handler handler = handlerMap.valueAt(i);
+ if (handler != null) {
+ handler.post(() -> callback.onGesture(gesture));
+ } else {
+ callback.onGesture(gesture);
+ }
+ }
+ }
+
+ /**
+ * Class that is called back when fingerprint gestures are being used for accessibility.
+ */
+ public abstract static class FingerprintGestureCallback {
+ /**
+ * Called when the fingerprint sensor's gesture detection becomes available or unavailable.
+ *
+ * @param available Whether or not the sensor's gesture detection is now available.
+ */
+ public void onGestureDetectionAvailabilityChanged(boolean available) {}
+
+ /**
+ * Called when the fingerprint sensor detects gestures.
+ *
+ * @param gesture The id of the gesture that was detected. For example,
+ * {@link #FINGERPRINT_GESTURE_SWIPE_RIGHT}.
+ */
+ public void onGesture(int gesture) {}
+ }
+}
diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl
index da16a65..3f778ad 100644
--- a/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl
+++ b/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl
@@ -46,4 +46,8 @@
void onSoftKeyboardShowModeChanged(int showMode);
void onPerformGestureResult(int sequence, boolean completedSuccessfully);
+
+ void onFingerprintCapturingGesturesChanged(boolean capturing);
+
+ void onFingerprintGesture(int gesture);
}
diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
index 81cddba..5499bd5 100644
--- a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
+++ b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
@@ -89,4 +89,6 @@
void setSoftKeyboardCallbackEnabled(boolean enabled);
void sendGesture(int sequence, in ParceledListSlice gestureSteps);
+
+ boolean isFingerprintGestureDetectionAvailable();
}
diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java
index 54cc4a0..6d1d1a3 100644
--- a/core/java/android/app/UiAutomation.java
+++ b/core/java/android/app/UiAutomation.java
@@ -1108,6 +1108,16 @@
public void onPerformGestureResult(int sequence, boolean completedSuccessfully) {
/* do nothing */
}
+
+ @Override
+ public void onFingerprintCapturingGesturesChanged(boolean active) {
+ /* do nothing */
+ }
+
+ @Override
+ public void onFingerprintGesture(int gesture) {
+ /* do nothing */
+ }
});
}
}
diff --git a/core/java/android/hardware/fingerprint/IFingerprintClientActiveCallback.aidl b/core/java/android/hardware/fingerprint/IFingerprintClientActiveCallback.aidl
new file mode 100644
index 0000000..5bcf476
--- /dev/null
+++ b/core/java/android/hardware/fingerprint/IFingerprintClientActiveCallback.aidl
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2017 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.hardware.fingerprint;
+
+/**
+ * Callback when clients become active or inactive.
+ * @hide
+ */
+oneway interface IFingerprintClientActiveCallback {
+ void onClientActiveChanged(boolean isActive);
+}
\ No newline at end of file
diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
index ae3fc37..4879d54 100644
--- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl
+++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
@@ -16,6 +16,7 @@
package android.hardware.fingerprint;
import android.os.Bundle;
+import android.hardware.fingerprint.IFingerprintClientActiveCallback;
import android.hardware.fingerprint.IFingerprintServiceReceiver;
import android.hardware.fingerprint.IFingerprintServiceLockoutResetCallback;
import android.hardware.fingerprint.Fingerprint;
@@ -82,4 +83,13 @@
// Enumerate all fingerprints
void enumerate(IBinder token, int userId, IFingerprintServiceReceiver receiver);
+
+ // Check if a client request is currently being handled
+ boolean isClientActive();
+
+ // Add a callback which gets notified when the service starts and stops handling client requests
+ void addClientActiveCallback(IFingerprintClientActiveCallback callback);
+
+ // Removes a callback set by addClientActiveCallback
+ void removeClientActiveCallback(IFingerprintClientActiveCallback callback);
}
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index bfb8d83..1ef0d17 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -633,6 +633,28 @@
}
/**
+ * Report a fingerprint gesture to accessibility. Only available for the system process.
+ *
+ * @param keyCode The key code of the gesture
+ * @return {@code true} if accessibility consumes the event. {@code false} if not.
+ * @hide
+ */
+ public boolean sendFingerprintGesture(int keyCode) {
+ final IAccessibilityManager service;
+ synchronized (mLock) {
+ service = getServiceLocked();
+ if (service == null) {
+ return false;
+ }
+ }
+ try {
+ return service.sendFingerprintGesture(keyCode);
+ } catch (RemoteException e) {
+ return false;
+ }
+ }
+
+ /**
* Sets the current state and notifies listeners, if necessary.
*
* @param stateFlags The state flags.
diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl
index ed77f68..136bbbe 100644
--- a/core/java/android/view/accessibility/IAccessibilityManager.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl
@@ -1,5 +1,4 @@
-/* //device/java/android/android/app/INotificationManager.aidl
-**
+/*
** Copyright 2009, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License");
@@ -62,4 +61,7 @@
// Requires WRITE_SECURE_SETTINGS
void performAccessibilityShortcut();
+
+ // System process only
+ boolean sendFingerprintGesture(int gestureKeyCode);
}
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 34f78f3..c548219 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -3392,6 +3392,8 @@
<flag name="flagRetrieveInteractiveWindows" value="0x00000040" />
<!-- Has flag {@link android.accessibilityservice.AccessibilityServiceInfo#FLAG_ENABLE_ACCESSIBILITY_VOLUME} -->
<flag name="flagEnableAccessibilityVolume" value="0x00000080" />
+ <!-- Has flag {@link android.accessibilityservice.AccessibilityServiceInfo#FLAG_CAPTURE_FINGERPRINT_GESTURES} -->
+ <flag name="flagCaptureFingerprintGestures" value="0x00000200" />
</attr>
<!-- Component name of an activity that allows the user to modify
the settings for this service. This setting cannot be changed at runtime. -->
@@ -3440,6 +3442,14 @@
</p>
-->
<attr name="canPerformGestures" format="boolean" />
+ <!-- Attribute whether the accessibility service wants to be able to capture gestures from
+ the fingerprint sensor.
+ <p>
+ Required to allow setting the {@link android.accessibilityservice
+ #AccessibilityServiceInfo#FLAG_CAN_CAPTURE_FINGERPRINT_GESTURES} flag.
+ </p>
+ -->
+ <attr name="canCaptureFingerprintGestures" format="boolean" />
<!-- Short description of the accessibility service 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 737dab0..d795d80 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2791,6 +2791,7 @@
<public name="colorMode" />
<public name="isolatedSplits" />
<public name="targetSandboxVersion" />
+ <public name="canCaptureFingerprintGestures" />
</public-group>
<public-group type="style" first-id="0x010302e0">
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index ac8c896..81582d2 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -681,6 +681,12 @@
<string name="capability_desc_canPerformGestures">Can tap, swipe, pinch, and perform other
gestures.</string>
+ <!-- Title for the capability of an accessibility service to capture fingerprint gestures. -->
+ <string name="capability_title_canCaptureFingerprintGestures">Fingerprint gestures</string>
+ <!-- Description for the capability of an accessibility service to perform gestures. -->
+ <string name="capability_desc_canCaptureFingerprintGestures">Can capture gestures performed on
+ the device's fingerprint sensor.</string>
+
<!-- Permissions -->
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 25ebb87..e550236 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2833,4 +2833,8 @@
<java-symbol type="id" name="autofill_save_title" />
<java-symbol type="id" name="autofill_save_no" />
<java-symbol type="id" name="autofill_save_yes" />
+
+ <!-- Accessibility fingerprint gestures -->
+ <java-symbol type="string" name="capability_title_canCaptureFingerprintGestures" />
+ <java-symbol type="string" name="capability_desc_canCaptureFingerprintGestures" />
</resources>
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index ece5149..b68ac3b 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -63,11 +63,13 @@
import android.os.Process;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
+import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
import android.os.UserManagerInternal;
import android.provider.Settings;
+import android.hardware.fingerprint.IFingerprintService;
import android.text.TextUtils;
import android.text.TextUtils.SimpleStringSplitter;
import android.util.Slog;
@@ -203,6 +205,8 @@
private MotionEventInjector mMotionEventInjector;
+ private FingerprintGestureDispatcher mFingerprintGestureDispatcher;
+
private final Set<ComponentName> mTempComponentNameSet = new HashSet<>();
private final List<AccessibilityServiceInfo> mTempAccessibilityServiceInfoList =
@@ -1374,6 +1378,10 @@
mMainHandler.obtainMessage(MainHandler.MSG_UPDATE_INPUT_FILTER, userState).sendToTarget();
}
+ private void scheduleUpdateFingerprintGestureHandling(UserState userState) {
+ mMainHandler.obtainMessage(MainHandler.MSG_UPDATE_FINGERPRINT, userState).sendToTarget();
+ }
+
private void updateInputFilter(UserState userState) {
boolean setInputFilter = false;
AccessibilityInputFilter inputFilter = null;
@@ -1501,6 +1509,7 @@
updateDisplayInversionLocked(userState);
updateMagnificationLocked(userState);
updateSoftKeyboardShowModeLocked(userState);
+ scheduleUpdateFingerprintGestureHandling(userState);
scheduleUpdateInputFilter(userState);
scheduleUpdateClientsIfNeededLocked(userState);
}
@@ -1919,6 +1928,35 @@
}
}
+ private void updateFingerprintGestureHandling(UserState userState) {
+ final List<Service> services;
+ synchronized (mLock) {
+ // Only create the controller when a service wants to use the feature
+ services = userState.mBoundServices;
+ int numServices = services.size();
+ for (int i = 0; i < numServices; i++) {
+ if (services.get(i).isCapturingFingerprintGestures()) {
+ final long identity = Binder.clearCallingIdentity();
+ IFingerprintService service = null;
+ try {
+ service = IFingerprintService.Stub.asInterface(
+ ServiceManager.getService(Context.FINGERPRINT_SERVICE));
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ if (service != null) {
+ mFingerprintGestureDispatcher = new FingerprintGestureDispatcher(
+ service, mLock);
+ break;
+ }
+ }
+ }
+ }
+ if (mFingerprintGestureDispatcher != null) {
+ mFingerprintGestureDispatcher.updateClientList(services);
+ }
+ }
+
private MagnificationSpec getCompatibleMagnificationSpecLocked(int windowId) {
IBinder windowToken = mGlobalWindowTokens.get(windowId);
if (windowToken == null) {
@@ -2001,6 +2039,27 @@
}
}
+ /**
+ * AIDL-exposed method. System only.
+ * Inform accessibility that a fingerprint gesture was performed
+ *
+ * @param gestureKeyCode The key code corresponding to the fingerprint gesture.
+ * @return {@code true} if accessibility consumes the fingerprint gesture, {@code false} if it
+ * doesn't.
+ */
+ @Override
+ public boolean sendFingerprintGesture(int gestureKeyCode) {
+ synchronized(mLock) {
+ if (UserHandle.getAppId(Binder.getCallingUid()) != Process.SYSTEM_UID) {
+ throw new SecurityException("Only SYSTEM can call sendFingerprintGesture");
+ }
+ }
+ if (mFingerprintGestureDispatcher == null) {
+ return false;
+ }
+ return mFingerprintGestureDispatcher.onFingerprintGesture(gestureKeyCode);
+ }
+
private class SettingsStringHelper {
private static final String SETTINGS_DELIMITER = ":";
private ContentResolver mContentResolver;
@@ -2131,6 +2190,7 @@
public static final int MSG_SEND_KEY_EVENT_TO_INPUT_FILTER = 8;
public static final int MSG_CLEAR_ACCESSIBILITY_FOCUS = 9;
public static final int MSG_SEND_SERVICES_STATE_CHANGED_TO_CLIENTS = 10;
+ public static final int MSG_UPDATE_FINGERPRINT = 11;
public MainHandler(Looper looper) {
super(looper);
@@ -2199,6 +2259,10 @@
case MSG_SEND_SERVICES_STATE_CHANGED_TO_CLIENTS: {
notifyClientsOfServicesStateChange();
} break;
+
+ case MSG_UPDATE_FINGERPRINT: {
+ updateFingerprintGestureHandling((UserState) msg.obj);
+ } break;
}
}
@@ -2329,7 +2393,8 @@
* connection for the service.
*/
class Service extends IAccessibilityServiceConnection.Stub
- implements ServiceConnection, DeathRecipient, KeyEventDispatcher.KeyEventFilter {;
+ implements ServiceConnection, DeathRecipient, KeyEventDispatcher.KeyEventFilter,
+ FingerprintGestureDispatcher.FingerprintGestureClient {
final int mUserId;
@@ -2359,6 +2424,8 @@
boolean mRetrieveInteractiveWindows;
+ boolean mCaptureFingerprintGestures;
+
int mFetchFlags;
long mNotificationTimeout;
@@ -2438,6 +2505,47 @@
return true;
}
+ @Override
+ public boolean isCapturingFingerprintGestures() {
+ return (mServiceInterface != null)
+ && mSecurityPolicy.canCaptureFingerprintGestures(this)
+ && mCaptureFingerprintGestures;
+ }
+
+ @Override
+ public void onFingerprintGestureDetectionActiveChanged(boolean active) {
+ if (!isCapturingFingerprintGestures()) {
+ return;
+ }
+ IAccessibilityServiceClient serviceInterface;
+ synchronized (mLock) {
+ serviceInterface = mServiceInterface;
+ }
+ if (serviceInterface != null) {
+ try {
+ mServiceInterface.onFingerprintCapturingGesturesChanged(active);
+ } catch (RemoteException e) {
+ }
+ }
+ }
+
+ @Override
+ public void onFingerprintGesture(int gesture) {
+ if (!isCapturingFingerprintGestures()) {
+ return;
+ }
+ IAccessibilityServiceClient serviceInterface;
+ synchronized (mLock) {
+ serviceInterface = mServiceInterface;
+ }
+ if (serviceInterface != null) {
+ try {
+ mServiceInterface.onFingerprintGesture(gesture);
+ } catch (RemoteException e) {
+ }
+ }
+ }
+
public void setDynamicallyConfigurableProperties(AccessibilityServiceInfo info) {
mEventTypes = info.eventTypes;
mFeedbackType = info.feedbackType;
@@ -2471,6 +2579,8 @@
& AccessibilityServiceInfo.FLAG_REQUEST_FILTER_KEY_EVENTS) != 0;
mRetrieveInteractiveWindows = (info.flags
& AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS) != 0;
+ mCaptureFingerprintGestures = (info.flags
+ & AccessibilityServiceInfo.FLAG_CAPTURE_FINGERPRINT_GESTURES) != 0;
}
/**
@@ -3060,6 +3170,13 @@
}
@Override
+ public boolean isFingerprintGestureDetectionAvailable() {
+ return isCapturingFingerprintGestures()
+ && (mFingerprintGestureDispatcher != null)
+ && mFingerprintGestureDispatcher.isFingerprintGestureDetectionAvailable();
+ }
+
+ @Override
public float getMagnificationScale() {
synchronized (mLock) {
if (!isCalledForCurrentUserLocked()) {
@@ -4234,6 +4351,11 @@
& AccessibilityServiceInfo.CAPABILITY_CAN_PERFORM_GESTURES) != 0;
}
+ public boolean canCaptureFingerprintGestures(Service service) {
+ return (service.mAccessibilityServiceInfo.getCapabilities()
+ & AccessibilityServiceInfo.CAPABILITY_CAN_CAPTURE_FINGERPRINT_GESTURES) != 0;
+ }
+
private int resolveProfileParentLocked(int userId) {
if (userId != mCurrentUserId) {
final long identity = Binder.clearCallingIdentity();
diff --git a/services/accessibility/java/com/android/server/accessibility/FingerprintGestureDispatcher.java b/services/accessibility/java/com/android/server/accessibility/FingerprintGestureDispatcher.java
new file mode 100644
index 0000000..fe787b3
--- /dev/null
+++ b/services/accessibility/java/com/android/server/accessibility/FingerprintGestureDispatcher.java
@@ -0,0 +1,212 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.accessibility;
+
+import android.accessibilityservice.FingerprintGestureController;
+import android.hardware.fingerprint.IFingerprintClientActiveCallback;
+import android.hardware.fingerprint.IFingerprintService;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.Message;
+import android.os.RemoteException;
+import android.util.Slog;
+import android.view.KeyEvent;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Encapsulate fingerprint gesture logic
+ */
+public class FingerprintGestureDispatcher extends IFingerprintClientActiveCallback.Stub
+ implements Handler.Callback{
+ private static final int MSG_REGISTER = 1;
+ private static final int MSG_UNREGISTER = 2;
+ private static final String LOG_TAG = "FingerprintGestureDispatcher";
+
+ private final List<FingerprintGestureClient> mCapturingClients = new ArrayList<>(0);
+ private final Object mLock;
+ private final IFingerprintService mFingerprintService;
+ private final Handler mHandler;
+
+ // This field is ground truth for whether or not we are registered. Only write to it in handler.
+ private boolean mRegisteredReadOnlyExceptInHandler;
+
+ /**
+ * @param fingerprintService The system's fingerprint service
+ * @param lock A lock to use when managing internal state
+ */
+ public FingerprintGestureDispatcher(IFingerprintService fingerprintService, Object lock) {
+ mFingerprintService = fingerprintService;
+ mLock = lock;
+ mHandler = new Handler(this);
+ }
+
+ /**
+ * @param fingerprintService The system's fingerprint service
+ * @param lock A lock to use when managing internal state
+ * @param handler A handler to use internally. Used for testing.
+ */
+ public FingerprintGestureDispatcher(IFingerprintService fingerprintService, Object lock,
+ Handler handler) {
+ mFingerprintService = fingerprintService;
+ mLock = lock;
+ mHandler = handler;
+ }
+
+ /**
+ * Update the list of clients that are interested in fingerprint gestures.
+ *
+ * @param clientList The list of potential clients.
+ */
+ public void updateClientList(List<? extends FingerprintGestureClient> clientList) {
+ synchronized (mLock) {
+ mCapturingClients.clear();
+ for (int i = 0; i < clientList.size(); i++) {
+ FingerprintGestureClient client = clientList.get(i);
+ if (client.isCapturingFingerprintGestures()) {
+ mCapturingClients.add(client);
+ }
+ }
+ if (mCapturingClients.isEmpty()) {
+ if (mRegisteredReadOnlyExceptInHandler) {
+ mHandler.obtainMessage(MSG_UNREGISTER).sendToTarget();
+ }
+ } else {
+ if(!mRegisteredReadOnlyExceptInHandler) {
+ mHandler.obtainMessage(MSG_REGISTER).sendToTarget();
+ }
+ }
+ }
+ }
+
+ @Override
+ public void onClientActiveChanged(boolean nonGestureFingerprintClientActive) {
+ synchronized (mLock) {
+ for (int i = 0; i < mCapturingClients.size(); i++) {
+ mCapturingClients.get(i).onFingerprintGestureDetectionActiveChanged(
+ !nonGestureFingerprintClientActive);
+ }
+ }
+ }
+
+ public boolean isFingerprintGestureDetectionAvailable() {
+ long identity = Binder.clearCallingIdentity();
+ try {
+ return !mFingerprintService.isClientActive();
+ } catch (RemoteException re) {
+ return false;
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ /**
+ * Called when the fingerprint sensor detects a gesture
+ *
+ * @param fingerprintKeyCode
+ * @return {@code true} if the gesture is consumed. {@code false} otherwise.
+ */
+ public boolean onFingerprintGesture(int fingerprintKeyCode) {
+ int idForFingerprintGestureManager;
+
+ final List<FingerprintGestureClient> clientList;
+ synchronized (mLock) {
+ if (mCapturingClients.isEmpty()) {
+ return false;
+ }
+ switch (fingerprintKeyCode) {
+ case KeyEvent.KEYCODE_SYSTEM_NAVIGATION_UP:
+ idForFingerprintGestureManager =
+ FingerprintGestureController.FINGERPRINT_GESTURE_SWIPE_UP;
+ break;
+ case KeyEvent.KEYCODE_SYSTEM_NAVIGATION_DOWN:
+ idForFingerprintGestureManager =
+ FingerprintGestureController.FINGERPRINT_GESTURE_SWIPE_DOWN;
+ break;
+ case KeyEvent.KEYCODE_SYSTEM_NAVIGATION_RIGHT:
+ idForFingerprintGestureManager =
+ FingerprintGestureController.FINGERPRINT_GESTURE_SWIPE_RIGHT;
+ break;
+ case KeyEvent.KEYCODE_SYSTEM_NAVIGATION_LEFT:
+ idForFingerprintGestureManager =
+ FingerprintGestureController.FINGERPRINT_GESTURE_SWIPE_LEFT;
+ break;
+ default:
+ return false;
+ }
+ clientList = new ArrayList<>(mCapturingClients);
+ }
+ for (int i = 0; i < clientList.size(); i++) {
+ clientList.get(i).onFingerprintGesture(idForFingerprintGestureManager);
+ }
+ return true;
+ }
+
+ @Override
+ public boolean handleMessage(Message message) {
+ if (message.what == MSG_REGISTER) {
+ long identity = Binder.clearCallingIdentity();
+ try {
+ mFingerprintService.addClientActiveCallback(this);
+ mRegisteredReadOnlyExceptInHandler = true;
+ } catch (RemoteException re) {
+ Slog.e(LOG_TAG, "Failed to register for fingerprint activity callbacks");
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ return false;
+ } else if (message.what == MSG_UNREGISTER) {
+ long identity = Binder.clearCallingIdentity();
+ try {
+ mFingerprintService.removeClientActiveCallback(this);
+ } catch (RemoteException re) {
+ Slog.e(LOG_TAG, "Failed to unregister for fingerprint activity callbacks");
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ mRegisteredReadOnlyExceptInHandler = false;
+ } else {
+ Slog.e(LOG_TAG, "Unknown message: " + message.what);
+ return false;
+ }
+ return true;
+ }
+
+ // Interface for potential clients.
+ public interface FingerprintGestureClient {
+ /**
+ * @return {@code true} if the client is capturing fingerprint gestures
+ */
+ boolean isCapturingFingerprintGestures();
+
+ /**
+ * Callback when gesture detection becomes active or inactive.
+ *
+ * @param active {@code true} when detection is active
+ */
+ void onFingerprintGestureDetectionActiveChanged(boolean active);
+
+ /**
+ * Callback when gesture is detected
+ *
+ * @param gesture The identifier for the gesture. For example,
+ * {@link FingerprintGestureController#FINGERPRINT_GESTURE_SWIPE_LEFT}
+ */
+ void onFingerprintGesture(int gesture);
+ }
+}
diff --git a/services/core/java/com/android/server/fingerprint/FingerprintService.java b/services/core/java/com/android/server/fingerprint/FingerprintService.java
index e2e0d6b..d1f7cfd 100644
--- a/services/core/java/com/android/server/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/fingerprint/FingerprintService.java
@@ -30,6 +30,7 @@
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
+import android.hardware.fingerprint.IFingerprintClientActiveCallback;
import android.hardware.fingerprint.IFingerprintServiceLockoutResetCallback;
import android.os.Binder;
import android.os.Bundle;
@@ -82,6 +83,7 @@
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
/**
* A service to manage multiple clients that want to access the fingerprint HAL API.
@@ -109,6 +111,8 @@
private final ArrayList<FingerprintServiceLockoutResetMonitor> mLockoutMonitors =
new ArrayList<>();
+ private final CopyOnWriteArrayList<IFingerprintClientActiveCallback> mClientActiveCallbacks =
+ new CopyOnWriteArrayList<>();
private final AppOpsManager mAppOps;
private static final long FAIL_LOCKOUT_TIMEOUT_MS = 30*1000;
private static final int MAX_FAILED_ATTEMPTS = 5;
@@ -338,6 +342,9 @@
if (DEBUG) Slog.v(TAG, "Done with client: " + client.getOwnerString());
mCurrentClient = null;
}
+ if (mPendingClient == null) {
+ notifyClientActiveCallbacks(false);
+ }
}
private boolean inLockoutMode() {
@@ -407,6 +414,8 @@
+ newClient.getClass().getSuperclass().getSimpleName()
+ "(" + newClient.getOwnerString() + ")"
+ ", initiatedByClient = " + initiatedByClient + ")");
+ notifyClientActiveCallbacks(true);
+
newClient.start();
}
}
@@ -578,6 +587,18 @@
}
}
+ private void notifyClientActiveCallbacks(boolean isActive) {
+ List<IFingerprintClientActiveCallback> callbacks = mClientActiveCallbacks;
+ for (int i = 0; i < callbacks.size(); i++) {
+ try {
+ callbacks.get(i).onClientActiveChanged(isActive);
+ } catch (RemoteException re) {
+ // If the remote is dead, stop notifying it
+ mClientActiveCallbacks.remove(callbacks.get(i));
+ }
+ }
+ }
+
private void startAuthentication(IBinder token, long opId, int callingUserId, int groupId,
IFingerprintServiceReceiver receiver, int flags, boolean restricted,
String opPackageName) {
@@ -1047,6 +1068,26 @@
}
});
}
+
+ @Override
+ public boolean isClientActive() {
+ checkPermission(MANAGE_FINGERPRINT);
+ synchronized(FingerprintService.this) {
+ return (mCurrentClient != null) || (mPendingClient != null);
+ }
+ }
+
+ @Override
+ public void addClientActiveCallback(IFingerprintClientActiveCallback callback) {
+ checkPermission(MANAGE_FINGERPRINT);
+ mClientActiveCallbacks.add(callback);
+ }
+
+ @Override
+ public void removeClientActiveCallback(IFingerprintClientActiveCallback callback) {
+ checkPermission(MANAGE_FINGERPRINT);
+ mClientActiveCallbacks.remove(callback);
+ }
}
private void dumpInternal(PrintWriter pw) {
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 4350ed9..b69ae98 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -6106,13 +6106,18 @@
* @param event
*/
private void interceptSystemNavigationKey(KeyEvent event) {
- if (event.getAction() == KeyEvent.ACTION_UP && areSystemNavigationKeysEnabled()) {
- IStatusBarService sbar = getStatusBarService();
- if (sbar != null) {
- try {
- sbar.handleSystemNavigationKey(event.getKeyCode());
- } catch (RemoteException e1) {
- // oops, no statusbar. Ignore event.
+ if (event.getAction() == KeyEvent.ACTION_UP) {
+ if (!mAccessibilityManager.isEnabled()
+ || !mAccessibilityManager.sendFingerprintGesture(event.getKeyCode())) {
+ if (areSystemNavigationKeysEnabled()) {
+ IStatusBarService sbar = getStatusBarService();
+ if (sbar != null) {
+ try {
+ sbar.handleSystemNavigationKey(event.getKeyCode());
+ } catch (RemoteException e1) {
+ // oops, no statusbar. Ignore event.
+ }
+ }
}
}
}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/FingerprintGestureControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/FingerprintGestureControllerTest.java
new file mode 100644
index 0000000..cf477f2
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/accessibility/FingerprintGestureControllerTest.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.accessibility;
+
+import android.accessibilityservice.FingerprintGestureController;
+import android.accessibilityservice.FingerprintGestureController.FingerprintGestureCallback;
+import android.accessibilityservice.IAccessibilityServiceConnection;
+import android.os.Looper;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import static android.accessibilityservice.FingerprintGestureController.FINGERPRINT_GESTURE_SWIPE_DOWN;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+/**
+ * Tests for FingerprintGestureController.
+ * TODO: These tests aren't really for server code, so this isn't their ideal home.
+ */
+public class FingerprintGestureControllerTest {
+ @Mock IAccessibilityServiceConnection mMockAccessibilityServiceConnection;
+ @Mock FingerprintGestureCallback mMockFingerprintGestureCallback;
+ FingerprintGestureController mFingerprintGestureController;
+
+ @BeforeClass
+ public static void oneTimeInitialization() {
+ if (Looper.myLooper() == null) {
+ Looper.prepare();
+ }
+ }
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mFingerprintGestureController =
+ new FingerprintGestureController(mMockAccessibilityServiceConnection);
+ }
+
+ @Test
+ public void testIsGestureDetectionActive_returnsValueFromServer() throws Exception {
+ when(mMockAccessibilityServiceConnection.isFingerprintGestureDetectionAvailable())
+ .thenReturn(true);
+ assertTrue(mFingerprintGestureController.isGestureDetectionAvailable());
+ when(mMockAccessibilityServiceConnection.isFingerprintGestureDetectionAvailable())
+ .thenReturn(false);
+ assertFalse(mFingerprintGestureController.isGestureDetectionAvailable());
+ }
+
+ @Test
+ public void testCallbacks_withNoListeners_shouldNotCrash() {
+ mFingerprintGestureController.onGestureDetectionActiveChanged(true);
+ mFingerprintGestureController.onGesture(FINGERPRINT_GESTURE_SWIPE_DOWN);
+ }
+
+ @Test
+ public void testDetectionActiveCallback_noHandler_shouldCallback() {
+ mFingerprintGestureController.registerFingerprintGestureCallback(
+ mMockFingerprintGestureCallback, null);
+ mFingerprintGestureController.onGestureDetectionActiveChanged(true);
+ verify(mMockFingerprintGestureCallback, times(1))
+ .onGestureDetectionAvailabilityChanged(true);
+ mFingerprintGestureController.onGestureDetectionActiveChanged(false);
+ verify(mMockFingerprintGestureCallback, times(1))
+ .onGestureDetectionAvailabilityChanged(false);
+
+ reset(mMockFingerprintGestureCallback);
+ mFingerprintGestureController.unregisterFingerprintGestureCallback(
+ mMockFingerprintGestureCallback);
+ mFingerprintGestureController.onGestureDetectionActiveChanged(true);
+ mFingerprintGestureController.onGestureDetectionActiveChanged(false);
+ verifyZeroInteractions(mMockFingerprintGestureCallback);
+ }
+
+ @Test
+ public void testDetectionActiveCallback_withHandler_shouldPostRunnableToHandler() {
+ MessageCapturingHandler messageCapturingHandler = new MessageCapturingHandler((message) -> {
+ message.getCallback().run();
+ return true;
+ });
+
+ mFingerprintGestureController.registerFingerprintGestureCallback(
+ mMockFingerprintGestureCallback, messageCapturingHandler);
+ mFingerprintGestureController.onGestureDetectionActiveChanged(true);
+ verify(mMockFingerprintGestureCallback, times(0))
+ .onGestureDetectionAvailabilityChanged(true);
+ messageCapturingHandler.sendLastMessage();
+ verify(mMockFingerprintGestureCallback, times(1))
+ .onGestureDetectionAvailabilityChanged(true);
+
+ mFingerprintGestureController.onGestureDetectionActiveChanged(false);
+ verify(mMockFingerprintGestureCallback, times(0))
+ .onGestureDetectionAvailabilityChanged(false);
+ messageCapturingHandler.sendLastMessage();
+ verify(mMockFingerprintGestureCallback, times(1))
+ .onGestureDetectionAvailabilityChanged(false);
+
+ reset(mMockFingerprintGestureCallback);
+ mFingerprintGestureController.unregisterFingerprintGestureCallback(
+ mMockFingerprintGestureCallback);
+ mFingerprintGestureController.onGestureDetectionActiveChanged(true);
+ mFingerprintGestureController.onGestureDetectionActiveChanged(false);
+ assertFalse(messageCapturingHandler.hasMessages());
+ verifyZeroInteractions(mMockFingerprintGestureCallback);
+ }
+
+ @Test
+ public void testGestureCallback_noHandler_shouldCallListener() {
+ mFingerprintGestureController.registerFingerprintGestureCallback(
+ mMockFingerprintGestureCallback, null);
+ mFingerprintGestureController.onGesture(FINGERPRINT_GESTURE_SWIPE_DOWN);
+ verify(mMockFingerprintGestureCallback, times(1)).onGesture(FINGERPRINT_GESTURE_SWIPE_DOWN);
+
+ reset(mMockFingerprintGestureCallback);
+ mFingerprintGestureController.unregisterFingerprintGestureCallback(
+ mMockFingerprintGestureCallback);
+ mFingerprintGestureController.onGesture(FINGERPRINT_GESTURE_SWIPE_DOWN);
+ verifyZeroInteractions(mMockFingerprintGestureCallback);
+ }
+
+ @Test
+ public void testGestureCallback_withHandler_shouldPostRunnableToHandler() {
+ MessageCapturingHandler messageCapturingHandler = new MessageCapturingHandler((message) -> {
+ message.getCallback().run();
+ return true;
+ });
+
+ mFingerprintGestureController.registerFingerprintGestureCallback(
+ mMockFingerprintGestureCallback, messageCapturingHandler);
+ mFingerprintGestureController.onGesture(FINGERPRINT_GESTURE_SWIPE_DOWN);
+ verify(mMockFingerprintGestureCallback, times(0)).onGesture(FINGERPRINT_GESTURE_SWIPE_DOWN);
+ messageCapturingHandler.sendLastMessage();
+ verify(mMockFingerprintGestureCallback, times(1)).onGesture(FINGERPRINT_GESTURE_SWIPE_DOWN);
+
+ reset(mMockFingerprintGestureCallback);
+ mFingerprintGestureController.unregisterFingerprintGestureCallback(
+ mMockFingerprintGestureCallback);
+ mFingerprintGestureController.onGesture(FINGERPRINT_GESTURE_SWIPE_DOWN);
+ assertFalse(messageCapturingHandler.hasMessages());
+ verifyZeroInteractions(mMockFingerprintGestureCallback);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/FingerprintGestureDispatcherTest.java b/services/tests/servicestests/src/com/android/server/accessibility/FingerprintGestureDispatcherTest.java
new file mode 100644
index 0000000..98bf53c
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/accessibility/FingerprintGestureDispatcherTest.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.accessibility;
+
+import android.accessibilityservice.FingerprintGestureController;
+import android.hardware.fingerprint.IFingerprintService;
+import android.os.Handler;
+import android.os.Message;
+import android.view.KeyEvent;
+
+import com.android.server.accessibility.FingerprintGestureDispatcher.FingerprintGestureClient;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Arrays;
+import java.util.Collections;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.anyBoolean;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+/**
+ * Tests for FingerprintGestureDispatcher
+ */
+public class FingerprintGestureDispatcherTest {
+
+ private @Mock IFingerprintService mMockFingerprintService;
+ private @Mock FingerprintGestureClient mNonGestureCapturingClient;
+ private @Mock FingerprintGestureClient mGestureCapturingClient;
+ private @Mock FingerprintGestureDispatcher mFingerprintGestureDispatcher;
+ private MessageCapturingHandler mMessageCapturingHandler;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mMessageCapturingHandler = new MessageCapturingHandler(
+ msg -> mFingerprintGestureDispatcher.handleMessage(msg));
+ mFingerprintGestureDispatcher = new FingerprintGestureDispatcher(mMockFingerprintService,
+ new Object(), mMessageCapturingHandler);
+ when(mNonGestureCapturingClient.isCapturingFingerprintGestures()).thenReturn(false);
+ when(mGestureCapturingClient.isCapturingFingerprintGestures()).thenReturn(true);
+ }
+
+ @Test
+ public void testNoServices_doesNotCrashOrConsumeGestures() {
+ mFingerprintGestureDispatcher.onClientActiveChanged(true);
+ mFingerprintGestureDispatcher.onClientActiveChanged(false);
+ assertFalse(mFingerprintGestureDispatcher.onFingerprintGesture(
+ KeyEvent.KEYCODE_SYSTEM_NAVIGATION_UP));
+ }
+
+ @Test
+ public void testOneNonCapturingService_doesNotCrashOrConsumeGestures() {
+ mFingerprintGestureDispatcher.updateClientList(
+ Arrays.asList(mNonGestureCapturingClient));
+ mFingerprintGestureDispatcher.onClientActiveChanged(true);
+ mFingerprintGestureDispatcher.onClientActiveChanged(false);
+ assertFalse(mFingerprintGestureDispatcher.onFingerprintGesture(
+ KeyEvent.KEYCODE_SYSTEM_NAVIGATION_UP));
+ verify(mNonGestureCapturingClient, times(0))
+ .onFingerprintGestureDetectionActiveChanged(anyBoolean());
+ verify(mNonGestureCapturingClient, times(0)).onFingerprintGesture(anyInt());
+ }
+
+ @Test
+ public void testOneCapturingService_notifiesClientOfActivityChanges() {
+ mFingerprintGestureDispatcher.updateClientList(
+ Arrays.asList(mGestureCapturingClient));
+ mFingerprintGestureDispatcher.onClientActiveChanged(true);
+ // Client active means gesture detection isn't.
+ verify(mGestureCapturingClient, times(1)).onFingerprintGestureDetectionActiveChanged(false);
+ verify(mGestureCapturingClient, times(0)).onFingerprintGestureDetectionActiveChanged(true);
+ mFingerprintGestureDispatcher.onClientActiveChanged(false);
+ verify(mGestureCapturingClient, times(1)).onFingerprintGestureDetectionActiveChanged(false);
+ verify(mGestureCapturingClient, times(1)).onFingerprintGestureDetectionActiveChanged(true);
+ }
+
+ @Test
+ public void testOneCapturingService_consumesGesturesAndPassesThemAlong() {
+ mFingerprintGestureDispatcher.updateClientList(
+ Arrays.asList(mGestureCapturingClient));
+ assertTrue(mFingerprintGestureDispatcher.onFingerprintGesture(
+ KeyEvent.KEYCODE_SYSTEM_NAVIGATION_UP));
+ verify(mGestureCapturingClient, times(1)).onFingerprintGesture(
+ FingerprintGestureController.FINGERPRINT_GESTURE_SWIPE_UP);
+ assertTrue(mFingerprintGestureDispatcher.onFingerprintGesture(
+ KeyEvent.KEYCODE_SYSTEM_NAVIGATION_DOWN));
+ verify(mGestureCapturingClient, times(1)).onFingerprintGesture(
+ FingerprintGestureController.FINGERPRINT_GESTURE_SWIPE_DOWN);
+ assertTrue(mFingerprintGestureDispatcher.onFingerprintGesture(
+ KeyEvent.KEYCODE_SYSTEM_NAVIGATION_LEFT));
+ verify(mGestureCapturingClient, times(1)).onFingerprintGesture(
+ FingerprintGestureController.FINGERPRINT_GESTURE_SWIPE_LEFT);
+ assertTrue(mFingerprintGestureDispatcher.onFingerprintGesture(
+ KeyEvent.KEYCODE_SYSTEM_NAVIGATION_RIGHT));
+ verify(mGestureCapturingClient, times(1)).onFingerprintGesture(
+ FingerprintGestureController.FINGERPRINT_GESTURE_SWIPE_RIGHT);
+ }
+
+ @Test
+ public void testInvalidKeyCodes_areNotCaptured() {
+ mFingerprintGestureDispatcher.updateClientList(
+ Arrays.asList(mGestureCapturingClient));
+ assertFalse(mFingerprintGestureDispatcher.onFingerprintGesture(
+ KeyEvent.KEYCODE_SPACE));
+ verify(mGestureCapturingClient, times(0)).onFingerprintGesture(anyInt());
+ }
+
+ @Test
+ public void testWithCapturingService_registersForFingerprintUpdates() throws Exception {
+ verifyNoMoreInteractions(mMockFingerprintService);
+ mFingerprintGestureDispatcher.updateClientList(
+ Arrays.asList(mGestureCapturingClient));
+ mMessageCapturingHandler.sendOneMessage();
+ verify(mMockFingerprintService).addClientActiveCallback(mFingerprintGestureDispatcher);
+ }
+
+ @Test
+ public void testWhenCapturingServiceStops_unregistersForFingerprintUpdates() throws Exception {
+ verifyNoMoreInteractions(mMockFingerprintService);
+ mFingerprintGestureDispatcher.updateClientList(
+ Arrays.asList(mGestureCapturingClient));
+ mMessageCapturingHandler.sendOneMessage();
+ mFingerprintGestureDispatcher.updateClientList(Collections.emptyList());
+ mMessageCapturingHandler.sendOneMessage();
+ verify(mMockFingerprintService).removeClientActiveCallback(mFingerprintGestureDispatcher);
+ }
+
+ @Test
+ public void testIsGestureDetectionActive_dependsOnFingerprintService() throws Exception {
+ when(mMockFingerprintService.isClientActive()).thenReturn(true);
+ assertFalse(mFingerprintGestureDispatcher.isFingerprintGestureDetectionAvailable());
+ when(mMockFingerprintService.isClientActive()).thenReturn(false);
+ assertTrue(mFingerprintGestureDispatcher.isFingerprintGestureDetectionAvailable());
+ }
+}