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

@@ -2,7 +2,6 @@ package com.bintianqi.owndroid
import android.annotation.SuppressLint
import android.app.Activity
import android.app.admin.DevicePolicyManager
import android.os.Build.VERSION
import android.os.Bundle
import android.widget.Toast
@@ -22,14 +21,18 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
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.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme.colorScheme
import androidx.compose.material3.MaterialTheme.typography
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.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
@@ -57,8 +60,6 @@ import androidx.navigation.compose.composable
import androidx.navigation.compose.dialog
import androidx.navigation.compose.rememberNavController
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.AddApnSettingScreen
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.DeviceInfo
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.DisableAccountManagementScreen
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.PrivateDns
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.RecommendedGlobalProxy
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.SetDefaultDialerScreen
import com.bintianqi.owndroid.dpm.SetSystemUpdatePolicy
import com.bintianqi.owndroid.dpm.ShizukuScreen
import com.bintianqi.owndroid.dpm.SupportMessage
import com.bintianqi.owndroid.dpm.SupportMessageScreen
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.WipeData
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.WorkProfileScreen
import com.bintianqi.owndroid.dpm.dhizukuErrorStatus
@@ -299,6 +297,14 @@ fun Home(vm: MyViewModel) {
val userRestrictions by vm.userRestrictions.collectAsStateWithLifecycle()
fun navigateUp() { navController.navigateUp() }
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(
navController = navController,
startDestination = Home,
@@ -313,20 +319,35 @@ fun Home(vm: MyViewModel) {
popExitTransition = Animations.navHostPopExitTransition
) {
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> {
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<AddDelegatedAdmin>{ AddDelegatedAdminScreen(it.toRoute(), ::navigateUp) }
composable<DeviceInfo> { DeviceInfoScreen(::navigateUp) }
composable<LockScreenInfo> { LockScreenInfoScreen(::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<SystemOptions> { SystemOptionsScreen(::navigateUp) }
@@ -492,10 +513,10 @@ fun Home(vm: MyViewModel) {
@Serializable private object Home
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun HomeScreen(onNavigate: (Any) -> Unit) {
val context = LocalContext.current
val dpm = context.getDPM()
val privilege by myPrivilege.collectAsStateWithLifecycle()
val activateType = (if(privilege.dhizuku) context.getString(R.string.dhizuku) + " - " else "") +
context.getString(
@@ -504,9 +525,18 @@ private fun HomeScreen(onNavigate: (Any) -> Unit) {
else if(privilege.profile) R.string.profile_owner
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())) {
Spacer(Modifier.padding(vertical = 25.dp))
Spacer(Modifier.padding(vertical = 8.dp))
Text(
text = stringResource(R.string.app_name), style = typography.headlineLarge,
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.network, R.drawable.wifi_fill0) { onNavigate(Network) }
}
if(
privilege.work || (VERSION.SDK_INT < 24 && !privilege.device) ||
dpm.isProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE)
) {
if(privilege.work) {
HomePageItem(R.string.work_profile, R.drawable.work_fill0) {
onNavigate(
if(VERSION.SDK_INT < 24 ||
dpm.isProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE)
) WorkProfile else CreateWorkProfile
)
onNavigate(WorkProfile)
}
}
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.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))
}
}

View File

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

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))
}
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")
}
val Context.dpcPackageName: String
get() {
return if(SharedPrefs(this).dhizuku) {
Dhizuku.getOwnerPackageName()
} else {
"com.bintianqi.owndroid"
}
}
@SuppressLint("PrivateApi")
private fun binderWrapperDevicePolicyManager(appContext: Context): DevicePolicyManager? {
fun binderWrapperDevicePolicyManager(appContext: Context): DevicePolicyManager? {
try {
val context = appContext.createPackageContext(Dhizuku.getOwnerComponent().packageName, Context.CONTEXT_IGNORE_SECURITY)
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.Context
import android.content.pm.PackageManager
import android.os.Binder
import android.os.Build.VERSION
import android.os.Bundle
import android.os.IBinder
import android.os.RemoteException
import android.os.PersistableBundle
import android.widget.Toast
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.annotation.Keep
import androidx.annotation.RequiresApi
import androidx.annotation.StringRes
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.background
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.KeyboardOptions
import androidx.compose.foundation.text.selection.SelectionContainer
import androidx.compose.foundation.verticalScroll
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.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.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.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.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalFocusManager
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.unit.dp
import androidx.compose.ui.window.Dialog
import androidx.core.os.bundleOf
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.bintianqi.owndroid.ChoosePackageContract
import com.bintianqi.owndroid.HorizontalPadding
import com.bintianqi.owndroid.IUserService
import com.bintianqi.owndroid.R
import com.bintianqi.owndroid.Receiver
import com.bintianqi.owndroid.Settings
import com.bintianqi.owndroid.SharedPrefs
import com.bintianqi.owndroid.backToHomeStateFlow
import com.bintianqi.owndroid.myPrivilege
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.useShizuku
import com.bintianqi.owndroid.writeClipBoard
import com.bintianqi.owndroid.yesOrNo
import com.rosan.dhizuku.api.Dhizuku
import com.rosan.dhizuku.api.DhizukuRequestPermissionListener
import com.topjohnwu.superuser.Shell
import kotlinx.coroutines.launch
import kotlinx.serialization.Serializable
import rikka.shizuku.Shizuku
import rikka.sui.Sui
@Serializable object Permissions
@Composable
fun PermissionsScreen(onNavigateUp: () -> Unit, onNavigate: (Any) -> Unit, onNavigateToShizuku: (Bundle) -> Unit) {
fun PermissionsScreen(onNavigateUp: () -> Unit, onNavigate: (Any) -> Unit) {
val context = LocalContext.current
val dpm = context.getDPM()
val receiver = context.getReceiver()
@@ -69,57 +111,6 @@ fun PermissionsScreen(onNavigateUp: () -> Unit, onNavigate: (Any) -> Unit, onNav
var bindingShizuku by remember { mutableStateOf(false) }
val enrollmentSpecificId = if(VERSION.SDK_INT >= 31 && (privilege.device || privilege.profile)) dpm.enrollmentSpecificId else ""
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) }
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))) {
@@ -137,9 +128,6 @@ fun PermissionsScreen(onNavigateUp: () -> Unit, onNavigate: (Any) -> Unit, onNav
if(VERSION.SDK_INT >= 24) {
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) {
Dialog(onDismissRequest = { bindingShizuku = false }) {
@@ -225,39 +213,329 @@ fun PermissionsScreen(onNavigateUp: () -> Unit, onNavigate: (Any) -> Unit, onNav
}
}
private fun toggleDhizukuMode(status: Boolean, context: Context) {
val sp = SharedPrefs(context)
if(!status) {
sp.dhizuku = false
backToHomeStateFlow.value = true
return
}
if(!Dhizuku.init(context)) {
dhizukuErrorStatus.value = 1
return
}
if(dhizukuPermissionGranted()) {
sp.dhizuku = true
Dhizuku.init(context)
updatePrivilege(context)
backToHomeStateFlow.value = true
} else {
Dhizuku.requestPermission(object: DhizukuRequestPermissionListener() {
@Throws(RemoteException::class)
override fun onRequestPermission(grantResult: Int) {
if(grantResult == PackageManager.PERMISSION_GRANTED) {
sp.dhizuku = true
Dhizuku.init(context)
updatePrivilege(context)
backToHomeStateFlow.value = true
} else {
dhizukuErrorStatus.value = 2
@Serializable data class WorkModes(val canNavigateUp: Boolean)
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun WorkModesScreen(
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)
}
}
},
navigationIcon = {
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)
handlePrivilegeChange(context)
} else {
context.showOperationResultToast(false)
}
})
}
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) {
if(grantResult == PackageManager.PERMISSION_GRANTED) doTransfer()
else callback(false, false, null)
}
})
}
} 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
@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
@Suppress("InlinedApi")
enum class DelegatedScope(val id: String, @StringRes val string: Int, val requiresApi: Int = 0) {
@@ -698,7 +847,7 @@ fun SupportMessageScreen(onNavigateUp: () -> Unit) {
@RequiresApi(28)
@Composable
fun TransferOwnershipScreen(onNavigateUp: () -> Unit) {
fun TransferOwnershipScreen(onNavigateUp: () -> Unit, onTransferred: () -> Unit) {
val context = LocalContext.current
val privilege by myPrivilege.collectAsStateWithLifecycle()
val focusMgr = LocalFocusManager.current
@@ -742,7 +891,7 @@ fun TransferOwnershipScreen(onNavigateUp: () -> Unit) {
context.showOperationResultToast(true)
updatePrivilege(context)
dialog = false
onNavigateUp()
onTransferred()
} catch(e: Exception) {
e.printStackTrace()
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.WIPE_EUICC
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.Build.VERSION
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.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.showOperationResultToast
import com.bintianqi.owndroid.ui.CheckBoxItem
import com.bintianqi.owndroid.ui.CopyTextButton
import com.bintianqi.owndroid.ui.FunctionItem
import com.bintianqi.owndroid.ui.InfoItem
import com.bintianqi.owndroid.ui.MyScaffold
import com.bintianqi.owndroid.ui.Notes
import com.bintianqi.owndroid.ui.SwitchItem
import com.bintianqi.owndroid.yesOrNo
import com.bintianqi.owndroid.useShizuku
import kotlinx.serialization.Serializable
@Serializable object WorkProfile
@@ -73,7 +74,7 @@ import kotlinx.serialization.Serializable
fun WorkProfileScreen(onNavigateUp: () -> Unit, onNavigate: (Any) -> Unit) {
val privilege by myPrivilege.collectAsStateWithLifecycle()
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) }
}
if(privilege.org) {
@@ -162,22 +163,38 @@ fun CreateWorkProfileScreen(onNavigateUp: () -> Unit) {
@Composable
fun OrganizationOwnedProfileScreen(onNavigateUp: () -> Unit) {
val context = LocalContext.current
val dpm = context.getDPM()
var dialog by remember { mutableStateOf(false) }
MyScaffold(R.string.org_owned_work_profile, onNavigateUp) {
InfoItem(R.string.org_owned_work_profile, dpm.isOrganizationOwnedDeviceWithManagedProfile.yesOrNo)
Spacer(Modifier.padding(vertical = 5.dp))
if(!dpm.isOrganizationOwnedDeviceWithManagedProfile) {
SelectionContainer {
Text(
text = stringResource(R.string.activate_org_profile_command, Binder.getCallingUid()/100000),
color = colorScheme.onTertiaryContainer
)
Button({
useShizuku(context) { service ->
val result = IUserService.Stub.asInterface(service).execute(activateOrgProfileCommand)
if (result?.getInt("code", -1) == 0) {
context.showOperationResultToast(true)
} else {
context.showOperationResultToast(false)
}
}
CopyTextButton(R.string.copy_command, stringResource(R.string.activate_org_profile_command, Binder.getCallingUid()/100000))
}) {
Text(stringResource(R.string.shizuku))
}
Button({ dialog = true }) { Text(stringResource(R.string.adb_command)) }
if(dialog) AlertDialog(
text = {
SelectionContainer {
Text(activateOrgProfileCommand)
}
},
confirmButton = {
TextButton({ dialog = false }) { Text(stringResource(R.string.confirm)) }
},
onDismissRequest = { dialog = false }
)
}
}
val activateOrgProfileCommand = "dpm mark-profile-owner-on-organization-owned-device --user " +
"${Binder.getCallingUid()/100000} com.bintianqi.owndroid/com.bintianqi.owndroid.Receiver"
@Serializable object SuspendPersonalApp
@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
fun InfoItem(title: Int, text: Int, withInfo: Boolean = false, onClick: () -> Unit = {}) =
InfoItem(title, stringResource(text), withInfo, onClick)