Add WorkModesScreen

Update dependency
Remove some Shizuku features
This commit is contained in:
BinTianqi
2025-04-12 21:39:11 +08:00
parent 6c92c7dcbe
commit 5110536b59
18 changed files with 567 additions and 616 deletions

View File

@@ -94,6 +94,7 @@ dependencies {
implementation(libs.dhizuku.api) implementation(libs.dhizuku.api)
implementation(libs.androidx.fragment) implementation(libs.androidx.fragment)
implementation(libs.hiddenApiBypass) implementation(libs.hiddenApiBypass)
implementation(libs.libsu)
implementation(libs.serialization) implementation(libs.serialization)
implementation(kotlin("reflect")) implementation(kotlin("reflect"))
} }

View File

@@ -1,10 +1,9 @@
package com.bintianqi.owndroid; package com.bintianqi.owndroid;
import android.accounts.Account; import android.accounts.Account;
import android.os.Bundle;
interface IUserService { interface IUserService {
String execute(String command) = 1; Bundle execute(String command) = 1;
int getUid() = 2;
Account[] listAccounts() = 3;
void destroy() = 16777114; void destroy() = 16777114;
} }

View File

@@ -2,7 +2,6 @@ package com.bintianqi.owndroid
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.Activity import android.app.Activity
import android.app.admin.DevicePolicyManager
import android.os.Build.VERSION import android.os.Build.VERSION
import android.os.Bundle import android.os.Bundle
import android.widget.Toast import android.widget.Toast
@@ -22,14 +21,18 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Settings
import androidx.compose.material3.AlertDialog import androidx.compose.material3.AlertDialog
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.MaterialTheme.colorScheme import androidx.compose.material3.MaterialTheme.colorScheme
import androidx.compose.material3.MaterialTheme.typography import androidx.compose.material3.MaterialTheme.typography
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
@@ -57,8 +60,6 @@ import androidx.navigation.compose.composable
import androidx.navigation.compose.dialog import androidx.navigation.compose.dialog
import androidx.navigation.compose.rememberNavController import androidx.navigation.compose.rememberNavController
import androidx.navigation.toRoute import androidx.navigation.toRoute
import com.bintianqi.owndroid.dpm.Accounts
import com.bintianqi.owndroid.dpm.AccountsScreen
import com.bintianqi.owndroid.dpm.AddApnSetting import com.bintianqi.owndroid.dpm.AddApnSetting
import com.bintianqi.owndroid.dpm.AddApnSettingScreen import com.bintianqi.owndroid.dpm.AddApnSettingScreen
import com.bintianqi.owndroid.dpm.AddDelegatedAdmin import com.bintianqi.owndroid.dpm.AddDelegatedAdmin
@@ -107,8 +108,6 @@ import com.bintianqi.owndroid.dpm.DeleteWorkProfile
import com.bintianqi.owndroid.dpm.DeleteWorkProfileScreen import com.bintianqi.owndroid.dpm.DeleteWorkProfileScreen
import com.bintianqi.owndroid.dpm.DeviceInfo import com.bintianqi.owndroid.dpm.DeviceInfo
import com.bintianqi.owndroid.dpm.DeviceInfoScreen import com.bintianqi.owndroid.dpm.DeviceInfoScreen
import com.bintianqi.owndroid.dpm.DeviceOwner
import com.bintianqi.owndroid.dpm.DeviceOwnerScreen
import com.bintianqi.owndroid.dpm.DisableAccountManagement import com.bintianqi.owndroid.dpm.DisableAccountManagement
import com.bintianqi.owndroid.dpm.DisableAccountManagementScreen import com.bintianqi.owndroid.dpm.DisableAccountManagementScreen
import com.bintianqi.owndroid.dpm.DisableMeteredData import com.bintianqi.owndroid.dpm.DisableMeteredData
@@ -172,8 +171,6 @@ import com.bintianqi.owndroid.dpm.PreferentialNetworkService
import com.bintianqi.owndroid.dpm.PreferentialNetworkServiceScreen import com.bintianqi.owndroid.dpm.PreferentialNetworkServiceScreen
import com.bintianqi.owndroid.dpm.PrivateDns import com.bintianqi.owndroid.dpm.PrivateDns
import com.bintianqi.owndroid.dpm.PrivateDnsScreen import com.bintianqi.owndroid.dpm.PrivateDnsScreen
import com.bintianqi.owndroid.dpm.ProfileOwner
import com.bintianqi.owndroid.dpm.ProfileOwnerScreen
import com.bintianqi.owndroid.dpm.QueryNetworkStats import com.bintianqi.owndroid.dpm.QueryNetworkStats
import com.bintianqi.owndroid.dpm.RecommendedGlobalProxy import com.bintianqi.owndroid.dpm.RecommendedGlobalProxy
import com.bintianqi.owndroid.dpm.RecommendedGlobalProxyScreen import com.bintianqi.owndroid.dpm.RecommendedGlobalProxyScreen
@@ -191,7 +188,6 @@ import com.bintianqi.owndroid.dpm.SecurityLoggingScreen
import com.bintianqi.owndroid.dpm.SetDefaultDialer import com.bintianqi.owndroid.dpm.SetDefaultDialer
import com.bintianqi.owndroid.dpm.SetDefaultDialerScreen import com.bintianqi.owndroid.dpm.SetDefaultDialerScreen
import com.bintianqi.owndroid.dpm.SetSystemUpdatePolicy import com.bintianqi.owndroid.dpm.SetSystemUpdatePolicy
import com.bintianqi.owndroid.dpm.ShizukuScreen
import com.bintianqi.owndroid.dpm.SupportMessage import com.bintianqi.owndroid.dpm.SupportMessage
import com.bintianqi.owndroid.dpm.SupportMessageScreen import com.bintianqi.owndroid.dpm.SupportMessageScreen
import com.bintianqi.owndroid.dpm.Suspend import com.bintianqi.owndroid.dpm.Suspend
@@ -230,6 +226,8 @@ import com.bintianqi.owndroid.dpm.WifiSecurityLevelScreen
import com.bintianqi.owndroid.dpm.WifiSsidPolicyScreen import com.bintianqi.owndroid.dpm.WifiSsidPolicyScreen
import com.bintianqi.owndroid.dpm.WipeData import com.bintianqi.owndroid.dpm.WipeData
import com.bintianqi.owndroid.dpm.WipeDataScreen import com.bintianqi.owndroid.dpm.WipeDataScreen
import com.bintianqi.owndroid.dpm.WorkModes
import com.bintianqi.owndroid.dpm.WorkModesScreen
import com.bintianqi.owndroid.dpm.WorkProfile import com.bintianqi.owndroid.dpm.WorkProfile
import com.bintianqi.owndroid.dpm.WorkProfileScreen import com.bintianqi.owndroid.dpm.WorkProfileScreen
import com.bintianqi.owndroid.dpm.dhizukuErrorStatus import com.bintianqi.owndroid.dpm.dhizukuErrorStatus
@@ -299,6 +297,14 @@ fun Home(vm: MyViewModel) {
val userRestrictions by vm.userRestrictions.collectAsStateWithLifecycle() val userRestrictions by vm.userRestrictions.collectAsStateWithLifecycle()
fun navigateUp() { navController.navigateUp() } fun navigateUp() { navController.navigateUp() }
fun navigate(destination: Any) { navController.navigate(destination) } fun navigate(destination: Any) { navController.navigate(destination) }
LaunchedEffect(Unit) {
val privilege = myPrivilege.value
if(!privilege.device && !privilege.profile) {
navController.navigate(WorkModes(false)) {
popUpTo<Home> { inclusive = true }
}
}
}
@Suppress("NewApi") NavHost( @Suppress("NewApi") NavHost(
navController = navController, navController = navController,
startDestination = Home, startDestination = Home,
@@ -313,20 +319,35 @@ fun Home(vm: MyViewModel) {
popExitTransition = Animations.navHostPopExitTransition popExitTransition = Animations.navHostPopExitTransition
) { ) {
composable<Home> { HomeScreen { navController.navigate(it) } } composable<Home> { HomeScreen { navController.navigate(it) } }
composable<WorkModes> {
WorkModesScreen(it.toRoute(), ::navigateUp, {
navController.navigate(Home) {
popUpTo<WorkModes> { inclusive = true }
}
}, {
navController.navigate(WorkModes(false)) {
popUpTo<Home> { inclusive = true }
}
}, {
navController.navigate(it)
})
}
composable<Permissions> { composable<Permissions> {
PermissionsScreen(::navigateUp, { navController.navigate(it) }) { navController.navigate(ShizukuScreen, it) } PermissionsScreen(::navigateUp) { navController.navigate(it) }
} }
composable<ShizukuScreen> { ShizukuScreen(it.arguments!!, ::navigateUp) { dest -> navController.navigate(dest) } }
composable<Accounts>(mapOf(serializableNavTypePair<List<Accounts.Account>>())) { AccountsScreen(it.toRoute(), ::navigateUp) }
composable<ProfileOwner> { ProfileOwnerScreen(::navigateUp) }
composable<DeviceOwner> { DeviceOwnerScreen(::navigateUp) }
composable<DelegatedAdmins> { DelegatedAdminsScreen(::navigateUp, ::navigate) } composable<DelegatedAdmins> { DelegatedAdminsScreen(::navigateUp, ::navigate) }
composable<AddDelegatedAdmin>{ AddDelegatedAdminScreen(it.toRoute(), ::navigateUp) } composable<AddDelegatedAdmin>{ AddDelegatedAdminScreen(it.toRoute(), ::navigateUp) }
composable<DeviceInfo> { DeviceInfoScreen(::navigateUp) } composable<DeviceInfo> { DeviceInfoScreen(::navigateUp) }
composable<LockScreenInfo> { LockScreenInfoScreen(::navigateUp) } composable<LockScreenInfo> { LockScreenInfoScreen(::navigateUp) }
composable<SupportMessage> { SupportMessageScreen(::navigateUp) } composable<SupportMessage> { SupportMessageScreen(::navigateUp) }
composable<TransferOwnership> { TransferOwnershipScreen(::navigateUp) } composable<TransferOwnership> {
TransferOwnershipScreen(::navigateUp) {
navController.navigate(WorkModes(false)) {
popUpTo(Home) { inclusive = true }
}
}
}
composable<SystemManager> { SystemManagerScreen(::navigateUp, ::navigate) } composable<SystemManager> { SystemManagerScreen(::navigateUp, ::navigate) }
composable<SystemOptions> { SystemOptionsScreen(::navigateUp) } composable<SystemOptions> { SystemOptionsScreen(::navigateUp) }
@@ -492,10 +513,10 @@ fun Home(vm: MyViewModel) {
@Serializable private object Home @Serializable private object Home
@OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
private fun HomeScreen(onNavigate: (Any) -> Unit) { private fun HomeScreen(onNavigate: (Any) -> Unit) {
val context = LocalContext.current val context = LocalContext.current
val dpm = context.getDPM()
val privilege by myPrivilege.collectAsStateWithLifecycle() val privilege by myPrivilege.collectAsStateWithLifecycle()
val activateType = (if(privilege.dhizuku) context.getString(R.string.dhizuku) + " - " else "") + val activateType = (if(privilege.dhizuku) context.getString(R.string.dhizuku) + " - " else "") +
context.getString( context.getString(
@@ -504,9 +525,18 @@ private fun HomeScreen(onNavigate: (Any) -> Unit) {
else if(privilege.profile) R.string.profile_owner else if(privilege.profile) R.string.profile_owner
else R.string.click_to_activate else R.string.click_to_activate
) )
Scaffold { Scaffold(
topBar = {
TopAppBar(
{}, actions = {
IconButton({ onNavigate(WorkModes(true)) }) { Icon(painterResource(R.drawable.security_fill0), null) }
IconButton({ onNavigate(Settings) }) { Icon(Icons.Default.Settings, null) }
}
)
}
) {
Column(modifier = Modifier.padding(it).verticalScroll(rememberScrollState())) { Column(modifier = Modifier.padding(it).verticalScroll(rememberScrollState())) {
Spacer(Modifier.padding(vertical = 25.dp)) Spacer(Modifier.padding(vertical = 8.dp))
Text( Text(
text = stringResource(R.string.app_name), style = typography.headlineLarge, text = stringResource(R.string.app_name), style = typography.headlineLarge,
modifier = Modifier.padding(start = HorizontalPadding) modifier = Modifier.padding(start = HorizontalPadding)
@@ -541,16 +571,9 @@ private fun HomeScreen(onNavigate: (Any) -> Unit) {
HomePageItem(R.string.system, R.drawable.android_fill0) { onNavigate(SystemManager) } HomePageItem(R.string.system, R.drawable.android_fill0) { onNavigate(SystemManager) }
HomePageItem(R.string.network, R.drawable.wifi_fill0) { onNavigate(Network) } HomePageItem(R.string.network, R.drawable.wifi_fill0) { onNavigate(Network) }
} }
if( if(privilege.work) {
privilege.work || (VERSION.SDK_INT < 24 && !privilege.device) ||
dpm.isProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE)
) {
HomePageItem(R.string.work_profile, R.drawable.work_fill0) { HomePageItem(R.string.work_profile, R.drawable.work_fill0) {
onNavigate( onNavigate(WorkProfile)
if(VERSION.SDK_INT < 24 ||
dpm.isProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE)
) WorkProfile else CreateWorkProfile
)
} }
} }
if(privilege.device || privilege.profile) { if(privilege.device || privilege.profile) {
@@ -563,7 +586,6 @@ private fun HomeScreen(onNavigate: (Any) -> Unit) {
HomePageItem(R.string.users,R.drawable.manage_accounts_fill0) { onNavigate(Users) } HomePageItem(R.string.users,R.drawable.manage_accounts_fill0) { onNavigate(Users) }
HomePageItem(R.string.password_and_keyguard, R.drawable.password_fill0) { onNavigate(Password) } HomePageItem(R.string.password_and_keyguard, R.drawable.password_fill0) { onNavigate(Password) }
} }
HomePageItem(R.string.settings, R.drawable.settings_fill0) { onNavigate(Settings) }
Spacer(Modifier.padding(vertical = 20.dp)) Spacer(Modifier.padding(vertical = 20.dp))
} }
} }

View File

@@ -45,6 +45,7 @@ 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.content.edit
import androidx.core.net.toUri import androidx.core.net.toUri
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.bintianqi.owndroid.ui.FunctionItem import com.bintianqi.owndroid.ui.FunctionItem
import com.bintianqi.owndroid.ui.MyScaffold import com.bintianqi.owndroid.ui.MyScaffold
import com.bintianqi.owndroid.ui.Notes import com.bintianqi.owndroid.ui.Notes
@@ -60,6 +61,7 @@ import java.util.Locale
@Composable @Composable
fun SettingsScreen(onNavigateUp: () -> Unit, onNavigate: (Any) -> Unit) { fun SettingsScreen(onNavigateUp: () -> Unit, onNavigate: (Any) -> Unit) {
val context = LocalContext.current val context = LocalContext.current
val privilege by myPrivilege.collectAsStateWithLifecycle()
val exportLogsLauncher = rememberLauncherForActivityResult(ActivityResultContracts.CreateDocument("text/plain")) { val exportLogsLauncher = rememberLauncherForActivityResult(ActivityResultContracts.CreateDocument("text/plain")) {
if(it != null) exportLogs(context, it) if(it != null) exportLogs(context, it)
} }
@@ -67,7 +69,9 @@ fun SettingsScreen(onNavigateUp: () -> Unit, onNavigate: (Any) -> Unit) {
FunctionItem(title = R.string.options, icon = R.drawable.tune_fill0) { onNavigate(SettingsOptions) } FunctionItem(title = R.string.options, icon = R.drawable.tune_fill0) { onNavigate(SettingsOptions) }
FunctionItem(title = R.string.appearance, icon = R.drawable.format_paint_fill0) { onNavigate(Appearance) } FunctionItem(title = R.string.appearance, icon = R.drawable.format_paint_fill0) { onNavigate(Appearance) }
FunctionItem(R.string.app_lock, icon = R.drawable.lock_fill0) { onNavigate(AppLockSettings) } FunctionItem(R.string.app_lock, icon = R.drawable.lock_fill0) { onNavigate(AppLockSettings) }
if (privilege.device || privilege.profile)
FunctionItem(title = R.string.api, icon = R.drawable.code_fill0) { onNavigate(ApiSettings) } FunctionItem(title = R.string.api, icon = R.drawable.code_fill0) { onNavigate(ApiSettings) }
if (privilege.device && !privilege.dhizuku)
FunctionItem(R.string.notifications, icon = R.drawable.notifications_fill0) { onNavigate(Notifications) } FunctionItem(R.string.notifications, icon = R.drawable.notifications_fill0) { onNavigate(Notifications) }
FunctionItem(title = R.string.export_logs, icon = R.drawable.description_fill0) { FunctionItem(title = R.string.export_logs, icon = R.drawable.description_fill0) {
val time = SimpleDateFormat("yyyyMMddHHmmss", Locale.getDefault()).format(Date(System.currentTimeMillis())) val time = SimpleDateFormat("yyyyMMddHHmmss", Locale.getDefault()).format(Date(System.currentTimeMillis()))

View File

@@ -0,0 +1,72 @@
package com.bintianqi.owndroid
import android.content.ComponentName
import android.content.Context
import android.content.ServiceConnection
import android.content.pm.PackageManager
import android.os.Bundle
import android.os.IBinder
import android.widget.Toast
import androidx.annotation.Keep
import rikka.shizuku.Shizuku
import rikka.sui.Sui
import kotlin.system.exitProcess
@Keep
class ShizukuService: IUserService.Stub() {
override fun execute(command: String): Bundle? {
try {
val bundle = Bundle()
val process = Runtime.getRuntime().exec(command)
val exitCode = process.waitFor()
bundle.putInt("code", exitCode)
bundle.putString("output", process.inputStream.readBytes().decodeToString())
bundle.putString("error", process.errorStream.readBytes().decodeToString())
return bundle
} catch(e: Exception) {
e.printStackTrace()
return null
}
}
override fun destroy() {
exitProcess(0)
}
}
fun getShizukuArgs(context: Context): Shizuku.UserServiceArgs {
return Shizuku.UserServiceArgs(ComponentName(context, ShizukuService::class.java))
.daemon(false)
.processNameSuffix("shizuku-service")
.debuggable(false)
.version(1)
}
fun useShizuku(context: Context, action: (IBinder?) -> Unit) {
val connection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
action(service)
Shizuku.unbindUserService(getShizukuArgs(context), this, true)
}
override fun onServiceDisconnected(name: ComponentName?) {}
}
try {
if (Shizuku.checkSelfPermission() == PackageManager.PERMISSION_GRANTED) {
Shizuku.bindUserService(getShizukuArgs(context), connection)
} else if(Shizuku.shouldShowRequestPermissionRationale()) {
Toast.makeText(context, R.string.permission_denied, Toast.LENGTH_SHORT).show()
} else {
Sui.init(context.packageName)
fun requestPermissionResultListener(requestCode: Int, grantResult: Int) {
if(grantResult != PackageManager.PERMISSION_GRANTED) {
Toast.makeText(context, R.string.permission_denied, Toast.LENGTH_SHORT).show()
}
Shizuku.removeRequestPermissionResultListener(::requestPermissionResultListener)
}
Shizuku.addRequestPermissionResultListener(::requestPermissionResultListener)
Shizuku.requestPermission(0)
}
} catch (e: Exception) {
e.printStackTrace()
}
}

View File

@@ -1062,7 +1062,7 @@ fun EnableSystemAppScreen(onNavigateUp: () -> Unit) {
) { ) {
Text(stringResource(R.string.enable)) Text(stringResource(R.string.enable))
} }
Notes(R.string.enable_system_app) Notes(R.string.info_enable_system_app)
} }
} }

View File

@@ -61,17 +61,8 @@ val Context.isProfileOwner: Boolean
return dpm.isProfileOwnerApp("com.bintianqi.owndroid") return dpm.isProfileOwnerApp("com.bintianqi.owndroid")
} }
val Context.dpcPackageName: String
get() {
return if(SharedPrefs(this).dhizuku) {
Dhizuku.getOwnerPackageName()
} else {
"com.bintianqi.owndroid"
}
}
@SuppressLint("PrivateApi") @SuppressLint("PrivateApi")
private fun binderWrapperDevicePolicyManager(appContext: Context): DevicePolicyManager? { fun binderWrapperDevicePolicyManager(appContext: Context): DevicePolicyManager? {
try { try {
val context = appContext.createPackageContext(Dhizuku.getOwnerComponent().packageName, Context.CONTEXT_IGNORE_SECURITY) val context = appContext.createPackageContext(Dhizuku.getOwnerComponent().packageName, Context.CONTEXT_IGNORE_SECURITY)
val manager = context.getSystemService(Context.DEVICE_POLICY_SERVICE) as DevicePolicyManager val manager = context.getSystemService(Context.DEVICE_POLICY_SERVICE) as DevicePolicyManager

View File

@@ -4,31 +4,67 @@ import android.app.admin.DevicePolicyManager
import android.content.ComponentName import android.content.ComponentName
import android.content.Context import android.content.Context
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.os.Binder
import android.os.Build.VERSION import android.os.Build.VERSION
import android.os.Bundle import android.os.PersistableBundle
import android.os.IBinder
import android.os.RemoteException
import android.widget.Toast import android.widget.Toast
import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.annotation.Keep import androidx.annotation.Keep
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.background
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
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.text.selection.SelectionContainer import androidx.compose.foundation.text.selection.SelectionContainer
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight
import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.Check
import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material.icons.filled.Settings
import androidx.compose.material.icons.outlined.Edit import androidx.compose.material.icons.outlined.Edit
import androidx.compose.material3.* import androidx.compose.material.icons.outlined.Warning
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Checkbox
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api
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.runtime.* import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateMapOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
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.draw.alpha
import androidx.compose.ui.graphics.Color
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.painterResource
@@ -37,30 +73,36 @@ 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.compose.ui.window.Dialog import androidx.compose.ui.window.Dialog
import androidx.core.os.bundleOf
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.bintianqi.owndroid.ChoosePackageContract import com.bintianqi.owndroid.ChoosePackageContract
import com.bintianqi.owndroid.HorizontalPadding import com.bintianqi.owndroid.HorizontalPadding
import com.bintianqi.owndroid.IUserService
import com.bintianqi.owndroid.R import com.bintianqi.owndroid.R
import com.bintianqi.owndroid.Receiver
import com.bintianqi.owndroid.Settings
import com.bintianqi.owndroid.SharedPrefs import com.bintianqi.owndroid.SharedPrefs
import com.bintianqi.owndroid.backToHomeStateFlow
import com.bintianqi.owndroid.myPrivilege import com.bintianqi.owndroid.myPrivilege
import com.bintianqi.owndroid.showOperationResultToast import com.bintianqi.owndroid.showOperationResultToast
import com.bintianqi.owndroid.ui.* import com.bintianqi.owndroid.ui.FunctionItem
import com.bintianqi.owndroid.ui.InfoItem
import com.bintianqi.owndroid.ui.MyScaffold
import com.bintianqi.owndroid.ui.MySmallTitleScaffold
import com.bintianqi.owndroid.ui.NavIcon
import com.bintianqi.owndroid.ui.Notes
import com.bintianqi.owndroid.updatePrivilege import com.bintianqi.owndroid.updatePrivilege
import com.bintianqi.owndroid.useShizuku
import com.bintianqi.owndroid.writeClipBoard import com.bintianqi.owndroid.writeClipBoard
import com.bintianqi.owndroid.yesOrNo import com.bintianqi.owndroid.yesOrNo
import com.rosan.dhizuku.api.Dhizuku import com.rosan.dhizuku.api.Dhizuku
import com.rosan.dhizuku.api.DhizukuRequestPermissionListener import com.rosan.dhizuku.api.DhizukuRequestPermissionListener
import com.topjohnwu.superuser.Shell
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import rikka.shizuku.Shizuku
import rikka.sui.Sui
@Serializable object Permissions @Serializable object Permissions
@Composable @Composable
fun PermissionsScreen(onNavigateUp: () -> Unit, onNavigate: (Any) -> Unit, onNavigateToShizuku: (Bundle) -> Unit) { fun PermissionsScreen(onNavigateUp: () -> Unit, onNavigate: (Any) -> Unit) {
val context = LocalContext.current val context = LocalContext.current
val dpm = context.getDPM() val dpm = context.getDPM()
val receiver = context.getReceiver() val receiver = context.getReceiver()
@@ -69,57 +111,6 @@ fun PermissionsScreen(onNavigateUp: () -> Unit, onNavigate: (Any) -> Unit, onNav
var bindingShizuku by remember { mutableStateOf(false) } var bindingShizuku by remember { mutableStateOf(false) }
val enrollmentSpecificId = if(VERSION.SDK_INT >= 31 && (privilege.device || privilege.profile)) dpm.enrollmentSpecificId else "" val enrollmentSpecificId = if(VERSION.SDK_INT >= 31 && (privilege.device || privilege.profile)) dpm.enrollmentSpecificId else ""
MyScaffold(R.string.permissions, onNavigateUp, 0.dp) { MyScaffold(R.string.permissions, onNavigateUp, 0.dp) {
if(!dpm.isDeviceOwnerApp(context.packageName)) {
SwitchItem(
R.string.dhizuku,
getState = { SharedPrefs(context).dhizuku },
onCheckedChange = { toggleDhizukuMode(it, context) },
onClickBlank = { dialog = 4 }
)
}
if(privilege.profile || !privilege.primary) {
FunctionItem(
R.string.profile_owner, stringResource(if(privilege.profile) R.string.activated else R.string.deactivated),
operation = { onNavigate(ProfileOwner) }
)
}
if(!privilege.profile && privilege.primary) {
FunctionItem(
R.string.device_owner, stringResource(if(privilege.device) R.string.activated else R.string.deactivated),
operation = { onNavigate(DeviceOwner) }
)
}
FunctionItem(R.string.shizuku) {
try {
if(Shizuku.checkSelfPermission() == PackageManager.PERMISSION_GRANTED) {
bindingShizuku = true
fun onBind(binder: IBinder) {
bindingShizuku = false
onNavigateToShizuku(bundleOf("binder" to binder))
}
try {
controlShizukuService(context, ::onBind, { bindingShizuku = false }, true)
} catch(e: Exception) {
e.printStackTrace()
bindingShizuku = false
}
} else if(Shizuku.shouldShowRequestPermissionRationale()) {
Toast.makeText(context, R.string.permission_denied, Toast.LENGTH_SHORT).show()
} else {
Sui.init(context.packageName)
fun requestPermissionResultListener(requestCode: Int, grantResult: Int) {
if(grantResult != PackageManager.PERMISSION_GRANTED) {
Toast.makeText(context, R.string.permission_denied, Toast.LENGTH_SHORT).show()
}
Shizuku.removeRequestPermissionResultListener(::requestPermissionResultListener)
}
Shizuku.addRequestPermissionResultListener(::requestPermissionResultListener)
Shizuku.requestPermission(0)
}
} catch(_: IllegalStateException) {
Toast.makeText(context, R.string.shizuku_not_started, Toast.LENGTH_SHORT).show()
}
}
if(VERSION.SDK_INT >= 26) FunctionItem(R.string.delegated_admins) { onNavigate(DelegatedAdmins) } if(VERSION.SDK_INT >= 26) FunctionItem(R.string.delegated_admins) { onNavigate(DelegatedAdmins) }
FunctionItem(R.string.device_info, icon = R.drawable.perm_device_information_fill0) { onNavigate(DeviceInfo) } FunctionItem(R.string.device_info, icon = R.drawable.perm_device_information_fill0) { onNavigate(DeviceInfo) }
if(VERSION.SDK_INT >= 24 && (privilege.profile || (VERSION.SDK_INT >= 26 && privilege.device))) { if(VERSION.SDK_INT >= 24 && (privilege.profile || (VERSION.SDK_INT >= 26 && privilege.device))) {
@@ -137,9 +128,6 @@ fun PermissionsScreen(onNavigateUp: () -> Unit, onNavigate: (Any) -> Unit, onNav
if(VERSION.SDK_INT >= 24) { if(VERSION.SDK_INT >= 24) {
FunctionItem(R.string.support_messages, icon = R.drawable.chat_fill0) { onNavigate(SupportMessage) } FunctionItem(R.string.support_messages, icon = R.drawable.chat_fill0) { onNavigate(SupportMessage) }
} }
if(VERSION.SDK_INT >= 28) {
FunctionItem(R.string.transfer_ownership, icon = R.drawable.admin_panel_settings_fill0) { onNavigate(TransferOwnership) }
}
} }
if(bindingShizuku) { if(bindingShizuku) {
Dialog(onDismissRequest = { bindingShizuku = false }) { Dialog(onDismissRequest = { bindingShizuku = false }) {
@@ -225,39 +213,329 @@ fun PermissionsScreen(onNavigateUp: () -> Unit, onNavigate: (Any) -> Unit, onNav
} }
} }
private fun toggleDhizukuMode(status: Boolean, context: Context) { @Serializable data class WorkModes(val canNavigateUp: Boolean)
val sp = SharedPrefs(context)
if(!status) { @OptIn(ExperimentalMaterial3Api::class)
sp.dhizuku = false @Composable
backToHomeStateFlow.value = true fun WorkModesScreen(
return params: WorkModes, onNavigateUp: () -> Unit, onActivate: () -> Unit, onDeactivate: () -> Unit,
onNavigate: (Any) -> Unit
) {
val context = LocalContext.current
val coroutine = rememberCoroutineScope()
val privilege by myPrivilege.collectAsStateWithLifecycle()
/** 0: none, 1: device owner, 2: circular progress indicator, 3: result, 4: deactivate, 5: command */
var dialog by remember { mutableIntStateOf(0) }
Scaffold(
topBar = {
TopAppBar(
{
if(!params.canNavigateUp) {
Column {
Text(stringResource(R.string.app_name))
Text(stringResource(R.string.choose_work_mode), Modifier.alpha(0.8F), style = typography.bodyLarge)
} }
if(!Dhizuku.init(context)) {
dhizukuErrorStatus.value = 1
return
} }
if(dhizukuPermissionGranted()) { },
sp.dhizuku = true navigationIcon = {
Dhizuku.init(context) if(params.canNavigateUp) NavIcon(onNavigateUp)
},
actions = {
var expanded by remember { mutableStateOf(false) }
if(privilege.device || privilege.profile) Box {
IconButton({ expanded = true }) {
Icon(Icons.Default.MoreVert, null)
}
DropdownMenu(expanded, { expanded = false }) {
DropdownMenuItem({ Text(stringResource(R.string.deactivate)) }, { dialog = 4 })
if(!privilege.dhizuku && VERSION.SDK_INT >= 28) DropdownMenuItem(
{ Text(stringResource(R.string.transfer_ownership)) },
{
expanded = false
onNavigate(TransferOwnership)
}
)
}
}
if(!params.canNavigateUp) IconButton({ onNavigate(Settings) }) {
Icon(Icons.Default.Settings, null)
}
}
)
}
) { paddingValues ->
var operationSucceed by remember { mutableStateOf(false) }
var resultText by remember { mutableStateOf("") }
fun handleResult(succeeded: Boolean, activateSucceeded: Boolean, output: String?) {
if(succeeded) {
operationSucceed = activateSucceeded
resultText = output ?: ""
dialog = 3
updatePrivilege(context) updatePrivilege(context)
backToHomeStateFlow.value = true handlePrivilegeChange(context)
} else { } else {
Dhizuku.requestPermission(object: DhizukuRequestPermissionListener() { context.showOperationResultToast(false)
@Throws(RemoteException::class) }
}
Column(Modifier.fillMaxSize().padding(paddingValues)) {
if(!privilege.profile && (VERSION.SDK_INT >= 28 || !privilege.dhizuku)) Row(
Modifier
.fillMaxWidth().clickable(!privilege.device || privilege.dhizuku) { dialog = 1 }
.background(if(privilege.device) colorScheme.primaryContainer else Color.Transparent)
.padding(HorizontalPadding, 10.dp),
Arrangement.SpaceBetween, Alignment.CenterVertically
) {
Column {
Text(stringResource(R.string.device_owner), style = typography.titleLarge)
if(!privilege.device || privilege.dhizuku) Text(
stringResource(R.string.recommended), color = colorScheme.primary, style = typography.labelLarge
)
}
Icon(
if(privilege.device) Icons.Default.Check else Icons.AutoMirrored.Default.KeyboardArrowRight, null,
tint = if(privilege.device) colorScheme.primary else colorScheme.onBackground
)
}
if(privilege.profile) Row(
Modifier
.fillMaxWidth()
.background(if(privilege.device) colorScheme.primaryContainer else Color.Transparent)
.padding(HorizontalPadding, 10.dp),
Arrangement.SpaceBetween, Alignment.CenterVertically
) {
Column {
Text(stringResource(R.string.profile_owner), style = typography.titleLarge)
}
Icon(
if(privilege.device) Icons.Default.Check else Icons.AutoMirrored.Default.KeyboardArrowRight, null,
tint = if(privilege.device) colorScheme.primary else colorScheme.onBackground
)
}
if(privilege.dhizuku || !(privilege.device || privilege.profile)) Row(
Modifier
.fillMaxWidth()
.clickable(!privilege.dhizuku) {
dialog = 2
activateDhizukuMode(context, ::handleResult)
}
.background(if(privilege.dhizuku) colorScheme.primaryContainer else Color.Transparent)
.padding(HorizontalPadding, 10.dp),
Arrangement.SpaceBetween, Alignment.CenterVertically
) {
Text(stringResource(R.string.dhizuku), style = typography.titleLarge)
Icon(
if(privilege.dhizuku) Icons.Default.Check else Icons.AutoMirrored.Default.KeyboardArrowRight, null,
tint = if(privilege.dhizuku) colorScheme.primary else colorScheme.onBackground
)
}
if(
privilege.work || (VERSION.SDK_INT < 24 ||
context.getDPM().isProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE))
) Row(
Modifier
.fillMaxWidth().clickable(!privilege.work) { onNavigate(CreateWorkProfile) }
.background(if(privilege.device) colorScheme.primaryContainer else Color.Transparent)
.padding(HorizontalPadding, 10.dp),
Arrangement.SpaceBetween, Alignment.CenterVertically
) {
Column {
Text(stringResource(R.string.work_profile), style = typography.titleLarge)
}
Icon(
if(privilege.work) Icons.Default.Check else Icons.AutoMirrored.Default.KeyboardArrowRight, null,
tint = if(privilege.device) colorScheme.primary else colorScheme.onBackground
)
}
Column(Modifier.padding(HorizontalPadding, 20.dp)) {
Row(Modifier.padding(bottom = 4.dp), verticalAlignment = Alignment.CenterVertically) {
Icon(Icons.Outlined.Warning, null, Modifier.padding(end = 4.dp), colorScheme.error)
Text(stringResource(R.string.warning), color = colorScheme.error, style = typography.labelLarge)
}
Text(stringResource(R.string.owndroid_warning))
}
}
if(dialog == 1) AlertDialog(
title = { Text(stringResource(R.string.activate_method)) },
text = {
Column(Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) {
if(!privilege.dhizuku) Button({
dialog = 2
coroutine.launch {
activateUsingShizuku(context, ::handleResult)
}
}) {
Text(stringResource(R.string.shizuku))
}
if(!privilege.dhizuku) Button({
dialog = 2
activateUsingRoot(context, ::handleResult)
}) {
Text("Root")
}
if(VERSION.SDK_INT >= 28) Button({
dialog = 2
activateUsingDhizuku(context, ::handleResult)
}) {
Text(stringResource(R.string.dhizuku))
}
Button({ dialog = 5 }) { Text(stringResource(R.string.adb_command)) }
}
},
confirmButton = {
TextButton({ dialog = 0 }) { Text(stringResource(R.string.cancel)) }
},
onDismissRequest = { dialog = 0 }
)
if(dialog == 2) Dialog({}) {
CircularProgressIndicator()
}
if(dialog == 3) AlertDialog(
title = { Text(stringResource(if(operationSucceed) R.string.succeeded else R.string.failed)) },
text = {
Column(Modifier.fillMaxWidth().verticalScroll(rememberScrollState())) {
Text(resultText)
}
},
confirmButton = {
TextButton({
dialog = 0
if(operationSucceed && !params.canNavigateUp) onActivate()
}) {
Text(stringResource(R.string.confirm))
}
},
onDismissRequest = {}
)
if(dialog == 4) AlertDialog(
title = { Text(stringResource(R.string.deactivate)) },
text = { Text(stringResource(R.string.info_deactivate)) },
confirmButton = {
TextButton(
{
if(privilege.dhizuku) {
SharedPrefs(context).dhizuku = false
} else {
val dpm = context.getDPM()
if(privilege.device) {
dpm.clearDeviceOwnerApp(context.packageName)
} else if(VERSION.SDK_INT >= 24) {
dpm.clearProfileOwner(ComponentName(context, Receiver::class.java))
}
}
updatePrivilege(context)
handlePrivilegeChange(context)
onDeactivate()
},
colors = ButtonDefaults.textButtonColors(contentColor = colorScheme.error)
) { Text(stringResource(R.string.confirm)) }
},
dismissButton = {
TextButton({ dialog = 0 }) { Text(stringResource(R.string.cancel)) }
},
onDismissRequest = { dialog = 0 }
)
if(dialog == 5) AlertDialog(
text = {
SelectionContainer {
Text(ACTIVATE_DEVICE_OWNER_COMMAND)
}
},
confirmButton = {
TextButton({ dialog = 0 }) { Text(stringResource(R.string.confirm)) }
},
onDismissRequest = { dialog = 0 }
)
}
}
fun activateUsingShizuku(context: Context, callback: (Boolean, Boolean, String?) -> Unit) {
useShizuku(context) { service ->
try {
val result = IUserService.Stub.asInterface(service).execute(ACTIVATE_DEVICE_OWNER_COMMAND)
if (result == null) {
callback(false, false, null)
} else {
callback(
true, result.getInt("code", -1) == 0,
result.getString("output") + "\n" + result.getString("error")
)
}
} catch (e: Exception) {
callback(false, false, null)
e.printStackTrace()
}
}
}
fun activateUsingRoot(context: Context, callback: (Boolean, Boolean, String?) -> Unit) {
Shell.getShell { shell ->
if(shell.isRoot) {
val result = Shell.cmd(ACTIVATE_DEVICE_OWNER_COMMAND).exec()
val output = result.out.joinToString("\n") + "\n" + result.err.joinToString("\n")
callback(true, result.isSuccess, output)
} else {
callback(true, false, context.getString(R.string.permission_denied))
}
}
}
@RequiresApi(28)
fun activateUsingDhizuku(context: Context, callback: (Boolean, Boolean, String?) -> Unit) {
fun doTransfer() {
try {
val dpm = binderWrapperDevicePolicyManager(context)
if(dpm == null) {
context.showOperationResultToast(false)
} else {
dpm.transferOwnership(
Dhizuku.getOwnerComponent(),
ComponentName(context, Receiver::class.java), PersistableBundle()
)
callback(true, true, null)
}
} catch (e: Exception) {
e.printStackTrace()
callback(true, false, null)
}
}
if(Dhizuku.init()) {
if(Dhizuku.isPermissionGranted()) {
doTransfer()
} else {
Dhizuku.requestPermission(object : DhizukuRequestPermissionListener() {
override fun onRequestPermission(grantResult: Int) { override fun onRequestPermission(grantResult: Int) {
if(grantResult == PackageManager.PERMISSION_GRANTED) { if(grantResult == PackageManager.PERMISSION_GRANTED) doTransfer()
sp.dhizuku = true else callback(false, false, null)
Dhizuku.init(context)
updatePrivilege(context)
backToHomeStateFlow.value = true
} else {
dhizukuErrorStatus.value = 2
}
} }
}) })
} }
} else {
callback(true, false, context.getString(R.string.failed_to_init_dhizuku))
}
} }
fun activateDhizukuMode(context: Context, callback: (Boolean, Boolean, String?) -> Unit) {
fun onSucceed() {
SharedPrefs(context).dhizuku = true
callback(true, true, null)
}
if(Dhizuku.init()) {
if(Dhizuku.isPermissionGranted()) {
onSucceed()
} else {
Dhizuku.requestPermission(object : DhizukuRequestPermissionListener() {
override fun onRequestPermission(grantResult: Int) {
if(grantResult == PackageManager.PERMISSION_GRANTED) onSucceed()
}
})
}
} else {
callback(true, false, context.getString(R.string.failed_to_init_dhizuku))
}
}
const val ACTIVATE_DEVICE_OWNER_COMMAND = "dpm set-device-owner com.bintianqi.owndroid/com.bintianqi.owndroid.Receiver"
@Serializable object LockScreenInfo @Serializable object LockScreenInfo
@RequiresApi(24) @RequiresApi(24)
@@ -302,135 +580,6 @@ fun LockScreenInfoScreen(onNavigateUp: () -> Unit) {
} }
} }
@Serializable object ProfileOwner
@Composable
fun ProfileOwnerScreen(onNavigateUp: () -> Unit) {
val context = LocalContext.current
val dpm = context.getDPM()
val receiver = context.getReceiver()
var deactivateDialog by remember { mutableStateOf(false) }
val privilege by myPrivilege.collectAsStateWithLifecycle()
val profileOwner = privilege.profile
MyScaffold(R.string.profile_owner, onNavigateUp) {
Text(stringResource(if(profileOwner) R.string.activated else R.string.deactivated), style = typography.titleLarge)
Spacer(Modifier.padding(vertical = 5.dp))
if(VERSION.SDK_INT >= 24 && profileOwner) {
Button(
onClick = { deactivateDialog = true },
enabled = !dpm.isManagedProfile(receiver),
colors = ButtonDefaults.buttonColors(containerColor = colorScheme.error, contentColor = colorScheme.onError)
) {
Text(stringResource(R.string.deactivate))
}
}
if(!profileOwner) {
val command = context.getString(R.string.activate_profile_owner_command, (Binder.getCallingUid() / 100000).toString())
SelectionContainer {
Text(command)
}
CopyTextButton(R.string.copy_command, command)
}
}
if(deactivateDialog && VERSION.SDK_INT >= 24) {
AlertDialog(
title = { Text(stringResource(R.string.deactivate)) },
onDismissRequest = { deactivateDialog = false },
dismissButton = {
TextButton(
onClick = { deactivateDialog = false }
) {
Text(stringResource(R.string.cancel))
}
},
confirmButton = {
TextButton(
onClick = {
dpm.clearProfileOwner(receiver)
deactivateDialog = false
},
colors = ButtonDefaults.textButtonColors(contentColor = colorScheme.error)
) {
Text(stringResource(R.string.confirm))
}
}
)
}
}
@Serializable object DeviceOwner
@Composable
fun DeviceOwnerScreen(onNavigateUp: () -> Unit) {
val context = LocalContext.current
val dpm = context.getDPM()
var deactivateDialog by remember { mutableStateOf(false) }
val privilege by myPrivilege.collectAsStateWithLifecycle()
val deviceOwner = privilege.device
MyScaffold(R.string.device_owner, onNavigateUp) {
Text(text = stringResource(if(deviceOwner) R.string.activated else R.string.deactivated), style = typography.titleLarge)
Spacer(Modifier.padding(vertical = 5.dp))
AnimatedVisibility(deviceOwner) {
Button(
onClick = { deactivateDialog = true },
colors = ButtonDefaults.buttonColors(containerColor = colorScheme.error, contentColor = colorScheme.onError)
) {
Text(text = stringResource(R.string.deactivate))
}
}
AnimatedVisibility(!deviceOwner) {
Column {
SelectionContainer{
Text(text = stringResource(R.string.activate_device_owner_command))
}
CopyTextButton(R.string.copy_command, stringResource(R.string.activate_device_owner_command))
}
}
}
if(deactivateDialog) {
val sp = SharedPrefs(context)
var resetPolicy by remember { mutableStateOf(false) }
val coroutine = rememberCoroutineScope()
AlertDialog(
title = { Text(stringResource(R.string.deactivate)) },
text = {
Column {
if(sp.dhizuku) Text(stringResource(R.string.dhizuku_will_be_deactivated))
Spacer(Modifier.padding(vertical = 4.dp))
CheckBoxItem(text = R.string.reset_device_policy, checked = resetPolicy, operation = { resetPolicy = it })
}
},
onDismissRequest = { deactivateDialog = false },
dismissButton = {
TextButton(
onClick = { deactivateDialog = false }
) {
Text(stringResource(R.string.cancel))
}
},
confirmButton = {
TextButton(
onClick = {
coroutine.launch {
if(resetPolicy) context.resetDevicePolicy()
dpm.clearDeviceOwnerApp(context.dpcPackageName)
if(sp.dhizuku) {
if (!Dhizuku.init(context)) {
sp.dhizuku = false
backToHomeStateFlow.value = true
}
}
deactivateDialog = false
}
}
) {
Text(stringResource(R.string.confirm))
}
}
)
}
}
@Keep @Keep
@Suppress("InlinedApi") @Suppress("InlinedApi")
enum class DelegatedScope(val id: String, @StringRes val string: Int, val requiresApi: Int = 0) { enum class DelegatedScope(val id: String, @StringRes val string: Int, val requiresApi: Int = 0) {
@@ -698,7 +847,7 @@ fun SupportMessageScreen(onNavigateUp: () -> Unit) {
@RequiresApi(28) @RequiresApi(28)
@Composable @Composable
fun TransferOwnershipScreen(onNavigateUp: () -> Unit) { fun TransferOwnershipScreen(onNavigateUp: () -> Unit, onTransferred: () -> Unit) {
val context = LocalContext.current val context = LocalContext.current
val privilege by myPrivilege.collectAsStateWithLifecycle() val privilege by myPrivilege.collectAsStateWithLifecycle()
val focusMgr = LocalFocusManager.current val focusMgr = LocalFocusManager.current
@@ -742,7 +891,7 @@ fun TransferOwnershipScreen(onNavigateUp: () -> Unit) {
context.showOperationResultToast(true) context.showOperationResultToast(true)
updatePrivilege(context) updatePrivilege(context)
dialog = false dialog = false
onNavigateUp() onTransferred()
} catch(e: Exception) { } catch(e: Exception) {
e.printStackTrace() e.printStackTrace()
context.showOperationResultToast(false) context.showOperationResultToast(false)

View File

@@ -1,200 +0,0 @@
package com.bintianqi.owndroid.dpm
import android.content.ComponentName
import android.content.Context
import android.content.ServiceConnection
import android.os.Binder
import android.os.Build.VERSION
import android.os.Bundle
import android.os.IBinder
import android.util.Log
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.background
import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.selection.SelectionContainer
import androidx.compose.material3.Button
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.bintianqi.owndroid.IUserService
import com.bintianqi.owndroid.R
import com.bintianqi.owndroid.myPrivilege
import com.bintianqi.owndroid.ui.MySmallTitleScaffold
import com.bintianqi.owndroid.updatePrivilege
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.serialization.Serializable
import rikka.shizuku.Shizuku
@Serializable object ShizukuScreen
@Composable
fun ShizukuScreen(navArgs: Bundle, onNavigateUp: () -> Unit, onNavigateToAccountsViewer: (Accounts) -> Unit) {
val context = LocalContext.current
val privilege by myPrivilege.collectAsStateWithLifecycle()
val coroutine = rememberCoroutineScope()
val outputTextScrollState = rememberScrollState()
var outputText by rememberSaveable { mutableStateOf("") }
val binder = navArgs.getBinder("binder")!!
var service by remember { mutableStateOf<IUserService?>(null) }
LaunchedEffect(Unit) {
service = if(binder.pingBinder()) {
IUserService.Stub.asInterface(binder)
} else {
null
}
}
MySmallTitleScaffold(R.string.shizuku, onNavigateUp, 0.dp) {
Button(
onClick = {
coroutine.launch{
outputText = service!!.execute("dpm list-owners")
outputTextScrollState.animateScrollTo(0)
}
},
modifier = Modifier.align(Alignment.CenterHorizontally)
) {
Text(text = stringResource(R.string.list_owners))
}
Button(
onClick = {
coroutine.launch{
outputText = service!!.execute("pm list users")
outputTextScrollState.animateScrollTo(0)
}
},
modifier = Modifier.align(Alignment.CenterHorizontally)
) {
Text(text = stringResource(R.string.list_users))
}
Button(
onClick = {
Log.d("Shizuku", "List accounts")
try {
val accounts = service!!.listAccounts().map {
Accounts.Account(it.type, it.name)
}
onNavigateToAccountsViewer(Accounts(accounts))
} catch(_: Exception) {
outputText = service!!.execute("dumpsys account")
coroutine.launch{
outputTextScrollState.animateScrollTo(0)
}
}
},
modifier = Modifier.align(Alignment.CenterHorizontally)
) {
Text(text = stringResource(R.string.list_accounts))
}
Spacer(Modifier.padding(vertical = 5.dp))
AnimatedVisibility(!privilege.device, modifier = Modifier.align(Alignment.CenterHorizontally)) {
Button(
onClick = {
coroutine.launch{
outputText = service!!.execute(context.getString(R.string.dpm_activate_do_command))
outputTextScrollState.animateScrollTo(0)
delay(500)
updatePrivilege(context)
}
}
) {
Text(text = stringResource(R.string.activate_device_owner))
}
}
AnimatedVisibility(VERSION.SDK_INT >= 30 && privilege.work && !privilege.org) {
Button(
onClick = {
coroutine.launch{
val userID = Binder.getCallingUid() / 100000
outputText = service!!.execute(
"dpm mark-profile-owner-on-organization-owned-device --user $userID com.bintianqi.owndroid/com.bintianqi.owndroid.Receiver"
)
outputTextScrollState.animateScrollTo(0)
delay(500)
updatePrivilege(context)
}
},
modifier = Modifier.align(Alignment.CenterHorizontally)
) {
Text(text = stringResource(R.string.activate_org_profile))
}
}
SelectionContainer(modifier = Modifier.fillMaxWidth().horizontalScroll(outputTextScrollState)) {
Text(text = outputText, softWrap = false, modifier = Modifier.padding(vertical = 9.dp, horizontal = 12.dp))
}
}
}
fun controlShizukuService(
context: Context,
onServiceConnected: (IBinder) -> Unit,
onServiceDisconnected: () -> Unit,
state: Boolean
) {
val userServiceConnection = object : ServiceConnection {
override fun onServiceConnected(componentName: ComponentName, binder: IBinder) {
onServiceConnected(binder)
}
override fun onServiceDisconnected(componentName: ComponentName) {
onServiceDisconnected()
}
}
val userServiceArgs = Shizuku.UserServiceArgs(ComponentName(context, ShizukuService::class.java))
.daemon(false)
.processNameSuffix("shizuku-service")
.debuggable(false)
.version(26)
if(state) Shizuku.bindUserService(userServiceArgs, userServiceConnection)
else Shizuku.unbindUserService(userServiceArgs, userServiceConnection, true)
}
@Serializable
data class Accounts(
val list: List<Account>
) {
@Serializable data class Account(val type: String, val name: String)
}
@Composable
fun AccountsScreen(accounts: Accounts, onNavigateUp: () -> Unit) {
MySmallTitleScaffold(R.string.accounts, onNavigateUp) {
accounts.list.forEach {
Column(
modifier = Modifier
.fillMaxWidth().padding(vertical = 4.dp)
.clip(RoundedCornerShape(15)).background(MaterialTheme.colorScheme.surfaceVariant)
) {
SelectionContainer {
Column(modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp)) {
Text(stringResource(R.string.type) + ": " + it.type)
Text(stringResource(R.string.name) + ": " + it.name)
}
}
}
}
}
}

View File

@@ -1,50 +0,0 @@
package com.bintianqi.owndroid.dpm
import android.accounts.Account
import android.accounts.AccountManager
import android.annotation.SuppressLint
import android.content.Context
import android.system.Os
import androidx.annotation.Keep
import com.bintianqi.owndroid.IUserService
import com.bintianqi.owndroid.getContext
import kotlin.system.exitProcess
@Keep
class ShizukuService: IUserService.Stub() {
override fun execute(command: String): String {
var result = ""
val process: Process
try {
process = Runtime.getRuntime().exec(command)
val exitCode = process.waitFor()
if(exitCode != 0) { result += "Error: $exitCode" }
} catch(e: Exception) {
e.printStackTrace()
return e.toString()
}
try {
val outputReader = process.inputStream.bufferedReader()
var outputLine: String
while(outputReader.readLine().also {outputLine = it} != null) { result += "$outputLine\n" }
val errorReader = process.errorStream.bufferedReader()
var errorLine: String
while(errorReader.readLine().also {errorLine = it} != null) { result += "$errorLine\n" }
} catch(e: NullPointerException) {
e.printStackTrace()
}
return result
}
override fun getUid(): Int = Os.getuid()
@SuppressLint("MissingPermission")
override fun listAccounts(): Array<Account> {
val am = getContext().getSystemService(Context.ACCOUNT_SERVICE) as AccountManager
return am.accounts
}
override fun destroy() {
exitProcess(0)
}
}

View File

@@ -14,7 +14,9 @@ import android.app.admin.DevicePolicyManager.PERSONAL_APPS_NOT_SUSPENDED
import android.app.admin.DevicePolicyManager.PERSONAL_APPS_SUSPENDED_PROFILE_TIMEOUT import android.app.admin.DevicePolicyManager.PERSONAL_APPS_SUSPENDED_PROFILE_TIMEOUT
import android.app.admin.DevicePolicyManager.WIPE_EUICC import android.app.admin.DevicePolicyManager.WIPE_EUICC
import android.app.admin.DevicePolicyManager.WIPE_EXTERNAL_STORAGE import android.app.admin.DevicePolicyManager.WIPE_EXTERNAL_STORAGE
import android.content.* import android.content.ActivityNotFoundException
import android.content.Intent
import android.content.IntentFilter
import android.os.Binder import android.os.Binder
import android.os.Build.VERSION import android.os.Build.VERSION
import android.widget.Toast import android.widget.Toast
@@ -54,17 +56,16 @@ 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.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.bintianqi.owndroid.IUserService
import com.bintianqi.owndroid.R import com.bintianqi.owndroid.R
import com.bintianqi.owndroid.myPrivilege import com.bintianqi.owndroid.myPrivilege
import com.bintianqi.owndroid.showOperationResultToast import com.bintianqi.owndroid.showOperationResultToast
import com.bintianqi.owndroid.ui.CheckBoxItem import com.bintianqi.owndroid.ui.CheckBoxItem
import com.bintianqi.owndroid.ui.CopyTextButton
import com.bintianqi.owndroid.ui.FunctionItem import com.bintianqi.owndroid.ui.FunctionItem
import com.bintianqi.owndroid.ui.InfoItem
import com.bintianqi.owndroid.ui.MyScaffold import com.bintianqi.owndroid.ui.MyScaffold
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 com.bintianqi.owndroid.yesOrNo import com.bintianqi.owndroid.useShizuku
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
@Serializable object WorkProfile @Serializable object WorkProfile
@@ -73,7 +74,7 @@ import kotlinx.serialization.Serializable
fun WorkProfileScreen(onNavigateUp: () -> Unit, onNavigate: (Any) -> Unit) { fun WorkProfileScreen(onNavigateUp: () -> Unit, onNavigate: (Any) -> Unit) {
val privilege by myPrivilege.collectAsStateWithLifecycle() val privilege by myPrivilege.collectAsStateWithLifecycle()
MyScaffold(R.string.work_profile, onNavigateUp, 0.dp) { MyScaffold(R.string.work_profile, onNavigateUp, 0.dp) {
if(VERSION.SDK_INT >= 30) { if(VERSION.SDK_INT >= 30 && !privilege.org) {
FunctionItem(R.string.org_owned_work_profile, icon = R.drawable.corporate_fare_fill0) { onNavigate(OrganizationOwnedProfile) } FunctionItem(R.string.org_owned_work_profile, icon = R.drawable.corporate_fare_fill0) { onNavigate(OrganizationOwnedProfile) }
} }
if(privilege.org) { if(privilege.org) {
@@ -162,22 +163,38 @@ fun CreateWorkProfileScreen(onNavigateUp: () -> Unit) {
@Composable @Composable
fun OrganizationOwnedProfileScreen(onNavigateUp: () -> Unit) { fun OrganizationOwnedProfileScreen(onNavigateUp: () -> Unit) {
val context = LocalContext.current val context = LocalContext.current
val dpm = context.getDPM() var dialog by remember { mutableStateOf(false) }
MyScaffold(R.string.org_owned_work_profile, onNavigateUp) { MyScaffold(R.string.org_owned_work_profile, onNavigateUp) {
InfoItem(R.string.org_owned_work_profile, dpm.isOrganizationOwnedDeviceWithManagedProfile.yesOrNo) Button({
Spacer(Modifier.padding(vertical = 5.dp)) useShizuku(context) { service ->
if(!dpm.isOrganizationOwnedDeviceWithManagedProfile) { val result = IUserService.Stub.asInterface(service).execute(activateOrgProfileCommand)
if (result?.getInt("code", -1) == 0) {
context.showOperationResultToast(true)
} else {
context.showOperationResultToast(false)
}
}
}) {
Text(stringResource(R.string.shizuku))
}
Button({ dialog = true }) { Text(stringResource(R.string.adb_command)) }
if(dialog) AlertDialog(
text = {
SelectionContainer { SelectionContainer {
Text( Text(activateOrgProfileCommand)
text = stringResource(R.string.activate_org_profile_command, Binder.getCallingUid()/100000), }
color = colorScheme.onTertiaryContainer },
confirmButton = {
TextButton({ dialog = false }) { Text(stringResource(R.string.confirm)) }
},
onDismissRequest = { dialog = false }
) )
} }
CopyTextButton(R.string.copy_command, stringResource(R.string.activate_org_profile_command, Binder.getCallingUid()/100000))
}
}
} }
val activateOrgProfileCommand = "dpm mark-profile-owner-on-organization-owned-device --user " +
"${Binder.getCallingUid()/100000} com.bintianqi.owndroid/com.bintianqi.owndroid.Receiver"
@Serializable object SuspendPersonalApp @Serializable object SuspendPersonalApp
@RequiresApi(30) @RequiresApi(30)

View File

@@ -211,31 +211,6 @@ fun SwitchItem(
} }
} }
@Composable
fun CopyTextButton(@StringRes label: Int, content: String) {
val context = LocalContext.current
var ok by remember{ mutableStateOf(false) }
val scope = rememberCoroutineScope()
Button(
onClick = {
if(!ok) {
scope.launch {
if(writeClipBoard(context,content)) { ok = true; delay(2000); ok = false }
else{ Toast.makeText(context, R.string.failed, Toast.LENGTH_SHORT).show() }
}
}
}
) {
Row(
verticalAlignment = Alignment.CenterVertically, modifier = Modifier.animateContentSize()
) {
Icon(painter = painterResource(if(ok) R.drawable.check_fill0 else R.drawable.content_copy_fill0), contentDescription = null)
Spacer(modifier = Modifier.padding(horizontal = 2.dp))
Text(text = stringResource(if(ok) R.string.success else label))
}
}
}
@Composable @Composable
fun InfoItem(title: Int, text: Int, withInfo: Boolean = false, onClick: () -> Unit = {}) = fun InfoItem(title: Int, text: Int, withInfo: Boolean = false, onClick: () -> Unit = {}) =
InfoItem(title, stringResource(text), withInfo, onClick) InfoItem(title, stringResource(text), withInfo, onClick)

View File

@@ -7,6 +7,7 @@
<string name="success">Успешно</string> <string name="success">Успешно</string>
<string name="failure">Ошибка</string> <string name="failure">Ошибка</string>
<string name="failed">Ошибка</string> <string name="failed">Ошибка</string>
<string name="succeeded">Succeeded</string> <!--TODO-->
<string name="add">Добавить</string> <string name="add">Добавить</string>
<string name="remove">Удалить</string> <string name="remove">Удалить</string>
<string name="install">Установить</string> <string name="install">Установить</string>
@@ -40,7 +41,6 @@
<string name="decide_by_user">Определять пользователем</string> <string name="decide_by_user">Определять пользователем</string>
<string name="unsupported">Не поддерживается</string> <string name="unsupported">Не поддерживается</string>
<string name="options">Опции</string> <string name="options">Опции</string>
<string name="copy_command">Копировать команду</string>
<string name="package_name">Имя пакета</string> <string name="package_name">Имя пакета</string>
<string name="not_exist">Не существует</string> <string name="not_exist">Не существует</string>
<string name="copy">Копировать</string> <string name="copy">Копировать</string>
@@ -85,7 +85,6 @@
<string name="change_package_state">Изменить состояние пакета</string> <string name="change_package_state">Изменить состояние пакета</string>
<string name="grant_permissions">Предоставить разрешения</string> <string name="grant_permissions">Предоставить разрешения</string>
<string name="add_delegated_admin">Добавить делегированного админа</string> <string name="add_delegated_admin">Добавить делегированного админа</string>
<string name="dhizuku_will_be_deactivated">Dhizuku будет деактивирован</string>
<string name="reset_device_policy">Сбросить политику устройства</string> <string name="reset_device_policy">Сбросить политику устройства</string>
<string name="device_info">Информация об устройстве</string> <string name="device_info">Информация об устройстве</string>
<string name="support_device_id_attestation">Поддержка аттестации идентификатора устройства</string> <string name="support_device_id_attestation">Поддержка аттестации идентификатора устройства</string>
@@ -111,7 +110,6 @@
<string name="long_support_msg">Подробное сообщение</string> <string name="long_support_msg">Подробное сообщение</string>
<string name="transfer">Передать</string> <string name="transfer">Передать</string>
<string name="transfer_ownership_warning">%1$s привилегия будет передана %2$s</string> <string name="transfer_ownership_warning">%1$s привилегия будет передана %2$s</string>
<string name="activate_device_admin_here">Активируйте администратора устройства здесь.</string>
<!--Приемник--> <!--Приемник-->
<string name="create_work_profile_success">Рабочий профиль успешно создан</string> <string name="create_work_profile_success">Рабочий профиль успешно создан</string>
@@ -120,14 +118,6 @@
<string name="failed_to_init_dhizuku">Не удалось инициализировать Dhizuku</string> <string name="failed_to_init_dhizuku">Не удалось инициализировать Dhizuku</string>
<string name="dhizuku_permission_not_granted">Разрешение Dhizuku не предоставлено</string> <string name="dhizuku_permission_not_granted">Разрешение Dhizuku не предоставлено</string>
<string name="dhizuku_mode_disabled">Режим Dhizuku отключен</string> <string name="dhizuku_mode_disabled">Режим Dhizuku отключен</string>
<!--Shizuku-->
<string name="list_owners">Список владельцев</string>
<string name="list_users">Список пользователей</string>
<string name="list_accounts">Список аккаунтов</string>
<string name="shizuku_not_started">Shizuku не запущен. </string>
<string name="activate_device_owner">Активировать владельца устройства</string>
<string name="activate_org_profile">Активировать рабочий профиль, принадлежащий организации</string>
<string name="accounts">Аккаунты</string>
<!--Системные--> <!--Системные-->
<string name="system">Система</string> <string name="system">Система</string>

View File

@@ -7,6 +7,7 @@
<string name="success">Başarılı</string> <string name="success">Başarılı</string>
<string name="failure">Başarısız</string> <string name="failure">Başarısız</string>
<string name="failed">Başarısız Oldu</string> <string name="failed">Başarısız Oldu</string>
<string name="succeeded">Succeeded</string> <!--TODO-->
<string name="add">Ekle</string> <string name="add">Ekle</string>
<string name="remove">Kaldır</string> <string name="remove">Kaldır</string>
<string name="install">Yükle</string> <string name="install">Yükle</string>
@@ -40,7 +41,6 @@
<string name="decide_by_user">Kullanıcı Tarafından Karar Ver</string> <string name="decide_by_user">Kullanıcı Tarafından Karar Ver</string>
<string name="unsupported">Desteklenmiyor</string> <string name="unsupported">Desteklenmiyor</string>
<string name="options">Seçenekler</string> <string name="options">Seçenekler</string>
<string name="copy_command">Komutu Kopyala</string>
<string name="package_name">Paket Adı</string> <string name="package_name">Paket Adı</string>
<string name="not_exist">Mevcut Değil</string> <string name="not_exist">Mevcut Değil</string>
<string name="copy">Kopyala</string> <string name="copy">Kopyala</string>
@@ -84,7 +84,6 @@
<string name="change_package_state">Paket Durumunu Değiştir</string> <string name="change_package_state">Paket Durumunu Değiştir</string>
<string name="grant_permissions">İzinleri Ver</string> <string name="grant_permissions">İzinleri Ver</string>
<string name="add_delegated_admin">Yetkilendirilmiş Yönetici Ekle</string> <string name="add_delegated_admin">Yetkilendirilmiş Yönetici Ekle</string>
<string name="dhizuku_will_be_deactivated">Dhizuku Devre Dışı Bırakılacak</string>
<string name="reset_device_policy">Cihaz Politikasını Sıfırla</string> <string name="reset_device_policy">Cihaz Politikasını Sıfırla</string>
<string name="device_info">Cihaz Bilgisi</string> <string name="device_info">Cihaz Bilgisi</string>
<string name="support_device_id_attestation">Cihaz Kimliği Doğrulamasını Destekler</string> <string name="support_device_id_attestation">Cihaz Kimliği Doğrulamasını Destekler</string>
@@ -110,7 +109,6 @@
<string name="long_support_msg">Uzun Mesaj</string> <string name="long_support_msg">Uzun Mesaj</string>
<string name="transfer">Aktar</string> <string name="transfer">Aktar</string>
<string name="transfer_ownership_warning">%1$s yetkisi %2$s\'e aktarılacak</string> <string name="transfer_ownership_warning">%1$s yetkisi %2$s\'e aktarılacak</string>
<string name="activate_device_admin_here">Cihaz Yöneticisini Burada Etkinleştir.</string>
<!--Receiver--> <!--Receiver-->
<string name="create_work_profile_success">İş Profili Başarıyla Oluşturuldu</string> <string name="create_work_profile_success">İş Profili Başarıyla Oluşturuldu</string>
@@ -119,14 +117,6 @@
<string name="failed_to_init_dhizuku">Dhizuku Başlatılamadı</string> <string name="failed_to_init_dhizuku">Dhizuku Başlatılamadı</string>
<string name="dhizuku_permission_not_granted">Dhizuku İzni Verilmedi</string> <string name="dhizuku_permission_not_granted">Dhizuku İzni Verilmedi</string>
<string name="dhizuku_mode_disabled">Dhizuku Modu Devre Dışı</string> <string name="dhizuku_mode_disabled">Dhizuku Modu Devre Dışı</string>
<!--Shizuku-->
<string name="list_owners">Sahip Listesi</string>
<string name="list_users">Kullanıcı Listesi</string>
<string name="list_accounts">Hesap Listesi</string>
<string name="shizuku_not_started">Shizuku Başlatılmadı.</string>
<string name="activate_device_owner">Cihaz Sahibini Etkinleştir</string>
<string name="activate_org_profile">Kuruluşa Ait İş Profilini Etkinleştir</string>
<string name="accounts">Hesaplar</string>
<!--System--> <!--System-->
<string name="system">Sistem</string> <string name="system">Sistem</string>

View File

@@ -7,6 +7,7 @@
<string name="success">成功</string> <string name="success">成功</string>
<string name="failure">失败</string> <string name="failure">失败</string>
<string name="failed">失败</string> <string name="failed">失败</string>
<string name="succeeded">成功</string>
<string name="add">添加</string> <string name="add">添加</string>
<string name="remove">移除</string> <string name="remove">移除</string>
<string name="install">安装</string> <string name="install">安装</string>
@@ -40,7 +41,6 @@
<string name="decide_by_user">由用户决定</string> <string name="decide_by_user">由用户决定</string>
<string name="unsupported">不支持</string> <string name="unsupported">不支持</string>
<string name="options">选项</string> <string name="options">选项</string>
<string name="copy_command">复制代码</string>
<string name="copy">复制</string> <string name="copy">复制</string>
<string name="file_not_exist">文件不存在</string> <string name="file_not_exist">文件不存在</string>
<string name="io_exception">IO异常</string> <string name="io_exception">IO异常</string>
@@ -83,7 +83,6 @@
<string name="grant_permissions">授予权限</string> <string name="grant_permissions">授予权限</string>
<string name="add_delegated_admin">添加委托管理员</string> <string name="add_delegated_admin">添加委托管理员</string>
<string name="reset_device_policy">重置设备策略</string> <string name="reset_device_policy">重置设备策略</string>
<string name="dhizuku_will_be_deactivated">Dhizuku将被停用</string>
<string name="device_info">设备信息</string> <string name="device_info">设备信息</string>
<string name="support_device_id_attestation">支持设备ID认证</string> <string name="support_device_id_attestation">支持设备ID认证</string>
<string name="support_unique_device_attestation">支持唯一设备认证</string> <string name="support_unique_device_attestation">支持唯一设备认证</string>
@@ -107,7 +106,6 @@
<string name="long_support_msg">提供支持的长消息</string> <string name="long_support_msg">提供支持的长消息</string>
<string name="transfer">转移</string> <string name="transfer">转移</string>
<string name="transfer_ownership_warning">%1$s 特权将被转移至 %2$s</string> <string name="transfer_ownership_warning">%1$s 特权将被转移至 %2$s</string>
<string name="activate_device_admin_here">在这里激活Device admin</string>
<!--Receiver--> <!--Receiver-->
<string name="create_work_profile_success">创建工作资料成功</string> <string name="create_work_profile_success">创建工作资料成功</string>
@@ -117,15 +115,6 @@
<string name="dhizuku_permission_not_granted">Dhizuku未授权</string> <string name="dhizuku_permission_not_granted">Dhizuku未授权</string>
<string name="dhizuku_mode_disabled">Dhizuku模式已禁用</string> <string name="dhizuku_mode_disabled">Dhizuku模式已禁用</string>
<!--Shizuku-->
<string name="list_owners">列出Owners</string>
<string name="list_users">列出用户</string>
<string name="list_accounts">列出账号</string>
<string name="shizuku_not_started">服务未启动</string>
<string name="activate_device_owner">激活Device owner</string>
<string name="activate_org_profile">激活由组织拥有的工作资料</string>
<string name="accounts">账号</string>
<!--System--> <!--System-->
<string name="system">系统</string> <string name="system">系统</string>
<string name="disable_cam">禁用相机</string> <string name="disable_cam">禁用相机</string>
@@ -555,7 +544,7 @@
<!--Settings&About--> <!--Settings&About-->
<string name="settings">设置</string> <string name="settings">设置</string>
<string name="show_dangerous_features">显示危险功能</string> <string name="show_dangerous_features">显示危险功能</string>
<string name="material_you_color">Material you 颜色</string> <string name="material_you_color">Material You 颜色</string>
<string name="dark_theme">深色主题</string> <string name="dark_theme">深色主题</string>
<string name="follow_system">跟随系统</string> <string name="follow_system">跟随系统</string>
<string name="black_theme">黑色主题</string> <string name="black_theme">黑色主题</string>
@@ -695,4 +684,11 @@
<string name="info_max_failed_password_other_user">如果设置为大于零的值,将会在输入一定次数的错误的锁屏密码后清除当前用户的所有数据</string> <string name="info_max_failed_password_other_user">如果设置为大于零的值,将会在输入一定次数的错误的锁屏密码后清除当前用户的所有数据</string>
<string name="info_password_history_length">设置后用户将无法输入与历史记录中任何密码相同的新密码。当前密码将保留直到用户设置新密码为止因此更改不会立即生效。值为0表示不做限制。</string> <string name="info_password_history_length">设置后用户将无法输入与历史记录中任何密码相同的新密码。当前密码将保留直到用户设置新密码为止因此更改不会立即生效。值为0表示不做限制。</string>
<string name="info_required_strong_auth_timeout">如果用户在这段时间内没有使用强认证密码、PIN或图案解锁设备则要求使用强认证解锁设备。值为0表示OwnDroid不参与控制超时。一般来说最少1小时最多72小时。</string> <string name="info_required_strong_auth_timeout">如果用户在这段时间内没有使用强认证密码、PIN或图案解锁设备则要求使用强认证解锁设备。值为0表示OwnDroid不参与控制超时。一般来说最少1小时最多72小时。</string>
<string name="info_deactivate">OwnDroid will lost its privilege</string>
<string name="choose_work_mode">选择一个工作模式</string>
<string name="recommended">推荐</string>
<string name="activate_method">激活方法</string>
<string name="adb_command">ADB命令</string>
<string name="owndroid_warning">此应用使用Device owner和Profile owner特权。这些特权十分危险请谨慎使用。如果操作不当可能会造成严重损失。开发者将不会对此负责。</string>
</resources> </resources>

View File

@@ -8,6 +8,7 @@
<string name="enable">Enable</string> <string name="enable">Enable</string>
<string name="success">Success</string> <string name="success">Success</string>
<string name="failure">Failure</string> <string name="failure">Failure</string>
<string name="succeeded">Succeeded</string>
<string name="failed">Failed</string> <string name="failed">Failed</string>
<string name="add">Add</string> <string name="add">Add</string>
<string name="remove">Remove</string> <string name="remove">Remove</string>
@@ -42,7 +43,6 @@
<string name="decide_by_user">Decide by user</string> <string name="decide_by_user">Decide by user</string>
<string name="unsupported">Unsupported</string> <string name="unsupported">Unsupported</string>
<string name="options">Options</string> <string name="options">Options</string>
<string name="copy_command">Copy Command</string>
<string name="package_name">Package name</string> <string name="package_name">Package name</string>
<string name="not_exist">Not exist</string> <string name="not_exist">Not exist</string>
<string name="copy">Copy</string> <string name="copy">Copy</string>
@@ -87,10 +87,7 @@
<string name="change_package_state">Change package state</string> <string name="change_package_state">Change package state</string>
<string name="grant_permissions">Grant permissions</string> <string name="grant_permissions">Grant permissions</string>
<string name="add_delegated_admin">Add delegated admin</string> <string name="add_delegated_admin">Add delegated admin</string>
<string name="dhizuku_will_be_deactivated">Dhizuku will be deactivated</string>
<string name="reset_device_policy">Reset device policy</string> <string name="reset_device_policy">Reset device policy</string>
<string name="activate_profile_owner_command" translatable="false">dpm set-profile-owner --user %1$s com.bintianqi.owndroid/com.bintianqi.owndroid.Receiver</string>
<string name="activate_device_owner_command" translatable="false">dpm set-device-owner com.bintianqi.owndroid/com.bintianqi.owndroid.Receiver</string>
<string name="device_info">Device info</string> <string name="device_info">Device info</string>
<string name="support_device_id_attestation">Support Device ID attestation</string> <string name="support_device_id_attestation">Support Device ID attestation</string>
<string name="support_unique_device_attestation">Support unique device attestation</string> <string name="support_unique_device_attestation">Support unique device attestation</string>
@@ -115,7 +112,6 @@
<string name="long_support_msg">Long message</string> <string name="long_support_msg">Long message</string>
<string name="transfer">Transfer</string> <string name="transfer">Transfer</string>
<string name="transfer_ownership_warning">%1$s privilege will be transferred to %2$s</string> <string name="transfer_ownership_warning">%1$s privilege will be transferred to %2$s</string>
<string name="activate_device_admin_here">Activate Device admin here.</string>
<!--Receiver--> <!--Receiver-->
<string name="create_work_profile_success">Create work profile success</string> <string name="create_work_profile_success">Create work profile success</string>
@@ -125,16 +121,8 @@
<string name="failed_to_init_dhizuku">Failed to initialize Dhizuku</string> <string name="failed_to_init_dhizuku">Failed to initialize Dhizuku</string>
<string name="dhizuku_permission_not_granted">Dhizuku permission not granted</string> <string name="dhizuku_permission_not_granted">Dhizuku permission not granted</string>
<string name="dhizuku_mode_disabled">Dhizuku mode disabled</string> <string name="dhizuku_mode_disabled">Dhizuku mode disabled</string>
<!--Shizuku-->
<string name="shizuku" translatable="false">Shizuku</string> <string name="shizuku" translatable="false">Shizuku</string>
<string name="list_owners">List owners</string>
<string name="list_users">List users</string>
<string name="list_accounts">List accounts</string>
<string name="shizuku_not_started">Shizuku not started. </string>
<string name="dpm_activate_do_command" translatable="false">dpm set-device-owner com.bintianqi.owndroid/com.bintianqi.owndroid.Receiver</string>
<string name="activate_device_owner">Activate Device owner</string>
<string name="activate_org_profile">Activate organization-owned work profile</string>
<string name="accounts">Accounts</string>
<!--System--> <!--System-->
<string name="system">System</string> <string name="system">System</string>
@@ -371,9 +359,6 @@
<string name="account_name">Account name</string> <string name="account_name">Account name</string>
<string name="keep_account">Keep account</string> <string name="keep_account">Keep account</string>
<string name="org_owned_work_profile">Organization work profile</string> <string name="org_owned_work_profile">Organization work profile</string>
<string name="activate_org_profile_command" tools:ignore="TypographyDashes" translatable="false">
dpm mark-profile-owner-on-organization-owned-device --user %1$s com.bintianqi.owndroid/com.bintianqi.owndroid.Receiver
</string>
<string name="skip_encryption">Skip encryption</string> <string name="skip_encryption">Skip encryption</string>
<string name="create">Create</string> <string name="create">Create</string>
<string name="suspend_personal_app">Suspend personal app</string> <string name="suspend_personal_app">Suspend personal app</string>
@@ -592,7 +577,7 @@
<!--Settings&About--> <!--Settings&About-->
<string name="settings">Settings</string> <string name="settings">Settings</string>
<string name="show_dangerous_features">Show dangerous features</string> <string name="show_dangerous_features">Show dangerous features</string>
<string name="material_you_color">Material you color</string> <string name="material_you_color">Material You color</string>
<string name="dark_theme">Dark theme</string> <string name="dark_theme">Dark theme</string>
<string name="follow_system">Follow system</string> <string name="follow_system">Follow system</string>
<string name="black_theme">Black theme</string> <string name="black_theme">Black theme</string>
@@ -733,4 +718,11 @@
<string name="info_max_failed_password_other_user">Setting this to a value greater than zero enables a policy that will wipe this user after too many incorrect unlock password have been entered.</string> <string name="info_max_failed_password_other_user">Setting this to a value greater than zero enables a policy that will wipe this user after too many incorrect unlock password have been entered.</string>
<string name="info_password_history_length">After setting this, the user will not be able to enter a new password that is the same as any password in the history. Note that the current password will remain until the user has set a new one, so the change does not take place immediately.\nA value of 0 means there is no restriction.</string> <string name="info_password_history_length">After setting this, the user will not be able to enter a new password that is the same as any password in the history. Note that the current password will remain until the user has set a new one, so the change does not take place immediately.\nA value of 0 means there is no restriction.</string>
<string name="info_required_strong_auth_timeout">Determine for how long the user will be able to use secondary, non strong auth for authentication, since last strong method authentication (password, pin or pattern) was used. After the returned timeout the user is required to use strong authentication method.\nA value of 0 means the admin is not participating in controlling the timeout. The minimum and maximum timeouts are platform-defined and are typically 1 hour and 72 hours, respectively.</string> <string name="info_required_strong_auth_timeout">Determine for how long the user will be able to use secondary, non strong auth for authentication, since last strong method authentication (password, pin or pattern) was used. After the returned timeout the user is required to use strong authentication method.\nA value of 0 means the admin is not participating in controlling the timeout. The minimum and maximum timeouts are platform-defined and are typically 1 hour and 72 hours, respectively.</string>
<string name="info_deactivate">OwnDroid will lost its privilege</string>
<string name="choose_work_mode">Choose a work mode</string>
<string name="recommended">Recommended</string>
<string name="activate_method">Activate method</string>
<string name="adb_command">ADB command</string>
<string name="owndroid_warning">This app uses Device owner and Profile owner privileges. These privileges are extremely dangerous, please use them with caution. If used improperly, they may result in severe losses. The developers will not be responsible for this.</string>
</resources> </resources>

View File

@@ -3,13 +3,14 @@ agp = "8.9.1"
kotlin = "2.1.20" kotlin = "2.1.20"
navigation-compose = "2.8.9" navigation-compose = "2.8.9"
composeBom = "2025.03.01" composeBom = "2025.04.00"
accompanist-drawablepainter = "0.35.0-alpha" accompanist-drawablepainter = "0.35.0-alpha"
accompanist-permissions = "0.37.0" accompanist-permissions = "0.37.0"
shizuku = "13.1.5" shizuku = "13.1.5"
fragment = "1.8.6" fragment = "1.8.6"
dhizuku = "2.5.3" dhizuku = "2.5.3"
hiddenApiBypass = "4.3" hiddenApiBypass = "4.3"
libsu = "6.0.0"
serialization = "1.7.3" serialization = "1.7.3"
[libraries] [libraries]
@@ -19,6 +20,7 @@ androidx-compose-ui-tooling = { group = "androidx.compose.ui", name = "ui-toolin
androidx-activity-compose = { module = "androidx.activity:activity-compose" } androidx-activity-compose = { module = "androidx.activity:activity-compose" }
androidx-material3 = { module = "androidx.compose.material3:material3" } androidx-material3 = { module = "androidx.compose.material3:material3" }
androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "navigation-compose" } androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "navigation-compose" }
androidx-fragment = { group = "androidx.fragment", name = "fragment", version.ref = "fragment" }
accompanist-drawablepainter = { module = "com.google.accompanist:accompanist-drawablepainter", version.ref = "accompanist-drawablepainter" } accompanist-drawablepainter = { module = "com.google.accompanist:accompanist-drawablepainter", version.ref = "accompanist-drawablepainter" }
accompanist-permissions = { group = "com.google.accompanist", name = "accompanist-permissions", version.ref = "accompanist-permissions" } accompanist-permissions = { group = "com.google.accompanist", name = "accompanist-permissions", version.ref = "accompanist-permissions" }
@@ -27,7 +29,7 @@ shizuku-provider = { module = "dev.rikka.shizuku:provider", version.ref = "shizu
shizuku-api = { module = "dev.rikka.shizuku:api", version.ref = "shizuku" } shizuku-api = { module = "dev.rikka.shizuku:api", version.ref = "shizuku" }
dhizuku-api = { module = "io.github.iamr0s:Dhizuku-API", version.ref = "dhizuku" } dhizuku-api = { module = "io.github.iamr0s:Dhizuku-API", version.ref = "dhizuku" }
hiddenApiBypass = { module = "org.lsposed.hiddenapibypass:hiddenapibypass", version.ref = "hiddenApiBypass" } hiddenApiBypass = { module = "org.lsposed.hiddenapibypass:hiddenapibypass", version.ref = "hiddenApiBypass" }
androidx-fragment = { group = "androidx.fragment", name = "fragment", version.ref = "fragment" } libsu = { module = "com.github.topjohnwu.libsu:core", version.ref = "libsu" }
serialization = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "serialization" } serialization = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "serialization" }

View File

@@ -10,6 +10,7 @@ dependencyResolutionManagement {
repositories { repositories {
google() google()
mavenCentral() mavenCentral()
maven("https://jitpack.io")
} }
} }