mirror of
https://github.com/awfixers-stuff/OwnDroid.git
synced 2026-03-23 19:15:58 +00:00
Optimize start/stop lock task mode flow
Optimize Package selector
This commit is contained in:
@@ -91,10 +91,6 @@
|
|||||||
android:description="@string/app_name"
|
android:description="@string/app_name"
|
||||||
android:permission="android.permission.BIND_DEVICE_ADMIN">
|
android:permission="android.permission.BIND_DEVICE_ADMIN">
|
||||||
</receiver>
|
</receiver>
|
||||||
<receiver
|
|
||||||
android:name=".StopLockTaskModeReceiver"
|
|
||||||
android:description="@string/app_name">
|
|
||||||
</receiver>
|
|
||||||
<receiver
|
<receiver
|
||||||
android:name=".ApiReceiver"
|
android:name=".ApiReceiver"
|
||||||
android:exported="true">
|
android:exported="true">
|
||||||
|
|||||||
@@ -37,7 +37,6 @@ import androidx.compose.runtime.DisposableEffect
|
|||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableIntStateOf
|
|
||||||
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
|
||||||
@@ -188,7 +187,6 @@ fun Home(activity: FragmentActivity, vm: MyViewModel) {
|
|||||||
val receiver = context.getReceiver()
|
val receiver = context.getReceiver()
|
||||||
val sharedPref = context.getSharedPreferences("data", Context.MODE_PRIVATE)
|
val sharedPref = context.getSharedPreferences("data", Context.MODE_PRIVATE)
|
||||||
val focusMgr = LocalFocusManager.current
|
val focusMgr = LocalFocusManager.current
|
||||||
val dialogStatus = remember { mutableIntStateOf(0) }
|
|
||||||
val backToHome by backToHomeStateFlow.collectAsState()
|
val backToHome by backToHomeStateFlow.collectAsState()
|
||||||
val lifecycleOwner = LocalLifecycleOwner.current
|
val lifecycleOwner = LocalLifecycleOwner.current
|
||||||
LaunchedEffect(backToHome) {
|
LaunchedEffect(backToHome) {
|
||||||
@@ -227,7 +225,7 @@ fun Home(activity: FragmentActivity, vm: MyViewModel) {
|
|||||||
composable(route = "PermissionPolicy") { PermissionPolicy(navCtrl) }
|
composable(route = "PermissionPolicy") { PermissionPolicy(navCtrl) }
|
||||||
composable(route = "MTEPolicy") { MTEPolicy(navCtrl) }
|
composable(route = "MTEPolicy") { MTEPolicy(navCtrl) }
|
||||||
composable(route = "NearbyStreamingPolicy") { NearbyStreamingPolicy(navCtrl) }
|
composable(route = "NearbyStreamingPolicy") { NearbyStreamingPolicy(navCtrl) }
|
||||||
composable(route = "LockTaskMode") { LockTaskMode(navCtrl) }
|
composable(route = "LockTaskMode") { LockTaskMode(navCtrl, vm) }
|
||||||
composable(route = "CACert") { CACert(navCtrl) }
|
composable(route = "CACert") { CACert(navCtrl) }
|
||||||
composable(route = "SecurityLogging") { SecurityLogging(navCtrl) }
|
composable(route = "SecurityLogging") { SecurityLogging(navCtrl) }
|
||||||
composable(route = "DisableAccountManagement") { DisableAccountManagement(navCtrl) }
|
composable(route = "DisableAccountManagement") { DisableAccountManagement(navCtrl) }
|
||||||
@@ -241,7 +239,7 @@ fun Home(activity: FragmentActivity, vm: MyViewModel) {
|
|||||||
composable(route = "MinWifiSecurityLevel") { WifiSecurityLevel(navCtrl) }
|
composable(route = "MinWifiSecurityLevel") { WifiSecurityLevel(navCtrl) }
|
||||||
composable(route = "WifiSsidPolicy") { WifiSsidPolicy(navCtrl) }
|
composable(route = "WifiSsidPolicy") { WifiSsidPolicy(navCtrl) }
|
||||||
composable(route = "PrivateDNS") { PrivateDNS(navCtrl) }
|
composable(route = "PrivateDNS") { PrivateDNS(navCtrl) }
|
||||||
composable(route = "AlwaysOnVpn") { AlwaysOnVPNPackage(navCtrl) }
|
composable(route = "AlwaysOnVpn") { AlwaysOnVPNPackage(navCtrl, vm) }
|
||||||
composable(route = "RecommendedGlobalProxy") { RecommendedGlobalProxy(navCtrl) }
|
composable(route = "RecommendedGlobalProxy") { RecommendedGlobalProxy(navCtrl) }
|
||||||
composable(route = "NetworkLog") { NetworkLogging(navCtrl) }
|
composable(route = "NetworkLog") { NetworkLogging(navCtrl) }
|
||||||
composable(route = "WifiAuthKeypair") { WifiAuthKeypair(navCtrl) }
|
composable(route = "WifiAuthKeypair") { WifiAuthKeypair(navCtrl) }
|
||||||
@@ -255,7 +253,7 @@ fun Home(activity: FragmentActivity, vm: MyViewModel) {
|
|||||||
composable(route = "IntentFilter") { IntentFilter(navCtrl) }
|
composable(route = "IntentFilter") { IntentFilter(navCtrl) }
|
||||||
composable(route = "DeleteWorkProfile") { DeleteWorkProfile(navCtrl) }
|
composable(route = "DeleteWorkProfile") { DeleteWorkProfile(navCtrl) }
|
||||||
|
|
||||||
composable(route = "Applications") { ApplicationManage(navCtrl, dialogStatus) }
|
composable(route = "Applications") { ApplicationManage(navCtrl, vm) }
|
||||||
|
|
||||||
composable(route = "UserRestriction") { UserRestriction(navCtrl) }
|
composable(route = "UserRestriction") { UserRestriction(navCtrl) }
|
||||||
composable(route = "UR-Internet") {
|
composable(route = "UR-Internet") {
|
||||||
@@ -302,7 +300,7 @@ fun Home(activity: FragmentActivity, vm: MyViewModel) {
|
|||||||
composable(route = "ApiSettings") { ApiSettings(navCtrl) }
|
composable(route = "ApiSettings") { ApiSettings(navCtrl) }
|
||||||
composable(route = "About") { About(navCtrl) }
|
composable(route = "About") { About(navCtrl) }
|
||||||
|
|
||||||
composable(route = "PackageSelector") { PackageSelector(navCtrl) }
|
composable(route = "PackageSelector") { PackageSelector(navCtrl, vm) }
|
||||||
|
|
||||||
composable(
|
composable(
|
||||||
route = "Authenticate",
|
route = "Authenticate",
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ import kotlinx.coroutines.launch
|
|||||||
|
|
||||||
class MyViewModel: ViewModel() {
|
class MyViewModel: ViewModel() {
|
||||||
val theme = MutableStateFlow(ThemeSettings())
|
val theme = MutableStateFlow(ThemeSettings())
|
||||||
|
val installedPackages = mutableListOf<PackageInfo>()
|
||||||
|
val selectedPackage = MutableStateFlow("")
|
||||||
val shizukuBinder = MutableStateFlow<IBinder?>(null)
|
val shizukuBinder = MutableStateFlow<IBinder?>(null)
|
||||||
|
|
||||||
var initialized = false
|
var initialized = false
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package com.bintianqi.owndroid
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
|
import android.app.NotificationChannel
|
||||||
|
import android.app.NotificationManager
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.os.Build
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ### Notification channels
|
||||||
|
* - LockTaskMode
|
||||||
|
*
|
||||||
|
* ### Notification IDs
|
||||||
|
* - 1: Stop lock task mode
|
||||||
|
*/
|
||||||
|
object NotificationUtils {
|
||||||
|
fun checkPermission(context: Context): Boolean {
|
||||||
|
return if(Build.VERSION.SDK_INT >= 33)
|
||||||
|
context.checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED
|
||||||
|
else false
|
||||||
|
}
|
||||||
|
fun registerChannels(context: Context) {
|
||||||
|
if(Build.VERSION.SDK_INT < 26) return
|
||||||
|
val nm = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||||
|
val channel = NotificationChannel("LockTaskMode", context.getString(R.string.lock_task_mode), NotificationManager.IMPORTANCE_HIGH)
|
||||||
|
nm.createNotificationChannel(channel)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -54,24 +54,19 @@ import com.bintianqi.owndroid.ui.NavIcon
|
|||||||
import com.google.accompanist.drawablepainter.rememberDrawablePainter
|
import com.google.accompanist.drawablepainter.rememberDrawablePainter
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
private data class PkgInfo(
|
data class PackageInfo(
|
||||||
val pkgName: String,
|
val pkgName: String,
|
||||||
val label: String,
|
val label: String,
|
||||||
val icon: Drawable,
|
val icon: Drawable,
|
||||||
val system: Boolean
|
val system: Boolean
|
||||||
)
|
)
|
||||||
|
|
||||||
private val pkgs = mutableListOf<PkgInfo>()
|
|
||||||
|
|
||||||
val selectedPackage = MutableStateFlow("")
|
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun PackageSelector(navCtrl: NavHostController) {
|
fun PackageSelector(navCtrl: NavHostController, vm: MyViewModel) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val pm = context.packageManager
|
val pm = context.packageManager
|
||||||
val apps = pm.getInstalledApplications(0)
|
val apps = pm.getInstalledApplications(0)
|
||||||
@@ -88,9 +83,9 @@ fun PackageSelector(navCtrl: NavHostController) {
|
|||||||
show = false
|
show = false
|
||||||
progress = 0
|
progress = 0
|
||||||
hideProgress = false
|
hideProgress = false
|
||||||
pkgs.clear()
|
vm.installedPackages.clear()
|
||||||
for(pkg in apps) {
|
for(pkg in apps) {
|
||||||
pkgs += PkgInfo(
|
vm.installedPackages += PackageInfo(
|
||||||
pkg.packageName, pkg.loadLabel(pm).toString(), pkg.loadIcon(pm),
|
pkg.packageName, pkg.loadLabel(pm).toString(), pkg.loadIcon(pm),
|
||||||
(pkg.flags and ApplicationInfo.FLAG_SYSTEM) != 0
|
(pkg.flags and ApplicationInfo.FLAG_SYSTEM) != 0
|
||||||
)
|
)
|
||||||
@@ -181,14 +176,14 @@ fun PackageSelector(navCtrl: NavHostController) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(show) {
|
if(show) {
|
||||||
items(pkgs) {
|
items(vm.installedPackages) {
|
||||||
if(system == it.system) {
|
if(system == it.system) {
|
||||||
if(search != "") {
|
if(search != "") {
|
||||||
if(it.pkgName.contains(search, ignoreCase = true) || it.label.contains(search, ignoreCase = true)) {
|
if(it.pkgName.contains(search, ignoreCase = true) || it.label.contains(search, ignoreCase = true)) {
|
||||||
PackageItem(it, navCtrl)
|
PackageItem(it, navCtrl, vm)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
PackageItem(it, navCtrl)
|
PackageItem(it, navCtrl, vm)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -201,13 +196,13 @@ fun PackageSelector(navCtrl: NavHostController) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
if(pkgs.size == 0) { getPkgList() }
|
if(vm.installedPackages.isEmpty()) { getPkgList() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun PackageItem(pkg: PkgInfo, navCtrl: NavHostController) {
|
private fun PackageItem(pkg: PackageInfo, navCtrl: NavHostController, vm: MyViewModel) {
|
||||||
val focusMgr = LocalFocusManager.current
|
val focusMgr = LocalFocusManager.current
|
||||||
Row(
|
Row(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
@@ -215,7 +210,7 @@ private fun PackageItem(pkg: PkgInfo, navCtrl: NavHostController) {
|
|||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.clickable{
|
.clickable{
|
||||||
focusMgr.clearFocus()
|
focusMgr.clearFocus()
|
||||||
selectedPackage.value = pkg.pkgName
|
vm.selectedPackage.value = pkg.pkgName
|
||||||
navCtrl.navigateUp()
|
navCtrl.navigateUp()
|
||||||
}
|
}
|
||||||
.padding(horizontal = 8.dp, vertical = 10.dp)
|
.padding(horizontal = 8.dp, vertical = 10.dp)
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
package com.bintianqi.owndroid
|
package com.bintianqi.owndroid
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.app.NotificationManager
|
import android.app.NotificationManager
|
||||||
|
import android.app.PendingIntent
|
||||||
import android.app.admin.DeviceAdminReceiver
|
import android.app.admin.DeviceAdminReceiver
|
||||||
import android.content.BroadcastReceiver
|
import android.content.BroadcastReceiver
|
||||||
|
import android.content.ComponentName
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.pm.PackageInstaller.EXTRA_STATUS
|
import android.content.pm.PackageInstaller.EXTRA_STATUS
|
||||||
@@ -21,8 +22,7 @@ import android.os.Build.VERSION
|
|||||||
import android.os.PersistableBundle
|
import android.os.PersistableBundle
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import com.bintianqi.owndroid.dpm.getDPM
|
import androidx.core.app.NotificationCompat
|
||||||
import com.bintianqi.owndroid.dpm.getReceiver
|
|
||||||
import com.bintianqi.owndroid.dpm.handleNetworkLogs
|
import com.bintianqi.owndroid.dpm.handleNetworkLogs
|
||||||
import com.bintianqi.owndroid.dpm.handleSecurityLogs
|
import com.bintianqi.owndroid.dpm.handleSecurityLogs
|
||||||
import com.bintianqi.owndroid.dpm.isDeviceAdmin
|
import com.bintianqi.owndroid.dpm.isDeviceAdmin
|
||||||
@@ -35,6 +35,17 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
class Receiver : DeviceAdminReceiver() {
|
class Receiver : DeviceAdminReceiver() {
|
||||||
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
|
super.onReceive(context, intent)
|
||||||
|
if(VERSION.SDK_INT >= 26 && intent.action == "com.bintianqi.owndroid.action.STOP_LOCK_TASK_MODE") {
|
||||||
|
val dpm = getManager(context)
|
||||||
|
val receiver = ComponentName(context, this::class.java)
|
||||||
|
val packages = dpm.getLockTaskPackages(receiver)
|
||||||
|
dpm.setLockTaskPackages(receiver, arrayOf())
|
||||||
|
dpm.setLockTaskPackages(receiver, packages)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onEnabled(context: Context, intent: Intent) {
|
override fun onEnabled(context: Context, intent: Intent) {
|
||||||
super.onEnabled(context, intent)
|
super.onEnabled(context, intent)
|
||||||
context.toggleInstallAppActivity()
|
context.toggleInstallAppActivity()
|
||||||
@@ -78,6 +89,26 @@ class Receiver : DeviceAdminReceiver() {
|
|||||||
sp.edit().putBoolean("dhizuku", false).apply()
|
sp.edit().putBoolean("dhizuku", false).apply()
|
||||||
context.toggleInstallAppActivity()
|
context.toggleInstallAppActivity()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onLockTaskModeEntering(context: Context, intent: Intent, pkg: String) {
|
||||||
|
super.onLockTaskModeEntering(context, intent, pkg)
|
||||||
|
NotificationUtils.registerChannels(context)
|
||||||
|
val nm = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||||
|
val intent = Intent(context, this::class.java).apply { action = "com.bintianqi.owndroid.action.STOP_LOCK_TASK_MODE" }
|
||||||
|
val pendingIntent = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_IMMUTABLE)
|
||||||
|
val builder = NotificationCompat.Builder(context, "LockTaskMode")
|
||||||
|
.setContentTitle(context.getText(R.string.lock_task_mode))
|
||||||
|
.setSmallIcon(R.drawable.lock_fill0)
|
||||||
|
.addAction(NotificationCompat.Action.Builder(null, context.getString(R.string.stop), pendingIntent).build())
|
||||||
|
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||||
|
nm.notify(1, builder.build())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLockTaskModeExiting(context: Context, intent: Intent) {
|
||||||
|
super.onLockTaskModeExiting(context, intent)
|
||||||
|
val nm = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||||
|
nm.cancel(1)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val installAppDone = MutableStateFlow(false)
|
val installAppDone = MutableStateFlow(false)
|
||||||
@@ -105,16 +136,3 @@ class PackageInstallerReceiver: BroadcastReceiver() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class StopLockTaskModeReceiver: BroadcastReceiver() {
|
|
||||||
@SuppressLint("NewApi")
|
|
||||||
override fun onReceive(context: Context, intent: Intent) {
|
|
||||||
val dpm = context.getDPM()
|
|
||||||
val receiver = context.getReceiver()
|
|
||||||
val packages = dpm.getLockTaskPackages(receiver)
|
|
||||||
dpm.setLockTaskPackages(receiver, arrayOf())
|
|
||||||
dpm.setLockTaskPackages(receiver, packages)
|
|
||||||
val nm = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
|
||||||
nm.cancel(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,15 +1,12 @@
|
|||||||
package com.bintianqi.owndroid
|
package com.bintianqi.owndroid
|
||||||
|
|
||||||
import android.Manifest
|
|
||||||
import android.app.admin.DevicePolicyManager
|
import android.app.admin.DevicePolicyManager
|
||||||
import android.content.ClipData
|
import android.content.ClipData
|
||||||
import android.content.ClipboardManager
|
import android.content.ClipboardManager
|
||||||
import android.content.ComponentName
|
import android.content.ComponentName
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.pm.PackageManager
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build.VERSION
|
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.activity.result.ActivityResultLauncher
|
import androidx.activity.result.ActivityResultLauncher
|
||||||
@@ -65,7 +62,6 @@ fun writeClipBoard(context: Context, string: String):Boolean{
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
lateinit var requestPermission: ActivityResultLauncher<String>
|
|
||||||
lateinit var exportFile: ActivityResultLauncher<Intent>
|
lateinit var exportFile: ActivityResultLauncher<Intent>
|
||||||
var exportFilePath: String? = null
|
var exportFilePath: String? = null
|
||||||
var isExportingSecurityOrNetworkLogs = false
|
var isExportingSecurityOrNetworkLogs = false
|
||||||
@@ -83,7 +79,6 @@ fun registerActivityResult(context: ComponentActivity){
|
|||||||
backToHomeStateFlow.value = true
|
backToHomeStateFlow.value = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
requestPermission = context.registerForActivityResult(ActivityResultContracts.RequestPermission()) { permissionGranted.value = it }
|
|
||||||
exportFile = context.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
exportFile = context.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
||||||
val intentData = result.data ?: return@registerForActivityResult
|
val intentData = result.data ?: return@registerForActivityResult
|
||||||
val uriData = intentData.data ?: return@registerForActivityResult
|
val uriData = intentData.data ?: return@registerForActivityResult
|
||||||
@@ -103,21 +98,6 @@ fun registerActivityResult(context: ComponentActivity){
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val permissionGranted = MutableStateFlow<Boolean?>(null)
|
|
||||||
|
|
||||||
suspend fun prepareForNotification(context: Context, action: ()->Unit) {
|
|
||||||
if(VERSION.SDK_INT >= 33) {
|
|
||||||
if(context.checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED) {
|
|
||||||
action()
|
|
||||||
} else {
|
|
||||||
requestPermission.launch(Manifest.permission.POST_NOTIFICATIONS)
|
|
||||||
permissionGranted.collect { if(it == true) action() }
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
action()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun formatFileSize(bytes: Long): String {
|
fun formatFileSize(bytes: Long): String {
|
||||||
val kb = 1024
|
val kb = 1024
|
||||||
val mb = kb * 1024
|
val mb = kb * 1024
|
||||||
|
|||||||
@@ -50,7 +50,6 @@ import androidx.compose.material3.TopAppBar
|
|||||||
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.LaunchedEffect
|
||||||
import androidx.compose.runtime.MutableIntState
|
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableIntStateOf
|
import androidx.compose.runtime.mutableIntStateOf
|
||||||
@@ -74,37 +73,37 @@ import androidx.compose.ui.text.input.KeyboardType
|
|||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.core.content.ContextCompat.startActivity
|
import androidx.core.content.ContextCompat.startActivity
|
||||||
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import androidx.navigation.NavHostController
|
import androidx.navigation.NavHostController
|
||||||
import androidx.navigation.compose.NavHost
|
import androidx.navigation.compose.NavHost
|
||||||
import androidx.navigation.compose.composable
|
import androidx.navigation.compose.composable
|
||||||
import androidx.navigation.compose.rememberNavController
|
import androidx.navigation.compose.rememberNavController
|
||||||
import com.bintianqi.owndroid.InstallAppActivity
|
import com.bintianqi.owndroid.InstallAppActivity
|
||||||
|
import com.bintianqi.owndroid.MyViewModel
|
||||||
import com.bintianqi.owndroid.PackageInstallerReceiver
|
import com.bintianqi.owndroid.PackageInstallerReceiver
|
||||||
import com.bintianqi.owndroid.R
|
import com.bintianqi.owndroid.R
|
||||||
import com.bintianqi.owndroid.fileUriFlow
|
import com.bintianqi.owndroid.fileUriFlow
|
||||||
import com.bintianqi.owndroid.getFile
|
import com.bintianqi.owndroid.getFile
|
||||||
import com.bintianqi.owndroid.selectedPackage
|
|
||||||
import com.bintianqi.owndroid.ui.Animations
|
import com.bintianqi.owndroid.ui.Animations
|
||||||
|
import com.bintianqi.owndroid.ui.FunctionItem
|
||||||
import com.bintianqi.owndroid.ui.InfoCard
|
import com.bintianqi.owndroid.ui.InfoCard
|
||||||
import com.bintianqi.owndroid.ui.Information
|
|
||||||
import com.bintianqi.owndroid.ui.ListItem
|
import com.bintianqi.owndroid.ui.ListItem
|
||||||
import com.bintianqi.owndroid.ui.NavIcon
|
import com.bintianqi.owndroid.ui.NavIcon
|
||||||
import com.bintianqi.owndroid.ui.RadioButtonItem
|
import com.bintianqi.owndroid.ui.RadioButtonItem
|
||||||
import com.bintianqi.owndroid.ui.FunctionItem
|
|
||||||
import com.bintianqi.owndroid.ui.SwitchItem
|
import com.bintianqi.owndroid.ui.SwitchItem
|
||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun ApplicationManage(navCtrl:NavHostController, dialogStatus: MutableIntState) {
|
fun ApplicationManage(navCtrl:NavHostController, vm: MyViewModel) {
|
||||||
val focusMgr = LocalFocusManager.current
|
val focusMgr = LocalFocusManager.current
|
||||||
val localNavCtrl = rememberNavController()
|
val localNavCtrl = rememberNavController()
|
||||||
var pkgName by rememberSaveable { mutableStateOf("") }
|
var pkgName by rememberSaveable { mutableStateOf("") }
|
||||||
val updatePackage by selectedPackage.collectAsState()
|
val updatePackage by vm.selectedPackage.collectAsStateWithLifecycle()
|
||||||
LaunchedEffect(updatePackage) {
|
LaunchedEffect(updatePackage) {
|
||||||
if(updatePackage != "") {
|
if(updatePackage != "") {
|
||||||
pkgName = updatePackage
|
pkgName = updatePackage
|
||||||
selectedPackage.value = ""
|
vm.selectedPackage.value = ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Scaffold(
|
Scaffold(
|
||||||
@@ -145,9 +144,7 @@ fun ApplicationManage(navCtrl:NavHostController, dialogStatus: MutableIntState)
|
|||||||
popEnterTransition = Animations.navHostPopEnterTransition,
|
popEnterTransition = Animations.navHostPopEnterTransition,
|
||||||
popExitTransition = Animations.navHostPopExitTransition
|
popExitTransition = Animations.navHostPopExitTransition
|
||||||
) {
|
) {
|
||||||
composable(route = "Home") {
|
composable(route = "Home") { Home(localNavCtrl, pkgName) }
|
||||||
Home(localNavCtrl, pkgName, dialogStatus)
|
|
||||||
}
|
|
||||||
composable(route = "UserControlDisabled") { UserCtrlDisabledPkg(pkgName) }
|
composable(route = "UserControlDisabled") { UserCtrlDisabledPkg(pkgName) }
|
||||||
composable(route = "PermissionManage") { PermissionManage(pkgName) }
|
composable(route = "PermissionManage") { PermissionManage(pkgName) }
|
||||||
composable(route = "CrossProfilePackage") { CrossProfilePkg(pkgName) }
|
composable(route = "CrossProfilePackage") { CrossProfilePkg(pkgName) }
|
||||||
@@ -160,23 +157,11 @@ fun ApplicationManage(navCtrl:NavHostController, dialogStatus: MutableIntState)
|
|||||||
composable(route = "UninstallApp") { UninstallApp(pkgName) }
|
composable(route = "UninstallApp") { UninstallApp(pkgName) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
when(dialogStatus.intValue) {
|
|
||||||
0 -> {}
|
|
||||||
1 -> EnableSystemAppDialog(dialogStatus, pkgName)
|
|
||||||
2 -> ClearAppDataDialog(dialogStatus, pkgName)
|
|
||||||
3 -> DefaultDialerAppDialog(dialogStatus, pkgName)
|
|
||||||
}
|
|
||||||
LaunchedEffect(dialogStatus.intValue) {
|
|
||||||
focusMgr.clearFocus()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun Home(
|
private fun Home(navCtrl:NavHostController, pkgName: String) {
|
||||||
navCtrl:NavHostController,
|
var dialogStatus by remember { mutableIntStateOf(0) }
|
||||||
pkgName: String,
|
|
||||||
dialogStatus: MutableIntState
|
|
||||||
) {
|
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val dpm = context.getDPM()
|
val dpm = context.getDPM()
|
||||||
val receiver = context.getReceiver()
|
val receiver = context.getReceiver()
|
||||||
@@ -190,7 +175,6 @@ private fun Home(
|
|||||||
hide = dpm.isApplicationHidden(receiver, pkgName)
|
hide = dpm.isApplicationHidden(receiver, pkgName)
|
||||||
var blockUninstall by remember { mutableStateOf(false) }
|
var blockUninstall by remember { mutableStateOf(false) }
|
||||||
blockUninstall = dpm.isUninstallBlocked(receiver,pkgName)
|
blockUninstall = dpm.isUninstallBlocked(receiver,pkgName)
|
||||||
var appControlDialog by remember { mutableStateOf(false) }
|
|
||||||
var appControlAction by remember { mutableIntStateOf(0) }
|
var appControlAction by remember { mutableIntStateOf(0) }
|
||||||
val focusMgr = LocalFocusManager.current
|
val focusMgr = LocalFocusManager.current
|
||||||
val appControl: (Boolean) -> Unit = {
|
val appControl: (Boolean) -> Unit = {
|
||||||
@@ -226,20 +210,20 @@ private fun Home(
|
|||||||
title = R.string.suspend, desc = "", icon = R.drawable.block_fill0,
|
title = R.string.suspend, desc = "", icon = R.drawable.block_fill0,
|
||||||
state = suspend,
|
state = suspend,
|
||||||
onCheckedChange = { appControlAction = 1; appControl(it) },
|
onCheckedChange = { appControlAction = 1; appControl(it) },
|
||||||
onClickBlank = { appControlAction = 1; appControlDialog = true }
|
onClickBlank = { appControlAction = 1; dialogStatus = 4 }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
SwitchItem(
|
SwitchItem(
|
||||||
title = R.string.hide, desc = stringResource(R.string.isapphidden_desc), icon = R.drawable.visibility_off_fill0,
|
title = R.string.hide, desc = stringResource(R.string.isapphidden_desc), icon = R.drawable.visibility_off_fill0,
|
||||||
state = hide,
|
state = hide,
|
||||||
onCheckedChange = { appControlAction = 2; appControl(it) },
|
onCheckedChange = { appControlAction = 2; appControl(it) },
|
||||||
onClickBlank = { appControlAction = 2; appControlDialog = true }
|
onClickBlank = { appControlAction = 2; dialogStatus = 4 }
|
||||||
)
|
)
|
||||||
SwitchItem(
|
SwitchItem(
|
||||||
title = R.string.block_uninstall, desc = "", icon = R.drawable.delete_forever_fill0,
|
title = R.string.block_uninstall, desc = "", icon = R.drawable.delete_forever_fill0,
|
||||||
state = blockUninstall,
|
state = blockUninstall,
|
||||||
onCheckedChange = { appControlAction = 3; appControl(it) },
|
onCheckedChange = { appControlAction = 3; appControl(it) },
|
||||||
onClickBlank = { appControlAction = 3; appControlDialog = true }
|
onClickBlank = { appControlAction = 3; dialogStatus = 4 }
|
||||||
)
|
)
|
||||||
if((VERSION.SDK_INT >= 33 && profileOwner) || (VERSION.SDK_INT >= 30 && deviceOwner)) {
|
if((VERSION.SDK_INT >= 33 && profileOwner) || (VERSION.SDK_INT >= 30 && deviceOwner)) {
|
||||||
FunctionItem(R.string.ucd, "", R.drawable.do_not_touch_fill0) { navCtrl.navigate("UserControlDisabled") }
|
FunctionItem(R.string.ucd, "", R.drawable.do_not_touch_fill0) { navCtrl.navigate("UserControlDisabled") }
|
||||||
@@ -259,32 +243,124 @@ private fun Home(
|
|||||||
FunctionItem(R.string.permitted_accessibility_services, "", R.drawable.settings_accessibility_fill0) { navCtrl.navigate("Accessibility") }
|
FunctionItem(R.string.permitted_accessibility_services, "", R.drawable.settings_accessibility_fill0) { navCtrl.navigate("Accessibility") }
|
||||||
FunctionItem(R.string.permitted_ime, "", R.drawable.keyboard_fill0) { navCtrl.navigate("IME") }
|
FunctionItem(R.string.permitted_ime, "", R.drawable.keyboard_fill0) { navCtrl.navigate("IME") }
|
||||||
FunctionItem(R.string.enable_system_app, "", R.drawable.enable_fill0) {
|
FunctionItem(R.string.enable_system_app, "", R.drawable.enable_fill0) {
|
||||||
if(pkgName != "") dialogStatus.intValue = 1
|
if(pkgName != "") dialogStatus = 1
|
||||||
}
|
}
|
||||||
if(VERSION.SDK_INT >= 28 && deviceOwner) {
|
if(VERSION.SDK_INT >= 28 && deviceOwner) {
|
||||||
FunctionItem(R.string.keep_uninstalled_packages, "", R.drawable.delete_fill0) { navCtrl.navigate("KeepUninstalled") }
|
FunctionItem(R.string.keep_uninstalled_packages, "", R.drawable.delete_fill0) { navCtrl.navigate("KeepUninstalled") }
|
||||||
}
|
}
|
||||||
if(VERSION.SDK_INT >= 28) {
|
if(VERSION.SDK_INT >= 28) {
|
||||||
FunctionItem(R.string.clear_app_storage, "", R.drawable.mop_fill0) {
|
FunctionItem(R.string.clear_app_storage, "", R.drawable.mop_fill0) {
|
||||||
if(pkgName != "") dialogStatus.intValue = 2
|
if(pkgName != "") dialogStatus = 2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
FunctionItem(R.string.install_app, "", R.drawable.install_mobile_fill0) { navCtrl.navigate("InstallApp") }
|
FunctionItem(R.string.install_app, "", R.drawable.install_mobile_fill0) { navCtrl.navigate("InstallApp") }
|
||||||
FunctionItem(R.string.uninstall_app, "", R.drawable.delete_fill0) { navCtrl.navigate("UninstallApp") }
|
FunctionItem(R.string.uninstall_app, "", R.drawable.delete_fill0) { navCtrl.navigate("UninstallApp") }
|
||||||
if(VERSION.SDK_INT >= 34 && (deviceOwner || dpm.isOrgProfile(receiver))) {
|
if(VERSION.SDK_INT >= 34 && (deviceOwner || dpm.isOrgProfile(receiver))) {
|
||||||
FunctionItem(R.string.set_default_dialer, "", R.drawable.call_fill0) {
|
FunctionItem(R.string.set_default_dialer, "", R.drawable.call_fill0) {
|
||||||
if(pkgName != "") dialogStatus.intValue = 3
|
if(pkgName != "") dialogStatus = 3
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Spacer(Modifier.padding(vertical = 30.dp))
|
Spacer(Modifier.padding(vertical = 30.dp))
|
||||||
LaunchedEffect(Unit) { fileUriFlow.value = Uri.parse("") }
|
LaunchedEffect(Unit) { fileUriFlow.value = Uri.parse("") }
|
||||||
}
|
}
|
||||||
if(appControlDialog) {
|
if(dialogStatus == 1) AlertDialog(
|
||||||
|
title = { Text(stringResource(R.string.enable_system_app)) },
|
||||||
|
text = {
|
||||||
|
Text(stringResource(R.string.enable_system_app_desc) + "\n" + pkgName)
|
||||||
|
},
|
||||||
|
onDismissRequest = { dialogStatus = 0 },
|
||||||
|
dismissButton = {
|
||||||
|
TextButton(onClick = { dialogStatus = 0 }) {
|
||||||
|
Text(stringResource(R.string.cancel))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(
|
||||||
|
onClick = {
|
||||||
|
try {
|
||||||
|
dpm.enableSystemApp(receiver, pkgName)
|
||||||
|
Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show()
|
||||||
|
} catch(_: IllegalArgumentException) {
|
||||||
|
Toast.makeText(context, R.string.failed, Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
dialogStatus = 0
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Text(stringResource(R.string.confirm))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
)
|
||||||
|
if(dialogStatus == 2 && VERSION.SDK_INT >= 28) AlertDialog(
|
||||||
|
title = { Text(text = stringResource(R.string.clear_app_storage)) },
|
||||||
|
text = {
|
||||||
|
Text(stringResource(R.string.app_storage_will_be_cleared) + "\n" + pkgName)
|
||||||
|
},
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(
|
||||||
|
onClick = {
|
||||||
|
val executor = Executors.newCachedThreadPool()
|
||||||
|
val onClear = DevicePolicyManager.OnClearApplicationUserDataListener { pkg: String, succeed: Boolean ->
|
||||||
|
Looper.prepare()
|
||||||
|
val toastText =
|
||||||
|
if(pkg!="") { "$pkg\n" }else{ "" } +
|
||||||
|
context.getString(R.string.clear_data) +
|
||||||
|
context.getString(if(succeed) R.string.success else R.string.failed )
|
||||||
|
Toast.makeText(context, toastText, Toast.LENGTH_SHORT).show()
|
||||||
|
Looper.loop()
|
||||||
|
}
|
||||||
|
dpm.clearApplicationUserData(receiver, pkgName, executor, onClear)
|
||||||
|
dialogStatus = 0
|
||||||
|
},
|
||||||
|
colors = ButtonDefaults.textButtonColors(contentColor = colorScheme.error)
|
||||||
|
) {
|
||||||
|
Text(text = stringResource(R.string.clear))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dismissButton = {
|
||||||
|
TextButton(
|
||||||
|
onClick = { dialogStatus = 0 }
|
||||||
|
) {
|
||||||
|
Text(text = stringResource(R.string.cancel))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onDismissRequest = { dialogStatus = 0 },
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
)
|
||||||
|
if(dialogStatus == 3 && VERSION.SDK_INT >= 34) AlertDialog(
|
||||||
|
title = { Text(stringResource(R.string.set_default_dialer)) },
|
||||||
|
text = {
|
||||||
|
Text(stringResource(R.string.app_will_be_default_dialer) + "\n" + pkgName)
|
||||||
|
},
|
||||||
|
onDismissRequest = { dialogStatus = 0 },
|
||||||
|
dismissButton = {
|
||||||
|
TextButton(onClick = { dialogStatus = 0 }) {
|
||||||
|
Text(stringResource(R.string.cancel))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(
|
||||||
|
onClick = {
|
||||||
|
try{
|
||||||
|
dpm.setDefaultDialerApplication(pkgName)
|
||||||
|
Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show()
|
||||||
|
} catch(_: IllegalArgumentException) {
|
||||||
|
Toast.makeText(context, R.string.failed, Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
dialogStatus = 0
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Text(stringResource(R.string.confirm))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
)
|
||||||
|
if(dialogStatus == 4) {
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
focusMgr.clearFocus()
|
focusMgr.clearFocus()
|
||||||
}
|
}
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
onDismissRequest = { appControlDialog = false },
|
onDismissRequest = { dialogStatus = 0 },
|
||||||
title = {
|
title = {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(
|
text = stringResource(
|
||||||
@@ -316,7 +392,7 @@ private fun Home(
|
|||||||
TextButton(
|
TextButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
appControl(true)
|
appControl(true)
|
||||||
appControlDialog = false
|
dialogStatus = 0
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
Text(text = stringResource(R.string.enable))
|
Text(text = stringResource(R.string.enable))
|
||||||
@@ -326,7 +402,7 @@ private fun Home(
|
|||||||
TextButton(
|
TextButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
appControl(false)
|
appControl(false)
|
||||||
appControlDialog = false
|
dialogStatus = 0
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
Text(text = stringResource(R.string.disable))
|
Text(text = stringResource(R.string.disable))
|
||||||
@@ -334,6 +410,7 @@ private fun Home(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
LaunchedEffect(dialogStatus) { focusMgr.clearFocus() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -697,9 +774,7 @@ private fun PermittedAccessibility(pkgName: String) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Information {
|
InfoCard(R.string.system_accessibility_always_allowed)
|
||||||
Text(stringResource(R.string.system_accessibility_always_allowed))
|
|
||||||
}
|
|
||||||
Spacer(Modifier.padding(vertical = 30.dp))
|
Spacer(Modifier.padding(vertical = 30.dp))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -755,9 +830,7 @@ private fun PermittedIME(pkgName: String) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Information {
|
InfoCard(R.string.system_ime_always_allowed)
|
||||||
Text(stringResource(R.string.system_ime_always_allowed))
|
|
||||||
}
|
|
||||||
Spacer(Modifier.padding(vertical = 30.dp))
|
Spacer(Modifier.padding(vertical = 30.dp))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -892,117 +965,3 @@ private fun InstallApp() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("NewApi")
|
|
||||||
@Composable
|
|
||||||
private fun ClearAppDataDialog(status: MutableIntState, pkgName: String) {
|
|
||||||
val context = LocalContext.current
|
|
||||||
val dpm = context.getDPM()
|
|
||||||
val receiver = context.getReceiver()
|
|
||||||
AlertDialog(
|
|
||||||
title = { Text(text = stringResource(R.string.clear_app_storage)) },
|
|
||||||
text = {
|
|
||||||
Text(stringResource(R.string.app_storage_will_be_cleared) + "\n" + pkgName)
|
|
||||||
},
|
|
||||||
confirmButton = {
|
|
||||||
TextButton(
|
|
||||||
onClick = {
|
|
||||||
val executor = Executors.newCachedThreadPool()
|
|
||||||
val onClear = DevicePolicyManager.OnClearApplicationUserDataListener { pkg: String, succeed: Boolean ->
|
|
||||||
Looper.prepare()
|
|
||||||
val toastText =
|
|
||||||
if(pkg!="") { "$pkg\n" }else{ "" } +
|
|
||||||
context.getString(R.string.clear_data) +
|
|
||||||
context.getString(if(succeed) R.string.success else R.string.failed )
|
|
||||||
Toast.makeText(context, toastText, Toast.LENGTH_SHORT).show()
|
|
||||||
Looper.loop()
|
|
||||||
}
|
|
||||||
dpm.clearApplicationUserData(receiver, pkgName, executor, onClear)
|
|
||||||
status.intValue = 0
|
|
||||||
},
|
|
||||||
colors = ButtonDefaults.textButtonColors(contentColor = colorScheme.error)
|
|
||||||
) {
|
|
||||||
Text(text = stringResource(R.string.clear))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
dismissButton = {
|
|
||||||
TextButton(
|
|
||||||
onClick = { status.intValue = 0 }
|
|
||||||
) {
|
|
||||||
Text(text = stringResource(R.string.cancel))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onDismissRequest = { status.intValue = 0 },
|
|
||||||
modifier = Modifier.fillMaxWidth()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("NewApi")
|
|
||||||
@Composable
|
|
||||||
private fun DefaultDialerAppDialog(status: MutableIntState, pkgName: String) {
|
|
||||||
val context = LocalContext.current
|
|
||||||
val dpm = context.getDPM()
|
|
||||||
AlertDialog(
|
|
||||||
title = { Text(stringResource(R.string.set_default_dialer)) },
|
|
||||||
text = {
|
|
||||||
Text(stringResource(R.string.app_will_be_default_dialer) + "\n" + pkgName)
|
|
||||||
},
|
|
||||||
onDismissRequest = { status.intValue = 0 },
|
|
||||||
dismissButton = {
|
|
||||||
TextButton(onClick = { status.intValue = 0 }) {
|
|
||||||
Text(stringResource(R.string.cancel))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
confirmButton = {
|
|
||||||
TextButton(
|
|
||||||
onClick = {
|
|
||||||
try{
|
|
||||||
dpm.setDefaultDialerApplication(pkgName)
|
|
||||||
Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show()
|
|
||||||
}catch(_: IllegalArgumentException) {
|
|
||||||
Toast.makeText(context, R.string.failed, Toast.LENGTH_SHORT).show()
|
|
||||||
}
|
|
||||||
status.intValue = 0
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
Text(stringResource(R.string.confirm))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
modifier = Modifier.fillMaxWidth()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun EnableSystemAppDialog(status: MutableIntState, pkgName: String) {
|
|
||||||
val context = LocalContext.current
|
|
||||||
val dpm = context.getDPM()
|
|
||||||
val receiver = context.getReceiver()
|
|
||||||
AlertDialog(
|
|
||||||
title = { Text(stringResource(R.string.enable_system_app)) },
|
|
||||||
text = {
|
|
||||||
Text(stringResource(R.string.enable_system_app_desc) + "\n" + pkgName)
|
|
||||||
},
|
|
||||||
onDismissRequest = { status.intValue = 0 },
|
|
||||||
dismissButton = {
|
|
||||||
TextButton(onClick = { status.intValue = 0 }) {
|
|
||||||
Text(stringResource(R.string.cancel))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
confirmButton = {
|
|
||||||
TextButton(
|
|
||||||
onClick = {
|
|
||||||
try {
|
|
||||||
dpm.enableSystemApp(receiver, pkgName)
|
|
||||||
Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show()
|
|
||||||
} catch(_: IllegalArgumentException) {
|
|
||||||
Toast.makeText(context, R.string.failed, Toast.LENGTH_SHORT).show()
|
|
||||||
}
|
|
||||||
status.intValue = 0
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
Text(stringResource(R.string.confirm))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
modifier = Modifier.fillMaxWidth()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -92,12 +92,12 @@ import androidx.compose.ui.text.input.KeyboardType
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import androidx.navigation.NavHostController
|
import androidx.navigation.NavHostController
|
||||||
|
import com.bintianqi.owndroid.MyViewModel
|
||||||
import com.bintianqi.owndroid.R
|
import com.bintianqi.owndroid.R
|
||||||
import com.bintianqi.owndroid.exportFile
|
import com.bintianqi.owndroid.exportFile
|
||||||
import com.bintianqi.owndroid.exportFilePath
|
import com.bintianqi.owndroid.exportFilePath
|
||||||
import com.bintianqi.owndroid.formatFileSize
|
import com.bintianqi.owndroid.formatFileSize
|
||||||
import com.bintianqi.owndroid.isExportingSecurityOrNetworkLogs
|
import com.bintianqi.owndroid.isExportingSecurityOrNetworkLogs
|
||||||
import com.bintianqi.owndroid.selectedPackage
|
|
||||||
import com.bintianqi.owndroid.ui.CheckBoxItem
|
import com.bintianqi.owndroid.ui.CheckBoxItem
|
||||||
import com.bintianqi.owndroid.ui.FunctionItem
|
import com.bintianqi.owndroid.ui.FunctionItem
|
||||||
import com.bintianqi.owndroid.ui.InfoCard
|
import com.bintianqi.owndroid.ui.InfoCard
|
||||||
@@ -400,7 +400,7 @@ fun PrivateDNS(navCtrl: NavHostController) {
|
|||||||
|
|
||||||
@SuppressLint("NewApi")
|
@SuppressLint("NewApi")
|
||||||
@Composable
|
@Composable
|
||||||
fun AlwaysOnVPNPackage(navCtrl: NavHostController) {
|
fun AlwaysOnVPNPackage(navCtrl: NavHostController, vm: MyViewModel) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val dpm = context.getDPM()
|
val dpm = context.getDPM()
|
||||||
val receiver = context.getReceiver()
|
val receiver = context.getReceiver()
|
||||||
@@ -409,11 +409,11 @@ fun AlwaysOnVPNPackage(navCtrl: NavHostController) {
|
|||||||
val focusMgr = LocalFocusManager.current
|
val focusMgr = LocalFocusManager.current
|
||||||
val refresh = { pkgName = dpm.getAlwaysOnVpnPackage(receiver) ?: "" }
|
val refresh = { pkgName = dpm.getAlwaysOnVpnPackage(receiver) ?: "" }
|
||||||
LaunchedEffect(Unit) { refresh() }
|
LaunchedEffect(Unit) { refresh() }
|
||||||
val updatePackage by selectedPackage.collectAsState()
|
val updatePackage by vm.selectedPackage.collectAsState()
|
||||||
LaunchedEffect(updatePackage) {
|
LaunchedEffect(updatePackage) {
|
||||||
if(selectedPackage.value != "") {
|
if(updatePackage != "") {
|
||||||
pkgName = selectedPackage.value
|
pkgName = updatePackage
|
||||||
selectedPackage.value = ""
|
vm.selectedPackage.value = ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val setAlwaysOnVpn: (String?, Boolean)->Boolean = { vpnPkg: String?, lockdownEnabled: Boolean ->
|
val setAlwaysOnVpn: (String?, Boolean)->Boolean = { vpnPkg: String?, lockdownEnabled: Boolean ->
|
||||||
|
|||||||
@@ -75,7 +75,6 @@ import com.bintianqi.owndroid.ui.CardItem
|
|||||||
import com.bintianqi.owndroid.ui.CheckBoxItem
|
import com.bintianqi.owndroid.ui.CheckBoxItem
|
||||||
import com.bintianqi.owndroid.ui.FunctionItem
|
import com.bintianqi.owndroid.ui.FunctionItem
|
||||||
import com.bintianqi.owndroid.ui.InfoCard
|
import com.bintianqi.owndroid.ui.InfoCard
|
||||||
import com.bintianqi.owndroid.ui.Information
|
|
||||||
import com.bintianqi.owndroid.ui.MyScaffold
|
import com.bintianqi.owndroid.ui.MyScaffold
|
||||||
import com.bintianqi.owndroid.ui.RadioButtonItem
|
import com.bintianqi.owndroid.ui.RadioButtonItem
|
||||||
import com.bintianqi.owndroid.yesOrNo
|
import com.bintianqi.owndroid.yesOrNo
|
||||||
@@ -303,7 +302,7 @@ fun ResetPasswordToken(navCtrl: NavHostController) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Spacer(Modifier.padding(vertical = 5.dp))
|
Spacer(Modifier.padding(vertical = 5.dp))
|
||||||
Information{ Text(stringResource(R.string.activate_token_not_required_when_no_password)) }
|
InfoCard(R.string.activate_token_not_required_when_no_password)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,9 +3,6 @@ package com.bintianqi.owndroid.dpm
|
|||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.app.ActivityOptions
|
import android.app.ActivityOptions
|
||||||
import android.app.AlertDialog
|
import android.app.AlertDialog
|
||||||
import android.app.NotificationChannel
|
|
||||||
import android.app.NotificationManager
|
|
||||||
import android.app.PendingIntent
|
|
||||||
import android.app.admin.DevicePolicyManager.FLAG_EVICT_CREDENTIAL_ENCRYPTION_KEY
|
import android.app.admin.DevicePolicyManager.FLAG_EVICT_CREDENTIAL_ENCRYPTION_KEY
|
||||||
import android.app.admin.DevicePolicyManager.InstallSystemUpdateCallback
|
import android.app.admin.DevicePolicyManager.InstallSystemUpdateCallback
|
||||||
import android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_BLOCK_ACTIVITY_START_IN_TASK
|
import android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_BLOCK_ACTIVITY_START_IN_TASK
|
||||||
@@ -107,10 +104,11 @@ 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.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import androidx.navigation.NavHostController
|
import androidx.navigation.NavHostController
|
||||||
|
import com.bintianqi.owndroid.MyViewModel
|
||||||
|
import com.bintianqi.owndroid.NotificationUtils
|
||||||
import com.bintianqi.owndroid.R
|
import com.bintianqi.owndroid.R
|
||||||
import com.bintianqi.owndroid.StopLockTaskModeReceiver
|
|
||||||
import com.bintianqi.owndroid.exportFile
|
import com.bintianqi.owndroid.exportFile
|
||||||
import com.bintianqi.owndroid.exportFilePath
|
import com.bintianqi.owndroid.exportFilePath
|
||||||
import com.bintianqi.owndroid.fileUriFlow
|
import com.bintianqi.owndroid.fileUriFlow
|
||||||
@@ -118,13 +116,10 @@ import com.bintianqi.owndroid.formatFileSize
|
|||||||
import com.bintianqi.owndroid.getFile
|
import com.bintianqi.owndroid.getFile
|
||||||
import com.bintianqi.owndroid.humanReadableDate
|
import com.bintianqi.owndroid.humanReadableDate
|
||||||
import com.bintianqi.owndroid.isExportingSecurityOrNetworkLogs
|
import com.bintianqi.owndroid.isExportingSecurityOrNetworkLogs
|
||||||
import com.bintianqi.owndroid.prepareForNotification
|
|
||||||
import com.bintianqi.owndroid.selectedPackage
|
|
||||||
import com.bintianqi.owndroid.toggle
|
import com.bintianqi.owndroid.toggle
|
||||||
import com.bintianqi.owndroid.ui.CheckBoxItem
|
import com.bintianqi.owndroid.ui.CheckBoxItem
|
||||||
import com.bintianqi.owndroid.ui.FunctionItem
|
import com.bintianqi.owndroid.ui.FunctionItem
|
||||||
import com.bintianqi.owndroid.ui.InfoCard
|
import com.bintianqi.owndroid.ui.InfoCard
|
||||||
import com.bintianqi.owndroid.ui.Information
|
|
||||||
import com.bintianqi.owndroid.ui.ListItem
|
import com.bintianqi.owndroid.ui.ListItem
|
||||||
import com.bintianqi.owndroid.ui.MyScaffold
|
import com.bintianqi.owndroid.ui.MyScaffold
|
||||||
import com.bintianqi.owndroid.ui.RadioButtonItem
|
import com.bintianqi.owndroid.ui.RadioButtonItem
|
||||||
@@ -522,9 +517,7 @@ fun ChangeTimeZone(navCtrl: NavHostController) {
|
|||||||
Text(stringResource(R.string.apply))
|
Text(stringResource(R.string.apply))
|
||||||
}
|
}
|
||||||
Spacer(Modifier.padding(vertical = 10.dp))
|
Spacer(Modifier.padding(vertical = 10.dp))
|
||||||
Information {
|
InfoCard(R.string.disable_auto_time_zone_before_set)
|
||||||
Text(stringResource(R.string.disable_auto_time_zone_before_set))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if(dialog) AlertDialog(
|
if(dialog) AlertDialog(
|
||||||
text = {
|
text = {
|
||||||
@@ -700,12 +693,11 @@ fun NearbyStreamingPolicy(navCtrl: NavHostController) {
|
|||||||
|
|
||||||
@SuppressLint("NewApi")
|
@SuppressLint("NewApi")
|
||||||
@Composable
|
@Composable
|
||||||
fun LockTaskMode(navCtrl: NavHostController) {
|
fun LockTaskMode(navCtrl: NavHostController, vm: MyViewModel) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val dpm = context.getDPM()
|
val dpm = context.getDPM()
|
||||||
val receiver = context.getReceiver()
|
val receiver = context.getReceiver()
|
||||||
val focusMgr = LocalFocusManager.current
|
val focusMgr = LocalFocusManager.current
|
||||||
val coroutine = rememberCoroutineScope()
|
|
||||||
var appSelectorRequest by rememberSaveable { mutableIntStateOf(0) }
|
var appSelectorRequest by rememberSaveable { mutableIntStateOf(0) }
|
||||||
MyScaffold(R.string.lock_task_mode, 8.dp, navCtrl, false) {
|
MyScaffold(R.string.lock_task_mode, 8.dp, navCtrl, false) {
|
||||||
val lockTaskFeatures = remember { mutableStateListOf<Int>() }
|
val lockTaskFeatures = remember { mutableStateListOf<Int>() }
|
||||||
@@ -788,7 +780,7 @@ fun LockTaskMode(navCtrl: NavHostController) {
|
|||||||
Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show()
|
Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show()
|
||||||
} catch (e: IllegalArgumentException) {
|
} catch (e: IllegalArgumentException) {
|
||||||
AlertDialog.Builder(context)
|
AlertDialog.Builder(context)
|
||||||
.setTitle("Error")
|
.setTitle(R.string.error)
|
||||||
.setMessage(e.message)
|
.setMessage(e.message)
|
||||||
.setPositiveButton(R.string.confirm) { dialog, _ -> dialog.dismiss() }
|
.setPositiveButton(R.string.confirm) { dialog, _ -> dialog.dismiss() }
|
||||||
.show()
|
.show()
|
||||||
@@ -863,11 +855,11 @@ fun LockTaskMode(navCtrl: NavHostController) {
|
|||||||
var startLockTaskApp by rememberSaveable { mutableStateOf("") }
|
var startLockTaskApp by rememberSaveable { mutableStateOf("") }
|
||||||
var startLockTaskActivity by rememberSaveable { mutableStateOf("") }
|
var startLockTaskActivity by rememberSaveable { mutableStateOf("") }
|
||||||
var specifyActivity by rememberSaveable { mutableStateOf(false) }
|
var specifyActivity by rememberSaveable { mutableStateOf(false) }
|
||||||
val updatePackage by selectedPackage.collectAsState()
|
val updatePackage by vm.selectedPackage.collectAsStateWithLifecycle()
|
||||||
LaunchedEffect(updatePackage) {
|
LaunchedEffect(updatePackage) {
|
||||||
if(updatePackage != "") {
|
if(updatePackage != "") {
|
||||||
if(appSelectorRequest == 1) inputLockTaskPkg = updatePackage else startLockTaskApp = updatePackage
|
if(appSelectorRequest == 1) inputLockTaskPkg = updatePackage else startLockTaskApp = updatePackage
|
||||||
selectedPackage.value = ""
|
vm.selectedPackage.value = ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Spacer(Modifier.padding(vertical = 10.dp))
|
Spacer(Modifier.padding(vertical = 10.dp))
|
||||||
@@ -906,7 +898,8 @@ fun LockTaskMode(navCtrl: NavHostController) {
|
|||||||
Button(
|
Button(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
onClick = {
|
onClick = {
|
||||||
if(!dpm.getLockTaskPackages(receiver).contains(startLockTaskApp)) {
|
if(!NotificationUtils.checkPermission(context)) return@Button
|
||||||
|
if(!dpm.isLockTaskPermitted(startLockTaskApp)) {
|
||||||
Toast.makeText(context, R.string.app_not_allowed, Toast.LENGTH_SHORT).show()
|
Toast.makeText(context, R.string.app_not_allowed, Toast.LENGTH_SHORT).show()
|
||||||
return@Button
|
return@Button
|
||||||
}
|
}
|
||||||
@@ -915,13 +908,7 @@ fun LockTaskMode(navCtrl: NavHostController) {
|
|||||||
val launchIntent = if(specifyActivity) Intent().setComponent(ComponentName(startLockTaskApp, startLockTaskActivity))
|
val launchIntent = if(specifyActivity) Intent().setComponent(ComponentName(startLockTaskApp, startLockTaskActivity))
|
||||||
else packageManager.getLaunchIntentForPackage(startLockTaskApp)
|
else packageManager.getLaunchIntentForPackage(startLockTaskApp)
|
||||||
if (launchIntent != null) {
|
if (launchIntent != null) {
|
||||||
coroutine.launch {
|
|
||||||
prepareForNotification(context) {
|
|
||||||
sendStopLockTaskNotification(context)
|
|
||||||
context.startActivity(launchIntent, options.toBundle())
|
context.startActivity(launchIntent, options.toBundle())
|
||||||
Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
Toast.makeText(context, R.string.failed, Toast.LENGTH_SHORT).show()
|
Toast.makeText(context, R.string.failed, Toast.LENGTH_SHORT).show()
|
||||||
}
|
}
|
||||||
@@ -1497,23 +1484,3 @@ fun InstallSystemUpdate(navCtrl: NavHostController) {
|
|||||||
InfoCard(R.string.auto_reboot_after_install_succeed)
|
InfoCard(R.string.auto_reboot_after_install_succeed)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("NewApi")
|
|
||||||
private fun sendStopLockTaskNotification(context: Context) {
|
|
||||||
val nm = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
|
||||||
if (VERSION.SDK_INT >= 26) {
|
|
||||||
val channel = NotificationChannel("LockTaskMode", context.getString(R.string.lock_task_mode), NotificationManager.IMPORTANCE_HIGH).apply {
|
|
||||||
description = "Notification channel for stop lock task mode"
|
|
||||||
setShowBadge(false)
|
|
||||||
}
|
|
||||||
nm.createNotificationChannel(channel)
|
|
||||||
}
|
|
||||||
val intent = Intent(context, StopLockTaskModeReceiver::class.java)
|
|
||||||
val pendingIntent = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_IMMUTABLE)
|
|
||||||
val builder = NotificationCompat.Builder(context, "LockTaskMode")
|
|
||||||
.setContentTitle(context.getText(R.string.lock_task_mode))
|
|
||||||
.setSmallIcon(R.drawable.lock_fill0)
|
|
||||||
.addAction(NotificationCompat.Action.Builder(R.drawable.lock_fill0, context.getText(R.string.stop), pendingIntent).build())
|
|
||||||
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
|
||||||
nm.notify(1, builder.build())
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ import androidx.compose.ui.res.painterResource
|
|||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.navigation.NavBackStackEntry
|
|
||||||
import androidx.navigation.NavHostController
|
import androidx.navigation.NavHostController
|
||||||
import com.bintianqi.owndroid.R
|
import com.bintianqi.owndroid.R
|
||||||
import com.bintianqi.owndroid.writeClipBoard
|
import com.bintianqi.owndroid.writeClipBoard
|
||||||
@@ -79,21 +78,6 @@ fun NavIcon(operation: () -> Unit) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun Information(content: @Composable ()->Unit) {
|
|
||||||
Column(modifier = Modifier.fillMaxWidth().padding(start = 5.dp, top = 20.dp)) {
|
|
||||||
Icon(
|
|
||||||
painter = painterResource(R.drawable.info_fill0),
|
|
||||||
contentDescription = "info",
|
|
||||||
tint = colorScheme.onBackground.copy(alpha = 0.8F)
|
|
||||||
)
|
|
||||||
Spacer(Modifier.padding(vertical = 1.dp))
|
|
||||||
Column(modifier = Modifier.padding(start = 2.dp)) {
|
|
||||||
content()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun RadioButtonItem(
|
fun RadioButtonItem(
|
||||||
@StringRes text: Int,
|
@StringRes text: Int,
|
||||||
|
|||||||
@@ -63,6 +63,7 @@
|
|||||||
<string name="alias">Alias</string>
|
<string name="alias">Alias</string>
|
||||||
<string name="unknown_error">Unknown error</string>
|
<string name="unknown_error">Unknown error</string>
|
||||||
<string name="permission_denied">Permission denied</string>
|
<string name="permission_denied">Permission denied</string>
|
||||||
|
<string name="error">Error</string>
|
||||||
|
|
||||||
|
|
||||||
<!--Разрешения-->
|
<!--Разрешения-->
|
||||||
|
|||||||
@@ -64,6 +64,7 @@
|
|||||||
<string name="alias">Alias</string>
|
<string name="alias">Alias</string>
|
||||||
<string name="unknown_error">Unknown error</string>
|
<string name="unknown_error">Unknown error</string>
|
||||||
<string name="permission_denied">Permission denied</string>
|
<string name="permission_denied">Permission denied</string>
|
||||||
|
<string name="error">Error</string>
|
||||||
|
|
||||||
<!--Permissions-->
|
<!--Permissions-->
|
||||||
<string name="click_to_activate">Etkinleştirmek İçin Tıklayın</string>
|
<string name="click_to_activate">Etkinleştirmek İçin Tıklayın</string>
|
||||||
|
|||||||
@@ -60,6 +60,7 @@
|
|||||||
<string name="alias">别名</string>
|
<string name="alias">别名</string>
|
||||||
<string name="unknown_error">未知错误</string>
|
<string name="unknown_error">未知错误</string>
|
||||||
<string name="permission_denied">无权限</string>
|
<string name="permission_denied">无权限</string>
|
||||||
|
<string name="error">错误</string>
|
||||||
|
|
||||||
<!--Permissions-->
|
<!--Permissions-->
|
||||||
<string name="click_to_activate">点击以激活</string>
|
<string name="click_to_activate">点击以激活</string>
|
||||||
|
|||||||
@@ -64,6 +64,7 @@
|
|||||||
<string name="unknown_error">Unknown error</string>
|
<string name="unknown_error">Unknown error</string>
|
||||||
<string name="permission_denied">Permission denied</string>
|
<string name="permission_denied">Permission denied</string>
|
||||||
<string name="api" translatable="false">API</string>
|
<string name="api" translatable="false">API</string>
|
||||||
|
<string name="error">Error</string>
|
||||||
|
|
||||||
<!--Permissions-->
|
<!--Permissions-->
|
||||||
<string name="click_to_activate">Click to activate</string>
|
<string name="click_to_activate">Click to activate</string>
|
||||||
|
|||||||
Reference in New Issue
Block a user