diff --git a/Guide.md b/Guide.md index 1a2af35..2b0576f 100644 --- a/Guide.md +++ b/Guide.md @@ -23,6 +23,8 @@ 8. [密码与锁屏](#密码与锁屏) +9. [其他功能](#其他功能) + ## 权限 ### Device admin @@ -202,12 +204,16 @@ adb shell dpm remove-active-admin com.binbin.androidowner/com.binbin.androidowne 需要API30或以上 +默认打开 + ### 自动设置时区 需要的权限:Device owner或由组织拥有的工作资料的Profile owner 需要API30或以上 +默认打开 + ### 自动设置时间(弃用) 需要的权限:Device owner或Profile owner @@ -244,6 +250,8 @@ adb shell dpm remove-active-admin com.binbin.androidowner/com.binbin.androidowne 有的设备不支持 +默认打开 + ### 锁屏方式 禁用和启用锁屏方式,需要无锁屏密码 @@ -394,6 +402,8 @@ MTE: Memory Tagging Extension(内存标记拓展)[安卓开发者:MTE](htt API34或以上将不能在系统用户中使用WipeData,如果要恢复出厂设置,请使用WipeDevice +在AVD(API34)中使用WipeDevice没有任何效果 + ## 网络 这个页面需要API24或以上才能进入 @@ -470,7 +480,7 @@ API34或以上将不能在系统用户中使用WipeData,如果要恢复出厂 需要API26或以上 -功能开发中 +功能开发中,所以现在使用收集不到任何内容 ### Wi-Fi密钥对 @@ -488,12 +498,14 @@ API34或以上将不能在系统用户中使用WipeData,如果要恢复出厂 需要API28或以上 -有懂这个的大佬吗? +我没用过这个功能,因为我不懂APN。如果这个功能存在问题,请打开一个issue ## 工作资料 工作资料是一种特殊的用户,使用`adb shell pm list user`命令可以看到工作资料,工作资料的默认用户名是“工作资料”或“Work Profile” +工作资料创建后默认禁用[安装未知来源应用](#应用) + ### 创建工作资料 设备上不能有Device owner或Profile owner @@ -508,23 +520,19 @@ API34或以上将不能在系统用户中使用WipeData,如果要恢复出厂 创建后工作资料中的Android owner会成为Profile owner -在WearOS上创建工作资料会导致SystemUI停止运行一次。WearOS原生的启动器不支持工作资料,你需要使用第三方启动器(比如微软桌面)。你可以通过[ADB命令移除工作资料](#删除工作资料) - -此外,不要作死给工作资料重置密码,不然你连输入密码的地方都没有 - -(只在原生WearOS4(AVD)上测试过) +在WearOS上可以创建工作资料,但是会导致SystemUI停止运行一次。WearOS原生的启动器不会显示工作资料中的应用,你需要使用支持工作资料的启动器。你可以通过[ADB命令移除工作资料](#删除工作资料)。此外,不要尝试给工作资料重置密码,因为WearOS不能输入工作资料的密码。(测试环境:WearOS4(AVD)) ### 由组织拥有的工作资料 需要API30或以上 -成为由组织拥有的工作资料会多很多权限 +成为由组织拥有的工作资料后可以使用更多功能 前提条件:Android owner是工作资料中的Profile owner 首先,你需要在工作资料中的Android owner的“用户管理”页面中查看UserID -然后使用ADB命令成为由组织拥有的工作资料 +然后执行下面这个ADB命令 ```shell adb shell @@ -539,13 +547,15 @@ dpm mark-profile-owner-on-organization-owned-device --user USER_ID com.binbin.an 需要的权限:由组织拥有的工作资料的Profile owner +只会挂起个人的用户应用,系统应用和Android owner仍然可以打开 + ### 资料最长关闭时间 需要的权限:工作资料的Profile owner -用户可以关闭工作资料,如果关闭工作资料的时间超过了在这里设置的时间,会停用个人应用 +用户可以关闭工作资料,如果关闭工作资料的时间超过了在这里设置的时间,会挂起个人应用 -设置的时间不能小于72小时 +设置的时间不能小于72小时,如果小于72小时,按72小时算 ### 跨资料Intent过滤器 @@ -563,11 +573,11 @@ dpm mark-profile-owner-on-organization-owned-device --user USER_ID com.binbin.an 组织ID长度需在6~64个字符之间 -设置组织ID后才能在“权限”页面查看设备唯一标识码,不同的组织ID会有不同的设备唯一标识码 +设置组织ID后才能在“权限”页面查看[设备唯一标识码](#设备唯一标识码)。不同的组织ID会有不同的设备唯一标识码 ### 删除工作资料 -你可以使用 [恢复出厂设置](#恢复出厂设置) 来删除工作资料 +你可以在工作资料中使用 [恢复出厂设置](#恢复出厂设置) 来删除工作资料 如果你的工作资料不是由组织拥有的,你可以打开安卓设置->安全->更多安全设置->设备管理器->带工作资料图标的Android owner->移除工作资料(非原生用户自己找) @@ -581,7 +591,7 @@ adb shell pm remove-user USER_ID 如果是工作资料,只能管理工作资料中的应用 -如果是受管理的用户,只能管理那个用户中的应用 +如果是受管理的用户,只能管理受管理用户中的应用 除了安装应用,所有的操作都需要应用的包名,你可以通过ADB命令查看已安装应用的包名 @@ -649,9 +659,11 @@ adb shell pm list permissions 设为允许或拒绝后用户无法在应用管理修改权限,用户也不能通过`pm grant`和`pm revoke`修改权限 -从允许或拒绝改为由用户决定会保持当前的允许、拒绝状态 +从允许或拒绝改为由用户决定会保持当前的状态 -在API31或以上,Profile owner不能再修改传感器相关权限 +有一些权限无法修改,比如安装应用 + +在API31或以上,Profile owner不能再修改传感器相关权限,如果能修改传感器相关权限,说明这个设备是完全受管理设备(Device owner) ### 跨资料应用 @@ -705,6 +717,8 @@ adb shell pm list permissions 需要API34或以上 +没有测试过 + ### 卸载应用 - 静默卸载(需要Device owner,否则没反应) @@ -814,7 +828,7 @@ Profile owner无法禁用部分功能,工作资料中部分功能无效,wear adb shell pm list users ``` -用户名前面的数字就是UserID +上面这条命令返回的结果中,用户名前面的数字就是UserID ### 用户信息 @@ -840,7 +854,14 @@ UserID:不是UID。系统用户的UserID为0,其他用户(包括工作资 ### 用户操作 +推荐使用用户序列号来标识用户,如果要使用UID,UID可以是运行在目标用户中的任意应用 + +无需输入UID/用户序列号的功能: + - 登出当前用户(需要是附属用户的Profile owner,需API28,如果是无头系统用户模式,会切换到前台用户) + +需要输入UID/用户序列号的功能: + - 在后台启动用户(需Device owner和API28) - 切换至用户(需Device owner) - 停止用户(需Device owner和API28) @@ -870,12 +891,6 @@ adb shell pm remove-user --set-ephemeral-if-in-use USER_ID (原生WearOS4(AVD)会出现这个问题,其他版本不知道有没有这个问题) -### 使用Intent创建用户 - -不需要任何权限,但也没啥用,建议Device owner创建并管理用户 - -可能会导致Android owner停止运行,但是停止运行后没log,所以不知道为什么无法创建 - ### 附属用户ID 需要Device owner或Profile owner(工作资料中的Profile owner虽然也能设置,但是没有实际作用) @@ -940,19 +955,21 @@ Device owner无论在何时都是附属于设备的用户 暂不支持自己输入令牌 -### 修改密码 +### 重置密码 -需要4位或以上密码 +设置一个新的密码,密码的长度需要4位或以上,不输入密码将会清除现有的密码 + +长度在6位或以下的纯数字密码将会设置为PIN码 选项: -- 开机时不要求输入密码( **危险!** 一旦设置,只能通过恢复出厂设置来取消) +- 开机时不要求输入密码( **危险!** 一旦设置,只能通过恢复出厂设置来取消。应该是给FDE全盘加密设备用的) - 不允许其他设备管理员重置密码直至用户输入一次密码 -方法: +方式: - 使用令牌重置密码(需API26或以上) -- 重置密码(弃用)(API24之前,Device admin可使用。API24或以上,Device admin只能在没有密码时设置密码,Device owner和Profile owner仍可以在用户解锁设备后更改密码。API26或以上弃用) +- 重置密码(弃用)(API24之前,Device admin可随时使用。API24或以上,Device admin只能在没有密码时设置密码,Device owner和Profile owner仍可以在用户解锁设备后更改密码。API26或以上完全弃用) ### 最大密码错误次数 @@ -1037,3 +1054,23 @@ API31及以上弃用,请使用[密码复杂度要求](#密码复杂度要求) - 生物识别(弱) - 复杂数字(无连续性) - 自定义(现在不支持,以后也不会支持,因为这已经弃用了) + +## 其他功能 + +### 手表模式 + +在Android owner的设置中打开 + +适配手表的屏幕大小,添加一些WearOS/AndroidWear相关的提示,比如[密码与锁屏](#密码与锁屏) + +### 动态取色 + +需要安卓12或以上 + +在安卓12或以上此功能默认打开 + +打开后Android owner中的颜色方案将会跟随系统 + +建议打开,因为自带的颜色方案不好看 + +打开或关闭此功能都要重启Android owner diff --git a/app/build.gradle.kts b/app/build.gradle.kts index df1e40d..9708fe7 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -11,8 +11,8 @@ android { applicationId = "com.binbin.androidowner" minSdk = 21 targetSdk = 34 - versionCode = 15 - versionName = "3.2" + versionCode = 16 + versionName = "3.3" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { diff --git a/app/src/main/java/com/binbin/androidowner/ApplicationManage.kt b/app/src/main/java/com/binbin/androidowner/ApplicationManage.kt index 7e29c63..2e15156 100644 --- a/app/src/main/java/com/binbin/androidowner/ApplicationManage.kt +++ b/app/src/main/java/com/binbin/androidowner/ApplicationManage.kt @@ -25,6 +25,7 @@ import androidx.compose.foundation.layout.* 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.material3.* import androidx.compose.material3.MaterialTheme.colorScheme @@ -111,7 +112,7 @@ fun ApplicationManage(){ } if(VERSION.SDK_INT>=24&&(isDeviceOwner(myDpm)||isProfileOwner(myDpm))){ AppManageItem( - R.string.always_on_vpn,R.string.experimental_feature,{pkgName == myDpm.getAlwaysOnVpnPackage(myComponent)}) {b-> + R.string.always_on_vpn,R.string.place_holder,{pkgName == myDpm.getAlwaysOnVpnPackage(myComponent)}) {b-> try { myDpm.setAlwaysOnVpnPackage(myComponent, pkgName, b) } catch(e: java.lang.UnsupportedOperationException) { @@ -171,7 +172,11 @@ fun ApplicationManage(){ Text(text = "用户将无法清除应用的存储空间和缓存", style = bodyTextStyle) Text(text = "应用列表:") if(listText!=""){ - Text(text = listText, style = bodyTextStyle) + SelectionContainer { + Text(text = listText, style = bodyTextStyle) + } + }else{ + Text(text = "无", style = bodyTextStyle) } Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween){ Button( @@ -277,7 +282,13 @@ fun ApplicationManage(){ } var inited by remember{mutableStateOf(false)} if(!inited){refresh();inited=true} - Text(text = if(list!=""){list}else{"无"}, style = bodyTextStyle) + if(list!=""){ + SelectionContainer { + Text(text = list, style = bodyTextStyle) + } + }else{ + Text(text = "无", style = bodyTextStyle) + } Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween){ Button( onClick = { @@ -317,7 +328,13 @@ fun ApplicationManage(){ if(!inited){refresh();inited=true} Text(text = "跨资料微件", style = typography.titleLarge, color = titleColor) Text(text = "(跨资料桌面小部件提供者)", style = bodyTextStyle) - Text(text = if(list!=""){list}else{"无"}, style = bodyTextStyle) + if(list!=""){ + SelectionContainer { + Text(text = list, style = bodyTextStyle) + } + }else{ + Text(text = "无", style = bodyTextStyle) + } Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween){ Button( onClick = { @@ -429,7 +446,13 @@ fun ApplicationManage(){ if(getList!=null){ permittedAccessibility = getList } refreshList(); inited=true } - Text(text = if(listText!=""){listText}else{"无"}, style = bodyTextStyle) + if(listText!=""){ + SelectionContainer { + Text(text = listText, style = bodyTextStyle) + } + }else{ + Text(text = "无", style = bodyTextStyle) + } Row(modifier = Modifier.fillMaxWidth(),horizontalArrangement = Arrangement.SpaceBetween){ Button( onClick = { permittedAccessibility.add(pkgName); refreshList()}, @@ -473,7 +496,13 @@ fun ApplicationManage(){ if(getList!=null){ permittedIme = getList } refreshList();inited=true } - Text(text = if(imeListText!=""){imeListText}else{"无"}, style = bodyTextStyle) + if(imeListText!=""){ + SelectionContainer { + Text(text = imeListText, style = bodyTextStyle) + } + }else{ + Text(text = "无", style = bodyTextStyle) + } Row(modifier = Modifier.fillMaxWidth(),horizontalArrangement = Arrangement.SpaceBetween){ Button( onClick = { permittedIme.add(pkgName); refreshList() }, @@ -518,7 +547,13 @@ fun ApplicationManage(){ if(getList!=null){ keepUninstallPkg = getList } refresh(); inited=true } - Text(text = if(listText==""){"无"}else{listText}, style = bodyTextStyle) + if(listText!=""){ + SelectionContainer { + Text(text = listText, style = bodyTextStyle) + } + }else{ + Text(text = "无", style = bodyTextStyle) + } Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween){ Button( onClick = { diff --git a/app/src/main/java/com/binbin/androidowner/DeviceControl.kt b/app/src/main/java/com/binbin/androidowner/DeviceControl.kt index 497ce35..5e92327 100644 --- a/app/src/main/java/com/binbin/androidowner/DeviceControl.kt +++ b/app/src/main/java/com/binbin/androidowner/DeviceControl.kt @@ -16,6 +16,7 @@ import androidx.compose.foundation.layout.* 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.material3.* import androidx.compose.material3.MaterialTheme.colorScheme @@ -36,7 +37,7 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.launch @Composable -fun DeviceControl(){ +fun SystemManage(){ val myContext = LocalContext.current val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) @@ -370,7 +371,13 @@ fun DeviceControl(){ } refreshWhitelist() Text(text = "白名单应用", style = typography.titleLarge, color = titleColor) - if(listText!=""){ Text(listText) }else{ Text(("无")) } + if(listText!=""){ + SelectionContainer { + Text(text = listText, style = bodyTextStyle) + } + }else{ + Text(text = "无", style = bodyTextStyle) + } OutlinedTextField( value = inputPkg, onValueChange = {inputPkg=it}, diff --git a/app/src/main/java/com/binbin/androidowner/MainActivity.kt b/app/src/main/java/com/binbin/androidowner/MainActivity.kt index 45df71c..34eb43b 100644 --- a/app/src/main/java/com/binbin/androidowner/MainActivity.kt +++ b/app/src/main/java/com/binbin/androidowner/MainActivity.kt @@ -165,7 +165,7 @@ fun MyScaffold(){ modifier = Modifier.padding(top = it.calculateTopPadding()).imePadding() ){ composable(route = "HomePage", content = { HomePage(navCtrl)}) - composable(route = "DeviceControl", content = { DeviceControl()}) + composable(route = "DeviceControl", content = { SystemManage()}) composable(route = "ManagedProfile", content = {ManagedProfile()}) composable(route = "Permissions", content = { DpmPermissions(navCtrl)}) composable(route = "ApplicationManage", content = { ApplicationManage()}) diff --git a/app/src/main/java/com/binbin/androidowner/ManagedProfile.kt b/app/src/main/java/com/binbin/androidowner/ManagedProfile.kt index 66e2351..2634aea 100644 --- a/app/src/main/java/com/binbin/androidowner/ManagedProfile.kt +++ b/app/src/main/java/com/binbin/androidowner/ManagedProfile.kt @@ -2,10 +2,12 @@ package com.binbin.androidowner import android.app.admin.DevicePolicyManager import android.app.admin.DevicePolicyManager.* +import android.content.ActivityNotFoundException import android.content.ComponentName import android.content.Context import android.content.Intent import android.content.IntentFilter +import android.content.pm.PackageManager.NameNotFoundException import android.os.Build.VERSION import android.widget.Toast import androidx.activity.ComponentActivity @@ -96,15 +98,19 @@ fun ManagedProfile() { if(VERSION.SDK_INT>=24){CheckBoxItem("跳过加密",{skipEncrypt},{skipEncrypt=!skipEncrypt})} Button( onClick = { - val intent = Intent(ACTION_PROVISION_MANAGED_PROFILE) - if(VERSION.SDK_INT>=23){ - intent.putExtra(EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME,myComponent) - }else{ - intent.putExtra(EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME,"com.binbin.androidowner") + try { + val intent = Intent(ACTION_PROVISION_MANAGED_PROFILE) + if(VERSION.SDK_INT>=23){ + intent.putExtra(EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME,myComponent) + }else{ + intent.putExtra(EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME,"com.binbin.androidowner") + } + if(VERSION.SDK_INT>=24){intent.putExtra(EXTRA_PROVISIONING_SKIP_ENCRYPTION,skipEncrypt)} + if(VERSION.SDK_INT>=33){intent.putExtra(EXTRA_PROVISIONING_ALLOW_OFFLINE,true)} + createManagedProfile.launch(intent) + }catch(e:ActivityNotFoundException){ + Toast.makeText(myContext,"不支持",Toast.LENGTH_SHORT).show() } - if(VERSION.SDK_INT>=24){intent.putExtra(EXTRA_PROVISIONING_SKIP_ENCRYPTION,skipEncrypt)} - if(VERSION.SDK_INT>=33){intent.putExtra(EXTRA_PROVISIONING_ALLOW_OFFLINE,true)} - createManagedProfile.launch(intent) }, modifier = Modifier.fillMaxWidth() ) { diff --git a/app/src/main/java/com/binbin/androidowner/Network.kt b/app/src/main/java/com/binbin/androidowner/Network.kt index 15aa1d7..90e74ea 100644 --- a/app/src/main/java/com/binbin/androidowner/Network.kt +++ b/app/src/main/java/com/binbin/androidowner/Network.kt @@ -19,6 +19,7 @@ import androidx.compose.foundation.layout.* 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.material3.Button import androidx.compose.material3.MaterialTheme.colorScheme @@ -32,6 +33,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalFocusManager +import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.style.TextAlign @@ -114,7 +116,13 @@ fun Network(){ RadioButtonItem("白名单",{selectedPolicyType==WIFI_SSID_POLICY_TYPE_ALLOWLIST},{selectedPolicyType=WIFI_SSID_POLICY_TYPE_ALLOWLIST}) RadioButtonItem("黑名单",{selectedPolicyType==WIFI_SSID_POLICY_TYPE_DENYLIST},{selectedPolicyType=WIFI_SSID_POLICY_TYPE_DENYLIST}) Text("SSID列表:") - Text(text = if(ssidList!=""){ssidList}else{"无"}, style = bodyTextStyle) + if(ssidList!=""){ + SelectionContainer { + Text(text = ssidList, style = bodyTextStyle) + } + }else{ + Text(text = "无", style = bodyTextStyle) + } OutlinedTextField( value = inputSsid, label = { Text("SSID")}, @@ -176,7 +184,7 @@ fun Network(){ DevicePolicyManager.PRIVATE_DNS_SET_ERROR_FAILURE_SETTING to "失败" ) var status by remember{mutableStateOf(dnsStatus[myDpm.getGlobalPrivateDnsMode(myComponent)])} - Text(text = "状态:$status") + Text(text = "当前状态:$status") Button( onClick = { val result = myDpm.setGlobalPrivateDnsModeOpportunistic(myComponent) @@ -199,6 +207,7 @@ fun Network(){ ) Button( onClick = { + focusMgr.clearFocus() val result: Int try{ result = myDpm.setGlobalPrivateDnsModeSpecifiedHost(myComponent,inputHost) @@ -647,7 +656,7 @@ fun Network(){ } } } - Text(text = "这个功能没有被全面测试过", style = bodyTextStyle) + Text(text = stringResource(id = R.string.developing), style = bodyTextStyle) } } Spacer(Modifier.padding(vertical = 30.dp)) diff --git a/app/src/main/java/com/binbin/androidowner/Permissions.kt b/app/src/main/java/com/binbin/androidowner/Permissions.kt index e62ff3a..eb6e824 100644 --- a/app/src/main/java/com/binbin/androidowner/Permissions.kt +++ b/app/src/main/java/com/binbin/androidowner/Permissions.kt @@ -1,6 +1,7 @@ package com.binbin.androidowner import android.app.admin.DevicePolicyManager +import android.content.ActivityNotFoundException import android.content.ComponentName import android.content.Context import android.content.Intent @@ -443,8 +444,12 @@ fun DeviceOwnerInfo( } fun activateDeviceAdmin(inputContext:Context,inputComponent:ComponentName){ - val intent = Intent(DevicePolicyManager.ACTION_ADD_DEVICE_ADMIN) - intent.putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN, inputComponent) - intent.putExtra(DevicePolicyManager.EXTRA_ADD_EXPLANATION, "在这里激活Android Owner") - startActivity(inputContext,intent,null) + try { + val intent = Intent(DevicePolicyManager.ACTION_ADD_DEVICE_ADMIN) + intent.putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN, inputComponent) + intent.putExtra(DevicePolicyManager.EXTRA_ADD_EXPLANATION, "在这里激活Android Owner") + startActivity(inputContext,intent,null) + }catch(e:ActivityNotFoundException){ + Toast.makeText(inputContext,"不支持",Toast.LENGTH_SHORT).show() + } } diff --git a/app/src/main/java/com/binbin/androidowner/Setting.kt b/app/src/main/java/com/binbin/androidowner/Setting.kt index d85eb06..9cc2cbc 100644 --- a/app/src/main/java/com/binbin/androidowner/Setting.kt +++ b/app/src/main/java/com/binbin/androidowner/Setting.kt @@ -29,6 +29,9 @@ fun AppSetting(navCtrl:NavHostController){ val isWear = sharedPref.getBoolean("isWear",false) val bodyTextStyle = if(isWear){typography.bodyMedium}else{typography.bodyLarge} val titleColor = colorScheme.onPrimaryContainer + val pkgInfo = myContext.packageManager.getPackageInfo(myContext.packageName,0) + val verCode = pkgInfo.versionCode + val verName = pkgInfo.versionName Column(modifier = sections()) { Row(modifier = Modifier.fillMaxWidth().padding(horizontal = 3.dp),horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically) { Text(text = "Wear", style = typography.titleLarge, color = titleColor) @@ -59,6 +62,7 @@ fun AppSetting(navCtrl:NavHostController){ modifier = Modifier.padding(start = 8.dp, end = 8.dp, bottom = 12.dp) ) { Text(text = "关于", style = typography.headlineSmall, color = titleColor) + Text(text = "Android owner v$verName ($verCode)", style = bodyTextStyle) Text(text = "使用安卓的Device admin、Device owner 、Profile owner,全方位掌控你的设备", style = bodyTextStyle) Spacer(Modifier.padding(vertical = 4.dp)) Text(text = "这个应用只在AOSP和LineageOS上测试过,不确保每个功能都在其它系统可用,尤其是国内的魔改系统。", style = bodyTextStyle) diff --git a/app/src/main/java/com/binbin/androidowner/User.kt b/app/src/main/java/com/binbin/androidowner/User.kt index 918017f..4b128c1 100644 --- a/app/src/main/java/com/binbin/androidowner/User.kt +++ b/app/src/main/java/com/binbin/androidowner/User.kt @@ -17,6 +17,7 @@ import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape 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.material3.Button import androidx.compose.material3.Checkbox @@ -35,13 +36,10 @@ import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.unit.dp import androidx.core.os.UserManagerCompat - var affiliationID = mutableSetOf() @Composable fun UserManage() { - Column( - modifier = Modifier.verticalScroll(rememberScrollState()) - ) { + Column(modifier = Modifier.verticalScroll(rememberScrollState())) { val myContext = LocalContext.current val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) @@ -97,19 +95,8 @@ fun UserManage() { keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number, imeAction = ImeAction.Done), keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}) ) - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier - .clip(RoundedCornerShape(15.dp)) - .clickable(enabled = VERSION.SDK_INT >= 24 && isDeviceOwner(myDpm)) { useUid = !useUid } - .padding(end = 12.dp) - ){ - Checkbox( - checked = useUid, - onCheckedChange = {useUid=it}, - enabled = VERSION.SDK_INT>=24&& isDeviceOwner(myDpm) - ) - Text(text = "使用UID(不靠谱)",modifier = Modifier.padding(bottom = 2.dp), style = bodyTextStyle) + if(VERSION.SDK_INT>=24&&isDeviceOwner(myDpm)){ + CheckBoxItem(text = "使用UID", checked = {useUid}, operation = {idInput=""; useUid = !useUid}) } if(VERSION.SDK_INT>28){ if(isProfileOwner(myDpm)&&myDpm.isAffiliatedUser){ @@ -224,18 +211,6 @@ fun UserManage() { ) { Text("创建(Owner)") } - if(UserManager.supportsMultipleUsers()&&(VERSION.SDK_INT<34||(VERSION.SDK_INT>=34&&userManager.isAdminUser))){ - Button( - onClick = { - val intent = UserManager.createUserCreationIntent(userName,null,null,null) - createUser.launch(intent) - }, - modifier = Modifier.fillMaxWidth() - ) { - Text("创建(Intent)") - } - Text(text = "尽量用Device owner模式创建,Intent模式可能没有效果", style = bodyTextStyle) - } if(newUserHandle!=null){ Text(text = "新用户的序列号:${userManager.getSerialNumberForUser(newUserHandle)}", style = bodyTextStyle) } } } @@ -259,7 +234,13 @@ fun UserManage() { keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}) ) - Text(text = if(list==""){"无"}else{list}, style = bodyTextStyle) + if(list!=""){ + SelectionContainer { + Text(text = list, style = bodyTextStyle) + } + }else{ + Text(text = "无", style = bodyTextStyle) + } Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween){ Button( onClick = { affiliationID.add(input); refresh() }, @@ -371,7 +352,7 @@ private fun UserSessionMessage(text:String, textField:String, profileOwner:Boole Toast.makeText(myContext, "成功", Toast.LENGTH_SHORT).show() }, enabled = isDeviceOwner(myDpm)||(isProfileOwner(myDpm)&&profileOwner), - modifier = if(isWear){Modifier.fillMaxWidth(0.48F)}else{Modifier.fillMaxWidth(0.65F)} + modifier = Modifier.fillMaxWidth(if(isWear){0.49F}else{0.65F}) ) { Text("应用") } @@ -379,11 +360,11 @@ private fun UserSessionMessage(text:String, textField:String, profileOwner:Boole onClick = { focusMgr.clearFocus() setMsg(null) - msg = if(get()==null){""}else{get().toString()} + msg = get()?.toString() ?: "" Toast.makeText(myContext, "成功", Toast.LENGTH_SHORT).show() }, enabled = isDeviceOwner(myDpm)||(isProfileOwner(myDpm)&&profileOwner), - modifier = Modifier.fillMaxWidth(0.92F) + modifier = Modifier.fillMaxWidth(0.96F) ) { Text("默认") } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c2df3a9..f8622a3 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -103,7 +103,7 @@ 禁止蓝牙分享联系人 用户管理 设置 - VPN常开 + VPN保持打开 实验性功能 屏幕超时 超时后锁屏(毫秒),0为由用户决定