mirror of
https://github.com/danog/termux-api.git
synced 2024-11-26 11:54:40 +01:00
Fix fingerprint APIs on android 9 and switch to android X
This commit is contained in:
parent
4dc5a210e0
commit
33414fdcc6
@ -25,7 +25,8 @@ android {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'com.android.support:design:28.0.0'
|
||||
implementation 'com.google.android.material:material:1.0.0'
|
||||
implementation 'androidx.biometric:biometric:1.0.0-alpha04'
|
||||
}
|
||||
|
||||
task versionName {
|
||||
|
@ -22,7 +22,7 @@
|
||||
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
|
||||
<uses-permission android:name="android.permission.WRITE_SETTINGS"
|
||||
tools:ignore="ProtectedPermissions" />
|
||||
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
|
||||
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
|
||||
|
||||
<!-- Some of the used permissions imply uses-feature, so we need to make it optional.
|
||||
See http://developer.android.com/guide/topics/manifest/uses-feature-element.html#permissions -->
|
||||
@ -48,7 +48,7 @@
|
||||
tools:ignore="GoogleAppIndexingWarning">
|
||||
<receiver android:name="com.termux.api.TermuxApiReceiver"/>
|
||||
<activity android:name="com.termux.api.DialogActivity" android:theme="@style/DialogTheme" android:noHistory="true" android:excludeFromRecents="true" android:exported="false"/>
|
||||
<activity android:name=".FingerprintAPI$FingerprintActivity" android:theme="@android:style/Theme.NoDisplay"
|
||||
<activity android:name=".FingerprintAPI$FingerprintActivity" android:theme="@style/TransparentTheme"
|
||||
android:noHistory="true"
|
||||
android:excludeFromRecents="true"
|
||||
android:exported="false"/>
|
||||
|
@ -14,11 +14,11 @@ import android.os.Bundle;
|
||||
import android.speech.RecognitionListener;
|
||||
import android.speech.RecognizerIntent;
|
||||
import android.speech.SpeechRecognizer;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.design.widget.BottomSheetDialog;
|
||||
import android.support.design.widget.BottomSheetDialogFragment;
|
||||
import android.support.v4.widget.NestedScrollView;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import androidx.annotation.NonNull;
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog;
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
|
||||
import androidx.core.widget.NestedScrollView;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import android.text.InputType;
|
||||
import android.util.JsonWriter;
|
||||
import android.view.View;
|
||||
|
@ -1,30 +1,30 @@
|
||||
package com.termux.api;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.hardware.fingerprint.FingerprintManager;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.CancellationSignal;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.security.keystore.KeyGenParameterSpec;
|
||||
import android.security.keystore.KeyProperties;
|
||||
import android.util.JsonWriter;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.biometric.BiometricConstants;
|
||||
import androidx.biometric.BiometricPrompt;
|
||||
import androidx.core.hardware.fingerprint.FingerprintManagerCompat;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
|
||||
import com.termux.api.util.ResultReturner;
|
||||
import com.termux.api.util.TermuxApiLogger;
|
||||
|
||||
import java.security.KeyStore;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.KeyGenerator;
|
||||
import javax.crypto.SecretKey;
|
||||
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
|
||||
|
||||
/**
|
||||
* This API allows users to use device fingerprint sensor as an authentication mechanism
|
||||
@ -71,12 +71,12 @@ public class FingerprintAPI {
|
||||
resetFingerprintResult();
|
||||
|
||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
|
||||
FingerprintManager fingerprintManager = (FingerprintManager)context.getSystemService(Context.FINGERPRINT_SERVICE);
|
||||
|
||||
FingerprintManagerCompat fingerprintManagerCompat = FingerprintManagerCompat.from(context);
|
||||
// make sure we have a valid fingerprint sensor before attempting to launch Fingerprint activity
|
||||
if (validateFingerprintSensor(context, fingerprintManager)) {
|
||||
if (validateFingerprintSensor(context, fingerprintManagerCompat)) {
|
||||
Intent fingerprintIntent = new Intent(context, FingerprintActivity.class);
|
||||
fingerprintIntent.putExtras(intent.getExtras());
|
||||
fingerprintIntent.setFlags(FLAG_ACTIVITY_NEW_TASK);
|
||||
context.startActivity(fingerprintIntent);
|
||||
} else {
|
||||
postFingerprintResult(context, intent, fingerprintResult);
|
||||
@ -120,16 +120,16 @@ public class FingerprintAPI {
|
||||
* Ensure that we have a fingerprint sensor and that the user has already enrolled fingerprints
|
||||
*/
|
||||
@TargetApi(Build.VERSION_CODES.M)
|
||||
protected static boolean validateFingerprintSensor(Context context, FingerprintManager fingerprintManager) {
|
||||
protected static boolean validateFingerprintSensor(Context context, FingerprintManagerCompat fingerprintManagerCompat) {
|
||||
boolean result = true;
|
||||
|
||||
if (!fingerprintManager.isHardwareDetected()) {
|
||||
if (!fingerprintManagerCompat.isHardwareDetected()) {
|
||||
Toast.makeText(context, "No fingerprint scanner found!", Toast.LENGTH_SHORT).show();
|
||||
appendFingerprintError(ERROR_NO_HARDWARE);
|
||||
result = false;
|
||||
}
|
||||
|
||||
if (!fingerprintManager.hasEnrolledFingerprints()) {
|
||||
if (!fingerprintManagerCompat.hasEnrolledFingerprints()) {
|
||||
Toast.makeText(context, "No fingerprints enrolled", Toast.LENGTH_SHORT).show();
|
||||
appendFingerprintError(ERROR_NO_ENROLLED_FINGERPRINTS);
|
||||
result = false;
|
||||
@ -143,48 +143,30 @@ public class FingerprintAPI {
|
||||
* Activity that is necessary for authenticating w/ fingerprint sensor
|
||||
*/
|
||||
@TargetApi(Build.VERSION_CODES.M)
|
||||
public static class FingerprintActivity extends Activity {
|
||||
public static class FingerprintActivity extends FragmentActivity{
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
handleFingerprint();
|
||||
finish();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle setup and listening of fingerprint sensor
|
||||
*/
|
||||
protected void handleFingerprint() {
|
||||
FingerprintManager fingerprintManager = (FingerprintManager)getSystemService(Context.FINGERPRINT_SERVICE);
|
||||
Cipher cipher = null;
|
||||
boolean hasError = false;
|
||||
|
||||
try {
|
||||
KeyStore keyStore = KeyStore.getInstance(KEYSTORE_NAME);
|
||||
generateKey(keyStore);
|
||||
cipher = getCipher();
|
||||
keyStore.load(null);
|
||||
SecretKey key = (SecretKey) keyStore.getKey(KEY_NAME, null);
|
||||
cipher.init(Cipher.ENCRYPT_MODE, key);
|
||||
} catch (Exception e) {
|
||||
TermuxApiLogger.error(TAG, e);
|
||||
hasError = true;
|
||||
}
|
||||
|
||||
if (cipher != null && !hasError) {
|
||||
authenticateWithFingerprint(this, getIntent(), fingerprintManager, cipher);
|
||||
}
|
||||
Executor executor = Executors.newSingleThreadExecutor();
|
||||
authenticateWithFingerprint(this, getIntent(), executor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles authentication callback from our fingerprint sensor
|
||||
*/
|
||||
protected static void authenticateWithFingerprint(final Context context, final Intent intent, final FingerprintManager fingerprintManager, Cipher cipher) {
|
||||
FingerprintManager.AuthenticationCallback authenticationCallback = new FingerprintManager.AuthenticationCallback() {
|
||||
protected static void authenticateWithFingerprint(final FragmentActivity context, final Intent intent, final Executor executor) {
|
||||
BiometricPrompt biometricPrompt = new BiometricPrompt(context, executor, new BiometricPrompt.AuthenticationCallback() {
|
||||
@Override
|
||||
public void onAuthenticationError(int errorCode, CharSequence errString) {
|
||||
if (errorCode == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT) {
|
||||
public void onAuthenticationError(int errorCode, @NonNull CharSequence errString) {
|
||||
if (errorCode == BiometricConstants.ERROR_LOCKOUT) {
|
||||
appendFingerprintError(ERROR_LOCKOUT);
|
||||
|
||||
// first time locked out, subsequent auth attempts will fail immediately for a bit
|
||||
@ -198,7 +180,7 @@ public class FingerprintAPI {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) {
|
||||
public void onAuthenticationSucceeded(@NonNull BiometricPrompt.AuthenticationResult result) {
|
||||
setAuthResult(AUTH_RESULT_SUCCESS);
|
||||
postFingerprintResult(context, intent, fingerprintResult);
|
||||
}
|
||||
@ -207,71 +189,38 @@ public class FingerprintAPI {
|
||||
public void onAuthenticationFailed() {
|
||||
addFailedAttempt();
|
||||
}
|
||||
});
|
||||
|
||||
// unused
|
||||
@Override
|
||||
public void onAuthenticationHelp(int helpCode, CharSequence helpString) { }
|
||||
};
|
||||
|
||||
Toast.makeText(context, "Scan fingerprint", Toast.LENGTH_LONG).show();
|
||||
BiometricPrompt.PromptInfo.Builder builder = new BiometricPrompt.PromptInfo.Builder();
|
||||
builder.setTitle(intent.hasExtra("title") ? intent.getStringExtra("title") : "Authenticate");
|
||||
builder.setNegativeButtonText(intent.hasExtra("cancel") ? intent.getStringExtra("cancel") : "Cancel");
|
||||
if (intent.hasExtra("description")) {
|
||||
builder.setDescription(intent.getStringExtra("description"));
|
||||
}
|
||||
if (intent.hasExtra("subtitle")) {
|
||||
builder.setSubtitle(intent.getStringExtra("subtitle"));
|
||||
}
|
||||
|
||||
// listen to fingerprint sensor
|
||||
FingerprintManager.CryptoObject cryptoObject = new FingerprintManager.CryptoObject(cipher);
|
||||
final CancellationSignal cancellationSignal = new CancellationSignal();
|
||||
fingerprintManager.authenticate(cryptoObject, cancellationSignal, 0, authenticationCallback, null);
|
||||
biometricPrompt.authenticate(builder.build());
|
||||
|
||||
addSensorTimeout(context, intent, cancellationSignal);
|
||||
addSensorTimeout(context, intent, biometricPrompt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a timeout for our fingerprint sensor which will force a result return if we
|
||||
* haven't already received one
|
||||
*/
|
||||
protected static void addSensorTimeout(final Context context, final Intent intent, final CancellationSignal cancellationSignal) {
|
||||
protected static void addSensorTimeout(final Context context, final Intent intent, final BiometricPrompt biometricPrompt) {
|
||||
final Handler timeoutHandler = new Handler(Looper.getMainLooper());
|
||||
timeoutHandler.postDelayed(() -> {
|
||||
if (!postedResult) {
|
||||
appendFingerprintError(ERROR_TIMEOUT);
|
||||
cancellationSignal.cancel();
|
||||
biometricPrompt.cancelAuthentication();
|
||||
postFingerprintResult(context, intent, fingerprintResult);
|
||||
}
|
||||
}, SENSOR_TIMEOUT);
|
||||
}
|
||||
|
||||
protected static void generateKey(KeyStore keyStore) {
|
||||
try {
|
||||
KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, KEYSTORE_NAME);
|
||||
keyStore.load(null);
|
||||
keyGenerator.init(
|
||||
new KeyGenParameterSpec.Builder(KEY_NAME,
|
||||
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
|
||||
.setBlockModes(KeyProperties.BLOCK_MODE_CBC)
|
||||
.setUserAuthenticationRequired(true)
|
||||
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
|
||||
.build());
|
||||
|
||||
keyGenerator.generateKey();
|
||||
} catch (Exception e) {
|
||||
TermuxApiLogger.error(TAG, e);
|
||||
appendFingerprintError(ERROR_KEY_GENERATOR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the cipher needed for use with our SecretKey
|
||||
*/
|
||||
protected static Cipher getCipher() {
|
||||
Cipher cipher = null;
|
||||
try {
|
||||
cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES
|
||||
+ "/" + KeyProperties.BLOCK_MODE_CBC
|
||||
+ "/" + KeyProperties.ENCRYPTION_PADDING_PKCS7);
|
||||
} catch (Exception e) {
|
||||
TermuxApiLogger.error(TAG, e);
|
||||
appendFingerprintError(ERROR_CIPHER);
|
||||
}
|
||||
return cipher;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -7,7 +7,7 @@ import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.os.PersistableBundle;
|
||||
import android.support.annotation.RequiresApi;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
|
@ -6,7 +6,7 @@ import android.os.Build;
|
||||
import android.security.keystore.KeyGenParameterSpec;
|
||||
import android.security.keystore.KeyInfo;
|
||||
import android.security.keystore.KeyProperties;
|
||||
import android.support.annotation.RequiresApi;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import android.util.Base64;
|
||||
import android.util.JsonWriter;
|
||||
|
||||
|
@ -17,6 +17,19 @@
|
||||
<item name="windowNoTitle">true</item>
|
||||
</style>
|
||||
|
||||
|
||||
<style name="TransparentTheme" parent="@style/Theme.AppCompat.Light">
|
||||
|
||||
<item name="android:background">@android:color/transparent</item>
|
||||
<item name="background">@android:color/transparent</item>
|
||||
|
||||
<item name="android:windowBackground">@android:color/transparent</item>
|
||||
<item name="android:colorBackgroundCacheHint">@android:color/transparent</item>
|
||||
<item name="android:windowContentOverlay">@android:color/transparent</item>
|
||||
<item name="android:windowIsTranslucent">true</item>
|
||||
<item name="android:windowAnimationStyle">@android:color/transparent</item>
|
||||
</style>
|
||||
|
||||
<style name="ButtonBar">
|
||||
<item name="android:paddingLeft">2dp</item>
|
||||
<item name="android:paddingTop">5dp</item>
|
||||
@ -26,5 +39,4 @@
|
||||
</style>
|
||||
|
||||
<style name="ButtonBarButton" />
|
||||
|
||||
</resources>
|
@ -17,3 +17,5 @@ org.gradle.jvmargs=-Xmx2048M
|
||||
# This option should only be used with decoupled projects. More details, visit
|
||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||
# org.gradle.parallel=true
|
||||
android.enableJetifier=true
|
||||
android.useAndroidX=true
|
Loading…
Reference in New Issue
Block a user