/*
* Copyright (c) 2020 Cobo
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* in the file COPYING. If not, see .
*/
package com.cobo.cold.fingerprint;
import android.annotation.UserIdInt;
import android.app.admin.DevicePolicyManager;
import android.content.Context;
import android.hardware.fingerprint.Fingerprint;
import android.hardware.fingerprint.FingerprintManager;
import android.os.CancellationSignal;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.internal.widget.LockPatternUtils;
import com.cobo.cold.AppExecutors;
import com.cobo.cold.Utilities;
import com.cobo.cold.util.HashUtil;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import static android.content.Context.FINGERPRINT_SERVICE;
public class FingerprintKit {
private static final String TAG = "Vault.FingerprintKit";
private FingerprintManager fp;
private final Context mContext;
private final LockPatternUtils lockPatternUtils;
@UserIdInt
private final int mUserId;
private final ExecutorService sExecutor = Executors.newSingleThreadExecutor();
private CancellationSignal mCancellationSignal;
private boolean isEnrolling;
private boolean isVerifying;
public FingerprintKit(Context context) {
mContext = context;
lockPatternUtils = new LockPatternUtils(mContext);
mUserId = UserHandle.myUserId();
fp = (FingerprintManager) mContext.getSystemService(FINGERPRINT_SERVICE);
if (!lockPatternUtils.isLockPasswordEnabled(mUserId)) {
String password = HashUtil.generateRandomPassword(20);
if (Utilities.setFingerprintPassword(mContext, password)) {
lockPatternUtils.saveLockPassword(password, null,
DevicePolicyManager.PASSWORD_QUALITY_NUMERIC, mUserId);
}
}
}
public static boolean isHardwareDetected(Context context) {
FingerprintManager fm = (FingerprintManager) context.getSystemService(FINGERPRINT_SERVICE);
return fm.isHardwareDetected();
}
public static void verifyPassword(Context context) {
AppExecutors.getInstance().diskIO().execute(() -> {
try {
String password = Utilities.getFingerprintPassword(context);
if (!TextUtils.isEmpty(password)) {
new LockPatternUtils(context).verifyPassword(password,
0L, UserHandle.myUserId());
}
} catch (LockPatternUtils.RequestThrottledException e) {
e.printStackTrace();
}
});
}
public List getEnrolledFingerprints() {
Objects.requireNonNull(fp, "fp is null");
return fp.getEnrolledFingerprints();
}
public boolean hasEnrolledFingerprint() {
Objects.requireNonNull(fp, "fp is null");
return fp.hasEnrolledFingerprints();
}
public void renameFingerprint(@NonNull Fingerprint fingerprint, @NonNull String newName) {
fp.rename(fingerprint.getFingerId(), UserHandle.myUserId(), newName);
}
public void removeFingerprint(@NonNull Fingerprint fingerprint, RemovalListener listener) {
fp.remove(fingerprint, UserHandle.myUserId(), new FingerprintManager.RemovalCallback() {
@Override
public void onRemovalError(Fingerprint fp, int errMsgId, CharSequence errString) {
super.onRemovalError(fp, errMsgId, errString);
if (listener != null) {
listener.onError(errMsgId, errString.toString());
}
}
@Override
public void onRemovalSucceeded(Fingerprint fp, int remaining) {
super.onRemovalSucceeded(fp, remaining);
if (listener != null) {
listener.onSuccess();
}
}
});
}
public void cancelEnroll() {
if (isEnrolling
&& mCancellationSignal != null
&& !mCancellationSignal.isCanceled()) {
mCancellationSignal.cancel();
isEnrolling = false;
}
}
public void startEnroll(@Nullable EnrollListener listener) {
long challenge = fp.preEnroll();
final Future future = sExecutor.submit(()
-> lockPatternUtils.verifyPassword(Utilities.getFingerprintPassword(mContext),
challenge, mUserId));
byte[] token;
try {
token = future.get();
} catch (ExecutionException | InterruptedException e) {
e.printStackTrace();
return;
}
if (mCancellationSignal != null) {
mCancellationSignal.cancel();
}
mCancellationSignal = new CancellationSignal();
fp.setActiveUser(mUserId);
isEnrolling = true;
fp.enroll(token, mCancellationSignal, 0, mUserId, new FingerprintManager.EnrollmentCallback() {
@Override
public void onEnrollmentError(int errMsgId, CharSequence errString) {
super.onEnrollmentError(errMsgId, errString);
if (listener != null) {
listener.onEnrollmentError(errMsgId, errString);
}
mCancellationSignal.cancel();
isEnrolling = false;
}
@Override
public void onEnrollmentHelp(int helpMsgId, CharSequence helpString) {
super.onEnrollmentHelp(helpMsgId, helpString);
if (listener != null) {
listener.onEnrollmentHelp(helpMsgId, helpString);
}
}
@Override
public void onEnrollmentProgress(int remaining) {
super.onEnrollmentProgress(remaining);
if (listener != null) {
listener.onEnrollmentProgress(remaining);
}
if (remaining == 0) {
fp.postEnroll();
isEnrolling = false;
}
}
});
}
public void cancelVerify() {
if (isVerifying
&& mCancellationSignal != null
&& !mCancellationSignal.isCanceled()) {
mCancellationSignal.cancel();
isVerifying = false;
}
}
public void startVerify(@NonNull VerifyListener listener, FingerprintManager.CryptoObject object) {
if (mCancellationSignal != null) {
mCancellationSignal.cancel();
}
mCancellationSignal = new CancellationSignal();
isVerifying = true;
Log.w(TAG, "fp kit startVerify");
fp.authenticate(object, mCancellationSignal, 0,
new FingerprintManager.AuthenticationCallback() {
int failCount = 0;
@Override
public void onAuthenticationError(int errorCode, CharSequence errString) {
listener.onAuthenticationError(errorCode, errString);
isVerifying = false;
mCancellationSignal.cancel();
}
@Override
public void onAuthenticationHelp(int helpCode, CharSequence helpString) {
}
@Override
public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) {
listener.onAuthenticationSucceeded(result.getCryptoObject());
isVerifying = false;
mCancellationSignal.cancel();
}
@Override
public void onAuthenticationFailed() {
failCount++;
if (failCount == 5) {
onAuthenticationError(0, "");
}
}
@Override
public void onAuthenticationAcquired(int acquireInfo) {
}
}, null, mUserId);
}
}