merge branch 'auth'

This commit is contained in:
BinTianqi
2024-05-17 13:28:12 +08:00
13 changed files with 344 additions and 44 deletions

View File

@@ -0,0 +1,153 @@
package com.bintianqi.owndroid
import android.annotation.SuppressLint
import android.content.Context
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.biometric.BiometricManager
import androidx.biometric.BiometricPrompt
import androidx.biometric.BiometricPrompt.AuthenticationCallback
import androidx.biometric.BiometricPrompt.PromptInfo.Builder
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Button
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.res.stringResource
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import com.bintianqi.owndroid.ui.theme.OwnDroidTheme
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
class AuthFragment: Fragment() {
@SuppressLint("UnrememberedMutableState")
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
val context = requireContext()
val sharedPref = context.getSharedPreferences("data", Context.MODE_PRIVATE)!!
val canStartAuth = mutableStateOf(true)
val onAuthSucceed = {
val fragmentManager = this.parentFragmentManager
val transaction = fragmentManager.beginTransaction()
transaction.setCustomAnimations(R.anim.enter, R.anim.exit)
transaction.remove(this@AuthFragment).commit()
}
val promptInfo = Builder()
.setTitle(context.getText(R.string.authenticate))
.setSubtitle(context.getText(R.string.auth_with_bio))
.setConfirmationRequired(true)
var fallback = false
val callback = object: AuthenticationCallback() {
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
super.onAuthenticationSucceeded(result)
onAuthSucceed()
}
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
super.onAuthenticationError(errorCode, errString)
when(errorCode){
BiometricPrompt.ERROR_NO_DEVICE_CREDENTIAL -> onAuthSucceed()
BiometricPrompt.ERROR_NEGATIVE_BUTTON -> fallback = true
else -> canStartAuth.value = true
}
Log.e("OwnDroid", errString.toString())
}
}
lifecycleScope.launch(Dispatchers.Main) {
while(true){
if(fallback){
val fallbackPromptInfo = Builder()
.setAllowedAuthenticators(BiometricManager.Authenticators.DEVICE_CREDENTIAL)
.setTitle(context.getText(R.string.authenticate))
.setSubtitle(context.getText(R.string.auth_with_password))
.setConfirmationRequired(true)
.build()
val executor = ContextCompat.getMainExecutor(requireContext())
val biometricPrompt = BiometricPrompt(requireActivity(), executor, callback)
biometricPrompt.authenticate(fallbackPromptInfo)
break
}
delay(50)
}
}
return ComposeView(requireContext()).apply {
setContent {
val materialYou = mutableStateOf(sharedPref.getBoolean("material_you",true))
val blackTheme = mutableStateOf(sharedPref.getBoolean("black_theme", false))
OwnDroidTheme(materialYou.value, blackTheme.value) {
Auth(this@AuthFragment, promptInfo, callback, canStartAuth)
}
}
}
}
}
@Composable
fun Auth(activity: Fragment, promptInfo: Builder, callback: AuthenticationCallback, canStartAuth: MutableState<Boolean>) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
modifier = Modifier.fillMaxSize().background(MaterialTheme.colorScheme.background)
){
Text(
text = stringResource(R.string.authenticate),
style = MaterialTheme.typography.headlineLarge,
color = MaterialTheme.colorScheme.onBackground
)
LaunchedEffect(Unit){
delay(300)
startAuth(activity, promptInfo, callback)
canStartAuth.value = false
}
Button(
onClick = {
startAuth(activity, promptInfo, callback)
canStartAuth.value = false
},
enabled = canStartAuth.value
){
Text(text = stringResource(R.string.start))
}
}
}
private fun startAuth(activity: Fragment, promptInfo: Builder, callback: AuthenticationCallback){
val context = activity.requireContext()
val bioManager = BiometricManager.from(context)
val sharedPref = context.getSharedPreferences("data", Context.MODE_PRIVATE)
if(sharedPref.getBoolean("bio_auth", false)){
when(BiometricManager.BIOMETRIC_SUCCESS){
bioManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_STRONG) ->
promptInfo
.setAllowedAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG)
.setNegativeButtonText(context.getText(R.string.use_password))
bioManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_WEAK) ->
promptInfo
.setAllowedAuthenticators(BiometricManager.Authenticators.BIOMETRIC_WEAK)
.setNegativeButtonText(context.getText(R.string.use_password))
else -> promptInfo
.setAllowedAuthenticators(BiometricManager.Authenticators.DEVICE_CREDENTIAL)
.setSubtitle(context.getText(R.string.auth_with_password))
}
}else{
promptInfo
.setAllowedAuthenticators(BiometricManager.Authenticators.DEVICE_CREDENTIAL)
.setSubtitle(context.getText(R.string.auth_with_password))
}
val executor = ContextCompat.getMainExecutor(context)
val biometricPrompt = BiometricPrompt(activity, executor, callback)
biometricPrompt.authenticate(promptInfo.build())
}

View File

@@ -6,9 +6,11 @@ import android.content.ComponentName
import android.content.Context
import android.os.Build.VERSION
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
@@ -27,12 +29,15 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.core.view.WindowCompat
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
@@ -45,21 +50,65 @@ import java.util.Locale
var backToHome = false
@ExperimentalMaterial3Api
class MainActivity : ComponentActivity() {
class MainActivity : FragmentActivity() {
private var auth = false
@SuppressLint("UnrememberedMutableState")
override fun onCreate(savedInstanceState: Bundle?) {
registerActivityResult(this)
enableEdgeToEdge()
WindowCompat.setDecorFitsSystemWindows(window, false)
super.onCreate(savedInstanceState)
registerActivityResult(this)
val locale = applicationContext.resources.configuration.locale
zhCN = locale==Locale.SIMPLIFIED_CHINESE||locale==Locale.CHINESE||locale==Locale.CHINA
setContentView(R.layout.base)
val sharedPref = applicationContext.getSharedPreferences("data", Context.MODE_PRIVATE)
setContent {
val materialYou = mutableStateOf(sharedPref.getBoolean("material_you",true))
val blackTheme = mutableStateOf(sharedPref.getBoolean("black_theme", false))
OwnDroidTheme(materialYou.value, blackTheme.value){
Home(materialYou, blackTheme)
val fragmentManager = supportFragmentManager
val transaction = fragmentManager.beginTransaction()
transaction.add(R.id.base, HomeFragment(), "home")
if(sharedPref.getBoolean("auth", false)){
transaction.add(R.id.base, AuthFragment(), "auth")
}
transaction.commit()
val locale = applicationContext.resources?.configuration?.locale
zhCN = locale==Locale.SIMPLIFIED_CHINESE||locale==Locale.CHINESE||locale==Locale.CHINA
}
override fun onResume() {
super.onResume()
if(auth){
val sharedPref = applicationContext.getSharedPreferences("data", Context.MODE_PRIVATE)
if(
sharedPref.getBoolean("auth", false) &&
sharedPref.getBoolean("lock_in_background", false)
){
val fragmentManager = supportFragmentManager
val fragment = fragmentManager.findFragmentByTag("auth")
if(fragment == null){
val transaction = fragmentManager.beginTransaction()
transaction.setCustomAnimations(R.anim.enter, R.anim.exit)
transaction.add(R.id.base, AuthFragment(), "auth").commit()
}
}
auth = false
}
}
override fun onRestart() {
super.onRestart()
auth = true
}
}
class HomeFragment: Fragment() {
@OptIn(ExperimentalMaterial3Api::class)
@SuppressLint("UnrememberedMutableState")
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
val sharedPref = context?.getSharedPreferences("data", Context.MODE_PRIVATE)!!
return ComposeView(requireContext()).apply {
setContent {
val materialYou = mutableStateOf(sharedPref.getBoolean("material_you",true))
val blackTheme = mutableStateOf(sharedPref.getBoolean("black_theme", false))
OwnDroidTheme(materialYou.value, blackTheme.value){
Home(materialYou, blackTheme)
}
}
}
}
@@ -96,18 +145,18 @@ fun Home(materialYou:MutableState<Boolean>, blackTheme:MutableState<Boolean>){
popEnterTransition = Animations.navHostPopEnterTransition,
popExitTransition = Animations.navHostPopExitTransition
){
composable(route = "HomePage", content = { HomePage(navCtrl, pkgName)})
composable(route = "SystemManage", content = { SystemManage(navCtrl) })
composable(route = "ManagedProfile", content = {ManagedProfile(navCtrl)})
composable(route = "Permissions", content = { DpmPermissions(navCtrl)})
composable(route = "ApplicationManage", content = { ApplicationManage(navCtrl, pkgName, dialogStatus)})
composable(route = "UserRestriction", content = { UserRestriction(navCtrl)})
composable(route = "UserManage", content = { UserManage(navCtrl)})
composable(route = "Password", content = { Password(navCtrl)})
composable(route = "AppSetting", content = { AppSetting(navCtrl, materialYou, blackTheme)})
composable(route = "Network", content = {Network(navCtrl)})
composable(route = "PackageSelector"){PackageSelector(navCtrl, pkgName)}
composable(route = "PermissionPicker"){PermissionPicker(navCtrl)}
composable(route = "HomePage"){ HomePage(navCtrl, pkgName) }
composable(route = "SystemManage"){ SystemManage(navCtrl) }
composable(route = "ManagedProfile"){ ManagedProfile(navCtrl) }
composable(route = "Permissions"){ DpmPermissions(navCtrl) }
composable(route = "ApplicationManage"){ ApplicationManage(navCtrl, pkgName, dialogStatus)}
composable(route = "UserRestriction"){ UserRestriction(navCtrl) }
composable(route = "UserManage"){ UserManage(navCtrl) }
composable(route = "Password"){ Password(navCtrl) }
composable(route = "AppSetting"){ AppSetting(navCtrl, materialYou, blackTheme) }
composable(route = "Network"){ Network(navCtrl) }
composable(route = "PackageSelector"){ PackageSelector(navCtrl, pkgName) }
composable(route = "PermissionPicker"){ PermissionPicker(navCtrl) }
}
LaunchedEffect(Unit){
val profileInited = sharedPref.getBoolean("ManagedProfileActivated",false)

View File

@@ -5,18 +5,13 @@ import android.content.Intent
import android.net.Uri
import android.os.Build.VERSION
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.MaterialTheme.typography
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
@@ -26,10 +21,7 @@ import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController
import com.bintianqi.owndroid.ui.Animations
import com.bintianqi.owndroid.ui.SubPageItem
import com.bintianqi.owndroid.ui.SwitchItem
import com.bintianqi.owndroid.ui.TopBar
import com.bintianqi.owndroid.ui.*
@Composable
fun AppSetting(navCtrl:NavHostController, materialYou: MutableState<Boolean>, blackTheme: MutableState<Boolean>){
@@ -56,9 +48,10 @@ fun AppSetting(navCtrl:NavHostController, materialYou: MutableState<Boolean>, bl
popExitTransition = Animations.navHostPopExitTransition,
modifier = Modifier.padding(top = it.calculateTopPadding())
){
composable(route = "Home"){Home(localNavCtrl)}
composable(route = "Settings"){Settings(materialYou, blackTheme)}
composable(route = "About"){About()}
composable(route = "Home"){ Home(localNavCtrl) }
composable(route = "Theme"){ ThemeSettings(materialYou, blackTheme) }
composable(route = "Auth"){ AuthSettings() }
composable(route = "About"){ About() }
}
}
}
@@ -66,13 +59,14 @@ fun AppSetting(navCtrl:NavHostController, materialYou: MutableState<Boolean>, bl
@Composable
private fun Home(navCtrl: NavHostController){
Column(modifier = Modifier.fillMaxSize()){
SubPageItem(R.string.setting,"",R.drawable.settings_fill0){navCtrl.navigate("Settings")}
SubPageItem(R.string.theme,"",R.drawable.format_paint_fill0){navCtrl.navigate("Theme")}
SubPageItem(R.string.security,"",R.drawable.lock_fill0){navCtrl.navigate("Auth")}
SubPageItem(R.string.about,"",R.drawable.info_fill0){navCtrl.navigate("About")}
}
}
@Composable
private fun Settings(materialYou:MutableState<Boolean>, blackTheme:MutableState<Boolean>){
private fun ThemeSettings(materialYou:MutableState<Boolean>, blackTheme:MutableState<Boolean>){
val sharedPref = LocalContext.current.getSharedPreferences("data", Context.MODE_PRIVATE)
Column(modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState())) {
if(VERSION.SDK_INT>=31){
@@ -98,6 +92,39 @@ private fun Settings(materialYou:MutableState<Boolean>, blackTheme:MutableState<
}
}
@Composable
private fun AuthSettings(){
val sharedPref = LocalContext.current.getSharedPreferences("data", Context.MODE_PRIVATE)
var auth by remember{ mutableStateOf(sharedPref.getBoolean("auth",false)) }
Column(modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState())) {
SwitchItem(
R.string.lock_owndroid, "", null,
{ auth },
{
sharedPref.edit().putBoolean("auth",it).apply()
auth = sharedPref.getBoolean("auth",false)
}
)
if(auth){
SwitchItem(
R.string.enable_bio_auth, "", null,
{ sharedPref.getBoolean("bio_auth",false) },
{ sharedPref.edit().putBoolean("bio_auth",it).apply() }
)
SwitchItem(
R.string.lock_in_background, "", null,
{ sharedPref.getBoolean("lock_in_background",false) },
{ sharedPref.edit().putBoolean("lock_in_background",it).apply() }
)
}
Box(modifier = Modifier.padding(horizontal = 8.dp)){
Information {
Text(text = stringResource(R.string.auth_on_start))
}
}
}
}
@Composable
private fun About(){
val context = LocalContext.current