diff --git a/app/build.gradle b/app/build.gradle index b7d0ce2d23..5685923d8f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -145,6 +145,7 @@ dependencies { implementation libs.androidX.cardview implementation libs.androidX.constraintLayout implementation libs.androidX.multidex //Multiple dex files + implementation libs.androidX.biometric implementation libs.room.runtime implementation libs.room.rxjava2 diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/files/EncryptDecryptUtils.java b/app/src/main/java/com/amaze/filemanager/filesystem/files/EncryptDecryptUtils.java deleted file mode 100644 index 66ce02a424..0000000000 --- a/app/src/main/java/com/amaze/filemanager/filesystem/files/EncryptDecryptUtils.java +++ /dev/null @@ -1,268 +0,0 @@ -/* - * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , - * Emmanuel Messulam, Raymond Lai and Contributors. - * - * This file is part of Amaze File Manager. - * - * Amaze File Manager 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 - * along with this program. If not, see . - */ - -package com.amaze.filemanager.filesystem.files; - -import static com.amaze.filemanager.asynchronous.services.EncryptService.TAG_AESCRYPT; -import static com.amaze.filemanager.filesystem.files.CryptUtil.AESCRYPT_EXTENSION; - -import java.io.IOException; -import java.security.GeneralSecurityException; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.amaze.filemanager.R; -import com.amaze.filemanager.asynchronous.management.ServiceWatcherUtil; -import com.amaze.filemanager.asynchronous.services.DecryptService; -import com.amaze.filemanager.asynchronous.services.EncryptService; -import com.amaze.filemanager.database.CryptHandler; -import com.amaze.filemanager.database.models.explorer.EncryptedEntry; -import com.amaze.filemanager.fileoperations.filesystem.OpenMode; -import com.amaze.filemanager.filesystem.HybridFileParcelable; -import com.amaze.filemanager.ui.activities.MainActivity; -import com.amaze.filemanager.ui.dialogs.DecryptFingerprintDialog; -import com.amaze.filemanager.ui.dialogs.GeneralDialogCreation; -import com.amaze.filemanager.ui.fragments.MainFragment; -import com.amaze.filemanager.ui.fragments.preferencefragments.PreferencesConstants; -import com.amaze.filemanager.ui.provider.UtilitiesProvider; -import com.amaze.filemanager.utils.PasswordUtil; - -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.os.Build; -import android.util.Base64; -import android.widget.Toast; - -import androidx.annotation.NonNull; -import androidx.appcompat.widget.AppCompatEditText; -import androidx.preference.PreferenceManager; - -/** - * Provides useful interfaces and methods for encryption/decryption - * - * @author Emmanuel on 25/5/2017, at 16:55. - */ -public class EncryptDecryptUtils { - - public static final String DECRYPT_BROADCAST = "decrypt_broadcast"; - private static final Logger LOG = LoggerFactory.getLogger(EncryptDecryptUtils.class); - - /** - * Queries database to map path and password. Starts the encryption process after database query - * - * @param path the path of file to encrypt - * @param password the password in plaintext - * @throws GeneralSecurityException Errors on encrypting file/folder - * @throws IOException I/O errors on encrypting file/folder - */ - public static void startEncryption( - Context c, final String path, final String password, Intent intent) - throws GeneralSecurityException, IOException { - String destPath = - path.substring(0, path.lastIndexOf('/') + 1) - .concat(intent.getStringExtra(EncryptService.TAG_ENCRYPT_TARGET)); - - // EncryptService.TAG_ENCRYPT_TARGET already has the .aze extension, no need to append again - if (!intent.getBooleanExtra(TAG_AESCRYPT, false)) { - EncryptedEntry encryptedEntry = new EncryptedEntry(destPath, password); - CryptHandler.INSTANCE.addEntry(encryptedEntry); - } - // start the encryption process - ServiceWatcherUtil.runService(c, intent); - } - - public static void decryptFile( - Context c, - final MainActivity mainActivity, - final MainFragment main, - OpenMode openMode, - HybridFileParcelable sourceFile, - String decryptPath, - UtilitiesProvider utilsProvider, - boolean broadcastResult) { - - Intent decryptIntent = new Intent(main.getContext(), DecryptService.class); - decryptIntent.putExtra(EncryptService.TAG_OPEN_MODE, openMode.ordinal()); - decryptIntent.putExtra(EncryptService.TAG_SOURCE, sourceFile); - decryptIntent.putExtra(EncryptService.TAG_DECRYPT_PATH, decryptPath); - SharedPreferences preferences1 = - PreferenceManager.getDefaultSharedPreferences(main.getContext()); - - if (sourceFile.getPath().endsWith(AESCRYPT_EXTENSION)) { - GeneralDialogCreation.showPasswordDialog( - c, - mainActivity, - utilsProvider.getAppTheme(), - R.string.crypt_decrypt, - R.string.authenticate_password, - (dialog, which) -> { - AppCompatEditText editText = dialog.getView().findViewById(R.id.singleedittext_input); - decryptIntent.putExtra(EncryptService.TAG_PASSWORD, editText.getText().toString()); - ServiceWatcherUtil.runService(c, decryptIntent); - dialog.dismiss(); - }, - null); - } else { - EncryptedEntry encryptedEntry; - - try { - encryptedEntry = findEncryptedEntry(sourceFile.getPath()); - } catch (GeneralSecurityException | IOException e) { - LOG.warn("failed to find encrypted entry while decrypting", e); - - // we couldn't find any entry in database or lost the key to decipher - Toast.makeText( - main.getContext(), - main.getActivity().getString(R.string.crypt_decryption_fail), - Toast.LENGTH_LONG) - .show(); - return; - } - - DecryptButtonCallbackInterface decryptButtonCallbackInterface = - new DecryptButtonCallbackInterface() { - @Override - public void confirm(Intent intent) { - ServiceWatcherUtil.runService(c, intent); - } - - @Override - public void failed() { - Toast.makeText( - main.getContext(), - main.getActivity().getString(R.string.crypt_decryption_fail_password), - Toast.LENGTH_LONG) - .show(); - } - }; - - if (encryptedEntry == null && !sourceFile.getPath().endsWith(AESCRYPT_EXTENSION)) { - // couldn't find the matching path in database, we lost the password - - Toast.makeText( - main.getContext(), - main.getActivity().getString(R.string.crypt_decryption_fail), - Toast.LENGTH_LONG) - .show(); - return; - } - - switch (encryptedEntry.getPassword().value) { - case PreferencesConstants.ENCRYPT_PASSWORD_FINGERPRINT: - try { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - DecryptFingerprintDialog.show( - c, - mainActivity, - decryptIntent, - utilsProvider.getAppTheme(), - decryptButtonCallbackInterface); - } else throw new IllegalStateException("API < M!"); - } catch (GeneralSecurityException | IOException | IllegalStateException e) { - LOG.warn("failed to form fingerprint dialog", e); - Toast.makeText( - main.getContext(), - main.getString(R.string.crypt_decryption_fail), - Toast.LENGTH_LONG) - .show(); - } - break; - case PreferencesConstants.ENCRYPT_PASSWORD_MASTER: - try { - GeneralDialogCreation.showDecryptDialog( - c, - mainActivity, - decryptIntent, - utilsProvider.getAppTheme(), - PasswordUtil.INSTANCE.decryptPassword( - c, - preferences1.getString( - PreferencesConstants.PREFERENCE_CRYPT_MASTER_PASSWORD, - PreferencesConstants.PREFERENCE_CRYPT_MASTER_PASSWORD_DEFAULT), - Base64.DEFAULT), - decryptButtonCallbackInterface); - } catch (GeneralSecurityException | IOException e) { - LOG.warn("failed to show decrypt dialog, e"); - Toast.makeText( - main.getContext(), - main.getString(R.string.crypt_decryption_fail), - Toast.LENGTH_LONG) - .show(); - } - break; - default: - GeneralDialogCreation.showDecryptDialog( - c, - mainActivity, - decryptIntent, - utilsProvider.getAppTheme(), - encryptedEntry.getPassword().value, - decryptButtonCallbackInterface); - break; - } - } - } - - /** - * Queries database to find entry for the specific path - * - * @param path the path to match with - * @return the entry - */ - private static EncryptedEntry findEncryptedEntry(String path) - throws GeneralSecurityException, IOException { - - CryptHandler handler = CryptHandler.INSTANCE; - - EncryptedEntry matchedEntry = null; - // find closest path which matches with database entry - for (EncryptedEntry encryptedEntry : handler.getAllEntries()) { - if (path.contains(encryptedEntry.getPath())) { - - if (matchedEntry == null - || matchedEntry.getPath().length() < encryptedEntry.getPath().length()) { - matchedEntry = encryptedEntry; - } - } - } - return matchedEntry; - } - - public interface EncryptButtonCallbackInterface { - /** - * Callback fired when user has entered a password for encryption Not called when we've a master - * password set or enable fingerprint authentication - * - * @param password the password entered by user - */ - default void onButtonPressed(@NonNull Intent intent, @NonNull String password) - throws GeneralSecurityException, IOException {} - } - - public interface DecryptButtonCallbackInterface { - /** Callback fired when we've confirmed the password matches the database */ - default void confirm(@NonNull Intent intent) {} - - /** Callback fired when password doesn't match the value entered by user */ - default void failed() {} - } -} diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/files/EncryptDecryptUtils.kt b/app/src/main/java/com/amaze/filemanager/filesystem/files/EncryptDecryptUtils.kt new file mode 100644 index 0000000000..a389000915 --- /dev/null +++ b/app/src/main/java/com/amaze/filemanager/filesystem/files/EncryptDecryptUtils.kt @@ -0,0 +1,315 @@ +/* + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , + * Emmanuel Messulam, Raymond Lai and Contributors. + * + * This file is part of Amaze File Manager. + * + * Amaze File Manager 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 + * along with this program. If not, see . + */ +package com.amaze.filemanager.filesystem.files + +import android.content.Context +import android.content.Intent +import android.content.SharedPreferences +import android.os.Build +import android.util.Base64 +import android.widget.Toast +import androidx.appcompat.widget.AppCompatEditText +import androidx.preference.PreferenceManager +import com.afollestad.materialdialogs.DialogAction +import com.afollestad.materialdialogs.MaterialDialog +import com.amaze.filemanager.R +import com.amaze.filemanager.asynchronous.management.ServiceWatcherUtil +import com.amaze.filemanager.asynchronous.services.DecryptService +import com.amaze.filemanager.asynchronous.services.EncryptService +import com.amaze.filemanager.database.CryptHandler +import com.amaze.filemanager.database.CryptHandler.addEntry +import com.amaze.filemanager.database.models.explorer.EncryptedEntry +import com.amaze.filemanager.fileoperations.filesystem.OpenMode +import com.amaze.filemanager.filesystem.HybridFileParcelable +import com.amaze.filemanager.ui.activities.MainActivity +import com.amaze.filemanager.ui.dialogs.DecryptFingerprintDialog.show +import com.amaze.filemanager.ui.dialogs.GeneralDialogCreation +import com.amaze.filemanager.ui.fragments.MainFragment +import com.amaze.filemanager.ui.fragments.preferencefragments.PreferencesConstants +import com.amaze.filemanager.ui.provider.UtilitiesProvider +import com.amaze.filemanager.utils.PasswordUtil.decryptPassword +import org.slf4j.Logger +import org.slf4j.LoggerFactory +import java.io.IOException +import java.security.GeneralSecurityException + +/** + * Provides useful interfaces and methods for encryption/decryption + * + * @author Emmanuel on 25/5/2017, at 16:55. + */ +object EncryptDecryptUtils { + const val DECRYPT_BROADCAST: String = "decrypt_broadcast" + + private val LOG: Logger = LoggerFactory.getLogger(EncryptDecryptUtils::class.java) + + /** + * Queries database to map path and password. Starts the encryption process after database query + * + * @param path the path of file to encrypt + * @param password the password in plaintext + * @throws GeneralSecurityException Errors on encrypting file/folder + * @throws IOException I/O errors on encrypting file/folder + */ + @JvmStatic + @Throws(GeneralSecurityException::class, IOException::class) + fun startEncryption( + c: Context?, + path: String, + password: String?, + intent: Intent, + ) { + val destPath = + path.substring( + 0, + path.lastIndexOf('/') + 1, + ) + intent.getStringExtra(EncryptService.TAG_ENCRYPT_TARGET) + + // EncryptService.TAG_ENCRYPT_TARGET already has the .aze extension, no need to append again + if (!intent.getBooleanExtra(EncryptService.TAG_AESCRYPT, false)) { + val encryptedEntry = EncryptedEntry(destPath, password) + addEntry(encryptedEntry) + } + // start the encryption process + ServiceWatcherUtil.runService(c, intent) + } + + /** + * Routine to decrypt file. Include branches for AESCrypt, password and fingerprint methods. + */ + @JvmStatic + fun decryptFile( + c: Context, + mainActivity: MainActivity, + main: MainFragment, + openMode: OpenMode, + sourceFile: HybridFileParcelable, + decryptPath: String?, + utilsProvider: UtilitiesProvider, + broadcastResult: Boolean, + ) { + val decryptIntent = Intent(main.context, DecryptService::class.java) + decryptIntent.putExtra(EncryptService.TAG_OPEN_MODE, openMode.ordinal) + decryptIntent.putExtra(EncryptService.TAG_SOURCE, sourceFile) + decryptIntent.putExtra(EncryptService.TAG_DECRYPT_PATH, decryptPath) + val preferences = PreferenceManager.getDefaultSharedPreferences(main.requireContext()) + + if (sourceFile.path.endsWith(CryptUtil.AESCRYPT_EXTENSION)) { + displayDecryptDialogForAescrypt(c, mainActivity, utilsProvider, decryptIntent, main) + } else { + val encryptedEntry: EncryptedEntry? + + try { + encryptedEntry = findEncryptedEntry(sourceFile.path) + } catch (e: GeneralSecurityException) { + LOG.warn("failed to find encrypted entry while decrypting", e) + // we couldn't find any entry in database or lost the key to decipher + toastDecryptionFailure(main) + return + } catch (e: IOException) { + LOG.warn("failed to find encrypted entry while decrypting", e) + toastDecryptionFailure(main) + return + } + + val decryptButtonCallbackInterface: DecryptButtonCallbackInterface = createCallback(main) + + if (encryptedEntry == null && !sourceFile.path.endsWith(CryptUtil.AESCRYPT_EXTENSION)) { + // couldn't find the matching path in database, we lost the password + toastDecryptionFailure(main) + return + } + + when (encryptedEntry!!.password.value) { + PreferencesConstants.ENCRYPT_PASSWORD_FINGERPRINT -> + try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + show( + c, + mainActivity, + decryptIntent, + decryptButtonCallbackInterface, + ) + } else { + throw IllegalStateException("API < M!") + } + } catch (e: GeneralSecurityException) { + LOG.warn("failed to form fingerprint dialog", e) + toastDecryptionFailure(main) + } catch (e: IOException) { + LOG.warn("failed to form fingerprint dialog", e) + toastDecryptionFailure(main) + } catch (e: IllegalStateException) { + LOG.warn("failed to form fingerprint dialog", e) + toastDecryptionFailure(main) + } + + PreferencesConstants.ENCRYPT_PASSWORD_MASTER -> + try { + displayDecryptDialogWithMasterPassword( + c, + mainActivity, + decryptIntent, + utilsProvider, + preferences, + decryptButtonCallbackInterface, + ) + } catch (e: GeneralSecurityException) { + LOG.warn("failed to show decrypt dialog, e") + toastDecryptionFailure(main) + } catch (e: IOException) { + LOG.warn("failed to show decrypt dialog, e") + toastDecryptionFailure(main) + } + + else -> + GeneralDialogCreation.showDecryptDialog( + c, + mainActivity, + decryptIntent, + utilsProvider.appTheme, + encryptedEntry.password.value, + decryptButtonCallbackInterface, + ) + } + } + } + + private fun displayDecryptDialogWithMasterPassword( + c: Context, + mainActivity: MainActivity, + decryptIntent: Intent, + utilsProvider: UtilitiesProvider, + preferences: SharedPreferences, + decryptButtonCallbackInterface: DecryptButtonCallbackInterface, + ) { + GeneralDialogCreation.showDecryptDialog( + c, + mainActivity, + decryptIntent, + utilsProvider.appTheme, + decryptPassword( + c, + preferences.getString( + PreferencesConstants.PREFERENCE_CRYPT_MASTER_PASSWORD, + PreferencesConstants.PREFERENCE_CRYPT_MASTER_PASSWORD_DEFAULT, + )!!, + Base64.DEFAULT, + ), + decryptButtonCallbackInterface, + ) + } + + private fun displayDecryptDialogForAescrypt( + c: Context?, + mainActivity: MainActivity?, + utilsProvider: UtilitiesProvider, + decryptIntent: Intent, + main: MainFragment, + ) { + GeneralDialogCreation.showPasswordDialog( + c!!, + mainActivity!!, + utilsProvider.appTheme, + R.string.crypt_decrypt, + R.string.authenticate_password, + { dialog: MaterialDialog, which: DialogAction? -> + val editText = + dialog.view.findViewById(R.id.singleedittext_input) + decryptIntent.putExtra(EncryptService.TAG_PASSWORD, editText.text.toString()) + ServiceWatcherUtil.runService(main.context, decryptIntent) + dialog.dismiss() + }, + null, + ) + } + + private fun createCallback(main: MainFragment): DecryptButtonCallbackInterface { + return object : DecryptButtonCallbackInterface { + override fun confirm(intent: Intent) { + ServiceWatcherUtil.runService(main.context, intent) + } + + override fun failed() { + Toast.makeText( + main.context, + main.requireMainActivity().getString(R.string.crypt_decryption_fail_password), + Toast.LENGTH_LONG, + ).show() + } + } + } + + private fun toastDecryptionFailure(main: MainFragment) { + Toast.makeText( + main.context, + main.requireMainActivity().getString(R.string.crypt_decryption_fail), + Toast.LENGTH_LONG, + ).show() + } + + /** + * Queries database to find entry for the specific path + * + * @param path the path to match with + * @return the entry + */ + @JvmStatic + @Throws(GeneralSecurityException::class, IOException::class) + private fun findEncryptedEntry(path: String): EncryptedEntry? { + val handler = CryptHandler + + var matchedEntry: EncryptedEntry? = null + // find closest path which matches with database entry + for (encryptedEntry in handler.allEntries) { + if (path.contains(encryptedEntry.path)) { + if (matchedEntry == null || + matchedEntry.path.length < encryptedEntry.path.length + ) { + matchedEntry = encryptedEntry + } + } + } + return matchedEntry + } + + interface EncryptButtonCallbackInterface { + /** + * Callback fired when user has entered a password for encryption Not called when we've a master + * password set or enable fingerprint authentication + * + * @param password the password entered by user + */ + @Throws(GeneralSecurityException::class, IOException::class) + fun onButtonPressed( + intent: Intent, + password: String, + ) { + } + } + + interface DecryptButtonCallbackInterface { + /** Callback fired when we've confirmed the password matches the database */ + fun confirm(intent: Intent) {} + + /** Callback fired when password doesn't match the value entered by user */ + fun failed() {} + } +} diff --git a/app/src/main/java/com/amaze/filemanager/ui/dialogs/DecryptFingerprintDialog.kt b/app/src/main/java/com/amaze/filemanager/ui/dialogs/DecryptFingerprintDialog.kt index f73cfec0ee..0fb2355abb 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/dialogs/DecryptFingerprintDialog.kt +++ b/app/src/main/java/com/amaze/filemanager/ui/dialogs/DecryptFingerprintDialog.kt @@ -22,17 +22,17 @@ package com.amaze.filemanager.ui.dialogs import android.content.Context import android.content.Intent -import android.hardware.fingerprint.FingerprintManager import android.os.Build -import android.view.View import androidx.annotation.RequiresApi -import androidx.appcompat.widget.AppCompatButton -import com.afollestad.materialdialogs.MaterialDialog +import androidx.biometric.BiometricManager +import androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_STRONG +import androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_WEAK +import androidx.biometric.BiometricManager.BIOMETRIC_SUCCESS +import androidx.biometric.BiometricPrompt import com.amaze.filemanager.R import com.amaze.filemanager.filesystem.files.CryptUtil import com.amaze.filemanager.filesystem.files.EncryptDecryptUtils.DecryptButtonCallbackInterface import com.amaze.filemanager.ui.activities.MainActivity -import com.amaze.filemanager.ui.theme.AppTheme import com.amaze.filemanager.utils.FingerprintHandler import java.io.IOException import java.security.GeneralSecurityException @@ -54,26 +54,21 @@ object DecryptFingerprintDialog { c: Context, main: MainActivity, intent: Intent, - appTheme: AppTheme, decryptButtonCallbackInterface: DecryptButtonCallbackInterface, ) { - val accentColor = main.accent - val builder = MaterialDialog.Builder(c) - builder.title(c.getString(R.string.crypt_decrypt)) - val rootView = View.inflate(c, R.layout.dialog_decrypt_fingerprint_authentication, null) - val cancelButton = - rootView.findViewById( - R.id.button_decrypt_fingerprint_cancel, - ) - cancelButton.setTextColor(accentColor) - builder.customView(rootView, true) - builder.canceledOnTouchOutside(false) - builder.theme(appTheme.getMaterialDialogTheme()) - val dialog = builder.show() - cancelButton.setOnClickListener { v: View? -> dialog.cancel() } - val manager = c.getSystemService(FingerprintManager::class.java) - val handler = FingerprintHandler(c, intent, dialog, decryptButtonCallbackInterface) - val `object` = FingerprintManager.CryptoObject(CryptUtil.initCipher()) - handler.authenticate(manager, `object`) + val manager = BiometricManager.from(c) + if (manager.canAuthenticate(BIOMETRIC_STRONG or BIOMETRIC_WEAK) == BIOMETRIC_SUCCESS) { + val promptInfo = + BiometricPrompt.PromptInfo.Builder() + .setTitle(c.getString(R.string.crypt_decrypt)) + .setDescription(c.getString(R.string.crypt_fingerprint_authenticate)) + .setConfirmationRequired(false) + .setNegativeButtonText(c.getString(android.R.string.cancel)) + .build() + + val handler = FingerprintHandler(main, intent, promptInfo, decryptButtonCallbackInterface) + val `object` = BiometricPrompt.CryptoObject(CryptUtil.initCipher()) + handler.authenticate(`object`) + } } } diff --git a/app/src/main/java/com/amaze/filemanager/ui/fragments/MainFragment.java b/app/src/main/java/com/amaze/filemanager/ui/fragments/MainFragment.java index 29c2a3393b..f27bf51aa3 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/fragments/MainFragment.java +++ b/app/src/main/java/com/amaze/filemanager/ui/fragments/MainFragment.java @@ -494,8 +494,8 @@ public void onListItemClicked( .replace(CryptUtil.AESCRYPT_EXTENSION, "")); EncryptDecryptUtils.decryptFile( - getContext(), - getMainActivity(), + requireContext(), + requireMainActivity(), this, mainFragmentViewModel.getOpenMode(), layoutElementParcelable.generateBaseFile(), diff --git a/app/src/main/java/com/amaze/filemanager/utils/FingerprintHandler.java b/app/src/main/java/com/amaze/filemanager/utils/FingerprintHandler.java deleted file mode 100644 index 4e7e6d580b..0000000000 --- a/app/src/main/java/com/amaze/filemanager/utils/FingerprintHandler.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , - * Emmanuel Messulam, Raymond Lai and Contributors. - * - * This file is part of Amaze File Manager. - * - * Amaze File Manager 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 - * along with this program. If not, see . - */ - -package com.amaze.filemanager.utils; - -import com.afollestad.materialdialogs.MaterialDialog; -import com.amaze.filemanager.filesystem.files.EncryptDecryptUtils; - -import android.Manifest; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.hardware.fingerprint.FingerprintManager; -import android.os.Build; -import android.os.CancellationSignal; - -import androidx.annotation.RequiresApi; -import androidx.core.app.ActivityCompat; - -/** Created by vishal on 15/4/17. */ -@RequiresApi(api = Build.VERSION_CODES.M) -public class FingerprintHandler extends FingerprintManager.AuthenticationCallback { - - private Context context; - private EncryptDecryptUtils.DecryptButtonCallbackInterface decryptButtonCallbackInterface; - private Intent decryptIntent; - private MaterialDialog materialDialog; - - // Constructor - public FingerprintHandler( - Context mContext, - Intent intent, - MaterialDialog materialDialog, - EncryptDecryptUtils.DecryptButtonCallbackInterface decryptButtonCallbackInterface) { - context = mContext; - this.decryptIntent = intent; - this.materialDialog = materialDialog; - this.decryptButtonCallbackInterface = decryptButtonCallbackInterface; - } - - @RequiresApi(api = Build.VERSION_CODES.M) - public void authenticate( - FingerprintManager manager, FingerprintManager.CryptoObject cryptoObject) { - - CancellationSignal cancellationSignal = new CancellationSignal(); - if (ActivityCompat.checkSelfPermission(context, Manifest.permission.USE_FINGERPRINT) - != PackageManager.PERMISSION_GRANTED) { - return; - } - manager.authenticate(cryptoObject, cancellationSignal, 0, this, null); - } - - @Override - public void onAuthenticationError(int errMsgId, CharSequence errString) {} - - @Override - public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) {} - - @Override - public void onAuthenticationFailed() { - materialDialog.cancel(); - decryptButtonCallbackInterface.failed(); - } - - @Override - public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) { - - materialDialog.cancel(); - decryptButtonCallbackInterface.confirm(decryptIntent); - } -} diff --git a/app/src/main/java/com/amaze/filemanager/utils/FingerprintHandler.kt b/app/src/main/java/com/amaze/filemanager/utils/FingerprintHandler.kt new file mode 100644 index 0000000000..45f109f483 --- /dev/null +++ b/app/src/main/java/com/amaze/filemanager/utils/FingerprintHandler.kt @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , + * Emmanuel Messulam, Raymond Lai and Contributors. + * + * This file is part of Amaze File Manager. + * + * Amaze File Manager 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 + * along with this program. If not, see . + */ +package com.amaze.filemanager.utils + +import android.Manifest +import android.content.Intent +import android.content.pm.PackageManager +import android.os.Build +import androidx.annotation.RequiresApi +import androidx.biometric.BiometricPrompt +import androidx.biometric.BiometricPrompt.PromptInfo +import androidx.core.app.ActivityCompat +import androidx.core.content.ContextCompat +import com.amaze.filemanager.filesystem.files.EncryptDecryptUtils.DecryptButtonCallbackInterface +import com.amaze.filemanager.ui.activities.MainActivity + +/** Created by vishal on 15/4/17. */ +@RequiresApi(api = Build.VERSION_CODES.M) +class FingerprintHandler( + private val mainActivity: MainActivity, + private val decryptIntent: Intent, + private val promptInfo: PromptInfo, + private val decryptButtonCallbackInterface: DecryptButtonCallbackInterface, +) : BiometricPrompt.AuthenticationCallback() { + /** + * Authenticate user to perform decryption. + */ + @RequiresApi(api = Build.VERSION_CODES.M) + fun authenticate(cryptoObject: BiometricPrompt.CryptoObject) { + if (ActivityCompat.checkSelfPermission(mainActivity, Manifest.permission.USE_FINGERPRINT) + != PackageManager.PERMISSION_GRANTED + ) { + return + } + + val prompt = + BiometricPrompt(mainActivity, ContextCompat.getMainExecutor(mainActivity), this) + prompt.authenticate(promptInfo, cryptoObject) + } + + override fun onAuthenticationError( + errMsgId: Int, + errString: CharSequence, + ) = Unit + + override fun onAuthenticationFailed() { + decryptButtonCallbackInterface.failed() + } + + override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) { + decryptButtonCallbackInterface.confirm(decryptIntent) + } +} diff --git a/app/src/main/res/drawable-hdpi/ic_fingerprint_grey600_36dp.png b/app/src/main/res/drawable-hdpi/ic_fingerprint_grey600_36dp.png deleted file mode 100644 index 263728840a..0000000000 Binary files a/app/src/main/res/drawable-hdpi/ic_fingerprint_grey600_36dp.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/ic_fingerprint_grey600_36dp.png b/app/src/main/res/drawable-mdpi/ic_fingerprint_grey600_36dp.png deleted file mode 100644 index 891cff892a..0000000000 Binary files a/app/src/main/res/drawable-mdpi/ic_fingerprint_grey600_36dp.png and /dev/null differ diff --git a/app/src/main/res/drawable-xhdpi/ic_fingerprint_grey600_36dp.png b/app/src/main/res/drawable-xhdpi/ic_fingerprint_grey600_36dp.png deleted file mode 100644 index 95492488d8..0000000000 Binary files a/app/src/main/res/drawable-xhdpi/ic_fingerprint_grey600_36dp.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_fingerprint_grey600_36dp.png b/app/src/main/res/drawable-xxhdpi/ic_fingerprint_grey600_36dp.png deleted file mode 100644 index 41c72da2f4..0000000000 Binary files a/app/src/main/res/drawable-xxhdpi/ic_fingerprint_grey600_36dp.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_fingerprint_grey600_36dp.png b/app/src/main/res/drawable-xxxhdpi/ic_fingerprint_grey600_36dp.png deleted file mode 100644 index e9fc09daf9..0000000000 Binary files a/app/src/main/res/drawable-xxxhdpi/ic_fingerprint_grey600_36dp.png and /dev/null differ diff --git a/app/src/main/res/layout/dialog_decrypt_fingerprint_authentication.xml b/app/src/main/res/layout/dialog_decrypt_fingerprint_authentication.xml deleted file mode 100644 index 8a7f2a11ae..0000000000 --- a/app/src/main/res/layout/dialog_decrypt_fingerprint_authentication.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/app/src/test/java/com/amaze/filemanager/ui/dialogs/DecryptFingerprintDialogTest.kt b/app/src/test/java/com/amaze/filemanager/ui/dialogs/DecryptFingerprintDialogTest.kt index 838aa2ff65..a3d20c6c30 100644 --- a/app/src/test/java/com/amaze/filemanager/ui/dialogs/DecryptFingerprintDialogTest.kt +++ b/app/src/test/java/com/amaze/filemanager/ui/dialogs/DecryptFingerprintDialogTest.kt @@ -26,7 +26,7 @@ import android.hardware.fingerprint.FingerprintManager import android.os.Build.VERSION_CODES.M import android.os.Build.VERSION_CODES.P import android.os.Environment -import androidx.annotation.RequiresApi +import androidx.test.filters.SdkSuppress import com.afollestad.materialdialogs.MaterialDialog import com.amaze.filemanager.application.AppConfig import com.amaze.filemanager.asynchronous.services.DecryptService.TAG_DECRYPT_PATH @@ -86,7 +86,7 @@ class DecryptFingerprintDialogTest : AbstractEncryptDialogTests() { * Test fingerprint authentication success scenario. */ @Test - @RequiresApi(M) + @SdkSuppress(minSdkVersion = M) fun testDecryptFingerprintDialogSuccess() { performTest( testContent = { @@ -111,7 +111,7 @@ class DecryptFingerprintDialogTest : AbstractEncryptDialogTests() { * Test fingerprint authentication failure scenario. */ @Test - @RequiresApi(M) + @SdkSuppress(minSdkVersion = M) fun testDecryptFingerprintDialogFailed() { performTest( testContent = { @@ -147,7 +147,6 @@ class DecryptFingerprintDialogTest : AbstractEncryptDialogTests() { TAG_DECRYPT_PATH, Environment.getExternalStorageDirectory().absolutePath, ), - activity.appTheme, callback, ) ShadowDialog.getLatestDialog()?.run { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index bc8b19e858..63db3a42e1 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -25,6 +25,7 @@ androidXPref = "1.2.1" androidXPalette = "1.0.0" androidXCardView = "1.0.0" androidXConstraintLayout = "1.1.3" +androidXBiometric = "1.1.0" androidXTest = "1.5.0" androidXTestRunner = "1.5.2" androidXTestExt = "1.1.5" @@ -84,6 +85,7 @@ androidX-multidex = { module = "androidx.multidex:multidex", version.ref = "andr androidX-palette = { module = "androidx.palette:palette-ktx", version.ref = "androidXPalette" } androidX-preference = { module = "androidx.preference:preference-ktx", version.ref = "androidXPref" } androidX-vectordrawable-animated = { module = "androidx.vectordrawable:vectordrawable-animated", version.ref = "vectordrawableAnimated" } +androidX-biometric = { module = "androidx.biometric:biometric", version.ref = "androidXBiometric" } androidX-test-core = { module = "androidx.test:core", version.ref = "androidXTest" } androidX-test-runner = { module = "androidx.test:runner", version.ref = "androidXTestRunner" }