Skip to content

Commit

Permalink
Use FingerprintManager instead of FingerprintManagerCompat (#31)
Browse files Browse the repository at this point in the history
* use fingerprintmanager instead of fingerprintmanagercompat

* update readme

* move around minSdk annotations

* bump support library version

* improve annotation placement

* improve testHelper
  • Loading branch information
Mauin authored Feb 5, 2017
1 parent f2001f4 commit 3b36220
Show file tree
Hide file tree
Showing 11 changed files with 135 additions and 75 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ Should the device not contain a fingerprint sensor or the user has not enrolled
After successful authentication or a recoverable error (e.g. the sensor could not read the fingerprint clearly) `onNext` will be called. You can check the result if the authentication was successful.
In the case of a recoverable error it provides the error message.

By unsubscribing from the Subscription, the fingerprint sensor will be disabled again with no result.
By disposing the `Disposable`, the fingerprint sensor will be disabled again with no result.

### Encryption-and-decryption

Expand Down Expand Up @@ -137,7 +137,7 @@ After the encryption step all results will be Base64 encoded for easier transpor
RxFingerprint brings the following dependencies:

- RxJava2
- AppCompat-v7 to allow for backwards compability (which will just do nothing)
- Android Support Annotations

## Bugs and Feedback

Expand Down
2 changes: 1 addition & 1 deletion dependencies.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ ext.versions = [
bintrayGradlePlugin : '1.7.1',

// Dependency Versions
supportLibrary : '24.2.0',
supportLibrary : '25.1.1',
rxJava : '2.0.1',

// Testing dependencies
Expand Down
2 changes: 1 addition & 1 deletion rxfingerprint/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ android {


dependencies {
compile "com.android.support:support-compat:$versions.supportLibrary"
compile "com.android.support:support-annotations:$versions.supportLibrary"
compile "io.reactivex.rxjava2:rxjava:$versions.rxJava"

testCompile "junit:junit:$versions.jUnit"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@
package com.mtramin.rxfingerprint;

import android.content.Context;
import android.hardware.fingerprint.FingerprintManager.AuthenticationResult;
import android.hardware.fingerprint.FingerprintManager.CryptoObject;
import android.support.annotation.Nullable;
import android.support.v4.hardware.fingerprint.FingerprintManagerCompat;

import com.mtramin.rxfingerprint.data.FingerprintAuthenticationResult;
import com.mtramin.rxfingerprint.data.FingerprintResult;
Expand Down Expand Up @@ -48,13 +49,13 @@ private FingerprintAuthenticationObservable(Context context) {

@Nullable
@Override
protected FingerprintManagerCompat.CryptoObject initCryptoObject(ObservableEmitter<FingerprintAuthenticationResult> subscriber) {
protected CryptoObject initCryptoObject(ObservableEmitter<FingerprintAuthenticationResult> subscriber) {
// Simple authentication does not need CryptoObject
return null;
}

@Override
protected void onAuthenticationSucceeded(ObservableEmitter<FingerprintAuthenticationResult> emitter, FingerprintManagerCompat.AuthenticationResult result) {
protected void onAuthenticationSucceeded(ObservableEmitter<FingerprintAuthenticationResult> emitter, AuthenticationResult result) {
emitter.onNext(new FingerprintAuthenticationResult(FingerprintResult.AUTHENTICATED, null));
emitter.onComplete();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,11 @@

package com.mtramin.rxfingerprint;

import android.annotation.SuppressLint;
import android.content.Context;
import android.hardware.fingerprint.FingerprintManager.AuthenticationResult;
import android.hardware.fingerprint.FingerprintManager.CryptoObject;
import android.support.annotation.Nullable;
import android.support.v4.hardware.fingerprint.FingerprintManagerCompat;

import com.mtramin.rxfingerprint.data.FingerprintDecryptionResult;
import com.mtramin.rxfingerprint.data.FingerprintEncryptionResult;
Expand Down Expand Up @@ -47,6 +49,7 @@
* <p/>
* The date handed in must be previously encrypted by a {@link FingerprintEncryptionObservable}.
*/
@SuppressLint("NewApi") // SDK check happens in {@link FingerprintObservable#subscribe}
class FingerprintDecryptionObservable extends FingerprintObservable<FingerprintDecryptionResult> {

private final String keyName;
Expand Down Expand Up @@ -87,20 +90,20 @@ private FingerprintDecryptionObservable(Context context, String keyName, String

@Nullable
@Override
protected FingerprintManagerCompat.CryptoObject initCryptoObject(ObservableEmitter<FingerprintDecryptionResult> subscriber) {
protected CryptoObject initCryptoObject(ObservableEmitter<FingerprintDecryptionResult> subscriber) {
CryptoProvider cryptoProvider = new CryptoProvider(context, keyName);
try {
CryptoData cryptoData = CryptoData.fromString(encodingProvider, encryptedString);
Cipher cipher = cryptoProvider.initDecryptionCipher(cryptoData.getIv());
return new FingerprintManagerCompat.CryptoObject(cipher);
return new CryptoObject(cipher);
} catch (CryptoDataException | NoSuchAlgorithmException | CertificateException | InvalidKeyException | KeyStoreException | InvalidAlgorithmParameterException | NoSuchPaddingException | IOException | UnrecoverableKeyException e) {
subscriber.onError(e);
return null;
}
}

@Override
protected void onAuthenticationSucceeded(ObservableEmitter<FingerprintDecryptionResult> emitter, FingerprintManagerCompat.AuthenticationResult result) {
protected void onAuthenticationSucceeded(ObservableEmitter<FingerprintDecryptionResult> emitter, AuthenticationResult result) {
try {
CryptoData cryptoData = CryptoData.fromString(encodingProvider, encryptedString);
Cipher cipher = result.getCryptoObject().getCipher();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,11 @@

package com.mtramin.rxfingerprint;

import android.annotation.SuppressLint;
import android.content.Context;
import android.hardware.fingerprint.FingerprintManager.AuthenticationResult;
import android.hardware.fingerprint.FingerprintManager.CryptoObject;
import android.support.annotation.Nullable;
import android.support.v4.hardware.fingerprint.FingerprintManagerCompat;

import com.mtramin.rxfingerprint.data.FingerprintEncryptionResult;
import com.mtramin.rxfingerprint.data.FingerprintResult;
Expand Down Expand Up @@ -48,6 +50,7 @@
* can only be used with fingerprint authentication and uses it once authentication was successful
* to encrypt the given data.
*/
@SuppressLint("NewApi") // SDK check happens in {@link FingerprintObservable#subscribe}
class FingerprintEncryptionObservable extends FingerprintObservable<FingerprintEncryptionResult> {

private final String keyName;
Expand Down Expand Up @@ -90,11 +93,11 @@ private FingerprintEncryptionObservable(Context context, String keyName, String

@Nullable
@Override
protected FingerprintManagerCompat.CryptoObject initCryptoObject(ObservableEmitter<FingerprintEncryptionResult> emitter) {
protected CryptoObject initCryptoObject(ObservableEmitter<FingerprintEncryptionResult> emitter) {
CryptoProvider cryptoProvider = new CryptoProvider(context, keyName);
try {
Cipher cipher = cryptoProvider.initEncryptionCipher();
return new FingerprintManagerCompat.CryptoObject(cipher);
return new CryptoObject(cipher);
} catch (InvalidKeyException | NoSuchAlgorithmException | NoSuchProviderException | NoSuchPaddingException | InvalidAlgorithmParameterException | CertificateException | UnrecoverableKeyException | KeyStoreException | IOException e) {
emitter.onError(e);
return null;
Expand All @@ -103,7 +106,7 @@ protected FingerprintManagerCompat.CryptoObject initCryptoObject(ObservableEmitt
}

@Override
protected void onAuthenticationSucceeded(ObservableEmitter<FingerprintEncryptionResult> emitter, FingerprintManagerCompat.AuthenticationResult result) {
protected void onAuthenticationSucceeded(ObservableEmitter<FingerprintEncryptionResult> emitter, AuthenticationResult result) {
try {
Cipher cipher = result.getCryptoObject().getCipher();
byte[] encryptedBytes = cipher.doFinal(toEncrypt.getBytes("UTF-8"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,18 @@

package com.mtramin.rxfingerprint;

import android.annotation.SuppressLint;
import android.app.Application;
import android.content.Context;
import android.support.annotation.NonNull;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintManager.AuthenticationCallback;
import android.hardware.fingerprint.FingerprintManager.AuthenticationResult;
import android.hardware.fingerprint.FingerprintManager.CryptoObject;
import android.os.Build;
import android.os.CancellationSignal;
import android.support.annotation.Nullable;
import android.support.v4.hardware.fingerprint.FingerprintManagerCompat;
import android.support.v4.hardware.fingerprint.FingerprintManagerCompat.AuthenticationCallback;
import android.support.v4.os.CancellationSignal;
import android.support.annotation.RequiresApi;
import android.support.annotation.RequiresPermission;
import android.util.Log;

import com.mtramin.rxfingerprint.data.FingerprintAuthenticationException;
Expand All @@ -33,10 +38,13 @@
import io.reactivex.ObservableOnSubscribe;
import io.reactivex.functions.Cancellable;

import static android.Manifest.permission.USE_FINGERPRINT;

/**
* Base observable for Fingerprint authentication. Provides abstract methods that allow
* to alter the input and result of the authentication.
*/
@SuppressLint("NewApi") // SDK check happens in {@link FingerprintObservable#subscribe}
abstract class FingerprintObservable<T> implements ObservableOnSubscribe<T> {

protected final Context context;
Expand All @@ -59,15 +67,18 @@ abstract class FingerprintObservable<T> implements ObservableOnSubscribe<T> {
}

@Override
@RequiresPermission(USE_FINGERPRINT)
@RequiresApi(Build.VERSION_CODES.M)
public void subscribe(ObservableEmitter<T> emitter) throws Exception {
if (RxFingerprint.isUnavailable(context)) {
emitter.onError(new FingerprintUnavailableException("Fingerprint authentication is not available on this device! Ensure that the device has a Fingerprint sensor and enrolled Fingerprints by calling RxFingerprint#isAvailable(Context) first"));
return;
}

AuthenticationCallback callback = createAuthenticationCallback(emitter);
cancellationSignal = new CancellationSignal();
FingerprintManagerCompat.CryptoObject cryptoObject = initCryptoObject(emitter);
FingerprintManagerCompat.from(context).authenticate(cryptoObject, 0, cancellationSignal, callback, null);
CryptoObject cryptoObject = initCryptoObject(emitter);
RxFingerprint.getFingerprintManager(context).authenticate(cryptoObject, cancellationSignal, 0, callback, null);

emitter.setCancellable(new Cancellable() {
@Override
Expand All @@ -79,47 +90,42 @@ public void cancel() throws Exception {
});
}

@NonNull
private AuthenticationCallback createAuthenticationCallback(final ObservableEmitter<T> emitter) {
return new AuthenticationCallback() {
@Override
public void onAuthenticationError(int errMsgId, CharSequence errString) {
super.onAuthenticationError(errMsgId, errString);
if (!emitter.isDisposed()) {
emitter.onError(new FingerprintAuthenticationException(errString));
}
}

@Override
public void onAuthenticationFailed() {
super.onAuthenticationFailed();
FingerprintObservable.this.onAuthenticationFailed(emitter);
}

@Override
public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) {
super.onAuthenticationHelp(helpMsgId, helpString);
FingerprintObservable.this.onAuthenticationHelp(emitter, helpMsgId, helpString.toString());
}

@Override
public void onAuthenticationSucceeded(FingerprintManagerCompat.AuthenticationResult result) {
super.onAuthenticationSucceeded(result);
public void onAuthenticationSucceeded(AuthenticationResult result) {
FingerprintObservable.this.onAuthenticationSucceeded(emitter, result);
}
};
}

/**
* Method to initialize the {@link FingerprintManagerCompat.CryptoObject}
* Method to initialize the {@link FingerprintManager.CryptoObject}
* used for the fingerprint authentication.
*
* @param subscriber current subscriber
* @return a {@link FingerprintManagerCompat.CryptoObject}
* @return a {@link FingerprintManager.CryptoObject}
* that is to be used in the authentication. May be {@code null}.
*/
@Nullable
protected abstract FingerprintManagerCompat.CryptoObject initCryptoObject(ObservableEmitter<T> subscriber);
protected abstract CryptoObject initCryptoObject(ObservableEmitter<T> subscriber);

/**
* Action to execute when fingerprint authentication was successful.
Expand All @@ -130,7 +136,7 @@ public void onAuthenticationSucceeded(FingerprintManagerCompat.AuthenticationRes
* @param emitter current subscriber
* @param result result of the successful fingerprint authentication
*/
protected abstract void onAuthenticationSucceeded(ObservableEmitter<T> emitter, FingerprintManagerCompat.AuthenticationResult result);
protected abstract void onAuthenticationSucceeded(ObservableEmitter<T> emitter, AuthenticationResult result);

/**
* Action to execute when the fingerprint authentication returned a help result.
Expand All @@ -139,8 +145,8 @@ public void onAuthenticationSucceeded(FingerprintManagerCompat.AuthenticationRes
* Should <b>not</b> {@link Emitter#onComplete()}.
*
* @param emitter current subscriber
* @param helpMessageId ID of the help message returned from the {@link FingerprintManagerCompat}
* @param helpString Help message string returned by the {@link FingerprintManagerCompat}
* @param helpMessageId ID of the help message returned from the {@link FingerprintManager}
* @param helpString Help message string returned by the {@link FingerprintManager}
*/
protected abstract void onAuthenticationHelp(ObservableEmitter<T> emitter, int helpMessageId, String helpString);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,21 @@
package com.mtramin.rxfingerprint;

import android.content.Context;
import android.content.pm.PackageManager;
import android.hardware.fingerprint.FingerprintManager;
import android.os.Build;
import android.security.keystore.KeyPermanentlyInvalidatedException;
import android.support.annotation.NonNull;
import android.support.v4.hardware.fingerprint.FingerprintManagerCompat;
import android.support.annotation.RequiresApi;

import com.mtramin.rxfingerprint.data.FingerprintAuthenticationResult;
import com.mtramin.rxfingerprint.data.FingerprintDecryptionResult;
import com.mtramin.rxfingerprint.data.FingerprintEncryptionResult;

import io.reactivex.Observable;

import static android.Manifest.permission.USE_FINGERPRINT;

/**
* Entry point for RxFingerprint. Contains all the base methods you need to interact with the
* fingerprint sensor of the device. Allows authentication of the user via the fingerprint
Expand Down Expand Up @@ -55,9 +60,9 @@ public class RxFingerprint {
* Authenticate the user with his fingerprint. This will enable the fingerprint sensor on the
* device and wait for the user to touch the sensor with his finger.
* <p/>
* All possible recoverable errors will be provided in {@link rx.Subscriber#onNext(Object)} and
* All possible recoverable errors will be provided in {@link org.reactivestreams.Subscriber#onNext(Object)} and
* should be handled there. Unrecoverable errors will be provided with
* {@link rx.Subscriber#onError(Throwable)} calls.
* {@link org.reactivestreams.Subscriber#onError(Throwable)} calls.
*
* @param context current context
* @return Observable {@link FingerprintAuthenticationResult}. Will complete once the
Expand Down Expand Up @@ -200,8 +205,13 @@ public static boolean isUnavailable(@NonNull Context context) {
* @param context a context
* @return {@code true} if fingerprint hardware exists in this device.
*/
@SuppressWarnings("MissingPermission")
public static boolean isHardwareDetected(@NonNull Context context) {
return getFingerprintManager(context).isHardwareDetected();
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
return false;
}

return fingerprintPermissionGranted(context) && getFingerprintManager(context).isHardwareDetected();
}

/**
Expand All @@ -213,13 +223,22 @@ public static boolean isHardwareDetected(@NonNull Context context) {
* @param context a context
* @return {@code true} if at least one fingerprint was enrolled.
*/
@SuppressWarnings("MissingPermission")
public static boolean hasEnrolledFingerprints(@NonNull Context context) {
return getFingerprintManager(context).hasEnrolledFingerprints();
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
return false;
}
return fingerprintPermissionGranted(context) && getFingerprintManager(context).hasEnrolledFingerprints();
}

@RequiresApi(Build.VERSION_CODES.M)
private static boolean fingerprintPermissionGranted(Context context) {
return context.checkSelfPermission(USE_FINGERPRINT) == PackageManager.PERMISSION_GRANTED;
}

@NonNull
private static FingerprintManagerCompat getFingerprintManager(Context context) {
return FingerprintManagerCompat.from(context);
@RequiresApi(Build.VERSION_CODES.M)
static FingerprintManager getFingerprintManager(Context context) {
return (FingerprintManager) context.getSystemService(Context.FINGERPRINT_SERVICE);
}

/**
Expand All @@ -232,7 +251,7 @@ private static FingerprintManagerCompat getFingerprintManager(Context context) {
* invalidated by the Android system. To continue using encryption you have to ask the user to
* encrypt the original data again. The old data is not accessible anymore.
*
* @param throwable Throwable received in {@link rx.Subscriber#onError(Throwable)} from
* @param throwable Throwable received in {@link org.reactivestreams.Subscriber#onError(Throwable)} from
* an {@link RxFingerprint} encryption method
* @return {@code true} if the requested key was permanently invalidated and cannot be used
* anymore
Expand Down
Loading

0 comments on commit 3b36220

Please sign in to comment.