mirror of
https://github.com/awfixers-stuff/OwnDroid.git
synced 2026-03-23 19:15:58 +00:00
Add WorkModesScreen
Update dependency Remove some Shizuku features
This commit is contained in:
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
|
||||
72
app/src/main/java/com/bintianqi/owndroid/ShizukuService.kt
Normal file
72
app/src/main/java/com/bintianqi/owndroid/ShizukuService.kt
Normal 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()
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user