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.v4.os.CancellationSignal;
25
26import java.security.Signature;
27
28import javax.crypto.Cipher;
29import javax.crypto.Mac;
30
31/**
32 * A class that coordinates access to the fingerprint hardware.
33 * <p>
34 * On platforms before {@link android.os.Build.VERSION_CODES#M}, this class behaves as there would
35 * be no fingerprint hardware available.
36 */
37public final class FingerprintManagerCompat {
38
39    private Context mContext;
40
41    /** Get a {@link FingerprintManagerCompat} instance for a provided context. */
42    public static FingerprintManagerCompat from(Context context) {
43        return new FingerprintManagerCompat(context);
44    }
45
46    private FingerprintManagerCompat(Context context) {
47        mContext = context;
48    }
49
50    static final FingerprintManagerCompatImpl IMPL;
51    static {
52        final int version = Build.VERSION.SDK_INT;
53        if (version >= 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    private static class Api23FingerprintManagerCompatImpl implements FingerprintManagerCompatImpl {
233
234        public Api23FingerprintManagerCompatImpl() {
235        }
236
237        @Override
238        public boolean hasEnrolledFingerprints(Context context) {
239            return FingerprintManagerCompatApi23.hasEnrolledFingerprints(context);
240        }
241
242        @Override
243        public boolean isHardwareDetected(Context context) {
244            return FingerprintManagerCompatApi23.isHardwareDetected(context);
245        }
246
247        @Override
248        public void authenticate(Context context, CryptoObject crypto, int flags,
249                CancellationSignal cancel, AuthenticationCallback callback, Handler handler) {
250            FingerprintManagerCompatApi23.authenticate(context, wrapCryptoObject(crypto), flags,
251                    cancel != null ? cancel.getCancellationSignalObject() : null,
252                    wrapCallback(callback), handler);
253        }
254
255        private static FingerprintManagerCompatApi23.CryptoObject wrapCryptoObject(
256                CryptoObject cryptoObject) {
257            if (cryptoObject == null) {
258                return null;
259            } else if (cryptoObject.getCipher() != null) {
260                return new FingerprintManagerCompatApi23.CryptoObject(cryptoObject.getCipher());
261            } else if (cryptoObject.getSignature() != null) {
262                return new FingerprintManagerCompatApi23.CryptoObject(cryptoObject.getSignature());
263            } else if (cryptoObject.getMac() != null) {
264                return new FingerprintManagerCompatApi23.CryptoObject(cryptoObject.getMac());
265            } else {
266                return null;
267            }
268        }
269
270        private static CryptoObject unwrapCryptoObject(
271                FingerprintManagerCompatApi23.CryptoObject cryptoObject) {
272            if (cryptoObject == null) {
273                return null;
274            } else if (cryptoObject.getCipher() != null) {
275                return new CryptoObject(cryptoObject.getCipher());
276            } else if (cryptoObject.getSignature() != null) {
277                return new CryptoObject(cryptoObject.getSignature());
278            } else if (cryptoObject.getMac() != null) {
279                return new CryptoObject(cryptoObject.getMac());
280            } else {
281                return null;
282            }
283        }
284
285        private static FingerprintManagerCompatApi23.AuthenticationCallback wrapCallback(
286                final AuthenticationCallback callback) {
287            return new FingerprintManagerCompatApi23.AuthenticationCallback() {
288                @Override
289                public void onAuthenticationError(int errMsgId, CharSequence errString) {
290                    callback.onAuthenticationError(errMsgId, errString);
291                }
292
293                @Override
294                public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) {
295                    callback.onAuthenticationHelp(helpMsgId, helpString);
296                }
297
298                @Override
299                public void onAuthenticationSucceeded(
300                        FingerprintManagerCompatApi23.AuthenticationResultInternal result) {
301                    callback.onAuthenticationSucceeded(new AuthenticationResult(
302                            unwrapCryptoObject(result.getCryptoObject())));
303                }
304
305                @Override
306                public void onAuthenticationFailed() {
307                    callback.onAuthenticationFailed();
308                }
309            };
310        }
311    }
312}
313