1/*
2 * Copyright (C) 2015 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.support.v4.hardware.fingerprint;
18
19import android.content.Context;
20import android.os.Build;
21import android.os.Handler;
22import android.support.annotation.NonNull;
23import android.support.annotation.Nullable;
24import android.support.annotation.RequiresApi;
25import android.support.v4.os.CancellationSignal;
26
27import java.security.Signature;
28
29import javax.crypto.Cipher;
30import javax.crypto.Mac;
31
32/**
33 * A class that coordinates access to the fingerprint hardware.
34 * <p>
35 * On platforms before {@link android.os.Build.VERSION_CODES#M}, this class behaves as there would
36 * be no fingerprint hardware available.
37 */
38public final class FingerprintManagerCompat {
39
40    private Context mContext;
41
42    /** Get a {@link FingerprintManagerCompat} instance for a provided context. */
43    public static FingerprintManagerCompat from(Context context) {
44        return new FingerprintManagerCompat(context);
45    }
46
47    private FingerprintManagerCompat(Context context) {
48        mContext = context;
49    }
50
51    static final FingerprintManagerCompatImpl IMPL;
52    static {
53        if (Build.VERSION.SDK_INT >= 23) {
54            IMPL = new Api23FingerprintManagerCompatImpl();
55        } else {
56            IMPL = new LegacyFingerprintManagerCompatImpl();
57        }
58    }
59
60    /**
61     * Determine if there is at least one fingerprint enrolled.
62     *
63     * @return true if at least one fingerprint is enrolled, false otherwise
64     */
65    public boolean hasEnrolledFingerprints() {
66        return IMPL.hasEnrolledFingerprints(mContext);
67    }
68
69    /**
70     * Determine if fingerprint hardware is present and functional.
71     *
72     * @return true if hardware is present and functional, false otherwise.
73     */
74    public boolean isHardwareDetected() {
75        return IMPL.isHardwareDetected(mContext);
76    }
77
78    /**
79     * Request authentication of a crypto object. This call warms up the fingerprint hardware
80     * and starts scanning for a fingerprint. It terminates when
81     * {@link AuthenticationCallback#onAuthenticationError(int, CharSequence)} or
82     * {@link AuthenticationCallback#onAuthenticationSucceeded(AuthenticationResult)} is called, at
83     * which point the object is no longer valid. The operation can be canceled by using the
84     * provided cancel object.
85     *
86     * @param crypto object associated with the call or null if none required.
87     * @param flags optional flags; should be 0
88     * @param cancel an object that can be used to cancel authentication
89     * @param callback an object to receive authentication events
90     * @param handler an optional handler for events
91     */
92    public void authenticate(@Nullable CryptoObject crypto, int flags,
93            @Nullable CancellationSignal cancel, @NonNull AuthenticationCallback callback,
94            @Nullable Handler handler) {
95        IMPL.authenticate(mContext, crypto, flags, cancel, callback, handler);
96    }
97
98    /**
99     * A wrapper class for the crypto objects supported by FingerprintManager. Currently the
100     * framework supports {@link Signature} and {@link Cipher} objects.
101     */
102    public static class CryptoObject {
103
104        private final Signature mSignature;
105        private final Cipher mCipher;
106        private final Mac mMac;
107
108        public CryptoObject(Signature signature) {
109            mSignature = signature;
110            mCipher = null;
111            mMac = null;
112
113        }
114
115        public CryptoObject(Cipher cipher) {
116            mCipher = cipher;
117            mSignature = null;
118            mMac = null;
119        }
120
121        public CryptoObject(Mac mac) {
122            mMac = mac;
123            mCipher = null;
124            mSignature = null;
125        }
126
127        /**
128         * Get {@link Signature} object.
129         * @return {@link Signature} object or null if this doesn't contain one.
130         */
131        public Signature getSignature() { return mSignature; }
132
133        /**
134         * Get {@link Cipher} object.
135         * @return {@link Cipher} object or null if this doesn't contain one.
136         */
137        public Cipher getCipher() { return mCipher; }
138
139        /**
140         * Get {@link Mac} object.
141         * @return {@link Mac} object or null if this doesn't contain one.
142         */
143        public Mac getMac() { return mMac; }
144    }
145
146    /**
147     * Container for callback data from {@link FingerprintManagerCompat#authenticate(CryptoObject,
148     *     int, CancellationSignal, AuthenticationCallback, Handler)}.
149     */
150    public static final class AuthenticationResult {
151        private CryptoObject mCryptoObject;
152
153        public AuthenticationResult(CryptoObject crypto) {
154            mCryptoObject = crypto;
155        }
156
157        /**
158         * Obtain the crypto object associated with this transaction
159         * @return crypto object provided to {@link FingerprintManagerCompat#authenticate(
160         *         CryptoObject, int, CancellationSignal, AuthenticationCallback, Handler)}.
161         */
162        public CryptoObject getCryptoObject() { return mCryptoObject; }
163    }
164
165    /**
166     * Callback structure provided to {@link FingerprintManagerCompat#authenticate(CryptoObject,
167     * int, CancellationSignal, AuthenticationCallback, Handler)}. Users of {@link
168     * FingerprintManagerCompat#authenticate(CryptoObject, int, CancellationSignal,
169     * AuthenticationCallback, Handler) } must provide an implementation of this for listening to
170     * fingerprint events.
171     */
172    public static abstract class AuthenticationCallback {
173        /**
174         * Called when an unrecoverable error has been encountered and the operation is complete.
175         * No further callbacks will be made on this object.
176         * @param errMsgId An integer identifying the error message
177         * @param errString A human-readable error string that can be shown in UI
178         */
179        public void onAuthenticationError(int errMsgId, CharSequence errString) { }
180
181        /**
182         * Called when a recoverable error has been encountered during authentication. The help
183         * string is provided to give the user guidance for what went wrong, such as
184         * "Sensor dirty, please clean it."
185         * @param helpMsgId An integer identifying the error message
186         * @param helpString A human-readable string that can be shown in UI
187         */
188        public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) { }
189
190        /**
191         * Called when a fingerprint is recognized.
192         * @param result An object containing authentication-related data
193         */
194        public void onAuthenticationSucceeded(AuthenticationResult result) { }
195
196        /**
197         * Called when a fingerprint is valid but not recognized.
198         */
199        public void onAuthenticationFailed() { }
200    }
201
202    private interface FingerprintManagerCompatImpl {
203        boolean hasEnrolledFingerprints(Context context);
204        boolean isHardwareDetected(Context context);
205        void authenticate(Context context, CryptoObject crypto, int flags,
206                CancellationSignal cancel, AuthenticationCallback callback, Handler handler);
207    }
208
209    private static class LegacyFingerprintManagerCompatImpl
210            implements FingerprintManagerCompatImpl {
211
212        public LegacyFingerprintManagerCompatImpl() {
213        }
214
215        @Override
216        public boolean hasEnrolledFingerprints(Context context) {
217            return false;
218        }
219
220        @Override
221        public boolean isHardwareDetected(Context context) {
222            return false;
223        }
224
225        @Override
226        public void authenticate(Context context, CryptoObject crypto, int flags,
227                CancellationSignal cancel, AuthenticationCallback callback, Handler handler) {
228            // TODO: Figure out behavior when there is no fingerprint hardware available
229        }
230    }
231
232    @RequiresApi(23)
233    private static class Api23FingerprintManagerCompatImpl implements FingerprintManagerCompatImpl {
234
235        public Api23FingerprintManagerCompatImpl() {
236        }
237
238        @Override
239        public boolean hasEnrolledFingerprints(Context context) {
240            return FingerprintManagerCompatApi23.hasEnrolledFingerprints(context);
241        }
242
243        @Override
244        public boolean isHardwareDetected(Context context) {
245            return FingerprintManagerCompatApi23.isHardwareDetected(context);
246        }
247
248        @Override
249        public void authenticate(Context context, CryptoObject crypto, int flags,
250                CancellationSignal cancel, AuthenticationCallback callback, Handler handler) {
251            FingerprintManagerCompatApi23.authenticate(context, wrapCryptoObject(crypto), flags,
252                    cancel != null ? cancel.getCancellationSignalObject() : null,
253                    wrapCallback(callback), handler);
254        }
255
256        private static FingerprintManagerCompatApi23.CryptoObject wrapCryptoObject(
257                CryptoObject cryptoObject) {
258            if (cryptoObject == null) {
259                return null;
260            } else if (cryptoObject.getCipher() != null) {
261                return new FingerprintManagerCompatApi23.CryptoObject(cryptoObject.getCipher());
262            } else if (cryptoObject.getSignature() != null) {
263                return new FingerprintManagerCompatApi23.CryptoObject(cryptoObject.getSignature());
264            } else if (cryptoObject.getMac() != null) {
265                return new FingerprintManagerCompatApi23.CryptoObject(cryptoObject.getMac());
266            } else {
267                return null;
268            }
269        }
270
271        static CryptoObject unwrapCryptoObject(
272                FingerprintManagerCompatApi23.CryptoObject cryptoObject) {
273            if (cryptoObject == null) {
274                return null;
275            } else if (cryptoObject.getCipher() != null) {
276                return new CryptoObject(cryptoObject.getCipher());
277            } else if (cryptoObject.getSignature() != null) {
278                return new CryptoObject(cryptoObject.getSignature());
279            } else if (cryptoObject.getMac() != null) {
280                return new CryptoObject(cryptoObject.getMac());
281            } else {
282                return null;
283            }
284        }
285
286        private static FingerprintManagerCompatApi23.AuthenticationCallback wrapCallback(
287                final AuthenticationCallback callback) {
288            return new FingerprintManagerCompatApi23.AuthenticationCallback() {
289                @Override
290                public void onAuthenticationError(int errMsgId, CharSequence errString) {
291                    callback.onAuthenticationError(errMsgId, errString);
292                }
293
294                @Override
295                public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) {
296                    callback.onAuthenticationHelp(helpMsgId, helpString);
297                }
298
299                @Override
300                public void onAuthenticationSucceeded(
301                        FingerprintManagerCompatApi23.AuthenticationResultInternal result) {
302                    callback.onAuthenticationSucceeded(new AuthenticationResult(
303                            unwrapCryptoObject(result.getCryptoObject())));
304                }
305
306                @Override
307                public void onAuthenticationFailed() {
308                    callback.onAuthenticationFailed();
309                }
310            };
311        }
312    }
313}
314