mirror of
https://github.com/awfixers-stuff/OwnDroid.git
synced 2026-03-23 19:15:58 +00:00
ViewModel refactoring: Settings part
User restriction shortcuts Optimize ShortcutUtils Fix Private DNS bug
This commit is contained in:
@@ -9,10 +9,9 @@ import android.util.Log
|
|||||||
class ApiReceiver: BroadcastReceiver() {
|
class ApiReceiver: BroadcastReceiver() {
|
||||||
override fun onReceive(context: Context, intent: Intent) {
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
val requestKey = intent.getStringExtra("key")
|
val requestKey = intent.getStringExtra("key")
|
||||||
var log = "OwnDroid API request received. action: ${intent.action}\nkey: $requestKey"
|
var log = "OwnDroid API request received. action: ${intent.action}"
|
||||||
if(!SP.isApiEnabled) return
|
val key = SP.apiKeyHash
|
||||||
val key = SP.apiKey
|
if(!key.isNullOrEmpty() && key == requestKey?.hash()) {
|
||||||
if(!key.isNullOrEmpty() && key == requestKey) {
|
|
||||||
val app = intent.getStringExtra("package")
|
val app = intent.getStringExtra("package")
|
||||||
val permission = intent.getStringExtra("permission")
|
val permission = intent.getStringExtra("permission")
|
||||||
val restriction = intent.getStringExtra("restriction")
|
val restriction = intent.getStringExtra("restriction")
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
package com.bintianqi.owndroid
|
package com.bintianqi.owndroid
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
import android.os.Build.VERSION
|
import android.os.Build.VERSION
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
import androidx.activity.enableEdgeToEdge
|
import androidx.activity.enableEdgeToEdge
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
@@ -240,6 +242,10 @@ class MainActivity : FragmentActivity() {
|
|||||||
val locale = context.resources?.configuration?.locale
|
val locale = context.resources?.configuration?.locale
|
||||||
zhCN = locale == Locale.SIMPLIFIED_CHINESE || locale == Locale.CHINESE || locale == Locale.CHINA
|
zhCN = locale == Locale.SIMPLIFIED_CHINESE || locale == Locale.CHINESE || locale == Locale.CHINA
|
||||||
val vm by viewModels<MyViewModel>()
|
val vm by viewModels<MyViewModel>()
|
||||||
|
if (VERSION.SDK_INT >= 33) {
|
||||||
|
val launcher = registerForActivityResult(ActivityResultContracts.RequestPermission()) {}
|
||||||
|
launcher.launch(Manifest.permission.POST_NOTIFICATIONS)
|
||||||
|
}
|
||||||
setContent {
|
setContent {
|
||||||
var appLockDialog by rememberSaveable { mutableStateOf(false) }
|
var appLockDialog by rememberSaveable { mutableStateOf(false) }
|
||||||
val theme by vm.theme.collectAsStateWithLifecycle()
|
val theme by vm.theme.collectAsStateWithLifecycle()
|
||||||
@@ -576,7 +582,7 @@ fun Home(vm: MyViewModel, onLock: () -> Unit) {
|
|||||||
}
|
}
|
||||||
composable<UserRestrictionOptions> {
|
composable<UserRestrictionOptions> {
|
||||||
UserRestrictionOptionsScreen(it.toRoute(), vm.userRestrictions,
|
UserRestrictionOptionsScreen(it.toRoute(), vm.userRestrictions,
|
||||||
vm::setUserRestriction, ::navigateUp)
|
vm::setUserRestriction, vm::createUserRestrictionShortcut, ::navigateUp)
|
||||||
}
|
}
|
||||||
|
|
||||||
composable<Users> { UsersScreen(vm, ::navigateUp, ::navigate) }
|
composable<Users> { UsersScreen(vm, ::navigateUp, ::navigate) }
|
||||||
@@ -619,14 +625,22 @@ fun Home(vm: MyViewModel, onLock: () -> Unit) {
|
|||||||
composable<RequiredPasswordQuality> { RequiredPasswordQualityScreen(::navigateUp) }
|
composable<RequiredPasswordQuality> { RequiredPasswordQualityScreen(::navigateUp) }
|
||||||
|
|
||||||
composable<Settings> { SettingsScreen(::navigateUp, ::navigate) }
|
composable<Settings> { SettingsScreen(::navigateUp, ::navigate) }
|
||||||
composable<SettingsOptions> { SettingsOptionsScreen(::navigateUp) }
|
composable<SettingsOptions> {
|
||||||
composable<Appearance> {
|
SettingsOptionsScreen(vm::getDisplayDangerousFeatures, vm::getShortcutsEnabled,
|
||||||
val theme by vm.theme.collectAsStateWithLifecycle()
|
vm::setDisplayDangerousFeatures, vm::setShortcutsEnabled, ::navigateUp)
|
||||||
AppearanceScreen(::navigateUp, theme, vm::changeTheme)
|
}
|
||||||
|
composable<Appearance> {
|
||||||
|
AppearanceScreen(::navigateUp, vm.theme, vm::changeTheme)
|
||||||
|
}
|
||||||
|
composable<AppLockSettings> {
|
||||||
|
AppLockSettingsScreen(vm::getAppLockConfig, vm::setAppLockConfig, ::navigateUp)
|
||||||
|
}
|
||||||
|
composable<ApiSettings> {
|
||||||
|
ApiSettings(vm::getApiEnabled, vm::setApiKey, ::navigateUp)
|
||||||
|
}
|
||||||
|
composable<Notifications> {
|
||||||
|
NotificationsScreen(vm::getEnabledNotifications, vm::setNotificationEnabled, ::navigateUp)
|
||||||
}
|
}
|
||||||
composable<AppLockSettings> { AppLockSettingsScreen(::navigateUp) }
|
|
||||||
composable<ApiSettings> { ApiSettings(::navigateUp) }
|
|
||||||
composable<Notifications> { NotificationsScreen(::navigateUp) }
|
|
||||||
composable<About> { AboutScreen(::navigateUp) }
|
composable<About> { AboutScreen(::navigateUp) }
|
||||||
}
|
}
|
||||||
DisposableEffect(lifecycleOwner) {
|
DisposableEffect(lifecycleOwner) {
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ class MyApplication : Application() {
|
|||||||
val dbHelper = MyDbHelper(this)
|
val dbHelper = MyDbHelper(this)
|
||||||
myRepo = MyRepository(dbHelper)
|
myRepo = MyRepository(dbHelper)
|
||||||
Privilege.initialize(applicationContext)
|
Privilege.initialize(applicationContext)
|
||||||
|
NotificationUtils.createChannels(this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -122,6 +122,7 @@ import kotlin.reflect.jvm.jvmErasure
|
|||||||
class MyViewModel(application: Application): AndroidViewModel(application) {
|
class MyViewModel(application: Application): AndroidViewModel(application) {
|
||||||
val myRepo = getApplication<MyApplication>().myRepo
|
val myRepo = getApplication<MyApplication>().myRepo
|
||||||
val PM = application.packageManager
|
val PM = application.packageManager
|
||||||
|
|
||||||
val theme = MutableStateFlow(ThemeSettings(SP.materialYou, SP.darkTheme, SP.blackTheme))
|
val theme = MutableStateFlow(ThemeSettings(SP.materialYou, SP.darkTheme, SP.blackTheme))
|
||||||
fun changeTheme(newTheme: ThemeSettings) {
|
fun changeTheme(newTheme: ThemeSettings) {
|
||||||
theme.value = newTheme
|
theme.value = newTheme
|
||||||
@@ -129,6 +130,54 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
|
|||||||
SP.darkTheme = newTheme.darkTheme
|
SP.darkTheme = newTheme.darkTheme
|
||||||
SP.blackTheme = newTheme.blackTheme
|
SP.blackTheme = newTheme.blackTheme
|
||||||
}
|
}
|
||||||
|
fun getDisplayDangerousFeatures(): Boolean {
|
||||||
|
return SP.displayDangerousFeatures
|
||||||
|
}
|
||||||
|
fun getShortcutsEnabled(): Boolean {
|
||||||
|
return SP.shortcuts
|
||||||
|
}
|
||||||
|
fun setDisplayDangerousFeatures(state: Boolean) {
|
||||||
|
SP.displayDangerousFeatures = state
|
||||||
|
}
|
||||||
|
fun setShortcutsEnabled(enabled: Boolean) {
|
||||||
|
SP.shortcuts = enabled
|
||||||
|
ShortcutUtils.setAllShortcuts(application, enabled)
|
||||||
|
}
|
||||||
|
fun getAppLockConfig(): AppLockConfig {
|
||||||
|
val passwordHash = SP.lockPasswordHash
|
||||||
|
return AppLockConfig(passwordHash?.ifEmpty { null }, SP.biometricsUnlock, SP.lockWhenLeaving)
|
||||||
|
}
|
||||||
|
fun setAppLockConfig(config: AppLockConfig) {
|
||||||
|
SP.lockPasswordHash = if (config.password == null) {
|
||||||
|
""
|
||||||
|
} else {
|
||||||
|
config.password.hash()
|
||||||
|
}
|
||||||
|
SP.biometricsUnlock = config.biometrics
|
||||||
|
SP.lockWhenLeaving = config.whenLeaving
|
||||||
|
}
|
||||||
|
fun getApiEnabled(): Boolean {
|
||||||
|
return SP.apiKeyHash?.isNotEmpty() ?: false
|
||||||
|
}
|
||||||
|
fun setApiKey(key: String) {
|
||||||
|
SP.apiKeyHash = if (key.isEmpty()) "" else key.hash()
|
||||||
|
}
|
||||||
|
fun getEnabledNotifications(): List<NotificationType> {
|
||||||
|
val list = SP.notifications?.split(',')?.mapNotNull { it.toIntOrNull() }
|
||||||
|
return if (list == null) {
|
||||||
|
NotificationType.entries
|
||||||
|
} else {
|
||||||
|
NotificationType.entries.filter { it.id in list }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fun setNotificationEnabled(type: NotificationType, enabled: Boolean) {
|
||||||
|
val list = SP.notifications?.split(',')?.mapNotNull { it.toIntOrNull() }
|
||||||
|
SP.notifications = if (list == null) {
|
||||||
|
NotificationType.entries.minus(type).map { it.id }
|
||||||
|
} else {
|
||||||
|
list.run { if (enabled) plus(type.id) else minus(type.id) }
|
||||||
|
}.joinToString { it.toString() }
|
||||||
|
}
|
||||||
|
|
||||||
val chosenPackage = Channel<String>(1, BufferOverflow.DROP_LATEST)
|
val chosenPackage = Channel<String>(1, BufferOverflow.DROP_LATEST)
|
||||||
|
|
||||||
@@ -1083,11 +1132,17 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
|
|||||||
DPM.clearUserRestriction(DAR, name)
|
DPM.clearUserRestriction(DAR, name)
|
||||||
}
|
}
|
||||||
userRestrictions.update { it.plus(name to state) }
|
userRestrictions.update { it.plus(name to state) }
|
||||||
|
ShortcutUtils.updateUserRestrictionShortcut(application, name, !state, true)
|
||||||
true
|
true
|
||||||
} catch (_: SecurityException) {
|
} catch (_: SecurityException) {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
fun createUserRestrictionShortcut(id: String): Boolean {
|
||||||
|
return ShortcutUtils.setUserRestrictionShortcut(
|
||||||
|
application, id, userRestrictions.value[id] ?: true
|
||||||
|
)
|
||||||
|
}
|
||||||
fun createWorkProfile(options: CreateWorkProfileOptions): Intent {
|
fun createWorkProfile(options: CreateWorkProfileOptions): Intent {
|
||||||
val intent = Intent(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE)
|
val intent = Intent(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE)
|
||||||
if (VERSION.SDK_INT >= 23) {
|
if (VERSION.SDK_INT >= 23) {
|
||||||
@@ -1478,7 +1533,7 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
|
|||||||
fun getPrivateDns(): PrivateDnsConfiguration {
|
fun getPrivateDns(): PrivateDnsConfiguration {
|
||||||
val mode = DPM.getGlobalPrivateDnsMode(DAR)
|
val mode = DPM.getGlobalPrivateDnsMode(DAR)
|
||||||
return PrivateDnsConfiguration(
|
return PrivateDnsConfiguration(
|
||||||
PrivateDnsMode.entries.find { it.id == mode }!!, DPM.getGlobalPrivateDnsHost(DAR) ?: ""
|
PrivateDnsMode.entries.find { it.id == mode }, DPM.getGlobalPrivateDnsHost(DAR) ?: ""
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@Suppress("PrivateApi")
|
@Suppress("PrivateApi")
|
||||||
@@ -1489,7 +1544,7 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
|
|||||||
field.isAccessible = true
|
field.isAccessible = true
|
||||||
val dpm = field.get(DPM) as IDevicePolicyManager
|
val dpm = field.get(DPM) as IDevicePolicyManager
|
||||||
val host = if (conf.mode == PrivateDnsMode.Host) conf.host else null
|
val host = if (conf.mode == PrivateDnsMode.Host) conf.host else null
|
||||||
val result = dpm.setGlobalPrivateDns(DAR, conf.mode.id, host)
|
val result = dpm.setGlobalPrivateDns(DAR, conf.mode!!.id, host)
|
||||||
result == DevicePolicyManager.PRIVATE_DNS_SET_NO_ERROR
|
result == DevicePolicyManager.PRIVATE_DNS_SET_NO_ERROR
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
@@ -1669,7 +1724,7 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
|
|||||||
fun getRpTokenState(): RpTokenState {
|
fun getRpTokenState(): RpTokenState {
|
||||||
return try {
|
return try {
|
||||||
RpTokenState(true, DPM.isResetPasswordTokenActive(DAR))
|
RpTokenState(true, DPM.isResetPasswordTokenActive(DAR))
|
||||||
} catch (_: IllegalArgumentException) {
|
} catch (_: IllegalStateException) {
|
||||||
RpTokenState(false, false)
|
RpTokenState(false, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import android.app.NotificationManager
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
|
import androidx.core.app.NotificationCompat
|
||||||
|
|
||||||
object NotificationUtils {
|
object NotificationUtils {
|
||||||
fun checkPermission(context: Context): Boolean {
|
fun checkPermission(context: Context): Boolean {
|
||||||
@@ -14,36 +15,57 @@ object NotificationUtils {
|
|||||||
context.checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED
|
context.checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED
|
||||||
else false
|
else false
|
||||||
}
|
}
|
||||||
fun registerChannels(context: Context) {
|
fun createChannels(context: Context) {
|
||||||
if(Build.VERSION.SDK_INT < 26) return
|
if (Build.VERSION.SDK_INT < 26) return
|
||||||
val nm = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
val nm = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||||
val lockTaskMode = NotificationChannel(Channel.LOCK_TASK_MODE, context.getString(R.string.lock_task_mode), NotificationManager.IMPORTANCE_HIGH)
|
val lockTaskMode = NotificationChannel(
|
||||||
val events = NotificationChannel(Channel.EVENTS, context.getString(R.string.events), NotificationManager.IMPORTANCE_HIGH)
|
MyNotificationChannel.LockTaskMode.id,
|
||||||
|
context.getString(MyNotificationChannel.LockTaskMode.text),
|
||||||
|
NotificationManager.IMPORTANCE_HIGH
|
||||||
|
)
|
||||||
|
val events = NotificationChannel(
|
||||||
|
MyNotificationChannel.Events.id,
|
||||||
|
context.getString(MyNotificationChannel.Events.text),
|
||||||
|
NotificationManager.IMPORTANCE_HIGH
|
||||||
|
)
|
||||||
nm.createNotificationChannels(listOf(lockTaskMode, events))
|
nm.createNotificationChannels(listOf(lockTaskMode, events))
|
||||||
}
|
}
|
||||||
fun notify(context: Context, id: Int, notification: Notification) {
|
fun notifyEvent(context: Context, type: NotificationType, text: String) {
|
||||||
val sp = context.getSharedPreferences("data", Context.MODE_PRIVATE)
|
val notification = NotificationCompat.Builder(context, MyNotificationChannel.Events.id)
|
||||||
if(sp.getBoolean("n_$id", true) && checkPermission(context)) {
|
.setSmallIcon(type.icon)
|
||||||
registerChannels(context)
|
.setContentTitle(context.getString(type.text))
|
||||||
|
.setContentText(text)
|
||||||
|
.build()
|
||||||
|
notify(context, type, notification)
|
||||||
|
}
|
||||||
|
fun notify(context: Context, type: NotificationType, notification: Notification) {
|
||||||
|
val enabledNotifications = SP.notifications?.split(',')?.mapNotNull { it.toIntOrNull() }
|
||||||
|
if (enabledNotifications == null || type.id in enabledNotifications) {
|
||||||
val nm = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
val nm = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||||
nm.notify(id, notification)
|
nm.notify(type.id, notification)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
object Channel {
|
fun cancel(context: Context, type: NotificationType) {
|
||||||
const val LOCK_TASK_MODE = "LockTaskMode"
|
val nm = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||||
const val EVENTS = "Events"
|
nm.cancel(type.id)
|
||||||
}
|
|
||||||
object ID {
|
|
||||||
const val LOCK_TASK_MODE = 1
|
|
||||||
const val PASSWORD_CHANGED = 2
|
|
||||||
const val USER_ADDED = 3
|
|
||||||
const val USER_STARTED = 4
|
|
||||||
const val USER_SWITCHED = 5
|
|
||||||
const val USER_STOPPED = 6
|
|
||||||
const val USER_REMOVED = 7
|
|
||||||
const val BUG_REPORT_SHARED = 8
|
|
||||||
const val BUG_REPORT_SHARING_DECLINED = 9
|
|
||||||
const val BUG_REPORT_FAILED = 10
|
|
||||||
const val SYSTEM_UPDATE_PENDING = 11
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum class NotificationType(val id: Int, val text: Int, val icon: Int) {
|
||||||
|
LockTaskMode(1, R.string.lock_task_mode, R.drawable.lock_fill0),
|
||||||
|
PasswordChanged(2, R.string.password_changed, R.drawable.password_fill0),
|
||||||
|
UserAdded(3, R.string.user_added, R.drawable.person_add_fill0),
|
||||||
|
UserStarted(4, R.string.user_started, R.drawable.person_fill0),
|
||||||
|
UserSwitched(5, R.string.user_switched, R.drawable.person_fill0),
|
||||||
|
UserStopped(6, R.string.user_stopped, R.drawable.person_off),
|
||||||
|
UserRemoved(7, R.string.user_removed, R.drawable.person_remove_fill0),
|
||||||
|
BugReportShared(8, R.string.bug_report_shared, R.drawable.bug_report_fill0),
|
||||||
|
BugReportSharingDeclined(9, R.string.bug_report_sharing_declined, R.drawable.bug_report_fill0),
|
||||||
|
BugReportFailed(10, R.string.bug_report_failed, R.drawable.bug_report_fill0),
|
||||||
|
SystemUpdatePending(11, R.string.system_update_pending, R.drawable.system_update_fill0)
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class MyNotificationChannel(val id: String, val text: Int) {
|
||||||
|
LockTaskMode("LockTaskMode", R.string.lock_task_mode),
|
||||||
|
Events("Events", R.string.events)
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package com.bintianqi.owndroid
|
package com.bintianqi.owndroid
|
||||||
|
|
||||||
import android.app.NotificationManager
|
|
||||||
import android.app.PendingIntent
|
import android.app.PendingIntent
|
||||||
import android.app.admin.DeviceAdminReceiver
|
import android.app.admin.DeviceAdminReceiver
|
||||||
import android.content.ComponentName
|
import android.content.ComponentName
|
||||||
@@ -17,9 +16,6 @@ import com.bintianqi.owndroid.dpm.processSecurityLogs
|
|||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.text.SimpleDateFormat
|
|
||||||
import java.util.Date
|
|
||||||
import java.util.Locale
|
|
||||||
|
|
||||||
class Receiver : DeviceAdminReceiver() {
|
class Receiver : DeviceAdminReceiver() {
|
||||||
override fun onReceive(context: Context, intent: Intent) {
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
@@ -75,68 +71,59 @@ class Receiver : DeviceAdminReceiver() {
|
|||||||
|
|
||||||
override fun onLockTaskModeEntering(context: Context, intent: Intent, pkg: String) {
|
override fun onLockTaskModeEntering(context: Context, intent: Intent, pkg: String) {
|
||||||
super.onLockTaskModeEntering(context, intent, pkg)
|
super.onLockTaskModeEntering(context, intent, pkg)
|
||||||
if(!NotificationUtils.checkPermission(context)) return
|
if (!NotificationUtils.checkPermission(context)) return
|
||||||
NotificationUtils.registerChannels(context)
|
|
||||||
val intent = Intent(context, this::class.java).setAction("com.bintianqi.owndroid.action.STOP_LOCK_TASK_MODE")
|
val intent = Intent(context, this::class.java).setAction("com.bintianqi.owndroid.action.STOP_LOCK_TASK_MODE")
|
||||||
val pendingIntent = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_IMMUTABLE)
|
val pendingIntent = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_IMMUTABLE)
|
||||||
val builder = NotificationCompat.Builder(context, NotificationUtils.Channel.LOCK_TASK_MODE)
|
val builder = NotificationCompat.Builder(context, MyNotificationChannel.LockTaskMode.id)
|
||||||
.setContentTitle(context.getText(R.string.lock_task_mode))
|
.setContentTitle(context.getText(R.string.lock_task_mode))
|
||||||
.setSmallIcon(R.drawable.lock_fill0)
|
.setSmallIcon(R.drawable.lock_fill0)
|
||||||
.addAction(NotificationCompat.Action.Builder(null, context.getString(R.string.stop), pendingIntent).build())
|
.addAction(NotificationCompat.Action.Builder(null, context.getString(R.string.stop), pendingIntent).build())
|
||||||
NotificationUtils.notify(context, NotificationUtils.ID.LOCK_TASK_MODE, builder.build())
|
NotificationUtils.notify(context, NotificationType.LockTaskMode, builder.build())
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onLockTaskModeExiting(context: Context, intent: Intent) {
|
override fun onLockTaskModeExiting(context: Context, intent: Intent) {
|
||||||
super.onLockTaskModeExiting(context, intent)
|
super.onLockTaskModeExiting(context, intent)
|
||||||
val nm = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
NotificationUtils.cancel(context, NotificationType.LockTaskMode)
|
||||||
nm.cancel(NotificationUtils.ID.LOCK_TASK_MODE)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPasswordChanged(context: Context, intent: Intent, userHandle: UserHandle) {
|
override fun onPasswordChanged(context: Context, intent: Intent, userHandle: UserHandle) {
|
||||||
super.onPasswordChanged(context, intent, userHandle)
|
super.onPasswordChanged(context, intent, userHandle)
|
||||||
sendUserRelatedNotification(context, userHandle, NotificationUtils.ID.PASSWORD_CHANGED, R.string.password_changed, R.drawable.password_fill0)
|
sendUserRelatedNotification(context, userHandle, NotificationType.PasswordChanged)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onUserAdded(context: Context, intent: Intent, addedUser: UserHandle) {
|
override fun onUserAdded(context: Context, intent: Intent, addedUser: UserHandle) {
|
||||||
super.onUserAdded(context, intent, addedUser)
|
super.onUserAdded(context, intent, addedUser)
|
||||||
sendUserRelatedNotification(context, addedUser, NotificationUtils.ID.USER_ADDED, R.string.user_added, R.drawable.person_add_fill0)
|
sendUserRelatedNotification(context, addedUser, NotificationType.UserAdded)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onUserStarted(context: Context, intent: Intent, startedUser: UserHandle) {
|
override fun onUserStarted(context: Context, intent: Intent, startedUser: UserHandle) {
|
||||||
super.onUserStarted(context, intent, startedUser)
|
super.onUserStarted(context, intent, startedUser)
|
||||||
sendUserRelatedNotification(context, startedUser, NotificationUtils.ID.USER_STARTED, R.string.user_started, R.drawable.person_fill0)
|
sendUserRelatedNotification(context, startedUser, NotificationType.UserStarted)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onUserSwitched(context: Context, intent: Intent, switchedUser: UserHandle) {
|
override fun onUserSwitched(context: Context, intent: Intent, switchedUser: UserHandle) {
|
||||||
super.onUserSwitched(context, intent, switchedUser)
|
super.onUserSwitched(context, intent, switchedUser)
|
||||||
sendUserRelatedNotification(context, switchedUser, NotificationUtils.ID.USER_SWITCHED, R.string.user_switched, R.drawable.person_fill0)
|
sendUserRelatedNotification(context, switchedUser, NotificationType.UserSwitched)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onUserStopped(context: Context, intent: Intent, stoppedUser: UserHandle) {
|
override fun onUserStopped(context: Context, intent: Intent, stoppedUser: UserHandle) {
|
||||||
super.onUserStopped(context, intent, stoppedUser)
|
super.onUserStopped(context, intent, stoppedUser)
|
||||||
sendUserRelatedNotification(context, stoppedUser, NotificationUtils.ID.USER_STOPPED, R.string.user_stopped, R.drawable.person_fill0)
|
sendUserRelatedNotification(context, stoppedUser, NotificationType.UserStopped)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onUserRemoved(context: Context, intent: Intent, removedUser: UserHandle) {
|
override fun onUserRemoved(context: Context, intent: Intent, removedUser: UserHandle) {
|
||||||
super.onUserRemoved(context, intent, removedUser)
|
super.onUserRemoved(context, intent, removedUser)
|
||||||
sendUserRelatedNotification(context, removedUser, NotificationUtils.ID.USER_REMOVED, R.string.user_removed, R.drawable.person_remove_fill0)
|
sendUserRelatedNotification(context, removedUser, NotificationType.UserRemoved)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBugreportShared(context: Context, intent: Intent, hash: String) {
|
override fun onBugreportShared(context: Context, intent: Intent, hash: String) {
|
||||||
super.onBugreportShared(context, intent, hash)
|
super.onBugreportShared(context, intent, hash)
|
||||||
val builder = NotificationCompat.Builder(context, NotificationUtils.Channel.EVENTS)
|
NotificationUtils.notifyEvent(context, NotificationType.BugReportShared, "SHA-256 hash: $hash")
|
||||||
.setContentTitle(context.getString(R.string.bug_report_shared))
|
|
||||||
.setContentText("SHA-256 hash: $hash")
|
|
||||||
.setSmallIcon(R.drawable.bug_report_fill0)
|
|
||||||
NotificationUtils.notify(context, NotificationUtils.ID.BUG_REPORT_SHARED, builder.build())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBugreportSharingDeclined(context: Context, intent: Intent) {
|
override fun onBugreportSharingDeclined(context: Context, intent: Intent) {
|
||||||
super.onBugreportSharingDeclined(context, intent)
|
super.onBugreportSharingDeclined(context, intent)
|
||||||
val builder = NotificationCompat.Builder(context, NotificationUtils.Channel.EVENTS)
|
NotificationUtils.notifyEvent(context, NotificationType.BugReportSharingDeclined, "")
|
||||||
.setContentTitle(context.getString(R.string.bug_report_sharing_declined))
|
|
||||||
.setSmallIcon(R.drawable.bug_report_fill0)
|
|
||||||
NotificationUtils.notify(context, NotificationUtils.ID.BUG_REPORT_SHARING_DECLINED, builder.build())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBugreportFailed(context: Context, intent: Intent, failureCode: Int) {
|
override fun onBugreportFailed(context: Context, intent: Intent, failureCode: Int) {
|
||||||
@@ -146,30 +133,21 @@ class Receiver : DeviceAdminReceiver() {
|
|||||||
BUGREPORT_FAILURE_FILE_NO_LONGER_AVAILABLE -> R.string.bug_report_failure_no_longer_available
|
BUGREPORT_FAILURE_FILE_NO_LONGER_AVAILABLE -> R.string.bug_report_failure_no_longer_available
|
||||||
else -> R.string.place_holder
|
else -> R.string.place_holder
|
||||||
}
|
}
|
||||||
val builder = NotificationCompat.Builder(context, NotificationUtils.Channel.EVENTS)
|
NotificationUtils.notifyEvent(context, NotificationType.BugReportFailed, context.getString(message))
|
||||||
.setContentTitle(context.getString(R.string.bug_report_failed))
|
|
||||||
.setContentText(context.getString(message))
|
|
||||||
.setSmallIcon(R.drawable.bug_report_fill0)
|
|
||||||
NotificationUtils.notify(context, NotificationUtils.ID.BUG_REPORT_FAILED, builder.build())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSystemUpdatePending(context: Context, intent: Intent, receivedTime: Long) {
|
override fun onSystemUpdatePending(context: Context, intent: Intent, receivedTime: Long) {
|
||||||
super.onSystemUpdatePending(context, intent, receivedTime)
|
super.onSystemUpdatePending(context, intent, receivedTime)
|
||||||
val time = SimpleDateFormat("yyyy/MM/dd HH:mm:ss", Locale.getDefault()).format(Date(receivedTime))
|
val text = context.getString(R.string.received_time) + ": " + formatDate(receivedTime)
|
||||||
val builder = NotificationCompat.Builder(context, NotificationUtils.Channel.EVENTS)
|
NotificationUtils.notifyEvent(context, NotificationType.SystemUpdatePending, text)
|
||||||
.setContentTitle(context.getString(R.string.system_update_pending))
|
|
||||||
.setContentText(context.getString(R.string.received_time) + ": $time")
|
|
||||||
.setSmallIcon(R.drawable.system_update_fill0)
|
|
||||||
NotificationUtils.notify(context, NotificationUtils.ID.SYSTEM_UPDATE_PENDING, builder.build())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun sendUserRelatedNotification(context: Context, userHandle: UserHandle, id: Int, title: Int, icon: Int) {
|
private fun sendUserRelatedNotification(
|
||||||
|
context: Context, userHandle: UserHandle, type: NotificationType
|
||||||
|
) {
|
||||||
val um = context.getSystemService(Context.USER_SERVICE) as UserManager
|
val um = context.getSystemService(Context.USER_SERVICE) as UserManager
|
||||||
val serial = um.getSerialNumberForUser(userHandle)
|
val serial = um.getSerialNumberForUser(userHandle)
|
||||||
val builder = NotificationCompat.Builder(context, NotificationUtils.Channel.EVENTS)
|
val text = context.getString(R.string.serial_number) + ": $serial"
|
||||||
.setContentTitle(context.getString(title))
|
NotificationUtils.notifyEvent(context, type, text)
|
||||||
.setContentText(context.getString(R.string.serial_number) + ": $serial")
|
|
||||||
.setSmallIcon(icon)
|
|
||||||
NotificationUtils.notify(context, id, builder.build())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,9 +17,7 @@ import androidx.compose.foundation.layout.fillMaxSize
|
|||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.ime
|
import androidx.compose.foundation.layout.ime
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.widthIn
|
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.text.KeyboardActions
|
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
@@ -39,24 +37,23 @@ import androidx.compose.material3.Switch
|
|||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TopAppBarDefaults
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateListOf
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.focus.FocusRequester
|
|
||||||
import androidx.compose.ui.focus.focusRequester
|
|
||||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.platform.LocalFocusManager
|
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.input.ImeAction
|
import androidx.compose.ui.text.input.ImeAction
|
||||||
import androidx.compose.ui.text.input.KeyboardType
|
import androidx.compose.ui.text.input.KeyboardType
|
||||||
|
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
||||||
import androidx.compose.ui.unit.DpOffset
|
import androidx.compose.ui.unit.DpOffset
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.core.content.edit
|
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import com.bintianqi.owndroid.ui.FunctionItem
|
import com.bintianqi.owndroid.ui.FunctionItem
|
||||||
@@ -64,8 +61,8 @@ import com.bintianqi.owndroid.ui.MyScaffold
|
|||||||
import com.bintianqi.owndroid.ui.NavIcon
|
import com.bintianqi.owndroid.ui.NavIcon
|
||||||
import com.bintianqi.owndroid.ui.Notes
|
import com.bintianqi.owndroid.ui.Notes
|
||||||
import com.bintianqi.owndroid.ui.SwitchItem
|
import com.bintianqi.owndroid.ui.SwitchItem
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import java.security.SecureRandom
|
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
@@ -142,20 +139,25 @@ fun SettingsScreen(onNavigateUp: () -> Unit, onNavigate: (Any) -> Unit) {
|
|||||||
@Serializable object SettingsOptions
|
@Serializable object SettingsOptions
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SettingsOptionsScreen(onNavigateUp: () -> Unit) {
|
fun SettingsOptionsScreen(
|
||||||
val context = LocalContext.current
|
getDisplayDangerousFeatures: () -> Boolean, getShortcutsEnabled: () -> Boolean,
|
||||||
|
setDisplayDangerousFeatures: (Boolean) -> Unit, setShortcutsEnabled: (Boolean) -> Unit,
|
||||||
|
onNavigateUp: () -> Unit
|
||||||
|
) {
|
||||||
|
var dangerousFeatures by remember { mutableStateOf(getDisplayDangerousFeatures()) }
|
||||||
|
var shortcuts by remember { mutableStateOf(getShortcutsEnabled()) }
|
||||||
MyScaffold(R.string.options, onNavigateUp, 0.dp) {
|
MyScaffold(R.string.options, onNavigateUp, 0.dp) {
|
||||||
SwitchItem(
|
SwitchItem(
|
||||||
R.string.show_dangerous_features, icon = R.drawable.warning_fill0,
|
R.string.show_dangerous_features, dangerousFeatures, {
|
||||||
getState = { SP.displayDangerousFeatures },
|
setDisplayDangerousFeatures(it)
|
||||||
onCheckedChange = { SP.displayDangerousFeatures = it }
|
dangerousFeatures = it
|
||||||
|
}, R.drawable.warning_fill0
|
||||||
)
|
)
|
||||||
SwitchItem(
|
SwitchItem(
|
||||||
R.string.shortcuts, icon = R.drawable.open_in_new,
|
R.string.shortcuts, shortcuts, {
|
||||||
getState = { SP.shortcuts }, onCheckedChange = {
|
setShortcutsEnabled(it)
|
||||||
SP.shortcuts = it
|
shortcuts = it
|
||||||
ShortcutUtils.setAllShortcuts(context)
|
}, R.drawable.open_in_new
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -163,13 +165,12 @@ fun SettingsOptionsScreen(onNavigateUp: () -> Unit) {
|
|||||||
@Serializable object Appearance
|
@Serializable object Appearance
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun AppearanceScreen(onNavigateUp: () -> Unit, currentTheme: ThemeSettings, onThemeChange: (ThemeSettings) -> Unit) {
|
fun AppearanceScreen(
|
||||||
|
onNavigateUp: () -> Unit, currentTheme: StateFlow<ThemeSettings>,
|
||||||
|
setTheme: (ThemeSettings) -> Unit
|
||||||
|
) {
|
||||||
var darkThemeMenu by remember { mutableStateOf(false) }
|
var darkThemeMenu by remember { mutableStateOf(false) }
|
||||||
var theme by remember { mutableStateOf(currentTheme) }
|
val theme by currentTheme.collectAsStateWithLifecycle()
|
||||||
fun update(it: ThemeSettings) {
|
|
||||||
theme = it
|
|
||||||
onThemeChange(it)
|
|
||||||
}
|
|
||||||
val darkThemeTextID = when(theme.darkTheme) {
|
val darkThemeTextID = when(theme.darkTheme) {
|
||||||
1 -> R.string.on
|
1 -> R.string.on
|
||||||
0 -> R.string.off
|
0 -> R.string.off
|
||||||
@@ -180,7 +181,7 @@ fun AppearanceScreen(onNavigateUp: () -> Unit, currentTheme: ThemeSettings, onTh
|
|||||||
SwitchItem(
|
SwitchItem(
|
||||||
R.string.material_you_color,
|
R.string.material_you_color,
|
||||||
state = theme.materialYou,
|
state = theme.materialYou,
|
||||||
onCheckedChange = { update(theme.copy(materialYou = it)) }
|
onCheckedChange = { setTheme(theme.copy(materialYou = it)) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Box {
|
Box {
|
||||||
@@ -192,22 +193,21 @@ fun AppearanceScreen(onNavigateUp: () -> Unit, currentTheme: ThemeSettings, onTh
|
|||||||
DropdownMenuItem(
|
DropdownMenuItem(
|
||||||
text = { Text(stringResource(R.string.follow_system)) },
|
text = { Text(stringResource(R.string.follow_system)) },
|
||||||
onClick = {
|
onClick = {
|
||||||
update(theme.copy(darkTheme = -1))
|
setTheme(theme.copy(darkTheme = -1))
|
||||||
darkThemeMenu = false
|
darkThemeMenu = false
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
DropdownMenuItem(
|
DropdownMenuItem(
|
||||||
text = { Text(stringResource(R.string.on)) },
|
text = { Text(stringResource(R.string.on)) },
|
||||||
onClick = {
|
onClick = {
|
||||||
update(theme.copy(darkTheme = 1))
|
setTheme(theme.copy(darkTheme = 1))
|
||||||
theme = theme.copy(darkTheme = 1)
|
|
||||||
darkThemeMenu = false
|
darkThemeMenu = false
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
DropdownMenuItem(
|
DropdownMenuItem(
|
||||||
text = { Text(stringResource(R.string.off)) },
|
text = { Text(stringResource(R.string.off)) },
|
||||||
onClick = {
|
onClick = {
|
||||||
update(theme.copy(darkTheme = 0))
|
setTheme(theme.copy(darkTheme = 0))
|
||||||
darkThemeMenu = false
|
darkThemeMenu = false
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -216,148 +216,138 @@ fun AppearanceScreen(onNavigateUp: () -> Unit, currentTheme: ThemeSettings, onTh
|
|||||||
AnimatedVisibility(theme.darkTheme == 1 || (theme.darkTheme == -1 && isSystemInDarkTheme())) {
|
AnimatedVisibility(theme.darkTheme == 1 || (theme.darkTheme == -1 && isSystemInDarkTheme())) {
|
||||||
SwitchItem(
|
SwitchItem(
|
||||||
R.string.black_theme, state = theme.blackTheme,
|
R.string.black_theme, state = theme.blackTheme,
|
||||||
onCheckedChange = { update(theme.copy(blackTheme = it)) }
|
onCheckedChange = { setTheme(theme.copy(blackTheme = it)) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data class AppLockConfig(
|
||||||
|
/** null means no password, empty means password already set */
|
||||||
|
val password: String?, val biometrics: Boolean, val whenLeaving: Boolean
|
||||||
|
)
|
||||||
|
|
||||||
@Serializable object AppLockSettings
|
@Serializable object AppLockSettings
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun AppLockSettingsScreen(onNavigateUp: () -> Unit) = MyScaffold(R.string.app_lock, onNavigateUp, 0.dp) {
|
fun AppLockSettingsScreen(
|
||||||
val fm = LocalFocusManager.current
|
getConfig: () -> AppLockConfig, setConfig: (AppLockConfig) -> Unit,
|
||||||
|
onNavigateUp: () -> Unit
|
||||||
|
) = MyScaffold(R.string.app_lock, onNavigateUp) {
|
||||||
var password by remember { mutableStateOf("") }
|
var password by remember { mutableStateOf("") }
|
||||||
var confirmPassword by remember { mutableStateOf("") }
|
var confirmPassword by remember { mutableStateOf("") }
|
||||||
var allowBiometrics by remember { mutableStateOf(SP.biometricsUnlock) }
|
var allowBiometrics by remember { mutableStateOf(false) }
|
||||||
var lockWhenLeaving by remember { mutableStateOf(SP.lockWhenLeaving) }
|
var lockWhenLeaving by remember { mutableStateOf(false) }
|
||||||
val fr = remember { FocusRequester() }
|
var alreadySet by remember { mutableStateOf(false) }
|
||||||
val alreadySet = !SP.lockPasswordHash.isNullOrEmpty()
|
val isInputLegal = password.length !in 1..3 && (alreadySet || password.isNotBlank())
|
||||||
val isInputLegal = password.length !in 1..3 && (alreadySet || (password.isNotEmpty() && password.isNotBlank()))
|
LaunchedEffect(Unit) {
|
||||||
Column(Modifier
|
val config = getConfig()
|
||||||
.widthIn(max = 300.dp)
|
password = config.password ?: ""
|
||||||
.align(Alignment.CenterHorizontally)) {
|
allowBiometrics = config.biometrics
|
||||||
OutlinedTextField(
|
lockWhenLeaving = config.whenLeaving
|
||||||
password, { password = it }, Modifier
|
alreadySet = config.password != null
|
||||||
.fillMaxWidth()
|
}
|
||||||
.padding(vertical = 4.dp),
|
OutlinedTextField(
|
||||||
label = { Text(stringResource(R.string.password)) },
|
password, { password = it }, Modifier.fillMaxWidth().padding(vertical = 4.dp),
|
||||||
supportingText = { Text(stringResource(if(alreadySet) R.string.leave_empty_to_remain_unchanged else R.string.minimum_length_4)) },
|
label = { Text(stringResource(R.string.password)) },
|
||||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password, imeAction = ImeAction.Next),
|
supportingText = { Text(stringResource(if(alreadySet) R.string.leave_empty_to_remain_unchanged else R.string.minimum_length_4)) },
|
||||||
keyboardActions = KeyboardActions { fr.requestFocus() }
|
visualTransformation = PasswordVisualTransformation(),
|
||||||
)
|
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password, imeAction = ImeAction.Next)
|
||||||
OutlinedTextField(
|
)
|
||||||
confirmPassword, { confirmPassword = it }, Modifier
|
OutlinedTextField(
|
||||||
.fillMaxWidth()
|
confirmPassword, { confirmPassword = it }, Modifier.fillMaxWidth(),
|
||||||
.focusRequester(fr),
|
label = { Text(stringResource(R.string.confirm_password)) },
|
||||||
label = { Text(stringResource(R.string.confirm_password)) },
|
visualTransformation = PasswordVisualTransformation(),
|
||||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password, imeAction = ImeAction.Done),
|
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password, imeAction = ImeAction.Done)
|
||||||
keyboardActions = KeyboardActions { fm.clearFocus() }
|
)
|
||||||
)
|
if (VERSION.SDK_INT >= 28) Row(
|
||||||
if(VERSION.SDK_INT >= 28) Row(Modifier
|
Modifier.fillMaxWidth().padding(vertical = 6.dp),
|
||||||
.fillMaxWidth()
|
Arrangement.SpaceBetween, Alignment.CenterVertically
|
||||||
.padding(vertical = 6.dp), Arrangement.SpaceBetween, Alignment.CenterVertically) {
|
) {
|
||||||
Text(stringResource(R.string.allow_biometrics))
|
Text(stringResource(R.string.allow_biometrics))
|
||||||
Switch(allowBiometrics, { allowBiometrics = it })
|
Switch(allowBiometrics, { allowBiometrics = it })
|
||||||
}
|
}
|
||||||
Row(Modifier
|
Row(
|
||||||
.fillMaxWidth()
|
Modifier.fillMaxWidth().padding(bottom = 6.dp),
|
||||||
.padding(bottom = 6.dp), Arrangement.SpaceBetween, Alignment.CenterVertically) {
|
Arrangement.SpaceBetween, Alignment.CenterVertically
|
||||||
Text(stringResource(R.string.lock_when_leaving))
|
) {
|
||||||
Switch(lockWhenLeaving, { lockWhenLeaving = it })
|
Text(stringResource(R.string.lock_when_leaving))
|
||||||
}
|
Switch(lockWhenLeaving, { lockWhenLeaving = it })
|
||||||
Button(
|
}
|
||||||
onClick = {
|
Button(
|
||||||
fm.clearFocus()
|
onClick = {
|
||||||
if(password.isNotEmpty()) SP.lockPasswordHash = password.hash()
|
setConfig(AppLockConfig(password, allowBiometrics, lockWhenLeaving))
|
||||||
SP.biometricsUnlock = allowBiometrics
|
onNavigateUp()
|
||||||
SP.lockWhenLeaving = lockWhenLeaving
|
},
|
||||||
onNavigateUp()
|
modifier = Modifier.fillMaxWidth(),
|
||||||
},
|
enabled = isInputLegal && confirmPassword == password
|
||||||
modifier = Modifier.fillMaxWidth(),
|
) {
|
||||||
enabled = isInputLegal && confirmPassword == password
|
Text(stringResource(if(alreadySet) R.string.update else R.string.set))
|
||||||
) {
|
}
|
||||||
Text(stringResource(if(alreadySet) R.string.update else R.string.set))
|
if (alreadySet) FilledTonalButton(
|
||||||
}
|
onClick = {
|
||||||
if(alreadySet) FilledTonalButton(
|
setConfig(AppLockConfig(null, false, false))
|
||||||
onClick = {
|
onNavigateUp()
|
||||||
fm.clearFocus()
|
},
|
||||||
SP.lockPasswordHash = ""
|
modifier = Modifier.fillMaxWidth()
|
||||||
SP.biometricsUnlock = false
|
) {
|
||||||
SP.lockWhenLeaving = false
|
Text(stringResource(R.string.disable))
|
||||||
onNavigateUp()
|
|
||||||
},
|
|
||||||
modifier = Modifier.fillMaxWidth()
|
|
||||||
) {
|
|
||||||
Text(stringResource(R.string.disable))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Serializable object ApiSettings
|
@Serializable object ApiSettings
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ApiSettings(onNavigateUp: () -> Unit) {
|
fun ApiSettings(
|
||||||
|
getEnabled: () -> Boolean, setKey: (String) -> Unit, onNavigateUp: () -> Unit
|
||||||
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
var alreadyEnabled by remember { mutableStateOf(getEnabled()) }
|
||||||
MyScaffold(R.string.api, onNavigateUp) {
|
MyScaffold(R.string.api, onNavigateUp) {
|
||||||
var enabled by remember { mutableStateOf(SP.isApiEnabled) }
|
var enabled by remember { mutableStateOf(alreadyEnabled) }
|
||||||
|
var key by remember { mutableStateOf("") }
|
||||||
SwitchItem(R.string.enable, state = enabled, onCheckedChange = {
|
SwitchItem(R.string.enable, state = enabled, onCheckedChange = {
|
||||||
enabled = it
|
enabled = it
|
||||||
SP.isApiEnabled = it
|
|
||||||
if(!it) SP.sharedPrefs.edit { remove("api.key") }
|
|
||||||
}, padding = false)
|
}, padding = false)
|
||||||
if(enabled) {
|
if (enabled) {
|
||||||
var key by remember { mutableStateOf("") }
|
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = key, onValueChange = { key = it }, label = { Text(stringResource(R.string.api_key)) },
|
key, { key = it }, Modifier.fillMaxWidth().padding(bottom = 4.dp),
|
||||||
modifier = Modifier
|
label = { Text(stringResource(R.string.api_key)) },
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(bottom = 4.dp), readOnly = true,
|
|
||||||
trailingIcon = {
|
trailingIcon = {
|
||||||
IconButton(
|
IconButton({ key = generateBase64Key(10) }) {
|
||||||
onClick = {
|
Icon(painterResource(R.drawable.casino_fill0), null)
|
||||||
val charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
|
|
||||||
val sr = SecureRandom()
|
|
||||||
key = (1..20).map { charset[sr.nextInt(charset.length)] }.joinToString("")
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
Icon(painter = painterResource(R.drawable.casino_fill0), contentDescription = "Random")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
Button(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(bottom = 10.dp),
|
|
||||||
onClick = {
|
|
||||||
SP.apiKey = key
|
|
||||||
context.showOperationResultToast(true)
|
|
||||||
},
|
|
||||||
enabled = key.isNotEmpty()
|
|
||||||
) {
|
|
||||||
Text(stringResource(R.string.apply))
|
|
||||||
}
|
|
||||||
if(SP.apiKey != null) Notes(R.string.api_key_exist)
|
|
||||||
}
|
}
|
||||||
|
Button(
|
||||||
|
onClick = {
|
||||||
|
setKey(if (enabled) key else "")
|
||||||
|
alreadyEnabled = enabled
|
||||||
|
context.showOperationResultToast(true)
|
||||||
|
},
|
||||||
|
modifier = Modifier.fillMaxWidth().padding(bottom = 10.dp),
|
||||||
|
enabled = !enabled || key.length !in 0..7
|
||||||
|
) {
|
||||||
|
Text(stringResource(R.string.apply))
|
||||||
|
}
|
||||||
|
if (enabled && alreadyEnabled) Notes(R.string.api_key_exist)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Serializable object Notifications
|
@Serializable object Notifications
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun NotificationsScreen(onNavigateUp: () -> Unit) = MyScaffold(R.string.notifications, onNavigateUp, 0.dp) {
|
fun NotificationsScreen(
|
||||||
val sp = LocalContext.current.getSharedPreferences("data", Context.MODE_PRIVATE)
|
getState: () -> List<NotificationType>, setNotification: (NotificationType, Boolean) -> Unit,
|
||||||
val map = mapOf(
|
onNavigateUp: () -> Unit
|
||||||
NotificationUtils.ID.PASSWORD_CHANGED to R.string.password_changed, NotificationUtils.ID.USER_ADDED to R.string.user_added,
|
) = MyScaffold(R.string.notifications, onNavigateUp, 0.dp) {
|
||||||
NotificationUtils.ID.USER_STARTED to R.string.user_started, NotificationUtils.ID.USER_SWITCHED to R.string.user_switched,
|
val enabledNotifications = remember { mutableStateListOf(*getState().toTypedArray()) }
|
||||||
NotificationUtils.ID.USER_STOPPED to R.string.user_stopped, NotificationUtils.ID.USER_REMOVED to R.string.user_removed,
|
NotificationType.entries.forEach { type ->
|
||||||
NotificationUtils.ID.BUG_REPORT_SHARED to R.string.bug_report_shared,
|
SwitchItem(type.text, type in enabledNotifications, {
|
||||||
NotificationUtils.ID.BUG_REPORT_SHARING_DECLINED to R.string.bug_report_sharing_declined,
|
setNotification(type, it)
|
||||||
NotificationUtils.ID.BUG_REPORT_FAILED to R.string.bug_report_failed,
|
enabledNotifications.run { if (it) plusAssign(type) else minusAssign(type) }
|
||||||
NotificationUtils.ID.SYSTEM_UPDATE_PENDING to R.string.system_update_pending
|
})
|
||||||
)
|
|
||||||
map.forEach { (k, v) ->
|
|
||||||
SwitchItem(v, getState = { sp.getBoolean("n_$k", true) }, onCheckedChange = { sp.edit(true) { putBoolean("n_$k", it) } })
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,8 +13,7 @@ class SharedPrefs(context: Context) {
|
|||||||
var dhizuku by BooleanSharedPref("dhizuku_mode")
|
var dhizuku by BooleanSharedPref("dhizuku_mode")
|
||||||
var isDefaultAffiliationIdSet by BooleanSharedPref("default_affiliation_id_set")
|
var isDefaultAffiliationIdSet by BooleanSharedPref("default_affiliation_id_set")
|
||||||
var displayDangerousFeatures by BooleanSharedPref("display_dangerous_features")
|
var displayDangerousFeatures by BooleanSharedPref("display_dangerous_features")
|
||||||
var isApiEnabled by BooleanSharedPref("api.enabled")
|
var apiKeyHash by StringSharedPref("api_key_hash")
|
||||||
var apiKey by StringSharedPref("api.key")
|
|
||||||
var materialYou by BooleanSharedPref("theme.material_you", Build.VERSION.SDK_INT >= 31)
|
var materialYou by BooleanSharedPref("theme.material_you", Build.VERSION.SDK_INT >= 31)
|
||||||
/** -1: follow system, 0: off, 1: on */
|
/** -1: follow system, 0: off, 1: on */
|
||||||
var darkTheme by IntSharedPref("theme.dark", -1)
|
var darkTheme by IntSharedPref("theme.dark", -1)
|
||||||
@@ -25,6 +24,8 @@ class SharedPrefs(context: Context) {
|
|||||||
var applicationsListView by BooleanSharedPref("applications.list_view", true)
|
var applicationsListView by BooleanSharedPref("applications.list_view", true)
|
||||||
var shortcuts by BooleanSharedPref("shortcuts")
|
var shortcuts by BooleanSharedPref("shortcuts")
|
||||||
var dhizukuServer by BooleanSharedPref("dhizuku_server")
|
var dhizukuServer by BooleanSharedPref("dhizuku_server")
|
||||||
|
var notifications by StringSharedPref("notifications")
|
||||||
|
var shortcutKey by StringSharedPref("shortcut_key")
|
||||||
}
|
}
|
||||||
|
|
||||||
private class BooleanSharedPref(val key: String, val defValue: Boolean = false): ReadWriteProperty<SharedPrefs, Boolean> {
|
private class BooleanSharedPref(val key: String, val defValue: Boolean = false): ReadWriteProperty<SharedPrefs, Boolean> {
|
||||||
|
|||||||
@@ -5,10 +5,13 @@ import android.content.Intent
|
|||||||
import androidx.core.content.pm.ShortcutInfoCompat
|
import androidx.core.content.pm.ShortcutInfoCompat
|
||||||
import androidx.core.content.pm.ShortcutManagerCompat
|
import androidx.core.content.pm.ShortcutManagerCompat
|
||||||
import androidx.core.graphics.drawable.IconCompat
|
import androidx.core.graphics.drawable.IconCompat
|
||||||
|
import java.security.SecureRandom
|
||||||
|
import kotlin.io.encoding.Base64
|
||||||
|
|
||||||
object ShortcutUtils {
|
object ShortcutUtils {
|
||||||
fun setAllShortcuts(context: Context) {
|
fun setAllShortcuts(context: Context, enabled: Boolean) {
|
||||||
if (SP.shortcuts) {
|
if (enabled) {
|
||||||
|
setShortcutKey()
|
||||||
val list = listOf(
|
val list = listOf(
|
||||||
createShortcut(context, MyShortcut.Lock, true),
|
createShortcut(context, MyShortcut.Lock, true),
|
||||||
createShortcut(context, MyShortcut.DisableCamera,
|
createShortcut(context, MyShortcut.DisableCamera,
|
||||||
@@ -22,6 +25,7 @@ object ShortcutUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
fun setShortcut(context: Context, shortcut: MyShortcut, state: Boolean) {
|
fun setShortcut(context: Context, shortcut: MyShortcut, state: Boolean) {
|
||||||
|
setShortcutKey()
|
||||||
ShortcutManagerCompat.pushDynamicShortcut(
|
ShortcutManagerCompat.pushDynamicShortcut(
|
||||||
context, createShortcut(context, shortcut, state)
|
context, createShortcut(context, shortcut, state)
|
||||||
)
|
)
|
||||||
@@ -41,9 +45,47 @@ object ShortcutUtils {
|
|||||||
.setIntent(
|
.setIntent(
|
||||||
Intent(context, ShortcutsReceiverActivity::class.java)
|
Intent(context, ShortcutsReceiverActivity::class.java)
|
||||||
.setAction("com.bintianqi.owndroid.action.${shortcut.id}")
|
.setAction("com.bintianqi.owndroid.action.${shortcut.id}")
|
||||||
|
.putExtra("key", SP.shortcutKey)
|
||||||
)
|
)
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
/** @param state If true, set the user restriction */
|
||||||
|
fun createUserRestrictionShortcut(context: Context, id: String, state: Boolean): ShortcutInfoCompat {
|
||||||
|
val restriction = UserRestrictionsRepository.findRestrictionById(id)
|
||||||
|
val label = context.getString(if (state) R.string.disable else R.string.enable) + " " +
|
||||||
|
context.getString(restriction.name)
|
||||||
|
setShortcutKey()
|
||||||
|
return ShortcutInfoCompat.Builder(context, "USER_RESTRICTION-$id")
|
||||||
|
.setIcon(IconCompat.createWithResource(context, restriction.icon))
|
||||||
|
.setShortLabel(label)
|
||||||
|
.setIntent(
|
||||||
|
Intent(context, ShortcutsReceiverActivity::class.java)
|
||||||
|
.setAction("com.bintianqi.owndroid.action.USER_RESTRICTION")
|
||||||
|
.putExtra("restriction", id)
|
||||||
|
.putExtra("state", state)
|
||||||
|
.putExtra("key", SP.shortcutKey)
|
||||||
|
)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
fun setUserRestrictionShortcut(context: Context, id: String, state: Boolean): Boolean {
|
||||||
|
val shortcut = createUserRestrictionShortcut(context, id, state)
|
||||||
|
return ShortcutManagerCompat.requestPinShortcut(context, shortcut, null)
|
||||||
|
}
|
||||||
|
fun updateUserRestrictionShortcut(context: Context, id: String, state: Boolean, checkExist: Boolean) {
|
||||||
|
if (checkExist) {
|
||||||
|
val shortcuts = ShortcutManagerCompat.getShortcuts(
|
||||||
|
context, ShortcutManagerCompat.FLAG_MATCH_PINNED
|
||||||
|
)
|
||||||
|
if (shortcuts.find { it.id == "USER_RESTRICTION-$id" } == null) return
|
||||||
|
}
|
||||||
|
val shortcut = createUserRestrictionShortcut(context, id, state)
|
||||||
|
ShortcutManagerCompat.updateShortcuts(context, listOf(shortcut))
|
||||||
|
}
|
||||||
|
fun setShortcutKey() {
|
||||||
|
if (SP.shortcutKey.isNullOrEmpty()) {
|
||||||
|
SP.shortcutKey = generateBase64Key(10)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class MyShortcut(
|
enum class MyShortcut(
|
||||||
|
|||||||
@@ -9,7 +9,9 @@ class ShortcutsReceiverActivity : Activity() {
|
|||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
try {
|
try {
|
||||||
val action = intent.action?.removePrefix("com.bintianqi.owndroid.action.")
|
val action = intent.action?.removePrefix("com.bintianqi.owndroid.action.")
|
||||||
if (action != null && SP.shortcuts) {
|
val key = SP.shortcutKey
|
||||||
|
val requestKey = intent?.getStringExtra("key")
|
||||||
|
if (action != null && SP.shortcuts && key != null && requestKey == key) {
|
||||||
when (action) {
|
when (action) {
|
||||||
"LOCK" -> Privilege.DPM.lockNow()
|
"LOCK" -> Privilege.DPM.lockNow()
|
||||||
"DISABLE_CAMERA" -> {
|
"DISABLE_CAMERA" -> {
|
||||||
@@ -22,9 +24,22 @@ class ShortcutsReceiverActivity : Activity() {
|
|||||||
Privilege.DPM.setMasterVolumeMuted(Privilege.DAR, !state)
|
Privilege.DPM.setMasterVolumeMuted(Privilege.DAR, !state)
|
||||||
ShortcutUtils.setShortcut(this, MyShortcut.Mute, state)
|
ShortcutUtils.setShortcut(this, MyShortcut.Mute, state)
|
||||||
}
|
}
|
||||||
|
"USER_RESTRICTION" -> {
|
||||||
|
val state = intent?.getBooleanExtra("state", false)
|
||||||
|
val id = intent?.getStringExtra("restriction")
|
||||||
|
if (state == null || id == null) return
|
||||||
|
if (state) {
|
||||||
|
Privilege.DPM.addUserRestriction(Privilege.DAR, id)
|
||||||
|
} else {
|
||||||
|
Privilege.DPM.clearUserRestriction(Privilege.DAR, id)
|
||||||
|
}
|
||||||
|
ShortcutUtils.updateUserRestrictionShortcut(this, id, !state, false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Log.d(TAG, "Received intent: $action")
|
Log.d(TAG, "Received intent: $action")
|
||||||
showOperationResultToast(true)
|
showOperationResultToast(true)
|
||||||
|
} else {
|
||||||
|
showOperationResultToast(false)
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
finish()
|
finish()
|
||||||
|
|||||||
@@ -98,6 +98,13 @@ object UserRestrictionsRepository {
|
|||||||
UserRestrictionCategory.Other -> other
|
UserRestrictionCategory.Other -> other
|
||||||
}.filter { Build.VERSION.SDK_INT >= it.requiresApi }
|
}.filter { Build.VERSION.SDK_INT >= it.requiresApi }
|
||||||
}
|
}
|
||||||
|
fun findRestrictionById(id: String): Restriction {
|
||||||
|
listOf(network, connectivity, applications, media, users, other).forEach { list ->
|
||||||
|
val restriction = list.find { it.id == id }
|
||||||
|
if (restriction != null) return restriction
|
||||||
|
}
|
||||||
|
throw Exception("User restriction not found")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class UserRestrictionCategory(val title: Int, val icon: Int) {
|
enum class UserRestrictionCategory(val title: Int, val icon: Int) {
|
||||||
|
|||||||
@@ -18,10 +18,12 @@ import java.io.FileNotFoundException
|
|||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.security.MessageDigest
|
import java.security.MessageDigest
|
||||||
|
import java.security.SecureRandom
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
import kotlin.io.encoding.Base64
|
||||||
|
|
||||||
var zhCN = true
|
var zhCN = true
|
||||||
|
|
||||||
@@ -64,8 +66,8 @@ fun formatFileSize(bytes: Long): String {
|
|||||||
val Boolean.yesOrNo
|
val Boolean.yesOrNo
|
||||||
@StringRes get() = if(this) R.string.yes else R.string.no
|
@StringRes get() = if(this) R.string.yes else R.string.no
|
||||||
|
|
||||||
fun formatTime(ms: Long): String {
|
fun formatDate(ms: Long): String {
|
||||||
return SimpleDateFormat("yyyy/MM/dd HH:mm:ss", Locale.getDefault()).format(Date(ms))
|
return formatDate(Date(ms))
|
||||||
}
|
}
|
||||||
fun formatDate(date: Date): String {
|
fun formatDate(date: Date): String {
|
||||||
return SimpleDateFormat("yyyy/MM/dd HH:mm:ss", Locale.getDefault()).format(date)
|
return SimpleDateFormat("yyyy/MM/dd HH:mm:ss", Locale.getDefault()).format(date)
|
||||||
@@ -121,3 +123,9 @@ class SerializableSaver<T>(val serializer: KSerializer<T>) : Saver<T, String> {
|
|||||||
return Json.encodeToString(serializer, value)
|
return Json.encodeToString(serializer, value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun generateBase64Key(length: Int): String {
|
||||||
|
val ba = ByteArray(length)
|
||||||
|
SecureRandom().nextBytes(ba)
|
||||||
|
return Base64.withPadding(Base64.PaddingOption.ABSENT).encode(ba)
|
||||||
|
}
|
||||||
|
|||||||
@@ -443,13 +443,13 @@ fun handlePrivilegeChange(context: Context) {
|
|||||||
SP.dhizukuServer = false
|
SP.dhizukuServer = false
|
||||||
SP.shortcuts = privilege.activated
|
SP.shortcuts = privilege.activated
|
||||||
if (privilege.activated) {
|
if (privilege.activated) {
|
||||||
ShortcutUtils.setAllShortcuts(context)
|
ShortcutUtils.setAllShortcuts(context, true)
|
||||||
if (!privilege.dhizuku) {
|
if (!privilege.dhizuku) {
|
||||||
setDefaultAffiliationID()
|
setDefaultAffiliationID()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
SP.isDefaultAffiliationIdSet = false
|
SP.isDefaultAffiliationIdSet = false
|
||||||
ShortcutUtils.setAllShortcuts(context)
|
ShortcutUtils.setAllShortcuts(context, false)
|
||||||
SP.isApiEnabled = false
|
SP.apiKeyHash = ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,7 +70,6 @@ import androidx.compose.material3.FilledTonalIconButton
|
|||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.MaterialTheme.colorScheme
|
|
||||||
import androidx.compose.material3.MenuAnchorType
|
import androidx.compose.material3.MenuAnchorType
|
||||||
import androidx.compose.material3.OutlinedTextField
|
import androidx.compose.material3.OutlinedTextField
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
@@ -115,7 +114,7 @@ import com.bintianqi.owndroid.MyViewModel
|
|||||||
import com.bintianqi.owndroid.Privilege
|
import com.bintianqi.owndroid.Privilege
|
||||||
import com.bintianqi.owndroid.R
|
import com.bintianqi.owndroid.R
|
||||||
import com.bintianqi.owndroid.formatFileSize
|
import com.bintianqi.owndroid.formatFileSize
|
||||||
import com.bintianqi.owndroid.formatTime
|
import com.bintianqi.owndroid.formatDate
|
||||||
import com.bintianqi.owndroid.popToast
|
import com.bintianqi.owndroid.popToast
|
||||||
import com.bintianqi.owndroid.showOperationResultToast
|
import com.bintianqi.owndroid.showOperationResultToast
|
||||||
import com.bintianqi.owndroid.ui.ErrorDialog
|
import com.bintianqi.owndroid.ui.ErrorDialog
|
||||||
@@ -511,7 +510,7 @@ fun UpdateNetworkScreen(info: WifiInfo, setNetwork: (WifiInfo) -> Boolean, onNav
|
|||||||
TopAppBar(
|
TopAppBar(
|
||||||
{ Text(stringResource(R.string.update_network)) },
|
{ Text(stringResource(R.string.update_network)) },
|
||||||
navigationIcon = { NavIcon(onNavigateUp) },
|
navigationIcon = { NavIcon(onNavigateUp) },
|
||||||
colors = TopAppBarDefaults.topAppBarColors(colorScheme.surfaceContainer)
|
colors = TopAppBarDefaults.topAppBarColors(MaterialTheme.colorScheme.surfaceContainer)
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
contentWindowInsets = WindowInsets.ime
|
contentWindowInsets = WindowInsets.ime
|
||||||
@@ -1046,14 +1045,14 @@ fun NetworkStatsScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = startTime.let { if(it == -1L) "" else formatTime(it) }, onValueChange = {}, readOnly = true,
|
value = startTime.let { if(it == -1L) "" else formatDate(it) }, onValueChange = {}, readOnly = true,
|
||||||
label = { Text(stringResource(R.string.start_time)) },
|
label = { Text(stringResource(R.string.start_time)) },
|
||||||
interactionSource = startTimeIs,
|
interactionSource = startTimeIs,
|
||||||
isError = startTime >= endTime,
|
isError = startTime >= endTime,
|
||||||
modifier = Modifier.fillMaxWidth().padding(bottom = 4.dp)
|
modifier = Modifier.fillMaxWidth().padding(bottom = 4.dp)
|
||||||
)
|
)
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = formatTime(endTime), onValueChange = {}, readOnly = true,
|
value = formatDate(endTime), onValueChange = {}, readOnly = true,
|
||||||
label = { Text(stringResource(R.string.end_time)) },
|
label = { Text(stringResource(R.string.end_time)) },
|
||||||
interactionSource = endTimeIs,
|
interactionSource = endTimeIs,
|
||||||
isError = startTime >= endTime,
|
isError = startTime >= endTime,
|
||||||
@@ -1292,7 +1291,7 @@ fun NetworkStatsViewerScreen(
|
|||||||
HorizontalPager(ps, Modifier.padding(top = 8.dp)) { page ->
|
HorizontalPager(ps, Modifier.padding(top = 8.dp)) { page ->
|
||||||
val item = data[index]
|
val item = data[index]
|
||||||
Column(Modifier.fillMaxWidth().padding(horizontal = HorizontalPadding)) {
|
Column(Modifier.fillMaxWidth().padding(horizontal = HorizontalPadding)) {
|
||||||
Text(formatTime(item.startTime) + "\n~\n" + formatTime(item.endTime),
|
Text(formatDate(item.startTime) + "\n~\n" + formatDate(item.endTime),
|
||||||
Modifier.align(Alignment.CenterHorizontally), textAlign = TextAlign.Center)
|
Modifier.align(Alignment.CenterHorizontally), textAlign = TextAlign.Center)
|
||||||
Spacer(Modifier.height(5.dp))
|
Spacer(Modifier.height(5.dp))
|
||||||
val txBytes = item.txBytes
|
val txBytes = item.txBytes
|
||||||
@@ -1350,7 +1349,7 @@ enum class PrivateDnsMode(val id: Int, val text: Int) {
|
|||||||
Host(DevicePolicyManager.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME, R.string.enabled)
|
Host(DevicePolicyManager.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME, R.string.enabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
data class PrivateDnsConfiguration(val mode: PrivateDnsMode, val host: String)
|
data class PrivateDnsConfiguration(val mode: PrivateDnsMode?, val host: String)
|
||||||
|
|
||||||
@Serializable object PrivateDns
|
@Serializable object PrivateDns
|
||||||
|
|
||||||
@@ -1362,7 +1361,7 @@ fun PrivateDnsScreen(
|
|||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val focusMgr = LocalFocusManager.current
|
val focusMgr = LocalFocusManager.current
|
||||||
var mode by remember { mutableStateOf(PrivateDnsMode.Opportunistic) }
|
var mode by remember { mutableStateOf<PrivateDnsMode?>(PrivateDnsMode.Opportunistic) }
|
||||||
var inputHost by remember { mutableStateOf("") }
|
var inputHost by remember { mutableStateOf("") }
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
val conf = getPrivateDns()
|
val conf = getPrivateDns()
|
||||||
@@ -1385,7 +1384,8 @@ fun PrivateDnsScreen(
|
|||||||
val result = setPrivateDns(PrivateDnsConfiguration(mode, inputHost))
|
val result = setPrivateDns(PrivateDnsConfiguration(mode, inputHost))
|
||||||
context.showOperationResultToast(result)
|
context.showOperationResultToast(result)
|
||||||
},
|
},
|
||||||
modifier = Modifier.fillMaxWidth().padding(horizontal = HorizontalPadding)
|
modifier = Modifier.fillMaxWidth().padding(horizontal = HorizontalPadding),
|
||||||
|
enabled = mode != null
|
||||||
) {
|
) {
|
||||||
Text(stringResource(R.string.apply))
|
Text(stringResource(R.string.apply))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ import androidx.compose.foundation.text.KeyboardOptions
|
|||||||
import androidx.compose.material3.AlertDialog
|
import androidx.compose.material3.AlertDialog
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
import androidx.compose.material3.ButtonDefaults
|
import androidx.compose.material3.ButtonDefaults
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.MaterialTheme.colorScheme
|
import androidx.compose.material3.MaterialTheme.colorScheme
|
||||||
import androidx.compose.material3.MaterialTheme.typography
|
import androidx.compose.material3.MaterialTheme.typography
|
||||||
import androidx.compose.material3.OutlinedTextField
|
import androidx.compose.material3.OutlinedTextField
|
||||||
@@ -45,6 +47,7 @@ import androidx.compose.runtime.setValue
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.platform.LocalFocusManager
|
import androidx.compose.ui.platform.LocalFocusManager
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.input.ImeAction
|
import androidx.compose.ui.text.input.ImeAction
|
||||||
import androidx.compose.ui.text.input.KeyboardType
|
import androidx.compose.ui.text.input.KeyboardType
|
||||||
@@ -56,6 +59,7 @@ import com.bintianqi.owndroid.MyViewModel
|
|||||||
import com.bintianqi.owndroid.Privilege
|
import com.bintianqi.owndroid.Privilege
|
||||||
import com.bintianqi.owndroid.R
|
import com.bintianqi.owndroid.R
|
||||||
import com.bintianqi.owndroid.SP
|
import com.bintianqi.owndroid.SP
|
||||||
|
import com.bintianqi.owndroid.generateBase64Key
|
||||||
import com.bintianqi.owndroid.popToast
|
import com.bintianqi.owndroid.popToast
|
||||||
import com.bintianqi.owndroid.showOperationResultToast
|
import com.bintianqi.owndroid.showOperationResultToast
|
||||||
import com.bintianqi.owndroid.ui.CheckBoxItem
|
import com.bintianqi.owndroid.ui.CheckBoxItem
|
||||||
@@ -250,7 +254,12 @@ fun ResetPasswordTokenScreen(
|
|||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
token, { token = it }, Modifier.fillMaxWidth(),
|
token, { token = it }, Modifier.fillMaxWidth(),
|
||||||
label = { Text(stringResource(R.string.token)) },
|
label = { Text(stringResource(R.string.token)) },
|
||||||
supportingText = { Text("${token.length}/32") }
|
supportingText = { Text("${token.length}/32") },
|
||||||
|
trailingIcon = {
|
||||||
|
IconButton({ token = generateBase64Key(24) }) {
|
||||||
|
Icon(painterResource(R.drawable.casino_fill0), null)
|
||||||
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
Button(
|
Button(
|
||||||
onClick = {
|
onClick = {
|
||||||
@@ -265,7 +274,12 @@ fun ResetPasswordTokenScreen(
|
|||||||
}
|
}
|
||||||
if (state.set && !state.active) Button(
|
if (state.set && !state.active) Button(
|
||||||
onClick = {
|
onClick = {
|
||||||
getIntent()?.let { launcher.launch(it) }
|
val intent = getIntent()
|
||||||
|
if (intent == null) {
|
||||||
|
context.showOperationResultToast(false)
|
||||||
|
} else {
|
||||||
|
launcher.launch(intent)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth()
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ import com.bintianqi.owndroid.Privilege
|
|||||||
import com.bintianqi.owndroid.R
|
import com.bintianqi.owndroid.R
|
||||||
import com.bintianqi.owndroid.SP
|
import com.bintianqi.owndroid.SP
|
||||||
import com.bintianqi.owndroid.formatFileSize
|
import com.bintianqi.owndroid.formatFileSize
|
||||||
import com.bintianqi.owndroid.formatTime
|
import com.bintianqi.owndroid.formatDate
|
||||||
import com.bintianqi.owndroid.popToast
|
import com.bintianqi.owndroid.popToast
|
||||||
import com.bintianqi.owndroid.showOperationResultToast
|
import com.bintianqi.owndroid.showOperationResultToast
|
||||||
import com.bintianqi.owndroid.ui.CheckBoxItem
|
import com.bintianqi.owndroid.ui.CheckBoxItem
|
||||||
@@ -588,7 +588,7 @@ fun ChangeTimeScreen(setTime: (Long, Boolean) -> Boolean, onNavigateUp: () -> Un
|
|||||||
) {
|
) {
|
||||||
if(page == 0) {
|
if(page == 0) {
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = datePickerState.selectedDateMillis?.let { formatTime(it) } ?: "",
|
value = datePickerState.selectedDateMillis?.let { formatDate(it) } ?: "",
|
||||||
onValueChange = {}, readOnly = true,
|
onValueChange = {}, readOnly = true,
|
||||||
label = { Text(stringResource(R.string.date)) },
|
label = { Text(stringResource(R.string.date)) },
|
||||||
interactionSource = dateInteractionSource,
|
interactionSource = dateInteractionSource,
|
||||||
@@ -1436,9 +1436,9 @@ fun CaCertScreen(
|
|||||||
Text("Issuer", style = typography.labelLarge)
|
Text("Issuer", style = typography.labelLarge)
|
||||||
SelectionContainer { Text(cert.issuer) }
|
SelectionContainer { Text(cert.issuer) }
|
||||||
Text("Issued on", style = typography.labelLarge)
|
Text("Issued on", style = typography.labelLarge)
|
||||||
SelectionContainer { Text(formatTime(cert.issuedTime)) }
|
SelectionContainer { Text(formatDate(cert.issuedTime)) }
|
||||||
Text("Expires on", style = typography.labelLarge)
|
Text("Expires on", style = typography.labelLarge)
|
||||||
SelectionContainer { Text(formatTime(cert.expiresTime)) }
|
SelectionContainer { Text(formatDate(cert.expiresTime)) }
|
||||||
Text("SHA-256 fingerprint", style = typography.labelLarge)
|
Text("SHA-256 fingerprint", style = typography.labelLarge)
|
||||||
SelectionContainer { Text(cert.hash) }
|
SelectionContainer { Text(cert.hash) }
|
||||||
if (dialog == 2) Row(
|
if (dialog == 2) Row(
|
||||||
@@ -1929,7 +1929,7 @@ fun SystemUpdatePolicyScreen(
|
|||||||
if (VERSION.SDK_INT >= 26) {
|
if (VERSION.SDK_INT >= 26) {
|
||||||
Column(Modifier.padding(HorizontalPadding)) {
|
Column(Modifier.padding(HorizontalPadding)) {
|
||||||
if (pendingUpdate.exists) {
|
if (pendingUpdate.exists) {
|
||||||
Text(stringResource(R.string.update_received_time, formatTime(pendingUpdate.time)))
|
Text(stringResource(R.string.update_received_time, formatDate(pendingUpdate.time)))
|
||||||
Text(stringResource(R.string.is_security_patch,
|
Text(stringResource(R.string.is_security_patch,
|
||||||
stringResource(pendingUpdate.securityPatch.yesOrNo)))
|
stringResource(pendingUpdate.securityPatch.yesOrNo)))
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package com.bintianqi.owndroid.dpm
|
package com.bintianqi.owndroid.dpm
|
||||||
|
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.combinedClickable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
@@ -14,6 +16,7 @@ import androidx.compose.foundation.layout.padding
|
|||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.foundation.text.KeyboardActions
|
import androidx.compose.foundation.text.KeyboardActions
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
@@ -21,6 +24,7 @@ import androidx.compose.material.icons.Icons
|
|||||||
import androidx.compose.material.icons.filled.Add
|
import androidx.compose.material.icons.filled.Add
|
||||||
import androidx.compose.material.icons.filled.Edit
|
import androidx.compose.material.icons.filled.Edit
|
||||||
import androidx.compose.material.icons.outlined.Delete
|
import androidx.compose.material.icons.outlined.Delete
|
||||||
|
import androidx.compose.material.icons.outlined.Info
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
@@ -54,6 +58,7 @@ import com.bintianqi.owndroid.Privilege
|
|||||||
import com.bintianqi.owndroid.R
|
import com.bintianqi.owndroid.R
|
||||||
import com.bintianqi.owndroid.UserRestrictionCategory
|
import com.bintianqi.owndroid.UserRestrictionCategory
|
||||||
import com.bintianqi.owndroid.UserRestrictionsRepository
|
import com.bintianqi.owndroid.UserRestrictionsRepository
|
||||||
|
import com.bintianqi.owndroid.popToast
|
||||||
import com.bintianqi.owndroid.showOperationResultToast
|
import com.bintianqi.owndroid.showOperationResultToast
|
||||||
import com.bintianqi.owndroid.ui.FunctionItem
|
import com.bintianqi.owndroid.ui.FunctionItem
|
||||||
import com.bintianqi.owndroid.ui.MyLazyScaffold
|
import com.bintianqi.owndroid.ui.MyLazyScaffold
|
||||||
@@ -117,6 +122,17 @@ fun UserRestrictionScreen(
|
|||||||
onNavigate(UserRestrictionOptions(it.name))
|
onNavigate(UserRestrictionOptions(it.name))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Row(
|
||||||
|
Modifier
|
||||||
|
.padding(HorizontalPadding, 10.dp)
|
||||||
|
.fillMaxWidth()
|
||||||
|
.background(colorScheme.primaryContainer, RoundedCornerShape(8.dp))
|
||||||
|
.padding(8.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Icon(Icons.Outlined.Info, null, Modifier.padding(end = 8.dp), colorScheme.onPrimaryContainer)
|
||||||
|
Text(stringResource(R.string.user_restriction_tip), color = colorScheme.onPrimaryContainer)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -128,7 +144,8 @@ data class UserRestrictionOptions(val id: String)
|
|||||||
@Composable
|
@Composable
|
||||||
fun UserRestrictionOptionsScreen(
|
fun UserRestrictionOptionsScreen(
|
||||||
args: UserRestrictionOptions, userRestrictions: StateFlow<Map<String, Boolean>>,
|
args: UserRestrictionOptions, userRestrictions: StateFlow<Map<String, Boolean>>,
|
||||||
setRestriction: (String, Boolean) -> Boolean, onNavigateUp: () -> Unit
|
setRestriction: (String, Boolean) -> Boolean, setShortcut: (String) -> Boolean,
|
||||||
|
onNavigateUp: () -> Unit
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val status by userRestrictions.collectAsStateWithLifecycle()
|
val status by userRestrictions.collectAsStateWithLifecycle()
|
||||||
@@ -136,7 +153,12 @@ fun UserRestrictionOptionsScreen(
|
|||||||
MyLazyScaffold(title, onNavigateUp) {
|
MyLazyScaffold(title, onNavigateUp) {
|
||||||
items(items) { restriction ->
|
items(items) { restriction ->
|
||||||
Row(
|
Row(
|
||||||
Modifier.fillMaxWidth().padding(15.dp, 6.dp),
|
Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.combinedClickable(onClick = {}, onLongClick = {
|
||||||
|
if (!setShortcut(restriction.id)) context.popToast(R.string.unsupported)
|
||||||
|
})
|
||||||
|
.padding(15.dp, 6.dp),
|
||||||
Arrangement.SpaceBetween, Alignment.CenterVertically
|
Arrangement.SpaceBetween, Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
Row(Modifier.weight(1F), verticalAlignment = Alignment.CenterVertically) {
|
Row(Modifier.weight(1F), verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ import com.bintianqi.owndroid.HorizontalPadding
|
|||||||
import com.bintianqi.owndroid.MyViewModel
|
import com.bintianqi.owndroid.MyViewModel
|
||||||
import com.bintianqi.owndroid.Privilege
|
import com.bintianqi.owndroid.Privilege
|
||||||
import com.bintianqi.owndroid.R
|
import com.bintianqi.owndroid.R
|
||||||
import com.bintianqi.owndroid.formatTime
|
import com.bintianqi.owndroid.formatDate
|
||||||
import com.bintianqi.owndroid.popToast
|
import com.bintianqi.owndroid.popToast
|
||||||
import com.bintianqi.owndroid.showOperationResultToast
|
import com.bintianqi.owndroid.showOperationResultToast
|
||||||
import com.bintianqi.owndroid.ui.CircularProgressDialog
|
import com.bintianqi.owndroid.ui.CircularProgressDialog
|
||||||
@@ -205,7 +205,7 @@ fun UserInfoScreen(getInfo: () -> UserInformation, onNavigateUp: () -> Unit) {
|
|||||||
if (VERSION.SDK_INT >= 23) InfoItem(R.string.system_user, info.system.yesOrNo)
|
if (VERSION.SDK_INT >= 23) InfoItem(R.string.system_user, info.system.yesOrNo)
|
||||||
if (VERSION.SDK_INT >= 34) InfoItem(R.string.admin_user, info.admin.yesOrNo)
|
if (VERSION.SDK_INT >= 34) InfoItem(R.string.admin_user, info.admin.yesOrNo)
|
||||||
if (VERSION.SDK_INT >= 25) InfoItem(R.string.demo_user, info.demo.yesOrNo)
|
if (VERSION.SDK_INT >= 25) InfoItem(R.string.demo_user, info.demo.yesOrNo)
|
||||||
if (info.time != 0L) InfoItem(R.string.creation_time, formatTime(info.time))
|
if (info.time != 0L) InfoItem(R.string.creation_time, formatDate(info.time))
|
||||||
|
|
||||||
if (VERSION.SDK_INT >= 28) {
|
if (VERSION.SDK_INT >= 28) {
|
||||||
InfoItem(R.string.logout_enabled, info.logout.yesOrNo)
|
InfoItem(R.string.logout_enabled, info.logout.yesOrNo)
|
||||||
|
|||||||
@@ -476,7 +476,6 @@
|
|||||||
<!--Пароль и блокировка экрана-->
|
<!--Пароль и блокировка экрана-->
|
||||||
<string name="password_and_keyguard">Пароль и блокировка экрана</string>
|
<string name="password_and_keyguard">Пароль и блокировка экрана</string>
|
||||||
<string name="password_info">Информация о пароле</string>
|
<string name="password_info">Информация о пароле</string>
|
||||||
<string name="reset_pwd_desc">Оставьте пустым, чтобы удалить пароль</string>
|
|
||||||
<string name="max_pwd_fail">Максимальное количество неудачных попыток ввода пароля</string>
|
<string name="max_pwd_fail">Максимальное количество неудачных попыток ввода пароля</string>
|
||||||
<string name="max_pwd_fail_textfield">Максимальное количество неудачных попыток</string>
|
<string name="max_pwd_fail_textfield">Максимальное количество неудачных попыток</string>
|
||||||
<string name="pwd_expiration_timeout">Время истечения срока действия пароля</string>
|
<string name="pwd_expiration_timeout">Время истечения срока действия пароля</string>
|
||||||
@@ -492,17 +491,14 @@
|
|||||||
<string name="unified_password">Единый пароль</string>
|
<string name="unified_password">Единый пароль</string>
|
||||||
<string name="reset_password_token">Сбросить токен пароля</string>
|
<string name="reset_password_token">Сбросить токен пароля</string>
|
||||||
<string name="token">Токен</string>
|
<string name="token">Токен</string>
|
||||||
<string name="token_must_longer_than_32_byte">Токен должен быть длиннее 32 байт</string>
|
|
||||||
<string name="token_activated">Тoken activated</string><!--TODO-->
|
<string name="token_activated">Тoken activated</string><!--TODO-->
|
||||||
<string name="clear">Очистить</string>
|
<string name="clear">Очистить</string>
|
||||||
<string name="set">Установить</string>
|
<string name="set">Установить</string>
|
||||||
<string name="please_set_a_token">Установите токен</string>
|
|
||||||
<string name="activate_token_not_required_when_no_password">Токен будет автоматически активирован, если пароль не установлен.</string>
|
<string name="activate_token_not_required_when_no_password">Токен будет автоматически активирован, если пароль не установлен.</string>
|
||||||
<string name="reset_password">Сбросить пароль</string>
|
<string name="reset_password">Сбросить пароль</string>
|
||||||
<string name="confirm_password">Подтвердите пароль</string>
|
<string name="confirm_password">Подтвердите пароль</string>
|
||||||
<string name="do_not_ask_credentials_on_boot">Не запрашивать учетные данные при загрузке</string>
|
<string name="do_not_ask_credentials_on_boot">Не запрашивать учетные данные при загрузке</string>
|
||||||
<string name="reset_password_require_entry">Требовать ввод</string>
|
<string name="reset_password_require_entry">Требовать ввод</string>
|
||||||
<string name="reset_password_with_token">Сбросить пароль с помощью токена</string>
|
|
||||||
<string name="required_password_complexity">Требуемая сложность пароля</string>
|
<string name="required_password_complexity">Требуемая сложность пароля</string>
|
||||||
<string name="disable_keyguard_features">Функции блокировки экрана (Keyguard)</string>
|
<string name="disable_keyguard_features">Функции блокировки экрана (Keyguard)</string>
|
||||||
<string name="enable_all">Включить все</string>
|
<string name="enable_all">Включить все</string>
|
||||||
|
|||||||
@@ -501,7 +501,6 @@
|
|||||||
<!--Password&Keyguard-->
|
<!--Password&Keyguard-->
|
||||||
<string name="password_and_keyguard">Parola ve Kilit Ekranı</string>
|
<string name="password_and_keyguard">Parola ve Kilit Ekranı</string>
|
||||||
<string name="password_info">Parola Bilgisi</string>
|
<string name="password_info">Parola Bilgisi</string>
|
||||||
<string name="reset_pwd_desc">Parolayı kaldırmak için boş bırakın</string>
|
|
||||||
<string name="max_pwd_fail">Maksimum Başarısız Parola</string>
|
<string name="max_pwd_fail">Maksimum Başarısız Parola</string>
|
||||||
<string name="max_pwd_fail_textfield">Maksimum başarısız deneme sayısı</string>
|
<string name="max_pwd_fail_textfield">Maksimum başarısız deneme sayısı</string>
|
||||||
<string name="pwd_expiration_timeout">Parola Son Kullanma Zaman Aşımı</string>
|
<string name="pwd_expiration_timeout">Parola Son Kullanma Zaman Aşımı</string>
|
||||||
@@ -516,17 +515,14 @@
|
|||||||
<string name="unified_password">Birleşik Parola</string>
|
<string name="unified_password">Birleşik Parola</string>
|
||||||
<string name="reset_password_token">Parola Sıfırlama Jetonu</string>
|
<string name="reset_password_token">Parola Sıfırlama Jetonu</string>
|
||||||
<string name="token">Jeton</string>
|
<string name="token">Jeton</string>
|
||||||
<string name="token_must_longer_than_32_byte">Jeton 32 bayttan uzun olmalıdır</string>
|
|
||||||
<string name="token_activated">Token activated</string><!--TODO-->
|
<string name="token_activated">Token activated</string><!--TODO-->
|
||||||
<string name="clear">Temizle</string>
|
<string name="clear">Temizle</string>
|
||||||
<string name="set">Ayarla</string>
|
<string name="set">Ayarla</string>
|
||||||
<string name="please_set_a_token">Lütfen bir jeton ayarlayın</string>
|
|
||||||
<string name="activate_token_not_required_when_no_password">Parola ayarlanmamışsa jeton otomatik olarak etkinleştirilir.</string>
|
<string name="activate_token_not_required_when_no_password">Parola ayarlanmamışsa jeton otomatik olarak etkinleştirilir.</string>
|
||||||
<string name="reset_password">Parolayı Sıfırla</string>
|
<string name="reset_password">Parolayı Sıfırla</string>
|
||||||
<string name="confirm_password">Parolayı Onayla</string>
|
<string name="confirm_password">Parolayı Onayla</string>
|
||||||
<string name="do_not_ask_credentials_on_boot">Başlangıçta kimlik bilgisi sorma</string>
|
<string name="do_not_ask_credentials_on_boot">Başlangıçta kimlik bilgisi sorma</string>
|
||||||
<string name="reset_password_require_entry">Giriş gerektir</string>
|
<string name="reset_password_require_entry">Giriş gerektir</string>
|
||||||
<string name="reset_password_with_token">Jeton ile parolayı sıfırla</string>
|
|
||||||
<string name="required_password_complexity">Gerekli Parola Karmaşıklığı</string>
|
<string name="required_password_complexity">Gerekli Parola Karmaşıklığı</string>
|
||||||
<string name="disable_keyguard_features">Kilit Ekranı Özelliklerini Devre Dışı Bırak</string>
|
<string name="disable_keyguard_features">Kilit Ekranı Özelliklerini Devre Dışı Bırak</string>
|
||||||
<string name="enable_all">Hepsini Etkinleştir</string>
|
<string name="enable_all">Hepsini Etkinleştir</string>
|
||||||
|
|||||||
@@ -372,6 +372,7 @@
|
|||||||
|
|
||||||
<!--UserRestriction-->
|
<!--UserRestriction-->
|
||||||
<string name="user_restriction">用户限制</string>
|
<string name="user_restriction">用户限制</string>
|
||||||
|
<string name="user_restriction_tip">长按一个条目以创建快捷方式</string>
|
||||||
<string name="profile_owner_is_restricted">Profile owner无法使用部分功能</string>
|
<string name="profile_owner_is_restricted">Profile owner无法使用部分功能</string>
|
||||||
<string name="switch_to_disable_feature">打开开关后会禁用对应的功能</string>
|
<string name="switch_to_disable_feature">打开开关后会禁用对应的功能</string>
|
||||||
<string name="some_features_invalid_in_work_profile">工作资料中部分功能无效</string>
|
<string name="some_features_invalid_in_work_profile">工作资料中部分功能无效</string>
|
||||||
@@ -489,7 +490,6 @@
|
|||||||
<!--Password&Keyguard-->
|
<!--Password&Keyguard-->
|
||||||
<string name="password_and_keyguard">密码与锁屏</string>
|
<string name="password_and_keyguard">密码与锁屏</string>
|
||||||
<string name="password_info">密码信息</string>
|
<string name="password_info">密码信息</string>
|
||||||
<string name="reset_pwd_desc">留空以清除密码</string>
|
|
||||||
<string name="max_pwd_fail">最大密码错误次数</string>
|
<string name="max_pwd_fail">最大密码错误次数</string>
|
||||||
<string name="max_pwd_fail_textfield">错误次数</string>
|
<string name="max_pwd_fail_textfield">错误次数</string>
|
||||||
<string name="pwd_expiration_timeout">密码失效超时</string>
|
<string name="pwd_expiration_timeout">密码失效超时</string>
|
||||||
@@ -504,18 +504,15 @@
|
|||||||
<string name="unified_password">一致的密码</string>
|
<string name="unified_password">一致的密码</string>
|
||||||
<string name="reset_password_token">密码重置令牌</string>
|
<string name="reset_password_token">密码重置令牌</string>
|
||||||
<string name="token">令牌</string>
|
<string name="token">令牌</string>
|
||||||
<string name="token_must_longer_than_32_byte">令牌必须大于32字节</string>
|
|
||||||
<string name="activate_reset_password_token">激活密码重置令牌</string>
|
<string name="activate_reset_password_token">激活密码重置令牌</string>
|
||||||
<string name="token_activated">令牌已激活</string>
|
<string name="token_activated">令牌已激活</string>
|
||||||
<string name="clear">清除</string>
|
<string name="clear">清除</string>
|
||||||
<string name="set">设置</string>
|
<string name="set">设置</string>
|
||||||
<string name="please_set_a_token">请先设置令牌</string>
|
|
||||||
<string name="activate_token_not_required_when_no_password">没有密码时会自动激活令牌</string>
|
<string name="activate_token_not_required_when_no_password">没有密码时会自动激活令牌</string>
|
||||||
<string name="reset_password">重置密码</string>
|
<string name="reset_password">重置密码</string>
|
||||||
<string name="confirm_password">确认密码</string>
|
<string name="confirm_password">确认密码</string>
|
||||||
<string name="do_not_ask_credentials_on_boot">启动(boot)时不要求密码</string>
|
<string name="do_not_ask_credentials_on_boot">启动(boot)时不要求密码</string>
|
||||||
<string name="reset_password_require_entry">不允许其他设备管理员重置密码直至用户输入一次密码</string>
|
<string name="reset_password_require_entry">不允许其他设备管理员重置密码直至用户输入一次密码</string>
|
||||||
<string name="reset_password_with_token">使用令牌重置密码</string>
|
|
||||||
<string name="required_password_complexity">密码复杂度要求</string>
|
<string name="required_password_complexity">密码复杂度要求</string>
|
||||||
<string name="disable_keyguard_features">锁屏功能</string>
|
<string name="disable_keyguard_features">锁屏功能</string>
|
||||||
<string name="enable_all">启用全部</string>
|
<string name="enable_all">启用全部</string>
|
||||||
|
|||||||
@@ -406,6 +406,7 @@
|
|||||||
|
|
||||||
<!--UserRestriction-->
|
<!--UserRestriction-->
|
||||||
<string name="user_restriction">User restriction</string>
|
<string name="user_restriction">User restriction</string>
|
||||||
|
<string name="user_restriction_tip">Long press an item to create a shortcut</string>
|
||||||
<string name="profile_owner_is_restricted">Profile owner can use limited function</string>
|
<string name="profile_owner_is_restricted">Profile owner can use limited function</string>
|
||||||
<string name="switch_to_disable_feature">Turn on a switch to disable that function. </string>
|
<string name="switch_to_disable_feature">Turn on a switch to disable that function. </string>
|
||||||
<string name="some_features_invalid_in_work_profile">Functions in work profile is limited. </string>
|
<string name="some_features_invalid_in_work_profile">Functions in work profile is limited. </string>
|
||||||
@@ -525,7 +526,6 @@
|
|||||||
<!--Password&Keyguard-->
|
<!--Password&Keyguard-->
|
||||||
<string name="password_and_keyguard">Password and keyguard</string>
|
<string name="password_and_keyguard">Password and keyguard</string>
|
||||||
<string name="password_info">Password Info</string>
|
<string name="password_info">Password Info</string>
|
||||||
<string name="reset_pwd_desc">Keep empty to remove password</string>
|
|
||||||
<string name="max_pwd_fail">Max failed passwords</string>
|
<string name="max_pwd_fail">Max failed passwords</string>
|
||||||
<string name="max_pwd_fail_textfield">Maximum failed attempts</string>
|
<string name="max_pwd_fail_textfield">Maximum failed attempts</string>
|
||||||
<string name="pwd_expiration_timeout">Password expiration timeout</string>
|
<string name="pwd_expiration_timeout">Password expiration timeout</string>
|
||||||
@@ -540,18 +540,15 @@
|
|||||||
<string name="unified_password">Unified password</string>
|
<string name="unified_password">Unified password</string>
|
||||||
<string name="reset_password_token">Reset password token</string>
|
<string name="reset_password_token">Reset password token</string>
|
||||||
<string name="token">Token</string>
|
<string name="token">Token</string>
|
||||||
<string name="token_must_longer_than_32_byte">The token must be longer than 32 byte</string>
|
|
||||||
<string name="activate_reset_password_token">Activate reset password token</string>
|
<string name="activate_reset_password_token">Activate reset password token</string>
|
||||||
<string name="token_activated">Token activated</string>
|
<string name="token_activated">Token activated</string>
|
||||||
<string name="clear">Clear</string>
|
<string name="clear">Clear</string>
|
||||||
<string name="set">Set</string>
|
<string name="set">Set</string>
|
||||||
<string name="please_set_a_token">Please set a token</string>
|
|
||||||
<string name="activate_token_not_required_when_no_password">Token will be automatically activated if no password is set. </string>
|
<string name="activate_token_not_required_when_no_password">Token will be automatically activated if no password is set. </string>
|
||||||
<string name="reset_password">Reset password</string>
|
<string name="reset_password">Reset password</string>
|
||||||
<string name="confirm_password">Confirm password</string>
|
<string name="confirm_password">Confirm password</string>
|
||||||
<string name="do_not_ask_credentials_on_boot">Do not ask credentials on boot</string>
|
<string name="do_not_ask_credentials_on_boot">Do not ask credentials on boot</string>
|
||||||
<string name="reset_password_require_entry">Require entry</string>
|
<string name="reset_password_require_entry">Require entry</string>
|
||||||
<string name="reset_password_with_token">Reset password with token</string>
|
|
||||||
<string name="required_password_complexity">Required password complexity</string>
|
<string name="required_password_complexity">Required password complexity</string>
|
||||||
<string name="disable_keyguard_features">Keyguard features</string>
|
<string name="disable_keyguard_features">Keyguard features</string>
|
||||||
<string name="enable_all">Enable all</string>
|
<string name="enable_all">Enable all</string>
|
||||||
|
|||||||
Reference in New Issue
Block a user