Android - 当用户最小化应用程序时使指纹验证无效,但在跨活动时不会
Android - invalidate Fingerprint authentication when user minimises app but not when when moving across activities
我有一个应用程序,其中使用指纹锁来防止未经指纹验证就使用该应用程序。
问题是验证仅在应用程序首次启动时发生;所以在第一次启动后,直到再次启动该应用程序时才进行验证,
但我希望当前验证在应用程序中的不同活动之间移动时有效,但如果用户最小化应用程序或 phone 屏幕熄灭时它应该无效,以便要求用户验证最小化并恢复到应用程序后再次指纹。
我已经尝试使用基础 activity 并覆盖 onPause 和 onResume,但是即使在跨活动时也会调用这些方法,这不是我想要的。
您可能会使用系统广播意图 ACTION_SCREEN_ON
、ACTION_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>
我有一个应用程序,其中使用指纹锁来防止未经指纹验证就使用该应用程序。 问题是验证仅在应用程序首次启动时发生;所以在第一次启动后,直到再次启动该应用程序时才进行验证, 但我希望当前验证在应用程序中的不同活动之间移动时有效,但如果用户最小化应用程序或 phone 屏幕熄灭时它应该无效,以便要求用户验证最小化并恢复到应用程序后再次指纹。
我已经尝试使用基础 activity 并覆盖 onPause 和 onResume,但是即使在跨活动时也会调用这些方法,这不是我想要的。
您可能会使用系统广播意图 ACTION_SCREEN_ON
、ACTION_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 之外,所有其他项目活动都继承自该 BaseActivitypackage 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>