1/*
2 * Copyright (C) 2017 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.accessibilityservice;
18
19import android.annotation.NonNull;
20import android.annotation.Nullable;
21import android.os.Handler;
22import android.os.RemoteException;
23import android.util.ArrayMap;
24import android.util.Log;
25import com.android.internal.annotations.VisibleForTesting;
26
27/**
28 * An {@link AccessibilityService} can capture gestures performed on a device's fingerprint
29 * sensor, as long as the device has a sensor capable of detecting gestures.
30 * <p>
31 * This capability must be declared by the service as
32 * {@link AccessibilityServiceInfo#CAPABILITY_CAN_CAPTURE_FINGERPRINT_GESTURES}. It also requires
33 * the permission {@link android.Manifest.permission#USE_FINGERPRINT}.
34 * <p>
35 * Because capturing fingerprint gestures may have side effects, services with the capability only
36 * capture gestures when {@link AccessibilityServiceInfo#FLAG_CAPTURE_FINGERPRINT_GESTURES} is set.
37 * <p>
38 * <strong>Note: </strong>The fingerprint sensor is used for authentication in critical use cases,
39 * so services must carefully design their user's experience when performing gestures on the sensor.
40 * When the sensor is in use by an app, for example, when authenticating or enrolling a user,
41 * the sensor will not detect gestures. Services need to ensure that users understand when the
42 * sensor is in-use for authentication to prevent users from authenticating unintentionally when
43 * trying to interact with the service. They can use
44 * {@link FingerprintGestureCallback#onGestureDetectionAvailabilityChanged(boolean)} to learn when
45 * gesture detection becomes unavailable.
46 * <p>
47 * Multiple accessibility services may listen for fingerprint gestures simultaneously, so services
48 * should provide a way for the user to disable the use of this feature so multiple services don't
49 * conflict with each other.
50 * <p>
51 * {@see android.hardware.fingerprint.FingerprintManager#isHardwareDetected}
52 */
53public final class FingerprintGestureController {
54    /** Identifier for a swipe right on the fingerprint sensor */
55    public static final int FINGERPRINT_GESTURE_SWIPE_RIGHT = 0x00000001;
56
57    /** Identifier for a swipe left on the fingerprint sensor */
58    public static final int FINGERPRINT_GESTURE_SWIPE_LEFT = 0x00000002;
59
60    /** Identifier for a swipe up on the fingerprint sensor */
61    public static final int FINGERPRINT_GESTURE_SWIPE_UP = 0x00000004;
62
63    /** Identifier for a swipe down on the fingerprint sensor */
64    public static final int FINGERPRINT_GESTURE_SWIPE_DOWN = 0x00000008;
65
66    private static final String LOG_TAG = "FingerprintGestureController";
67    private final Object mLock = new Object();
68    private final IAccessibilityServiceConnection mAccessibilityServiceConnection;
69
70    private final ArrayMap<FingerprintGestureCallback, Handler> mCallbackHandlerMap =
71            new ArrayMap<>(1);
72
73    /**
74     * @param connection The connection to use for system interactions
75     * @hide
76     */
77    @VisibleForTesting
78    public FingerprintGestureController(IAccessibilityServiceConnection connection) {
79        mAccessibilityServiceConnection = connection;
80    }
81
82    /**
83     * Gets if the fingerprint sensor's gesture detection is available.
84     *
85     * @return {@code true} if the sensor's gesture detection is available. {@code false} if it is
86     * not currently detecting gestures (for example, if it is enrolling a finger).
87     */
88    public boolean isGestureDetectionAvailable() {
89        try {
90            return mAccessibilityServiceConnection.isFingerprintGestureDetectionAvailable();
91        } catch (RemoteException re) {
92            Log.w(LOG_TAG, "Failed to check if fingerprint gestures are active", re);
93            re.rethrowFromSystemServer();
94            return false;
95        }
96    }
97
98    /**
99     * Register a callback to be informed of fingerprint sensor gesture events.
100     *
101     * @param callback The listener to be added.
102     * @param handler The handler to use for the callback. If {@code null}, callbacks will happen
103     * on the service's main thread.
104     */
105    public void registerFingerprintGestureCallback(
106            @NonNull FingerprintGestureCallback callback, @Nullable Handler handler) {
107        synchronized (mLock) {
108            mCallbackHandlerMap.put(callback, handler);
109        }
110    }
111
112    /**
113     * Unregister a listener added with {@link #registerFingerprintGestureCallback}.
114     *
115     * @param callback The callback to remove. Removing a callback that was never added has no
116     * effect.
117     */
118    public void unregisterFingerprintGestureCallback(FingerprintGestureCallback callback) {
119        synchronized (mLock) {
120            mCallbackHandlerMap.remove(callback);
121        }
122    }
123
124    /**
125     * Called when gesture detection becomes active or inactive
126     * @hide
127     */
128    public void onGestureDetectionActiveChanged(boolean active) {
129        final ArrayMap<FingerprintGestureCallback, Handler> handlerMap;
130        synchronized (mLock) {
131            handlerMap = new ArrayMap<>(mCallbackHandlerMap);
132        }
133        int numListeners = handlerMap.size();
134        for (int i = 0; i < numListeners; i++) {
135            FingerprintGestureCallback callback = handlerMap.keyAt(i);
136            Handler handler = handlerMap.valueAt(i);
137            if (handler != null) {
138                handler.post(() -> callback.onGestureDetectionAvailabilityChanged(active));
139            } else {
140                callback.onGestureDetectionAvailabilityChanged(active);
141            }
142        }
143    }
144
145    /**
146     * Called when gesture is detected.
147     * @hide
148     */
149    public void onGesture(int gesture) {
150        final ArrayMap<FingerprintGestureCallback, Handler> handlerMap;
151        synchronized (mLock) {
152            handlerMap = new ArrayMap<>(mCallbackHandlerMap);
153        }
154        int numListeners = handlerMap.size();
155        for (int i = 0; i < numListeners; i++) {
156            FingerprintGestureCallback callback = handlerMap.keyAt(i);
157            Handler handler = handlerMap.valueAt(i);
158            if (handler != null) {
159                handler.post(() -> callback.onGestureDetected(gesture));
160            } else {
161                callback.onGestureDetected(gesture);
162            }
163        }
164    }
165
166    /**
167     * Class that is called back when fingerprint gestures are being used for accessibility.
168     */
169    public abstract static class FingerprintGestureCallback {
170        /**
171         * Called when the fingerprint sensor's gesture detection becomes available or unavailable.
172         *
173         * @param available Whether or not the sensor's gesture detection is now available.
174         */
175        public void onGestureDetectionAvailabilityChanged(boolean available) {}
176
177        /**
178         * Called when the fingerprint sensor detects gestures.
179         *
180         * @param gesture The id of the gesture that was detected. For example,
181         * {@link #FINGERPRINT_GESTURE_SWIPE_RIGHT}.
182         */
183        public void onGestureDetected(int gesture) {}
184    }
185}
186