diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 998b4e4..33cfa72 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -61,7 +61,7 @@ jobs: upload-telegram: name: Upload Builds - if: ${{ github.ref_name == 'dev' }} + if: github.ref_name == 'dev' runs-on: ubuntu-latest needs: ["build"] steps: @@ -91,6 +91,6 @@ jobs: mv ./$RELEASE_SIGNED_PWD/app-release.apk ./$RELEASE_SIGNED_PWD.apk && rm -rf ./$RELEASE_SIGNED_PWD ../binaries/telegram-bot-api --api-id=${{ secrets.TELEGRAM_API_APP_ID }} --api-hash=${{ secrets.TELEGRAM_API_HASH }} --local 2>&1 > /dev/null & export token=${{ secrets.TELEGRAM_BOT_KEY }} - curl -v "http://127.0.0.1:8081/bot$token/sendMediaGroup?chat_id=-1002216379163&media=%5B%7B%22type%22%3A%22document%22%2C%22media%22%3A%22attach%3A%2F%2FreleaseTest%22%7D%2C%7B%22type%22%3A%22document%22%2C%22media%22%3A%22attach%3A%2F%2FreleaseSigned%22%2C%22parse_mode%22%3A%22HTML%22%2C%22caption%22%3A${ESCAPED}%7D%5D" \ + curl -v "http://127.0.0.1:8081/bot$token/sendMediaGroup?chat_id=-1002203528169&media=%5B%7B%22type%22%3A%22document%22%2C%22media%22%3A%22attach%3A%2F%2FreleaseTest%22%7D%2C%7B%22type%22%3A%22document%22%2C%22media%22%3A%22attach%3A%2F%2FreleaseSigned%22%2C%22parse_mode%22%3A%22HTML%22%2C%22caption%22%3A${ESCAPED}%7D%5D" \ -F releaseTest="@$RELEASE_TEST_PWD.apk" \ -F releaseSigned="@$RELEASE_SIGNED_PWD.apk" diff --git a/app/src/main/aidl/com/bintianqi/owndroid/IUserService.aidl b/app/src/main/aidl/com/bintianqi/owndroid/IUserService.aidl index 2175af9..c5b7b98 100644 --- a/app/src/main/aidl/com/bintianqi/owndroid/IUserService.aidl +++ b/app/src/main/aidl/com/bintianqi/owndroid/IUserService.aidl @@ -1,6 +1,9 @@ package com.bintianqi.owndroid; +import android.accounts.Account; + interface IUserService { String execute(String command) = 1; int getUid() = 2; + Account[] listAccounts() = 3; } diff --git a/app/src/main/java/com/bintianqi/owndroid/MainActivity.kt b/app/src/main/java/com/bintianqi/owndroid/MainActivity.kt index cd24c76..9e82345 100644 --- a/app/src/main/java/com/bintianqi/owndroid/MainActivity.kt +++ b/app/src/main/java/com/bintianqi/owndroid/MainActivity.kt @@ -59,6 +59,7 @@ import androidx.navigation.NavHostController import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController +import com.bintianqi.owndroid.dpm.AccountsViewer import com.bintianqi.owndroid.dpm.AffiliationID import com.bintianqi.owndroid.dpm.AlwaysOnVPNPackage import com.bintianqi.owndroid.dpm.ApplicationManage @@ -213,7 +214,8 @@ fun Home(activity: FragmentActivity, vm: MyViewModel) { composable(route = "HomePage") { HomePage(navCtrl) } composable(route = "Permissions") { Permissions(navCtrl) } - composable(route = "Shizuku") { Shizuku(vm, navCtrl) } + composable(route = "Shizuku") { Shizuku(navCtrl, it.arguments!!) } + composable(route = "AccountsViewer") { AccountsViewer(navCtrl, it.arguments!!) } composable(route = "DeviceAdmin") { DeviceAdmin(navCtrl) } composable(route = "ProfileOwner") { ProfileOwner(navCtrl) } composable(route = "DeviceOwner") { DeviceOwner(navCtrl) } @@ -360,7 +362,7 @@ private fun HomePage(navCtrl:NavHostController) { val profileOwner = context.isProfileOwner val refreshStatus by dhizukuErrorStatus.collectAsState() LaunchedEffect(refreshStatus) { - activated = context.isDeviceAdmin + activated = context.isProfileOwner || context.isDeviceOwner activateType = if(sharedPref.getBoolean("dhizuku", false)) context.getString(R.string.dhizuku) + " - " else "" activateType += context.getString( if(deviceOwner) { R.string.device_owner } diff --git a/app/src/main/java/com/bintianqi/owndroid/MyViewModel.kt b/app/src/main/java/com/bintianqi/owndroid/MyViewModel.kt index 932dc54..c31e7e0 100644 --- a/app/src/main/java/com/bintianqi/owndroid/MyViewModel.kt +++ b/app/src/main/java/com/bintianqi/owndroid/MyViewModel.kt @@ -2,7 +2,6 @@ package com.bintianqi.owndroid import android.content.Context import android.os.Build -import android.os.IBinder import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import kotlinx.coroutines.flow.MutableStateFlow @@ -12,7 +11,6 @@ class MyViewModel: ViewModel() { val theme = MutableStateFlow(ThemeSettings()) val installedPackages = mutableListOf() val selectedPackage = MutableStateFlow("") - val shizukuBinder = MutableStateFlow(null) var initialized = false fun initialize(context: Context) { diff --git a/app/src/main/java/com/bintianqi/owndroid/Utils.kt b/app/src/main/java/com/bintianqi/owndroid/Utils.kt index 5aeb61c..9f0e7e5 100644 --- a/app/src/main/java/com/bintianqi/owndroid/Utils.kt +++ b/app/src/main/java/com/bintianqi/owndroid/Utils.kt @@ -1,5 +1,6 @@ package com.bintianqi.owndroid +import android.annotation.SuppressLint import android.app.admin.DevicePolicyManager import android.content.ClipData import android.content.ClipboardManager @@ -85,3 +86,8 @@ val Long.humanReadableDate: String fun Context.showOperationResultToast(success: Boolean) { Toast.makeText(this, if(success) R.string.success else R.string.failed, Toast.LENGTH_SHORT).show() } + +@SuppressLint("PrivateApi") +fun getContext(): Context { + return Class.forName("android.app.ActivityThread").getMethod("currentApplication").invoke(null) as Context +} diff --git a/app/src/main/java/com/bintianqi/owndroid/dpm/Permissions.kt b/app/src/main/java/com/bintianqi/owndroid/dpm/Permissions.kt index 29ac872..790c1ed 100644 --- a/app/src/main/java/com/bintianqi/owndroid/dpm/Permissions.kt +++ b/app/src/main/java/com/bintianqi/owndroid/dpm/Permissions.kt @@ -9,6 +9,7 @@ import android.content.Intent import android.content.pm.PackageManager import android.os.Binder import android.os.Build.VERSION +import android.os.Bundle import android.os.RemoteException import android.os.UserManager import android.widget.Toast @@ -29,6 +30,7 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.Dialog import androidx.navigation.NavHostController import com.bintianqi.owndroid.R import com.bintianqi.owndroid.backToHomeStateFlow @@ -54,6 +56,7 @@ fun Permissions(navCtrl: NavHostController) { val profileOwner = context.isProfileOwner val userManager = context.getSystemService(Context.USER_SERVICE) as UserManager var dialog by remember { mutableIntStateOf(0) } + var bindingShizuku by remember { mutableStateOf(false) } val enrollmentSpecificId = if(VERSION.SDK_INT >= 31 && (deviceOwner || profileOwner)) dpm.enrollmentSpecificId else "" MyScaffold(R.string.permissions, 0.dp, navCtrl) { if(!dpm.isDeviceOwnerApp(context.packageName)) { @@ -82,8 +85,19 @@ fun Permissions(navCtrl: NavHostController) { } FunctionItem(R.string.shizuku) { try { - if(Shizuku.checkSelfPermission() == PackageManager.PERMISSION_GRANTED) { navCtrl.navigate("Shizuku") } - else if(Shizuku.shouldShowRequestPermissionRationale()) { + if(Shizuku.checkSelfPermission() == PackageManager.PERMISSION_GRANTED) { + bindingShizuku = true + val destination = navCtrl.graph.findNode("Shizuku")!!.id + bindShizukuService(context, { binder -> + val args = Bundle() + args.putBinder("binder", binder) + bindingShizuku = false + navCtrl.navigate(destination, args) + }, { + Toast.makeText(context, R.string.shizuku_service_disconnected, Toast.LENGTH_SHORT).show() + bindingShizuku = false + }) + } else if(Shizuku.shouldShowRequestPermissionRationale()) { Toast.makeText(context, R.string.permission_denied, Toast.LENGTH_SHORT).show() } else { Sui.init(context.packageName) @@ -124,6 +138,11 @@ fun Permissions(navCtrl: NavHostController) { FunctionItem(R.string.transfer_ownership, icon = R.drawable.admin_panel_settings_fill0) { navCtrl.navigate("TransferOwnership") } } } + if(bindingShizuku) { + Dialog(onDismissRequest = { bindingShizuku = false }) { + CircularProgressIndicator() + } + } if(dialog != 0) { var input by remember { mutableStateOf("") } AlertDialog( diff --git a/app/src/main/java/com/bintianqi/owndroid/dpm/Shizuku.kt b/app/src/main/java/com/bintianqi/owndroid/dpm/Shizuku.kt index 42422b4..a62f1ea 100644 --- a/app/src/main/java/com/bintianqi/owndroid/dpm/Shizuku.kt +++ b/app/src/main/java/com/bintianqi/owndroid/dpm/Shizuku.kt @@ -1,22 +1,27 @@ package com.bintianqi.owndroid.dpm +import android.accounts.Account 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.widget.Toast +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.CircularProgressIndicator import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -28,56 +33,39 @@ 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.compose.ui.window.Dialog -import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.navigation.NavHostController import com.bintianqi.owndroid.IUserService -import com.bintianqi.owndroid.MyViewModel import com.bintianqi.owndroid.R import com.bintianqi.owndroid.ui.MyScaffold import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.launch import rikka.shizuku.Shizuku @OptIn(ExperimentalMaterial3Api::class) @Composable -fun Shizuku(vm: MyViewModel, navCtrl: NavHostController) { +fun Shizuku(navCtrl: NavHostController, navArgs: Bundle) { val context = LocalContext.current val dpm = context.getDPM() val receiver = context.getReceiver() val coScope = rememberCoroutineScope() val outputTextScrollState = rememberScrollState() var outputText by rememberSaveable { mutableStateOf("") } - var showDeviceAdminButton by remember { mutableStateOf(!context.isDeviceAdmin) } var showDeviceOwnerButton by remember { mutableStateOf(!context.isDeviceOwner) } var showOrgProfileOwnerButton by remember { mutableStateOf(true) } - val binder by vm.shizukuBinder.collectAsStateWithLifecycle() + val binder = navArgs.getBinder("binder")!! var service by remember { mutableStateOf(null) } - var loading by remember { mutableStateOf(true) } - LaunchedEffect(binder) { - if(binder != null && binder!!.pingBinder()) { - service = IUserService.Stub.asInterface(binder) - loading = false - } else { - service = null - } - } - LaunchedEffect(service) { - if(service == null && !loading) navCtrl.navigateUp() - } LaunchedEffect(Unit) { - if(binder == null) bindShizukuService(context, vm.shizukuBinder) + service = if(binder.pingBinder()) { + IUserService.Stub.asInterface(binder) + } else { + null + } } MyScaffold(R.string.shizuku, 0.dp, navCtrl, false) { - if(loading) { - Dialog(onDismissRequest = { navCtrl.navigateUp() }) { - CircularProgressIndicator() - } - } Button( onClick = { @@ -103,9 +91,16 @@ fun Shizuku(vm: MyViewModel, navCtrl: NavHostController) { } Button( onClick = { - coScope.launch{ + Log.d("Shizuku", "List accounts") + try { + val accounts = service!!.listAccounts() + val dest = navCtrl.graph.findNode("AccountsViewer")!!.id + navCtrl.navigate(dest, Bundle().apply { putParcelableArray("accounts", accounts) }) + } catch(_: Exception) { outputText = service!!.execute("dumpsys account") - outputTextScrollState.animateScrollTo(0) + coScope.launch{ + outputTextScrollState.animateScrollTo(0) + } } }, modifier = Modifier.align(Alignment.CenterHorizontally) @@ -114,23 +109,7 @@ fun Shizuku(vm: MyViewModel, navCtrl: NavHostController) { } Spacer(Modifier.padding(vertical = 5.dp)) - AnimatedVisibility(showDeviceAdminButton && showDeviceOwnerButton) { - Button( - onClick = { - coScope.launch{ - outputText = service!!.execute(context.getString(R.string.dpm_activate_da_command)) - outputTextScrollState.animateScrollTo(0) - delay(500) - showDeviceAdminButton = !context.isDeviceAdmin - } - }, - modifier = Modifier.align(Alignment.CenterHorizontally) - ) { - Text(text = stringResource(R.string.activate_device_admin)) - } - } - - AnimatedVisibility(showDeviceOwnerButton) { + AnimatedVisibility(showDeviceOwnerButton, modifier = Modifier.align(Alignment.CenterHorizontally)) { Button( onClick = { coScope.launch{ @@ -139,8 +118,7 @@ fun Shizuku(vm: MyViewModel, navCtrl: NavHostController) { delay(500) showDeviceOwnerButton = !context.isDeviceOwner } - }, - modifier = Modifier.align(Alignment.CenterHorizontally) + } ) { Text(text = stringResource(R.string.activate_device_owner)) } @@ -173,14 +151,17 @@ fun Shizuku(vm: MyViewModel, navCtrl: NavHostController) { } } -fun bindShizukuService(context: Context, shizukuBinder: MutableStateFlow) { +fun bindShizukuService( + context: Context, + onServiceConnected: (IBinder) -> Unit, + onServiceDisconnected: () -> Unit +) { val userServiceConnection = object : ServiceConnection { override fun onServiceConnected(componentName: ComponentName, binder: IBinder) { - shizukuBinder.value = binder + onServiceConnected(binder) } override fun onServiceDisconnected(componentName: ComponentName) { - shizukuBinder.value = null - Toast.makeText(context, R.string.shizuku_service_disconnected, Toast.LENGTH_SHORT).show() + onServiceDisconnected() } } val userServiceArgs = Shizuku.UserServiceArgs(ComponentName(context, ShizukuService::class.java)) @@ -194,3 +175,24 @@ fun bindShizukuService(context: Context, shizukuBinder: MutableStateFlow + MyScaffold(R.string.accounts, 8.dp, navCtrl, false) { + accounts.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) + } + } + } + } + } +} diff --git a/app/src/main/java/com/bintianqi/owndroid/dpm/ShizukuService.kt b/app/src/main/java/com/bintianqi/owndroid/dpm/ShizukuService.kt index 7871433..1237528 100644 --- a/app/src/main/java/com/bintianqi/owndroid/dpm/ShizukuService.kt +++ b/app/src/main/java/com/bintianqi/owndroid/dpm/ShizukuService.kt @@ -1,10 +1,18 @@ package com.bintianqi.owndroid.dpm +import android.accounts.Account +import android.accounts.AccountManager +import android.annotation.SuppressLint +import android.content.Context +import android.os.Parcelable +import android.os.UserManager import android.system.Os import androidx.annotation.Keep import com.bintianqi.owndroid.IUserService +import com.bintianqi.owndroid.getContext import java.io.BufferedReader import java.io.InputStreamReader +import java.lang.Class @Keep class ShizukuService: IUserService.Stub() { @@ -33,4 +41,10 @@ class ShizukuService: IUserService.Stub() { } override fun getUid(): Int = Os.getuid() + + @SuppressLint("MissingPermission") + override fun listAccounts(): Array { + val am = getContext().getSystemService(Context.ACCOUNT_SERVICE) as AccountManager + return am.accounts + } } diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 9795cb6..09c4769 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -122,7 +122,7 @@ Активировать владельца устройства Активировать рабочий профиль, принадлежащий организации Служба Shizuku отключена - + Accounts Syetem diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 6b15eea..c684550 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -124,6 +124,7 @@ Cihaz Sahibini Etkinleştir Kuruluş Profili Sahibini Etkinleştir Shizuku Hizmeti Bağlantısı Kesildi + Accounts Sistem diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 57b6182..8d0a798 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -118,6 +118,7 @@ 激活Device owner 激活由组织拥有的工作资料 Shizuku服务断开连接 + 账号 系统 @@ -661,7 +662,7 @@ 设备上不能有非附属用户 并非所有设备都支持重启前安全日志 当某个帐户类型的帐户管理被禁用时,将无法添加或删除该类型的帐户。 - 恢复出厂设置保护(Factory reset protection)策略用于防止不受信任的重置(Fastboot或Recovery)。需要设备支持持久数据块服务(Persistent data block service) + 恢复出厂设置保护(Factory reset protection)策略决定哪些帐户可以解锁经过不受信任的恢复出厂设置(fastboot或recovery)的设备。\n要启用此功能,设备必须支持持久数据块服务(Persistent data block service)。\n如果在开发者选项中启用了OEM解锁,则FRP将被禁用。 此用户的所有数据将会被清除,但是用户不会被删除。 控制用户是否可以更改管理员配置的网络。启用此锁定后,用户仍然可以配置和连接到其他Wi-Fi,或使用其他Wi-Fi功能(如网络共享)。 指定Wi-Fi网络所需的最低安全等级。设备将无法连接到低于最低安全等级的网络。如果当前网络不满足要求,则会断开连接。 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6df61c2..abf376a 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -127,10 +127,10 @@ List accounts Shizuku not started. dpm set-device-owner com.bintianqi.owndroid/com.bintianqi.owndroid.Receiver - dpm set-active-admin com.bintianqi.owndroid/com.bintianqi.owndroid.Receiver Activate Device owner Activate organization-owned work profile Shizuku service disconnected + Accounts System @@ -701,7 +701,7 @@ If a Device owner use this function, all users should be affiliated. Not all devices support pre-reboot security logs. When account management is disabled for an account type, adding or removing an account of that type will not be possible. - Factory reset protection can protect the device after untrusted factory reset (in Recovery or Fastboot).\nTo enable this feature, the device must support persistent data block service. + The factory reset protection policy determines which accounts can unlock a device that has gone through untrusted factory reset(fastboot or recovery).\nTo enable this feature, the device must support persistent data block service.\nFactory reset protection is disabled if OEM unlocking is enabled in Developer Options. All data of this user will be wiped, but that user won\'t be removed. Control whether the user can change networks configured by the admin.\nWhen this lockdown is enabled, the user can still configure and connect to other Wi-Fi networks, or use other Wi-Fi capabilities such as tethering. Specify the minimum security level required for Wi-Fi networks. The device may not connect to networks that do not meet the minimum security level. If the current network does not meet the minimum security level set, it will be disconnected.