Android - 当用户最小化应用程序时使指纹验证无效,但在跨活动时不会

Android - invalidate Fingerprint authentication when user minimises app but not when when moving across activities

我有一个应用程序,其中使用指纹锁来防止未经指纹验证就使用该应用程序。 问题是验证仅在应用程序首次启动时发生;所以在第一次启动后,直到再次启动该应用程序时才进行验证, 但我希望当前验证在应用程序中的不同活动之间移动时有效,但如果用户最小化应用程序或 phone 屏幕熄灭时它应该无效,以便要求用户验证最小化并恢复到应用程序后再次指纹。

我已经尝试使用基础 activity 并覆盖 onPause 和 onResume,但是即使在跨活动时也会调用这些方法,这不是我想要的。

您可能会使用系统广播意图 ACTION_SCREEN_ONACTION_SCREEN_OFF、and/or ACTION_USER_PRESENT 来实现您的目标。查看 https://developer.android.com/reference/android/content/Intent 了解更多信息。

为此,请在您的 activity onCreate 中注册为广播接收器以接收这些通知,然后在 onDestroy 中取消注册。您可以使用这些通知来设置标志以指示是否需要身份验证。

示例代码:

    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
    .
    .
    .
        // register to receive appropriate events

        IntentFilter intentUserPresent = new IntentFilter(Intent.ACTION_USER_PRESENT);
        intentUserPresent.addAction(Intent.ACTION_USER_PRESENT);
        registerReceiver(userActionReceiver, intentUserPresent);
        
    .
    .
    .
    }
    

    // create a BroadcastReceiver to receive the notifications
    private BroadcastReceiver userActionReceiver = new BroadcastReceiver()
    {
        @Override
        public void onReceive(Context context, Intent intent)
        {
            Log.d("MyLog", String.format("MainActivity received broadcaast %s", intent.toString()));

            if (intent.getAction() == Intent.ACTION_USER_PRESENT)
            {
               // set flag indicating re-authentication is needed
            }
        }
    };
    
    @Override
    protected void onDestroy()
    {
        // make sure to un-register the receiver!
        unregisterReceiver(userActionReceiver);
        super.onDestroy();
    }

这不会检测到用户“最小化应用程序”,但不清楚为什么您会认为在这种情况下需要重新验证。生命周期事件 onStart/onStop 可以检测到 activity.

的 background/foreground

几周前解决了这个问题,当时我正在做一个与我问这个问题时正在做的项目完全不同的项目。 我很高兴分享我的解决方法。

应用程序最小化三秒或屏幕熄灭后应用程序被锁定;只要在应用程序的首选项设置中启用指纹开关即可。

首先,我创建了一个 BaseActivity class,除了直接从 AppCompatActivity()

继承的 Fingerprint activity class 之外,所有其他项目活动都继承自该 BaseActivity
package com.domainName.appName.ui.activities

import android.content.Intent
import android.os.Bundle
import android.os.Handler
import android.util.Log
import androidx.appcompat.app.AppCompatActivity


open class BaseActivity : AppCompatActivity() {
private var securitySet=false
private var backPressed=false
private var goingToFingerprint=false
companion object{
private var handler:Handler?=null
}
   override fun 
  onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    prefSaver.save(prefFileName, "launched_new_activity",sValue = true,saveImmediately=true)
    if(handler==null){
        handler= Handler(mainLooper)
    }      

  handler?.removeCallbacksAndMessages(null)
    
    }

override fun onDestroy() {
    super.onDestroy()        
handler?.removeCallbacksAndMessages(null)
   }

override fun onStart() {
    handler?.removeCallbacksAndMessages(null)
    goingToFingerprint=false
     securitySet=prefChecker.check(prefFileName,"fingerprint_security_switch",false)
    super.onStart()
}
override fun onPause() {
    super.onPause()
    prefSaver.save(prefFileName,"launched_new_activity",sValue = false,saveImmediately=true)
    if(securitySet && !backPressed){
       if(!goingToFingerprint){postLock()}
    }
}

override fun onResume() {
    super.onResume()
    backPressed=false
    if(prefChecker.check(prefFileName,"app_locked",false)&&securitySet){
        prefSaver.save(prefFileName,"launched_new_activity",sValue = true,saveImmediately=true)
            goingToFingerprint=true
        startActivity(Intent(this,FingerprintActivity::class.java))
    }
}

override fun onBackPressed() {
    backPressed=true
    super.onBackPressed()
}
private fun postLock(){
handler?.removeCallbacksAndMessages(null)
handler?.postDelayed({
      if(!prefChecker.check(prefFileName,"launched_new_activity",false)) {
          prefSaver.save(prefFileName,"app_locked",sValue = true,saveImmediately=true)
      }
},3000)
}

}

我创建了一个 InitialActivity,它在 Android 清单中设置了 noHistory 模式,并充当应用程序启动器 activity。 InitialActivity 也继承自 BaseActivity,并在 MainActivity activity.

的 onCreate 方法中简单地调用 startActivity()

InitialActivity 以这种方式覆盖 onPause :

override fun onPause() {
    if(prefChecker.check(prefFileName,"fingerprint_security_switch",false)) {
        prefSaver.save(prefFileName,"app_locked",sValue = true,true)
/*setting this so app is locked on First Launch if the fingerprint switch is on.
The fingerprint switch is turned on and off from the SettingsActivity */
    }
    super.onPause()
    }

那么FingerprintActivity是这样实现的:

    package com.domainName.appName.ui.activities
import android.Manifest
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Bundle
import android.view.Gravity
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.biometric.BiometricManager
import androidx.biometric.BiometricPrompt
import androidx.biometric.BiometricPrompt.PromptInfo
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat


class FingerprintActivity : AppCompatActivity() {

   private lateinit var executor: Executor
   var biometricPrompt: BiometricPrompt? = null
var promptInfo: PromptInfo? = null
var notifIntentLabel: String? = null
var finishImmediately=false
override fun 
onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState);setContentView(R.layout.fingerprint_main)

    executor = ContextCompat.getMainExecutor(this)
    
    finishImmediately = intent.getBooleanExtra("finishImmediately",false)
    if(finishImmediately)finish()

    biometricPrompt = BiometricPrompt(this,
        executor, object : BiometricPrompt.AuthenticationCallback() {
            override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
                super.onAuthenticationError(errorCode, errString)
                    biometricPrompt?.cancelAuthentication()
                val intent=Intent(this@FingerprintActivity,FingerprintActivity::class.java)
                intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TASK
                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
                intent.putExtra("finishImmediately",true)
                startActivity(intent)
            }

            override fun onAuthenticationSucceeded(
                result: BiometricPrompt.AuthenticationResult
            ) {
                super.onAuthenticationSucceeded(result)
                prefSaver.save(prefFileName,"app_locked",sValue = false,true)
                finish()
            }
        })
    promptInfo = PromptInfo.Builder()
        .setTitle("AppName Biometric Login")
        .setSubtitle("\t\tLog in using your fingerprint")
        .setNegativeButtonText("Cancel")
        .build()
    val biometricManager = BiometricManager.from(this)
    when (biometricManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_WEAK)) {
        BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE -> showToast("No fingerprint hardware detected on this device.")
        BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE -> showToast("Fingerprint hardware features are currently unavailable on this device.")
        BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED -> showToast("There is no any fingerprint credential associated with your account.\nPlease go to your phone settings to enrol fingerprints.")
        BiometricManager.BIOMETRIC_SUCCESS -> authenticateUser()
        BiometricManager.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED -> showToast("Fingerprint feature on device requires updating and is therefore currently unavailable.")
        BiometricManager.BIOMETRIC_ERROR_UNSUPPORTED -> showToast("Fingerprint features not supported on device")
        BiometricManager.BIOMETRIC_STATUS_UNKNOWN -> showToast("Fingerprint features status unknown")
    }
}

private fun showToast(txt_to_show: String) {
    val t = Toast.makeText(this, txt_to_show, Toast.LENGTH_SHORT)
    t.setGravity(Gravity.CENTER or Gravity.CENTER_HORIZONTAL, 0, 0)
    t.show()
}

private fun authenticateUser() {
    if(!finishImmediately){
        if (ActivityCompat.checkSelfPermission(
                this,
                Manifest.permission.USE_FINGERPRINT
            ) == PackageManager.PERMISSION_GRANTED
        ) {
            promptInfo?.let{ biometricPrompt?.authenticate(it)}
        } else if (ActivityCompat.checkSelfPermission(
                this,
                Manifest.permission.USE_BIOMETRIC
            ) == PackageManager.PERMISSION_GRANTED
        ) {
            promptInfo?.let{ biometricPrompt?.authenticate(it)}
        } else {
            showToast("Please grant the AppName app the necessary permissions to use fingerprint hardware.")
        }
    }
}
}

prefSaver 和prefChecker 基本上就是class。这些名称用于描述目的。 class 是这样实现的:

package com.domainName.appName.utils

import android.annotation.SuppressLint
import android.content.Context;
import android.content.SharedPreferences;

class PrefCheckerOrSaver(private val context:Context?){
fun <T>check(sFile: String,keyS: String,defaultS: T):T{
    if(context==null)return defaultS
    val sPref:SharedPreferences= context.getSharedPreferences(sFile,Context.MODE_PRIVATE)
    return when(defaultS){
        is String->sPref.getString(keyS,defaultS) as T
        is Int-> sPref.getInt(keyS,defaultS) as T
        is Long-> sPref.getLong(keyS,defaultS) as T
        is Boolean-> sPref.getBoolean(keyS,defaultS) as T
        else-> defaultS
    }
}
@SuppressLint("ApplySharedPref")
fun save(sFile:String, sKey:String, sValue:Any, saveImmediately:Boolean=false){
    if(context==null)return
    val sharedPreferences:SharedPreferences = context.getSharedPreferences(sFile, Context.MODE_PRIVATE);
    val editor:SharedPreferences.Editor = sharedPreferences.edit();
    when(sValue) {
        is String-> editor.putString(sKey, sValue)
        is Int->editor.putInt(sKey, sValue)
        is Long-> editor.putLong(sKey, sValue)
        is Boolean->editor.putBoolean(sKey, sValue)
    }
    if(saveImmediately){editor.commit()}
    else{ editor.apply()}
}
}

应用清单中的权限

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"
package="com.domainName.appName">

<uses-permission android:name="android.permission.USE_FINGERPRINT" />
<uses-permission android:name="android.permission.USE_BIOMETRIC" />

<application
    android:name=".MainApplication"
    android:allowBackup="false"
    android:icon="${appIcon}"
    android:label="@string/app_name"
    android:roundIcon="${appIconRound}"
    android:resizeableActivity="true"
    android:supportsRtl="false"
    android:theme="@style/ThemeAppMain"
    tools:targetApi="n"
    >

    <activity
        android:name=".ui.activities.InitialActivity"
        android:label="@string/app_name"
        android:exported="true"
        android:noHistory="true"
        >
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>
<activity android:name=".ui.activities.BaseActivity" />
<activity android:name=".ui.activities.MainActivity" android:launchMode="singleTask"/>

<activity android:name=".ui.activities.SettingsActivity" />

<activity android:name=".ui.activities.FingerprintActivity"
android:launchMode="singleTask"
android:noHistory="true"
/>
</application>

</manifest>