diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 2cfc0bb9c82895e091d217017e2e9a5fa7eab745..70c1c612b9017e04cf9d3503626f38cb31be7b51 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -18,8 +18,8 @@ android { versionProps["VERSION_CHANGE"] = "0" versionProps["VERSION_MAJOR"] = "1" versionProps["VERSION_MINOR"] = "0" - versionProps["VERSION_PATCH"] = "0" - versionProps["VERSION_CODE"] = "1" + versionProps["VERSION_PATCH"] = "1" + versionProps["VERSION_CODE"] = "2" // Attempt to write properties to the file versionPropsFile.writer().use { writer -> versionProps.store(writer, null) } @@ -71,7 +71,7 @@ android { return "${versionMajor}.${versionMinor}.${versionPatch}" } - return "1.0" + return "1.0.1" } defaultConfig { diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index c6631449afd4c4b08b60e9f8b7465ddcdf82da62..7c7cc4163662f586de5e4af00af54f8e0f0da20e 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -3,13 +3,8 @@ xmlns:tools="http://schemas.android.com/tools" android:sharedUserId="android.uid.system"> - - - diff --git a/app/src/main/java/foundation/e/parentalcontrol/DeviceAdmin.kt b/app/src/main/java/foundation/e/parentalcontrol/DeviceAdmin.kt index 0c56b8af676e12439a143a69b688505d6dc8efc2..1aa6d22449f9dfef220fb29140efa1273d7b176f 100644 --- a/app/src/main/java/foundation/e/parentalcontrol/DeviceAdmin.kt +++ b/app/src/main/java/foundation/e/parentalcontrol/DeviceAdmin.kt @@ -16,6 +16,8 @@ import android.util.Log import android.widget.Toast import foundation.e.parentalcontrol.ui.view.MainUI import foundation.e.parentalcontrol.utils.Constants +import foundation.e.parentalcontrol.utils.PrefsUtils +import foundation.e.parentalcontrol.utils.SystemUtils import java.util.Objects class DeviceAdmin : DeviceAdminReceiver() { @@ -52,7 +54,9 @@ class DeviceAdmin : DeviceAdminReceiver() { Intent.ACTION_MY_PACKAGE_REPLACED, Intent.ACTION_MY_PACKAGE_SUSPENDED, Constants.RESTART_SERVICE -> { - showToast(context, R.string.parental_control_restarted) + if (BuildConfig.DEBUG) { + showToast(context, R.string.parental_control_restarted) + } setSettings(context) } else -> super.onReceive(context, intent) @@ -60,11 +64,8 @@ class DeviceAdmin : DeviceAdminReceiver() { } override fun onEnabled(context: Context, intent: Intent) { - isAdminActive = isAdminActive(context) - if (isAdminActive) { - showToast(context, R.string.parental_control_is_activated) - setSettings(context) - } + showToast(context, R.string.parental_control_is_activated) + setSettings(context) } private fun setSettings(context: Context) { @@ -78,7 +79,25 @@ class DeviceAdmin : DeviceAdminReceiver() { } override fun onDisabled(context: Context, intent: Intent) { - isAdminActive = isAdminActive(context) + PrefsUtils.init(context) + PrefsUtils.clearAll() + } + + @Suppress("DEPRECATION") + fun removeAdmin(context: Context) { + val devicePolicyManager: DevicePolicyManager = getDevicePolicyManager(context) + SystemUtils.safeSetProp("persist.sys.mdm_active", "0") + val mainUI = MainUI(context) + mainUI.clearDefaultRestrictions() + if (isDeviceOwnerApp(context)) { + devicePolicyManager.clearDeviceOwnerApp(context.packageName) + } else if (isProfileOwner(context)) { + devicePolicyManager.clearProfileOwner(ComponentName(context, DeviceAdmin::class.java)) + } + } + + fun setAdmin() { + SystemUtils.safeSetProp("persist.sys.mdm_active", "1") } fun isAdminActive(context: Context): Boolean { diff --git a/app/src/main/java/foundation/e/parentalcontrol/MainActivity.kt b/app/src/main/java/foundation/e/parentalcontrol/MainActivity.kt index b6c73a2d20c630508bf7a4e9271137e97d2fa29d..b4259e50b8463927e5d57e3764304598638d4c12 100644 --- a/app/src/main/java/foundation/e/parentalcontrol/MainActivity.kt +++ b/app/src/main/java/foundation/e/parentalcontrol/MainActivity.kt @@ -7,11 +7,14 @@ */ package foundation.e.parentalcontrol +import android.accounts.AccountManager import android.app.admin.DevicePolicyManager -import android.content.ComponentName import android.content.Context import android.content.Intent +import android.os.Build import android.os.Bundle +import android.os.Handler +import android.os.Looper import android.os.UserManager import android.provider.Settings import android.widget.Toast @@ -64,8 +67,7 @@ import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.input.PasswordVisualTransformation import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import androidx.lifecycle.lifecycleScope -import foundation.e.parentalcontrol.data.Ages +import foundation.e.elib.BuildConfig import foundation.e.parentalcontrol.data.AuthenticationType import foundation.e.parentalcontrol.data.Pages import foundation.e.parentalcontrol.data.isNotLoggedIn @@ -77,9 +79,10 @@ import foundation.e.parentalcontrol.ui.text.textFieldColor import foundation.e.parentalcontrol.ui.text.textFieldColorError import foundation.e.parentalcontrol.ui.theme.ParentalControlTheme import foundation.e.parentalcontrol.ui.view.AskPassword +import foundation.e.parentalcontrol.ui.view.AuthenticationTypeSelectionView import foundation.e.parentalcontrol.ui.view.MainUI import foundation.e.parentalcontrol.ui.view.SelectAge -import foundation.e.parentalcontrol.ui.view.authenticationTypeSelectionView +import foundation.e.parentalcontrol.ui.view.selectedAge import foundation.e.parentalcontrol.utils.Constants import foundation.e.parentalcontrol.utils.CryptUtils import foundation.e.parentalcontrol.utils.PrefsUtils @@ -87,53 +90,40 @@ import foundation.e.parentalcontrol.utils.SystemUtils import kotlinx.coroutines.delay import kotlinx.coroutines.launch -var isAdminActive by mutableStateOf(false) -var selectedAuthenticationType by mutableStateOf(null) -var selectedAge: Ages? by mutableStateOf(null) - class MainActivity : ComponentActivity() { private var mActivity: ComponentActivity = this - private var passText by mutableStateOf("") private val dA: DeviceAdmin = DeviceAdmin() + + private var passText by mutableStateOf("") private var confirmPassText by mutableStateOf("") - private var enteringConfirmation by mutableStateOf(false) - private var isSetupFinished = false - private var passwordConfirmation by mutableStateOf(false) - private lateinit var currentPage: Pages - private var showMultiUsersDialog by mutableStateOf(false) - private var showLoginDialog by mutableStateOf(false) - private lateinit var mUserManager: UserManager override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) PrefsUtils.init(mActivity) - isSetupFinished = SystemUtils.isSetupFinished(mActivity) - selectedAge = PrefsUtils.getAge() - mUserManager = getSystemService(Context.USER_SERVICE) as UserManager - showMultiUsersDialog = mUserManager.userCount > 1 onStartUp() } private fun onStartUp() { - isAdminActive = isAdminActive() - lifecycleScope.launch { - val appLoungeData = AppLoungeData() - val loginStatus = appLoungeData.getLoginInfo(mActivity) - showLoginDialog = isNotLoggedIn(loginStatus) - } if (SystemUtils.getUserId() != 0) { mainScreen(page = Pages.NotMainUser) - } else if (isAdminActive) { + } else if (isAdminSet()) { mainScreen(page = Pages.ActivateAdmin) - } else if (!isSetupFinished) { + } else if (!SystemUtils.isSetupFinished(mActivity)) { mainScreen(page = Pages.SelectAge) } else { mainScreen(page = Pages.TempActivateAdmin) } } + private fun isAdminSet(): Boolean { + return isAdminActive() && + ((PrefsUtils.getAuthType() == AuthenticationType.PIN || + PrefsUtils.getAuthType() == AuthenticationType.PASSWORD) && + !PrefsUtils.getPassword().isNullOrEmpty()) + } + @Composable fun SetRestrictionsScreen() { BackHandler(onBack = { onStartUp() }) @@ -152,7 +142,7 @@ class MainActivity : ComponentActivity() { } private fun onExitApp(withResult: Boolean = false) { - if (withResult && !isSetupFinished) { + if (withResult && !SystemUtils.isSetupFinished(mActivity)) { setResult(RESULT_OK) } finishAfterTransition() @@ -167,7 +157,7 @@ class MainActivity : ComponentActivity() { fun SetupPinPassword() { val scope = rememberCoroutineScope() val keyboardController = LocalSoftwareKeyboardController.current - var isPasswordValid by remember { mutableStateOf(isPinPasswordValid()) } + var isPasswordValid by remember { mutableStateOf(false) } val focusRequester = FocusRequester() fun onBackPress() { @@ -177,7 +167,7 @@ class MainActivity : ComponentActivity() { @Composable fun titleText(): String { - return if (selectedAuthenticationType == AuthenticationType.PIN) + return if (PrefsUtils.getAuthType() == AuthenticationType.PIN) stringResource(R.string.enter_new_pin) else stringResource(R.string.enter_new_password) } @@ -193,7 +183,6 @@ class MainActivity : ComponentActivity() { ) { fun checkPassword() { if (isPasswordValid) { - enteringConfirmation = true mainScreen(page = Pages.SetupConfirmPinPassword) } } @@ -208,7 +197,7 @@ class MainActivity : ComponentActivity() { value = passText, onValueChange = { passText = it - isPasswordValid = isPinPasswordValid() + isPasswordValid = isPinPasswordValid(it) }, label = { Text( @@ -219,7 +208,7 @@ class MainActivity : ComponentActivity() { keyboardOptions = KeyboardOptions.Default.copy( keyboardType = - if (selectedAuthenticationType == AuthenticationType.PIN) + if (PrefsUtils.getAuthType() == AuthenticationType.PIN) KeyboardType.NumberPassword else KeyboardType.Password, imeAction = ImeAction.Done @@ -241,7 +230,7 @@ class MainActivity : ComponentActivity() { Text( text = - if (selectedAuthenticationType == AuthenticationType.PIN) + if (PrefsUtils.getAuthType() == AuthenticationType.PIN) stringResource(R.string.pin_requirements) else stringResource(R.string.password_requirements), Modifier.padding(10.dp), @@ -262,8 +251,8 @@ class MainActivity : ComponentActivity() { } } - private fun isPinPasswordValid(pass: String = passText): Boolean { - return if (selectedAuthenticationType == AuthenticationType.PIN) + private fun isPinPasswordValid(pass: String): Boolean { + return if (PrefsUtils.getAuthType() == AuthenticationType.PIN) pass.length >= 4 && pass.all { it.isDigit() } else pass.length >= 8 } @@ -273,7 +262,7 @@ class MainActivity : ComponentActivity() { val scope = rememberCoroutineScope() val context = LocalContext.current val keyboardController = LocalSoftwareKeyboardController.current - var isPasswordValid by remember { mutableStateOf(isPinPasswordValid()) } + var isPasswordValid by remember { mutableStateOf(false) } val focusRequester = FocusRequester() var isError by remember { mutableStateOf(false) } @@ -284,7 +273,7 @@ class MainActivity : ComponentActivity() { @Composable fun titleText(): String { - return if (selectedAuthenticationType == AuthenticationType.PIN) + return if (PrefsUtils.getAuthType() == AuthenticationType.PIN) stringResource(R.string.confirm_pin) else stringResource(R.string.confirm_password) } @@ -308,34 +297,33 @@ class MainActivity : ComponentActivity() { putString(Constants.PREF_PASSWORD, encryptedPass) putBoolean( Constants.PREF_PIN_SET, - selectedAuthenticationType == AuthenticationType.PIN + PrefsUtils.getAuthType() == AuthenticationType.PIN ) putBoolean( Constants.PREF_PASSWORD_SET, - selectedAuthenticationType == AuthenticationType.Password + PrefsUtils.getAuthType() == AuthenticationType.PASSWORD ) - putBoolean(Constants.PREF_FIRST_TIME, false) apply() } isError = false passText = "" confirmPassText = "" - enteringConfirmation = false onSetAge() - if (isSetupFinished) { - mainScreen(page = Pages.ActivateAdmin) - } + if (!isAdminActive()) { - setAdmin() + dA.setAdmin() } - if (!isSetupFinished) { + + if (SystemUtils.isSetupFinished(mActivity)) { + Handler(Looper.getMainLooper()).postDelayed({ onStartUp() }, 500L) + } else { onExitApp(true) } } else { isError = true if ( - selectedAuthenticationType == AuthenticationType.PIN && + PrefsUtils.getAuthType() == AuthenticationType.PIN && !passText.all { it.isDigit() } ) { Toast.makeText( @@ -348,7 +336,7 @@ class MainActivity : ComponentActivity() { // Passwords do not match, show error message Toast.makeText( context, - if (selectedAuthenticationType == AuthenticationType.PIN) + if (PrefsUtils.getAuthType() == AuthenticationType.PIN) getString(R.string.pin_mismatch) else getString(R.string.password_mismatch), Toast.LENGTH_SHORT @@ -371,14 +359,14 @@ class MainActivity : ComponentActivity() { onValueChange = { confirmPassText = it isError = false - isPasswordValid = isPinPasswordValid(confirmPassText) + isPasswordValid = isPinPasswordValid(it) }, label = { Text(titleText()) }, visualTransformation = PasswordVisualTransformation(), keyboardOptions = KeyboardOptions.Default.copy( keyboardType = - if (selectedAuthenticationType == AuthenticationType.PIN) + if (PrefsUtils.getAuthType() == AuthenticationType.PIN) KeyboardType.NumberPassword else KeyboardType.Password, imeAction = ImeAction.Done @@ -400,7 +388,7 @@ class MainActivity : ComponentActivity() { Text( text = - if (selectedAuthenticationType == AuthenticationType.PIN) + if (PrefsUtils.getAuthType() == AuthenticationType.PIN) stringResource(R.string.pin_confirm_text) else stringResource(R.string.password_confirm_text), Modifier.padding(10.dp), @@ -424,21 +412,10 @@ class MainActivity : ComponentActivity() { return dA.isAdminActive(mActivity) } - @Suppress("DEPRECATION") - private fun removeAdmin() { - val devicePolicyManager: DevicePolicyManager = dA.getDevicePolicyManager(mActivity) - SystemUtils.safeSetProp("persist.sys.mdm_active", "0") - MainUI(mActivity).clearDefaultRestrictions() - if (dA.isDeviceOwnerApp(mActivity)) { - devicePolicyManager.clearDeviceOwnerApp(mActivity.packageName) - } else if (dA.isProfileOwner(mActivity)) { - devicePolicyManager.clearProfileOwner(ComponentName(mActivity, DeviceAdmin::class.java)) - } - PrefsUtils.clearAll() - } - - private fun setAdmin() { - SystemUtils.safeSetProp("persist.sys.mdm_active", "1") + private fun noAccountsLoggedIn(): Boolean { + val accountManager = AccountManager.get(mActivity) + val accounts = accountManager.accounts + return accounts.isEmpty() } @Composable @@ -452,9 +429,122 @@ class MainActivity : ComponentActivity() { horizontalAlignment = Alignment.Start, verticalArrangement = Arrangement.Top ) { - if (showMultiUsersDialog) { + val devicePolicyManager: DevicePolicyManager = dA.getDevicePolicyManager(mActivity) + val activeAdmins = devicePolicyManager.activeAdmins + + if ( + !activeAdmins.isNullOrEmpty() && !activeAdmins.contains(dA.getAdminName(mActivity)) + ) { + AlertDialog( + onDismissRequest = { onExitApp() }, + title = { + Row( + verticalAlignment = Alignment.CenterVertically, + ) { + Icon( + imageVector = Icons.Default.Info, + contentDescription = "Info icon", + tint = Color.Gray + ) + Text( + text = stringResource(R.string.parental_control_is_blocked), + fontSize = 20.sp, + modifier = Modifier.padding(start = 10.dp) + ) + } + }, + text = { + Text( + text = + stringResource(R.string.another_device_admin_app_summary) + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) + stringResource(R.string.settings_device_admin_apps_t) + else stringResource(R.string.settings_device_admin_apps_s), + color = + colorResource(id = foundation.e.elib.R.color.e_secondary_text_color) + ) + }, + confirmButton = { + Text( + modifier = + Modifier.clickable { + val intent = Intent(Settings.ACTION_SECURITY_SETTINGS) + startActivity(intent) + } + .padding(start = 10.dp), + color = colorResource(id = foundation.e.elib.R.color.e_accent), + text = stringResource(R.string.open_settings).uppercase(), + fontSize = 14.sp + ) + }, + dismissButton = { + Text( + modifier = Modifier.clickable { onExitApp() }, + color = colorResource(id = foundation.e.elib.R.color.e_accent), + text = stringResource(R.string.discard).uppercase(), + fontSize = 14.sp + ) + }, + shape = RoundedCornerShape(4.dp) + ) + } + + if (!noAccountsLoggedIn()) { AlertDialog( - onDismissRequest = { showMultiUsersDialog = false }, + onDismissRequest = { onExitApp() }, + title = { + Row( + verticalAlignment = Alignment.CenterVertically, + ) { + Icon( + imageVector = Icons.Default.Info, + contentDescription = "Info icon", + tint = Color.Gray + ) + Text( + text = stringResource(R.string.parental_control_is_blocked), + fontSize = 20.sp, + modifier = Modifier.padding(start = 10.dp) + ) + } + }, + text = { + Text( + text = stringResource(R.string.remove_logged_in_accounts), + color = + colorResource(id = foundation.e.elib.R.color.e_secondary_text_color) + ) + }, + confirmButton = { + Text( + modifier = + Modifier.clickable { + val intent = Intent(Settings.ACTION_SYNC_SETTINGS) + startActivity(intent) + } + .padding(start = 10.dp), + color = colorResource(id = foundation.e.elib.R.color.e_accent), + text = stringResource(R.string.open_settings).uppercase(), + fontSize = 14.sp + ) + }, + dismissButton = { + Text( + modifier = Modifier.clickable { onExitApp() }, + color = colorResource(id = foundation.e.elib.R.color.e_accent), + text = stringResource(R.string.discard).uppercase(), + fontSize = 14.sp + ) + }, + shape = RoundedCornerShape(4.dp) + ) + } + + val userManager = getSystemService(Context.USER_SERVICE) as UserManager + var showDialog by remember { mutableStateOf(userManager.userCount > 1) } + if (showDialog) { + AlertDialog( + onDismissRequest = { showDialog = false }, title = { Row( verticalAlignment = Alignment.CenterVertically, @@ -482,8 +572,8 @@ class MainActivity : ComponentActivity() { Text( modifier = Modifier.clickable { - showMultiUsersDialog = false startActivity(Intent(Settings.ACTION_USER_SETTINGS)) + showDialog = false } .padding(start = 10.dp), color = colorResource(id = foundation.e.elib.R.color.e_accent), @@ -493,7 +583,7 @@ class MainActivity : ComponentActivity() { }, dismissButton = { Text( - modifier = Modifier.clickable { showMultiUsersDialog = false }, + modifier = Modifier.clickable { showDialog = false }, color = colorResource(id = foundation.e.elib.R.color.e_accent), text = stringResource(R.string.discard).uppercase(), fontSize = 14.sp @@ -508,15 +598,16 @@ class MainActivity : ComponentActivity() { color = colorResource(foundation.e.elib.R.color.e_primary_text_color), ) - var checkedState by remember { mutableStateOf(isAdminActive) } + var checkedState by remember { mutableStateOf(false) } var moveToNext by remember { mutableStateOf(false) } val coroutineScope = rememberCoroutineScope() ToggleWithText( text = stringResource(R.string.activate_parental_control), - isChecked = (isAdminActive || isAdminActive()) || checkedState, + isChecked = checkedState, onCheckedChange = { checkedState = it + PrefsUtils.clearAll() coroutineScope.launch { delay(200) moveToNext = true @@ -537,16 +628,20 @@ class MainActivity : ComponentActivity() { CustomTopAppBar(title = stringResource(R.string.app_name), onClick = { onExitApp() }) + var showLoginDialog by remember { mutableStateOf(false) } + + LaunchedEffect(Unit) { + val appLoungeData = AppLoungeData() + val loginStatus = appLoungeData.getLoginInfo(mActivity) + showLoginDialog = isNotLoggedIn(loginStatus) + } + Column( modifier = Modifier.fillMaxSize().padding(start = 24.dp, end = 24.dp), horizontalAlignment = Alignment.Start, verticalArrangement = Arrangement.Top ) { - if ( - showLoginDialog && - PrefsUtils.getBlockedApps() == null && - (isAdminActive || isAdminActive()) - ) { + if (showLoginDialog && PrefsUtils.getBlockedApps() == null) { val mainUI = MainUI(mActivity) AlertDialog( onDismissRequest = { @@ -623,48 +718,34 @@ class MainActivity : ComponentActivity() { ToggleWithText( text = stringResource(R.string.activate_parental_control), - isChecked = isAdminActive, + isChecked = isAdminActive(), onCheckedChange = { if (it) { - setAdmin() - } else if (PrefsUtils.isPinSet() || PrefsUtils.isPasswordSet()) { - mainScreen(page = Pages.AskPasswordForAdminRemoval) + dA.setAdmin() } else { - removeAdmin() + mainScreen(page = Pages.AskPasswordForAdminRemoval) } } ) - if (isAdminActive && PrefsUtils.isPinSet() || PrefsUtils.isPasswordSet()) { - val mainUI = MainUI(mActivity) - SelectAge( - onRadioClick = { - selectedAge = it - mainScreen(page = Pages.AskPasswordForAgeReset) - } - ) - - if (currentPage == Pages.ActivateAdmin && passwordConfirmation) { - onSetAge() - passwordConfirmation = false - onStartUp() - mainUI.blockBlackListedApps() - } + SelectAge( + onRadioClick = { mainScreen(page = Pages.AskPasswordForAgeReset) }, + onNextClick = {} + ) - Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { - Text( - text = stringResource(R.string.change_my_security_code).uppercase(), - color = colorResource(foundation.e.elib.R.color.e_accent), - fontWeight = FontWeight.Bold, - modifier = - Modifier.clickable( - onClick = { mainScreen(page = Pages.AskPasswordForPasswordReset) } - ), - style = MaterialTheme.typography.bodyLarge, - ) + Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { + Text( + text = stringResource(R.string.change_my_security_code).uppercase(), + color = colorResource(foundation.e.elib.R.color.e_accent), + fontWeight = FontWeight.Bold, + modifier = + Modifier.clickable( + onClick = { mainScreen(page = Pages.AskPasswordForPasswordReset) } + ), + style = MaterialTheme.typography.bodyLarge, + ) - OnDebugBuild() - } + OnDebugBuild() } } } @@ -673,32 +754,20 @@ class MainActivity : ComponentActivity() { if (selectedAge == null) return with(PrefsUtils.getEdit()) { putInt(Constants.PREF_AGE, selectedAge!!.ordinal) - putBoolean(Constants.PREF_AGE_SET, true) apply() } } @Composable private fun OnDebugBuild() { - if ( - (BuildConfig.DEBUG || !isSetupFinished) && - (isAdminActive || isAdminActive()) && - (PrefsUtils.isPinSet() || PrefsUtils.isPasswordSet()) - ) { + if (BuildConfig.DEBUG) { Box( modifier = Modifier.fillMaxSize().padding(bottom = 16.dp), contentAlignment = Alignment.BottomEnd, ) { Button( - onClick = { - if (BuildConfig.DEBUG && isSetupFinished) { - mainScreen(page = Pages.AskPasswordForDebugMenu) - } else { - onExitApp(true) - } - }, + onClick = { mainScreen(page = Pages.AskPasswordForDebugMenu) }, colors = buttonColor(), - enabled = isAdminActive || isAdminActive(), ) { Text(stringResource(R.string.next)) } @@ -709,7 +778,8 @@ class MainActivity : ComponentActivity() { @Composable fun SelectAgePage() { fun onBackPress() { - if (isSetupFinished) { + selectedAge = null + if (SystemUtils.isSetupFinished(mActivity)) { onStartUp() } else { onExitApp() @@ -734,24 +804,9 @@ class MainActivity : ComponentActivity() { ) SelectAge( - onRadioClick = { - selectedAge = it - mainScreen(page = Pages.SelectAge) - } + onRadioClick = {}, + onNextClick = { mainScreen(page = Pages.AuthenticationTypeSelectionView) } ) - - Box( - modifier = Modifier.fillMaxSize().padding(top = 16.dp), - contentAlignment = Alignment.TopCenter, - ) { - Button( - onClick = { mainScreen(page = Pages.AuthenticationTypeSelectionView) }, - colors = buttonColor(), - enabled = selectedAge != null - ) { - Text(stringResource(R.string.next)) - } - } } } @@ -785,22 +840,18 @@ class MainActivity : ComponentActivity() { horizontalAlignment = Alignment.Start, verticalArrangement = Arrangement.Top ) { - currentPage = page when (page) { Pages.AuthenticationTypeSelectionView -> { - selectedAuthenticationType = - authenticationTypeSelectionView( - onBackPressed = { - if (isAdminActive()) { - onStartUp() - } else { - mainScreen(page = Pages.SelectAge) - } + AuthenticationTypeSelectionView( + onBackPressed = { + if (isAdminActive()) { + mainScreen(page = Pages.ActivateAdmin) + } else { + mainScreen(page = Pages.SelectAge) } - ) - if (selectedAuthenticationType != null) { - mainScreen(page = Pages.SetupPinPassword) - } + }, + onSelection = { mainScreen(page = Pages.SetupPinPassword) } + ) } Pages.SetupPinPassword -> { SetupPinPassword() @@ -821,9 +872,9 @@ class MainActivity : ComponentActivity() { AskPassword( onPasswordMatch = { PrefsUtils.clearPassword() - removeAdmin() + selectedAge = null + dA.removeAdmin(mActivity) onStartUp() - isAdminActive = isAdminActive() }, onBackPressed = { onStartUp() } ) @@ -839,9 +890,11 @@ class MainActivity : ComponentActivity() { Pages.AskPasswordForAgeReset -> { AskPassword( onPasswordMatch = { - passwordConfirmation = true + onSetAge() + MainUI(mActivity).blockBlackListedApps() onStartUp() }, + onPasswordMisMatch = { selectedAge = PrefsUtils.getAge() }, onBackPressed = { onStartUp() } ) } diff --git a/app/src/main/java/foundation/e/parentalcontrol/data/AuthenticationType.kt b/app/src/main/java/foundation/e/parentalcontrol/data/AuthenticationType.kt index 76200fe25ed2a49d601911c46d8cbe49ad57b23a..71eb491527c72ad4810a82f56a36afbb3c5357f6 100644 --- a/app/src/main/java/foundation/e/parentalcontrol/data/AuthenticationType.kt +++ b/app/src/main/java/foundation/e/parentalcontrol/data/AuthenticationType.kt @@ -8,6 +8,7 @@ package foundation.e.parentalcontrol.data enum class AuthenticationType { + NONE, PIN, - Password + PASSWORD } diff --git a/app/src/main/java/foundation/e/parentalcontrol/ui/view/AskPassword.kt b/app/src/main/java/foundation/e/parentalcontrol/ui/view/AskPassword.kt index bdc76e064661511f5ab599d89996238ab0de12b7..1b7eb64909a9e45a4c806e91d0b5ca90ee7bcce1 100644 --- a/app/src/main/java/foundation/e/parentalcontrol/ui/view/AskPassword.kt +++ b/app/src/main/java/foundation/e/parentalcontrol/ui/view/AskPassword.kt @@ -42,6 +42,7 @@ import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.input.PasswordVisualTransformation import androidx.compose.ui.unit.dp import foundation.e.parentalcontrol.R +import foundation.e.parentalcontrol.data.AuthenticationType import foundation.e.parentalcontrol.ui.buttons.buttonColor import foundation.e.parentalcontrol.ui.text.CustomTopAppBar import foundation.e.parentalcontrol.ui.text.textFieldColor @@ -53,7 +54,11 @@ import kotlinx.coroutines.launch private var passText by mutableStateOf("") @Composable -fun AskPassword(onPasswordMatch: () -> Unit, onBackPressed: () -> Unit) { +fun AskPassword( + onPasswordMatch: () -> Unit, + onBackPressed: () -> Unit, + onPasswordMisMatch: () -> Unit = {} +) { val scope = rememberCoroutineScope() val context = LocalContext.current val keyboardController = LocalSoftwareKeyboardController.current @@ -63,16 +68,19 @@ fun AskPassword(onPasswordMatch: () -> Unit, onBackPressed: () -> Unit) { onBack = { passText = "" onBackPressed() + onPasswordMisMatch() } ) CustomTopAppBar( title = - if (PrefsUtils.isPinSet()) stringResource(R.string.enter_pin) + if (PrefsUtils.getAuthType() == AuthenticationType.PIN) + stringResource(R.string.enter_pin) else stringResource(R.string.enter_password), onClick = { passText = "" onBackPressed() + onPasswordMisMatch() } ) @@ -87,9 +95,11 @@ fun AskPassword(onPasswordMatch: () -> Unit, onBackPressed: () -> Unit) { isError = false } else { // Passwords do not match, show error message + onPasswordMisMatch() Toast.makeText( context, - if (PrefsUtils.isPinSet()) context.getString(R.string.pin_mismatch) + if (PrefsUtils.getAuthType() == AuthenticationType.PIN) + context.getString(R.string.pin_mismatch) else context.getString(R.string.password_mismatch), Toast.LENGTH_SHORT ) @@ -119,7 +129,7 @@ fun AskPassword(onPasswordMatch: () -> Unit, onBackPressed: () -> Unit) { }, label = { Text( - if (PrefsUtils.isPinSet()) + if (PrefsUtils.getAuthType() == AuthenticationType.PIN) stringResource(R.string.enter_your_parental_control_pin) else stringResource(R.string.enter_your_parental_control_password) ) @@ -128,7 +138,8 @@ fun AskPassword(onPasswordMatch: () -> Unit, onBackPressed: () -> Unit) { keyboardOptions = KeyboardOptions.Default.copy( keyboardType = - if (PrefsUtils.isPinSet()) KeyboardType.NumberPassword + if (PrefsUtils.getAuthType() == AuthenticationType.PIN) + KeyboardType.NumberPassword else KeyboardType.Password, imeAction = ImeAction.Done ), @@ -149,7 +160,8 @@ fun AskPassword(onPasswordMatch: () -> Unit, onBackPressed: () -> Unit) { Text( text = - if (PrefsUtils.isPinSet()) stringResource(R.string.enter_your_parental_control_pin) + if (PrefsUtils.getAuthType() == AuthenticationType.PIN) + stringResource(R.string.enter_your_parental_control_pin) else stringResource(R.string.enter_your_parental_control_password), Modifier.padding(10.dp), fontWeight = FontWeight.ExtraLight diff --git a/app/src/main/java/foundation/e/parentalcontrol/ui/view/authenticationTypeSelectionView.kt b/app/src/main/java/foundation/e/parentalcontrol/ui/view/AuthenticationTypeSelectionView.kt similarity index 75% rename from app/src/main/java/foundation/e/parentalcontrol/ui/view/authenticationTypeSelectionView.kt rename to app/src/main/java/foundation/e/parentalcontrol/ui/view/AuthenticationTypeSelectionView.kt index 6a07dd87111fbf27fb62908389b2b6822ca46234..6cb360ffa83c0e74cf849ef8295578e1c1d187f8 100644 --- a/app/src/main/java/foundation/e/parentalcontrol/ui/view/authenticationTypeSelectionView.kt +++ b/app/src/main/java/foundation/e/parentalcontrol/ui/view/AuthenticationTypeSelectionView.kt @@ -23,20 +23,24 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.vectorResource import androidx.compose.ui.unit.dp import foundation.e.parentalcontrol.R import foundation.e.parentalcontrol.data.AuthenticationType -import foundation.e.parentalcontrol.selectedAuthenticationType import foundation.e.parentalcontrol.ui.buttons.ImageButtonWithText import foundation.e.parentalcontrol.ui.buttons.buttonColor import foundation.e.parentalcontrol.ui.text.CustomTopAppBar +import foundation.e.parentalcontrol.utils.Constants +import foundation.e.parentalcontrol.utils.PrefsUtils @Composable -fun authenticationTypeSelectionView(onBackPressed: () -> Unit): AuthenticationType? { - var selectedType by remember { mutableStateOf(selectedAuthenticationType) } - var buttonIsOkay by remember { mutableStateOf(false) } +fun AuthenticationTypeSelectionView(onBackPressed: () -> Unit, onSelection: () -> Unit) { + val context = LocalContext.current + PrefsUtils.init(context) + + var selectedType by remember { mutableStateOf(PrefsUtils.getAuthType()) } BackHandler(onBack = { onBackPressed() }) @@ -55,8 +59,8 @@ fun authenticationTypeSelectionView(onBackPressed: () -> Unit): AuthenticationTy // Radio buttons for selecting authentication type ImageButtonWithText( text = stringResource(R.string.setup_password), - selected = selectedType == AuthenticationType.Password, - onClick = { selectedType = AuthenticationType.Password }, + selected = selectedType == AuthenticationType.PASSWORD, + onClick = { selectedType = AuthenticationType.PASSWORD }, image = ImageVector.vectorResource(R.drawable.baseline_password_24), ) @@ -69,19 +73,20 @@ fun authenticationTypeSelectionView(onBackPressed: () -> Unit): AuthenticationTy Box(modifier = Modifier.fillMaxSize()) { Button( - onClick = { buttonIsOkay = true }, + onClick = { + PrefsUtils.getEdit().apply { + putInt(Constants.PREF_AUTH_TYPE, selectedType.ordinal) + apply() + } + onSelection() + }, modifier = Modifier.align(Alignment.TopCenter).padding(top = 16.dp), - enabled = selectedType != null, // Enable only if an option is selected + enabled = + selectedType != AuthenticationType.NONE, // Enable only if an option is selected colors = buttonColor() ) { Text(stringResource(R.string.next)) } } } - - if (selectedType != null && buttonIsOkay) { - return selectedType - } - - return null } diff --git a/app/src/main/java/foundation/e/parentalcontrol/ui/view/MainUI.kt b/app/src/main/java/foundation/e/parentalcontrol/ui/view/MainUI.kt index 2a5949a2f652185d5fddf30faa452e502297a607..c09a3af6545782347d8108b66aa817413e0ed223 100644 --- a/app/src/main/java/foundation/e/parentalcontrol/ui/view/MainUI.kt +++ b/app/src/main/java/foundation/e/parentalcontrol/ui/view/MainUI.kt @@ -77,6 +77,8 @@ class MainUI(context: Context) { } fun setDefaultRestrictions() { + if (!dA.isAdminActive(mContext)) return + setRestriction(appSettingsRestriction) setRestriction(developerSettingsRestriction) setRestriction(appUnknownSourceRestriction) @@ -110,6 +112,8 @@ class MainUI(context: Context) { } fun clearDefaultRestrictions() { + if (!dA.isAdminActive(mContext)) return + clearRestriction(appSettingsRestriction) clearRestriction(developerSettingsRestriction) clearRestriction(appUnknownSourceRestriction) diff --git a/app/src/main/java/foundation/e/parentalcontrol/ui/view/SelectAge.kt b/app/src/main/java/foundation/e/parentalcontrol/ui/view/SelectAge.kt index 8022927cf139949496b950eedea5f7866f698c49..6422ca2a6ec6397e3805e2445fb415a9858fb5f0 100644 --- a/app/src/main/java/foundation/e/parentalcontrol/ui/view/SelectAge.kt +++ b/app/src/main/java/foundation/e/parentalcontrol/ui/view/SelectAge.kt @@ -7,22 +7,40 @@ */ package foundation.e.parentalcontrol.ui.view +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Button import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.colorResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp +import foundation.e.parentalcontrol.DeviceAdmin import foundation.e.parentalcontrol.R import foundation.e.parentalcontrol.data.Ages -import foundation.e.parentalcontrol.selectedAge import foundation.e.parentalcontrol.ui.buttons.RadioButtonWithText +import foundation.e.parentalcontrol.ui.buttons.buttonColor +import foundation.e.parentalcontrol.utils.PrefsUtils + +var selectedAge: Ages? by mutableStateOf(null) @Composable -fun SelectAge(onRadioClick: (age: Ages) -> Unit) { +fun SelectAge(onRadioClick: () -> Unit, onNextClick: () -> Unit) { + val context = LocalContext.current + + if (DeviceAdmin().isAdminActive(context)) { + selectedAge = PrefsUtils.getAge() + } + Text( text = stringResource(R.string.age_group_of_the_child), color = colorResource(foundation.e.elib.R.color.e_primary_text_color), @@ -36,36 +54,66 @@ fun SelectAge(onRadioClick: (age: Ages) -> Unit) { RadioButtonWithText( text = stringResource(R.string.three_years_old), selected = Ages.THREE == selectedAge, - onClick = { onRadioClick(Ages.THREE) }, + onClick = { + selectedAge = Ages.THREE + onRadioClick() + }, radioButtonOnRight = true ) RadioButtonWithText( text = stringResource(R.string.six_years_old), selected = Ages.SIX == selectedAge, - onClick = { onRadioClick(Ages.SIX) }, + onClick = { + selectedAge = Ages.SIX + onRadioClick() + }, radioButtonOnRight = true ) RadioButtonWithText( text = stringResource(R.string.eleven_years_old), selected = Ages.ELEVEN == selectedAge, - onClick = { onRadioClick(Ages.ELEVEN) }, + onClick = { + selectedAge = Ages.ELEVEN + onRadioClick() + }, radioButtonOnRight = true ) RadioButtonWithText( text = stringResource(R.string.fifteen_years_old), selected = Ages.FIFTEEN == selectedAge, - onClick = { onRadioClick(Ages.FIFTEEN) }, + onClick = { + selectedAge = Ages.FIFTEEN + onRadioClick() + }, radioButtonOnRight = true ) RadioButtonWithText( text = stringResource(R.string.seventeen_years_old), selected = Ages.SEVENTEEN == selectedAge, - onClick = { onRadioClick(Ages.SEVENTEEN) }, + onClick = { + selectedAge = Ages.SEVENTEEN + onRadioClick() + }, radioButtonOnRight = true ) } + + if (!DeviceAdmin().isAdminActive(context)) { + Box( + modifier = Modifier.fillMaxSize().padding(top = 16.dp), + contentAlignment = Alignment.TopCenter, + ) { + Button( + onClick = { onNextClick() }, + colors = buttonColor(), + enabled = selectedAge != null + ) { + Text(stringResource(R.string.next)) + } + } + } } diff --git a/app/src/main/java/foundation/e/parentalcontrol/utils/Constants.kt b/app/src/main/java/foundation/e/parentalcontrol/utils/Constants.kt index f76c7b800b33322ec7498e5f7cc6e8e278004d07..98f37750fc6cead4095b0178ab1a9cd541a7e0d7 100644 --- a/app/src/main/java/foundation/e/parentalcontrol/utils/Constants.kt +++ b/app/src/main/java/foundation/e/parentalcontrol/utils/Constants.kt @@ -8,12 +8,11 @@ package foundation.e.parentalcontrol.utils object Constants { + const val PREF_AUTH_TYPE = "auth_type" const val PREF_PIN_SET = "pin_set" - const val PREF_AGE_SET = "age_set" const val PREF_PASSWORD_SET = "password_set" const val PREF_PASSWORD = "password" const val PREF_AGE = "age" - const val PREF_FIRST_TIME = "first_time" const val RESTART_SERVICE = "foundation.e.parental_control.RESTART_SERVICE" diff --git a/app/src/main/java/foundation/e/parentalcontrol/utils/PrefsUtils.kt b/app/src/main/java/foundation/e/parentalcontrol/utils/PrefsUtils.kt index 97917adc9b24343afc5e2867ea44877e51ae0855..91bd8f83dd9fdf827d84419de6ecd9de3709a632 100644 --- a/app/src/main/java/foundation/e/parentalcontrol/utils/PrefsUtils.kt +++ b/app/src/main/java/foundation/e/parentalcontrol/utils/PrefsUtils.kt @@ -12,6 +12,7 @@ import android.content.SharedPreferences import com.google.gson.Gson import com.google.gson.reflect.TypeToken import foundation.e.parentalcontrol.data.Ages +import foundation.e.parentalcontrol.data.AuthenticationType object PrefsUtils { private lateinit var sharedPreferences: SharedPreferences @@ -23,22 +24,23 @@ object PrefsUtils { } } - fun isPinSet(): Boolean { - return sharedPreferences.getBoolean(Constants.PREF_PIN_SET, false) - } - - fun isPasswordSet(): Boolean { - return sharedPreferences.getBoolean(Constants.PREF_PASSWORD_SET, false) + fun getAuthType(): AuthenticationType { + val default = + if (sharedPreferences.getBoolean(Constants.PREF_PIN_SET, false)) { + AuthenticationType.PIN.ordinal + } else if (sharedPreferences.getBoolean(Constants.PREF_PASSWORD_SET, false)) { + AuthenticationType.PASSWORD.ordinal + } else { + AuthenticationType.NONE.ordinal + } + val type = sharedPreferences.getInt(Constants.PREF_AUTH_TYPE, default) + return AuthenticationType.entries[type] } fun getPassword(): String? { return sharedPreferences.getString(Constants.PREF_PASSWORD, "") } - fun isAgeSet(): Boolean { - return sharedPreferences.getBoolean(Constants.PREF_AGE_SET, false) - } - fun getEdit(): SharedPreferences.Editor { return sharedPreferences.edit() } @@ -49,10 +51,6 @@ object PrefsUtils { return Ages.entries[ordinal] } - fun getFirstTime(): Boolean { - return sharedPreferences.getBoolean(Constants.PREF_FIRST_TIME, true) - } - fun clearAll() { sharedPreferences.edit().clear().apply() } diff --git a/app/src/main/java/foundation/e/parentalcontrol/utils/SystemUtils.kt b/app/src/main/java/foundation/e/parentalcontrol/utils/SystemUtils.kt index 686613d0e8097e97dcf4252864f3bde06ece28a9..6ee6196360711a6810c5b448becd73e2d051dfa6 100644 --- a/app/src/main/java/foundation/e/parentalcontrol/utils/SystemUtils.kt +++ b/app/src/main/java/foundation/e/parentalcontrol/utils/SystemUtils.kt @@ -8,7 +8,6 @@ package foundation.e.parentalcontrol.utils import android.annotation.SuppressLint -import android.app.AppOpsManager import android.content.Context import android.content.pm.ApplicationInfo import android.content.pm.PackageManager @@ -55,59 +54,6 @@ object SystemUtils { return UserHandle.myUserId() } - fun getAppsAllowedToInstallPackages(context: Context): List { - val packageManager = context.packageManager - val appsWithInstallPermission = mutableListOf() - - val packages = packageManager.getInstalledPackages(PackageManager.GET_PERMISSIONS) - val appOpsManager = context.getSystemService(Context.APP_OPS_SERVICE) as AppOpsManager - - for (packageInfo in packages) { - val requestedPermissions = packageInfo.requestedPermissions - if ( - requestedPermissions != null && - requestedPermissions.contains("android.permission.REQUEST_INSTALL_PACKAGES") - ) { - val mode = - appOpsManager.unsafeCheckOpNoThrow( - AppOpsManager.OPSTR_REQUEST_INSTALL_PACKAGES, - packageInfo.applicationInfo.uid, - packageInfo.packageName - ) - if (mode == AppOpsManager.MODE_ALLOWED) { - appsWithInstallPermission.add(packageInfo.packageName) - } - } - } - - return appsWithInstallPermission - } - - fun allowAppToInstallPackages(context: Context, packageName: String, enable: Boolean): Boolean { - try { - val packageManager = context.packageManager - val appOpsManager = context.getSystemService(Context.APP_OPS_SERVICE) as AppOpsManager - - val packageInfo = - packageManager.getPackageInfo(packageName, PackageManager.GET_PERMISSIONS) - val uid = packageInfo.applicationInfo.uid - - appOpsManager.setMode( - AppOpsManager.OPSTR_REQUEST_INSTALL_PACKAGES, - uid, - packageName, - if (enable) AppOpsManager.MODE_ALLOWED else AppOpsManager.MODE_DEFAULT - ) - } catch (e: PackageManager.NameNotFoundException) { - e.printStackTrace() - return false - } catch (e: SecurityException) { - e.printStackTrace() - return false - } - return true - } - fun getInstalledUserApps(context: Context): List { val packageManager: PackageManager = context.packageManager val allApps: List = diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b81cc105d824a7473d6fd6231c054b1777f94983..60a149c9a99b8c88c16a10b06f1337c66015dd8d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -60,4 +60,10 @@ This setting is blocked Enter your parental control pin Enter your parental control password + Parental control is blocked + Another device admin app was detected. Please disable or remove it before activating parental control. To remove it, please go to the settings below.\n\n + "Security > More security settings > Device admin apps" + "Security > Device admin apps" + Open settings + "Please remove all logged-in accounts to activate parental control.\n\nSettings > Accounts" \ No newline at end of file