1
0
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:
Daniil Gentili 2019-08-18 14:59:57 +02:00
parent 4dc5a210e0
commit 33414fdcc6
8 changed files with 63 additions and 99 deletions

View File

@ -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 {

View File

@ -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"/>

View File

@ -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;

View File

@ -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;
}
}
/**

View File

@ -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;

View File

@ -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;

View File

@ -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>

View File

@ -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