Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion app/src/main/java/dev/pranav/applock/AppLockApplication.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import android.app.Application
import android.content.Context
import android.os.Build
import android.util.Log
import dev.pranav.applock.core.utils.LocaleManager
import dev.pranav.applock.core.utils.LogUtils
import dev.pranav.applock.data.repository.AppLockRepository
import org.lsposed.hiddenapibypass.HiddenApiBypass
Expand All @@ -16,7 +17,7 @@ class AppLockApplication : Application() {
private set

override fun attachBaseContext(base: Context?) {
super.attachBaseContext(base)
super.attachBaseContext(LocaleManager.wrap(base ?: return))
initializeHiddenApiBypass()
}

Expand Down
6 changes: 6 additions & 0 deletions app/src/main/java/dev/pranav/applock/MainActivity.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package dev.pranav.applock

import android.content.Context
import android.os.Bundle
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
Expand All @@ -10,12 +11,17 @@ import androidx.navigation.compose.rememberNavController
import dev.pranav.applock.core.navigation.AppNavHost
import dev.pranav.applock.core.navigation.NavigationManager
import dev.pranav.applock.core.navigation.Screen
import dev.pranav.applock.core.utils.LocaleManager
import dev.pranav.applock.ui.theme.AppLockTheme

class MainActivity : FragmentActivity() {

private lateinit var navigationManager: NavigationManager

override fun attachBaseContext(newBase: Context?) {
super.attachBaseContext(LocaleManager.wrap(newBase ?: return))
}

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package dev.pranav.applock.core.navigation

import android.content.Context
import android.util.Log
import androidx.activity.compose.LocalActivity
import androidx.biometric.BiometricManager
Expand All @@ -9,13 +10,15 @@ import androidx.compose.animation.fadeIn
import androidx.compose.animation.scaleIn
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.core.content.ContextCompat
import androidx.fragment.app.FragmentActivity
import androidx.navigation.NavController
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import dev.pranav.applock.AppLockApplication
import dev.pranav.applock.R
import dev.pranav.applock.core.utils.LogUtils
import dev.pranav.applock.data.repository.PreferencesRepository
import dev.pranav.applock.features.antiuninstall.ui.AntiUninstallScreen
Expand Down Expand Up @@ -189,18 +192,18 @@ private fun handleBiometricAuthentication(
}
)

val promptInfo = createBiometricPromptInfo()
val promptInfo = createBiometricPromptInfo(context)
biometricPrompt.authenticate(promptInfo)
} catch (e: Exception) {
Log.e(TAG, "Error during biometric authentication", e)
}
}

private fun createBiometricPromptInfo(): BiometricPrompt.PromptInfo {
private fun createBiometricPromptInfo(context: Context): BiometricPrompt.PromptInfo {
return BiometricPrompt.PromptInfo.Builder()
.setTitle(BIOMETRIC_TITLE)
.setSubtitle(BIOMETRIC_SUBTITLE)
.setNegativeButtonText(BIOMETRIC_NEGATIVE_BUTTON)
.setTitle(context.getString(R.string.biometric_prompt_title))
.setSubtitle(context.getString(R.string.biometric_prompt_subtitle))
.setNegativeButtonText(context.getString(R.string.biometric_prompt_negative_button))
.setAllowedAuthenticators(
BiometricManager.Authenticators.BIOMETRIC_WEAK or
BiometricManager.Authenticators.BIOMETRIC_STRONG
Expand All @@ -226,6 +229,3 @@ private fun navigateToMain(navController: NavHostController) {
private const val TAG = "AppNavHost"
private const val ANIMATION_DURATION = 400
private const val SCALE_INITIAL = 0.9f
private const val BIOMETRIC_TITLE = "Confirm password"
private const val BIOMETRIC_SUBTITLE = "Confirm biometric to continue"
private const val BIOMETRIC_NEGATIVE_BUTTON = "Use PIN"
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import android.provider.Settings
import android.util.Log
import android.widget.Toast
import androidx.core.net.toUri
import dev.pranav.applock.R
import dev.pranav.applock.AppLockApplication
import dev.pranav.applock.data.repository.AppLockRepository

Expand Down Expand Up @@ -53,7 +54,7 @@ fun launchBatterySettings(context: Context) {

if (requestIgnoreIntent.resolveActivity(pm) != null) {
context.startActivity(requestIgnoreIntent)
showBatteryOptimizationToast(context, "Battery optimization request sent")
showBatteryOptimizationToast(context, context.getString(R.string.utils_battery_optimization_sent_toast))
} else {
Log.w(
TAG,
Expand All @@ -63,7 +64,7 @@ fun launchBatterySettings(context: Context) {
}
} catch (e: Exception) {
Log.e(TAG, "Failed to launch battery settings", e)
showBatteryOptimizationToast(context, "Failed to open battery settings")
showBatteryOptimizationToast(context, context.getString(R.string.utils_battery_open_failed_toast))
}
}

Expand Down Expand Up @@ -92,7 +93,7 @@ private fun launchGeneralBatterySettings(context: Context) {
context.startActivity(generalBatteryIntent)
showBatteryOptimizationToast(
context,
"Please find and configure this app in battery settings"
context.getString(R.string.utils_battery_configure_toast)
)
} catch (e: Exception) {
Log.e(TAG, "Failed to launch general battery settings", e)
Expand Down
25 changes: 25 additions & 0 deletions app/src/main/java/dev/pranav/applock/core/utils/LocaleManager.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package dev.pranav.applock.core.utils

import android.content.Context
import android.content.res.Configuration
import dev.pranav.applock.data.repository.PreferencesRepository
import java.util.Locale

object LocaleManager {

fun wrap(context: Context): Context {
val prefs = context.getSharedPreferences("app_lock_settings", Context.MODE_PRIVATE)
val language = prefs.getString(PreferencesRepository.KEY_LANGUAGE, PreferencesRepository.LANGUAGE_SYSTEM)
?: PreferencesRepository.LANGUAGE_SYSTEM
if (language == PreferencesRepository.LANGUAGE_SYSTEM) {
return context
}

val locale = Locale.forLanguageTag(language)
Locale.setDefault(locale)

val config = Configuration(context.resources.configuration)
config.setLocale(locale)
return context.createConfigurationContext(config)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,9 @@ class AppLockRepository(private val context: Context) {
fun isLoggingEnabled(): Boolean = preferencesRepository.isLoggingEnabled()
fun setLoggingEnabled(enabled: Boolean) = preferencesRepository.setLoggingEnabled(enabled)

fun setLanguage(code: String) = preferencesRepository.setLanguage(code)
fun getLanguage(): String = preferencesRepository.getLanguage()

fun setActiveBackend(backend: BackendImplementation) =
backendServiceManager.setActiveBackend(backend)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,14 @@ class PreferencesRepository(context: Context) {
settingsPrefs.edit { putBoolean(KEY_LOGGING_ENABLED, enabled) }
}

fun setLanguage(code: String) {
settingsPrefs.edit { putString(KEY_LANGUAGE, code) }
}

fun getLanguage(): String {
return settingsPrefs.getString(KEY_LANGUAGE, LANGUAGE_SYSTEM) ?: LANGUAGE_SYSTEM
}

companion object {
private const val PREFS_NAME_APP_LOCK = "app_lock_prefs"
private const val PREFS_NAME_SETTINGS = "app_lock_settings"
Expand Down Expand Up @@ -197,5 +205,10 @@ class PreferencesRepository(context: Context) {
const val LOCK_TYPE_PIN = "pin"
const val LOCK_TYPE_PATTERN = "pattern"
const val LOCK_TYPE_PASSWORD = "password"

const val KEY_LANGUAGE = "app_language"
const val LANGUAGE_SYSTEM = "system"
const val LANGUAGE_ENGLISH = "en"
const val LANGUAGE_PORTUGUESE_BR = "pt-BR"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package dev.pranav.applock.features.admin

import android.app.admin.DevicePolicyManager
import android.content.ComponentName
import android.content.Context
import android.os.Bundle
import android.widget.Toast
import androidx.activity.ComponentActivity
Expand All @@ -27,6 +28,7 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import dev.pranav.applock.R
import dev.pranav.applock.core.broadcast.DeviceAdmin
import dev.pranav.applock.core.utils.LocaleManager
import dev.pranav.applock.core.utils.SecurityUtils
import dev.pranav.applock.core.utils.appLockRepository
import dev.pranav.applock.data.repository.AppLockRepository
Expand All @@ -41,6 +43,10 @@ class AdminDisableActivity : ComponentActivity() {
private lateinit var devicePolicyManager: DevicePolicyManager
private lateinit var deviceAdminComponentName: ComponentName

override fun attachBaseContext(newBase: Context?) {
super.attachBaseContext(LocaleManager.wrap(newBase ?: return))
}

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
devicePolicyManager = getSystemService(DEVICE_POLICY_SERVICE) as DevicePolicyManager
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
Expand All @@ -63,6 +64,7 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import dev.pranav.applock.R
import dev.pranav.applock.core.utils.appLockRepository
import dev.pranav.applock.core.utils.blockUninstallForUser
import dev.pranav.applock.core.utils.unblockUninstallForUser
Expand Down Expand Up @@ -263,7 +265,7 @@ fun AntiUninstallScreen(
modifier = Modifier.fillMaxSize(),
topBar = {
SearchTopBar(
title = "Anti-Uninstall Protection",
title = stringResource(R.string.anti_uninstall_screen_title),
searchQuery = searchQuery,
onSearchQueryChange = viewModel::updateSearchQuery,
onBack = { navController.navigateUp() },
Expand All @@ -283,7 +285,7 @@ fun AntiUninstallScreen(
) {
item {
Text(
text = "Select apps that will be protected from uninstallation.",
text = stringResource(R.string.anti_uninstall_screen_desc),
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
Expand All @@ -310,7 +312,7 @@ fun AntiUninstallScreen(
if (protectedNotInList.isNotEmpty()) {
item {
Text(
text = "Manually Added Packages",
text = stringResource(R.string.anti_uninstall_manual_packages_title),
style = MaterialTheme.typography.titleMedium,
fontWeight = FontWeight.SemiBold,
modifier = Modifier.padding(bottom = 4.dp)
Expand All @@ -324,7 +326,7 @@ fun AntiUninstallScreen(
}
item {
Text(
text = "Installed Apps",
text = stringResource(R.string.anti_uninstall_installed_apps_title),
style = MaterialTheme.typography.titleMedium,
fontWeight = FontWeight.SemiBold,
modifier = Modifier.padding(top = 8.dp, bottom = 4.dp)
Expand All @@ -347,19 +349,19 @@ fun AntiUninstallScreen(
AlertDialog(
onDismissRequest = { showManualAddDialog.value = false },
properties = DialogProperties(usePlatformDefaultWidth = false),
title = { Text("Add Package Manually") },
title = { Text(stringResource(R.string.anti_uninstall_manual_add_dialog_title)) },
text = {
Column(Modifier.fillMaxWidth(0.8f)) {
Text(
text = "Enter the package name of the app you want to protect:",
text = stringResource(R.string.anti_uninstall_manual_add_dialog_text),
style = MaterialTheme.typography.bodyMedium,
modifier = Modifier.padding(bottom = 16.dp)
)
OutlinedTextField(
value = manualPackageName,
onValueChange = viewModel::updateManualPackageName,
label = { Text("Package Name") },
placeholder = { Text("com.example.app") },
label = { Text(stringResource(R.string.anti_uninstall_package_name_label)) },
placeholder = { Text(stringResource(R.string.anti_uninstall_package_name_placeholder)) },
modifier = Modifier.fillMaxWidth(),
singleLine = true
)
Expand All @@ -372,10 +374,10 @@ fun AntiUninstallScreen(
showManualAddDialog.value = false
},
enabled = manualPackageName.isNotBlank()
) { Text("Add") }
) { Text(stringResource(R.string.add_button)) }
},
dismissButton = {
TextButton(onClick = { showManualAddDialog.value = false }) { Text("Cancel") }
TextButton(onClick = { showManualAddDialog.value = false }) { Text(stringResource(R.string.cancel_button)) }
}
)
}
Expand All @@ -391,20 +393,20 @@ fun AntiUninstallScreen(
title = {
Text(
text = when (shizukuState) {
ShizukuState.NOT_INSTALLED -> "Shizuku Not Installed"
ShizukuState.NOT_RUNNING -> "Shizuku Not Running"
ShizukuState.PERMISSION_DENIED -> "Permission Required"
else -> "Shizuku Setup"
ShizukuState.NOT_INSTALLED -> stringResource(R.string.anti_uninstall_shizuku_not_installed_title)
ShizukuState.NOT_RUNNING -> stringResource(R.string.anti_uninstall_shizuku_not_running_title)
ShizukuState.PERMISSION_DENIED -> stringResource(R.string.anti_uninstall_shizuku_permission_required_title)
else -> stringResource(R.string.anti_uninstall_shizuku_setup_title)
}
)
},
text = {
Column(Modifier.fillMaxWidth(0.8f)) {
Text(
text = when (shizukuState) {
ShizukuState.NOT_INSTALLED -> "Shizuku is required to configure Anti-Uninstall protection without root.\n\nPlease install Shizuku from the Play Store or GitHub to continue."
ShizukuState.NOT_RUNNING -> "Shizuku is installed, but the background service has not been started yet (Binder hasn't been received).\n\nPlease open the Shizuku application and start the service via Wireless Debugging or Root."
ShizukuState.PERMISSION_DENIED -> "Shizuku is active, but this application needs your permission authorization to configure package restrictions.\n\nThankfully, Shizuku is only used while blocking/unblocking uninstalls, so it doesn't need to stay running forever!"
ShizukuState.NOT_INSTALLED -> stringResource(R.string.anti_uninstall_shizuku_not_installed_text)
ShizukuState.NOT_RUNNING -> stringResource(R.string.anti_uninstall_shizuku_not_running_text)
ShizukuState.PERMISSION_DENIED -> stringResource(R.string.anti_uninstall_shizuku_permission_required_text)
else -> ""
},
style = MaterialTheme.typography.bodyMedium
Expand Down Expand Up @@ -441,18 +443,18 @@ fun AntiUninstallScreen(
) {
Text(
text = when (shizukuState) {
ShizukuState.NOT_INSTALLED -> "Install Shizuku"
ShizukuState.NOT_RUNNING -> "Open Shizuku"
ShizukuState.PERMISSION_DENIED -> "Grant Permission"
else -> "Confirm"
ShizukuState.NOT_INSTALLED -> stringResource(R.string.anti_uninstall_install_shizuku_button)
ShizukuState.NOT_RUNNING -> stringResource(R.string.anti_uninstall_open_shizuku_button)
ShizukuState.PERMISSION_DENIED -> stringResource(R.string.grant_permission_button)
else -> stringResource(R.string.confirm_button)
}
)
}
},
dismissButton = {
TextButton(onClick = {
navController.popBackStack()
}) { Text("Go Back") }
}) { Text(stringResource(R.string.go_back_button)) }
}
)
}
Expand Down Expand Up @@ -484,15 +486,15 @@ private fun SearchTopBar(
verticalAlignment = Alignment.CenterVertically
) {
IconButton(onClick = onBack) {
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back")
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = stringResource(R.string.back_cd))
}
Text(
text = title,
style = MaterialTheme.typography.titleLarge,
modifier = Modifier.weight(1f)
)
IconButton(onClick = onAdd) {
Icon(Icons.Default.Add, contentDescription = "Add package manually")
Icon(Icons.Default.Add, contentDescription = stringResource(R.string.anti_uninstall_add_manual_cd))
}
}

Expand All @@ -505,7 +507,7 @@ private fun SearchTopBar(
OutlinedTextField(
value = searchQuery,
onValueChange = onSearchQueryChange,
placeholder = { Text("Search apps or package names...") },
placeholder = { Text(stringResource(R.string.anti_uninstall_search_placeholder)) },
leadingIcon = { Icon(Icons.Default.Search, contentDescription = null) },
modifier = Modifier.fillMaxWidth(),
singleLine = true,
Expand Down Expand Up @@ -593,7 +595,7 @@ private fun ManualPackageItem(packageName: String, onToggle: () -> Unit) {
overflow = TextOverflow.Ellipsis
)
Text(
text = "Manually added package",
text = stringResource(R.string.anti_uninstall_manually_added_label),
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
Expand Down
Loading