diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 26ad53a..4c771b7 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -7,6 +7,8 @@ + + - + + + + + + + @@ -39,6 +47,10 @@ android:name="android.app.device_admin" android:resource="@xml/device_admin"/> + + + + diff --git a/app/src/main/java/com/binbin/androidowner/ApplicationManage.kt b/app/src/main/java/com/binbin/androidowner/ApplicationManage.kt index e6e4ee9..455fa6f 100644 --- a/app/src/main/java/com/binbin/androidowner/ApplicationManage.kt +++ b/app/src/main/java/com/binbin/androidowner/ApplicationManage.kt @@ -10,6 +10,7 @@ import android.content.pm.PackageInstaller import android.content.pm.PackageManager.NameNotFoundException import android.net.Uri import android.os.Build.VERSION +import android.util.Log import android.widget.Toast import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement @@ -48,13 +49,13 @@ fun ApplicationManage(myDpm:DevicePolicyManager, myComponent:ComponentName,myCon .padding(8.dp), horizontalAlignment = Alignment.CenterHorizontally ) { - Text("以下功能都需要DeviceOwner权限") TextField( value = pkgName, onValueChange = { pkgName = it }, label = { Text("包名") }, + enabled = isDeviceOwner(myDpm)|| isProfileOwner(myDpm), modifier = Modifier .fillMaxWidth() .padding(horizontal = 8.dp) @@ -94,6 +95,41 @@ fun ApplicationManage(myDpm:DevicePolicyManager, myComponent:ComponentName,myCon Text("防卸载") } } + Column( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 5.dp, vertical = 4.dp) + .clip(RoundedCornerShape(14.dp)) + .background(color = MaterialTheme.colorScheme.primaryContainer) + .padding(10.dp) + ) { + Text(text = "许可的输入法", style = MaterialTheme.typography.titleLarge) + val imeList = myDpm.getPermittedInputMethods(myComponent) + var imeListText = "" + if (imeList != null) { + for(eachIme in imeList){ + imeListText += "$eachIme \n" + //Log.e("",eachIme) + } + } + Text(imeListText) + Button( + onClick = { + imeList?.plus(pkgName) + myDpm.setPermittedInputMethods(myComponent, imeList) + } + ) { + Text("设为许可的输入法") + } + Button( + onClick = { + imeList?.remove(pkgName) + myDpm.setPermittedInputMethods(myComponent,imeList) + } + ) { + Text("从列表中移除") + } + } /*Button( onClick = { uninstallPkg(pkgName,myContext) diff --git a/app/src/main/java/com/binbin/androidowner/MainActivity.kt b/app/src/main/java/com/binbin/androidowner/MainActivity.kt index d65350c..005855e 100644 --- a/app/src/main/java/com/binbin/androidowner/MainActivity.kt +++ b/app/src/main/java/com/binbin/androidowner/MainActivity.kt @@ -49,6 +49,7 @@ import com.google.accompanist.systemuicontroller.rememberSystemUiController class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { WindowCompat.setDecorFitsSystemWindows(window, false) + super.onCreate(savedInstanceState) val context = applicationContext val dpm = context.getSystemService(DEVICE_POLICY_SERVICE) as DevicePolicyManager diff --git a/app/src/main/java/com/binbin/androidowner/Password.kt b/app/src/main/java/com/binbin/androidowner/Password.kt index 5e622a3..9d8d550 100644 --- a/app/src/main/java/com/binbin/androidowner/Password.kt +++ b/app/src/main/java/com/binbin/androidowner/Password.kt @@ -8,7 +8,6 @@ import android.content.Intent.FLAG_ACTIVITY_NEW_TASK import android.os.Build.VERSION import android.widget.Toast import androidx.compose.foundation.background -import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -16,7 +15,6 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.KeyboardActions @@ -34,6 +32,7 @@ import androidx.compose.material3.Text import androidx.compose.material3.TextField import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue @@ -83,7 +82,10 @@ fun Password(myDpm:DevicePolicyManager,myComponent:ComponentName,myContext:Conte ) { if(VERSION.SDK_INT>=29){ val pwdComplex = myDpm.passwordComplexity - Text(text = "密码复杂度:$pwdComplex") + Text(text = "当前密码复杂度:$pwdComplex") + } + if(isDeviceOwner(myDpm)|| isProfileOwner(myDpm)){ + Text("密码达到要求:${myDpm.isActivePasswordSufficient}") } val pwdFailedAttempts = myDpm.currentFailedPasswordAttempts Text(text = "密码已错误次数:$pwdFailedAttempts") @@ -204,12 +206,100 @@ fun Password(myDpm:DevicePolicyManager,myComponent:ComponentName,myContext:Conte } } } + PasswordItem(R.string.max_pwd_fail,R.string.max_pwd_fail_desc,R.string.max_pwd_fail_textfield, myDpm,focusMgr,false, {myDpm.getMaximumFailedPasswordsForWipe(null).toString()},{ic -> myDpm.setMaximumFailedPasswordsForWipe(myComponent, ic.toInt()) }) PasswordItem(R.string.pwd_timeout,R.string.pwd_timeout_desc,R.string.pwd_timeout_textfield, myDpm,focusMgr,true, {myDpm.getPasswordExpiration(null).toString()},{ic -> myDpm.setPasswordExpirationTimeout(myComponent, ic.toLong()) }) PasswordItem(R.string.pwd_history,R.string.pwd_history_desc,R.string.pwd_history_textfield,myDpm, focusMgr,true, {myDpm.getPasswordHistoryLength(null).toString()},{ic -> myDpm.setPasswordHistoryLength(myComponent, ic.toInt()) }) + + if(VERSION.SDK_INT>=31){ + Column( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 8.dp, vertical = 4.dp) + .clip(RoundedCornerShape(16.dp)) + .background(color = MaterialTheme.colorScheme.primaryContainer) + .padding(10.dp) + ) { + val passwordComplexity = mapOf( + DevicePolicyManager.PASSWORD_COMPLEXITY_NONE to "无复杂度(允许不设密码)", + DevicePolicyManager.PASSWORD_COMPLEXITY_LOW to "低复杂度(允许图案和连续性)", + DevicePolicyManager.PASSWORD_COMPLEXITY_MEDIUM to "中复杂度(无连续性,至少4位)", + DevicePolicyManager.PASSWORD_COMPLEXITY_HIGH to "高复杂度(无连续性,至少6位)" + ).toList() + var selectedItem by remember{ mutableIntStateOf(passwordComplexity[0].first) } + if(isDeviceOwner(myDpm) || isProfileOwner(myDpm)){ + selectedItem=myDpm.requiredPasswordComplexity + } + Text(text = "密码复杂度要求", style = MaterialTheme.typography.titleLarge) + Text(text = "不是实际密码复杂度") + Text(text = "设置密码复杂度将会取代密码质量") + RadioButtonItem(passwordComplexity[0].second,{selectedItem==passwordComplexity[0].first},{selectedItem=passwordComplexity[0].first}) + RadioButtonItem(passwordComplexity[1].second,{selectedItem==passwordComplexity[1].first},{selectedItem=passwordComplexity[1].first}) + RadioButtonItem(passwordComplexity[2].second,{selectedItem==passwordComplexity[2].first},{selectedItem=passwordComplexity[2].first}) + RadioButtonItem(passwordComplexity[3].second,{selectedItem==passwordComplexity[3].first},{selectedItem=passwordComplexity[3].first}) + Text(text = "连续性:密码重复(6666)或密码递增递减(4321、2468)", modifier = Modifier.padding(vertical = 3.dp)) + Button( + onClick = { + myDpm.requiredPasswordComplexity = selectedItem + Toast.makeText(myContext, "成功", Toast.LENGTH_SHORT).show() + }, + enabled = isDeviceOwner(myDpm)|| isProfileOwner(myDpm) + ) { + Text("应用") + } + } + } + + Column( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 8.dp, vertical = 4.dp) + .clip(RoundedCornerShape(16.dp)) + .background(color = MaterialTheme.colorScheme.primaryContainer) + .padding(10.dp) + ) { + val passwordQuality = mapOf( + DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED to "未指定", + DevicePolicyManager.PASSWORD_QUALITY_SOMETHING to "需要密码或图案,不管复杂度", + DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC to "至少1个字母", + DevicePolicyManager.PASSWORD_QUALITY_NUMERIC to "至少1个数字", + DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC to "数字字母各至少一个", + DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK to "生物识别(弱)", + DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX to "复杂数字(无连续性)", + DevicePolicyManager.PASSWORD_QUALITY_COMPLEX to "自定义", + ).toList() + var selectedItem by remember{ mutableIntStateOf(passwordQuality[0].first) } + if(isDeviceOwner(myDpm) || isProfileOwner(myDpm)){ + selectedItem=myDpm.getPasswordQuality(myComponent) + } + Text(text = "密码质量要求", style = MaterialTheme.typography.titleLarge) + Text(text = "不是实际密码质量") + if(VERSION.SDK_INT>=31){ + Text(text = "已弃用,请使用上面的”密码复杂度要求“", color = MaterialTheme.colorScheme.error) + } + RadioButtonItem(passwordQuality[0].second,{selectedItem==passwordQuality[0].first},{selectedItem=passwordQuality[0].first}) + RadioButtonItem(passwordQuality[1].second,{selectedItem==passwordQuality[1].first},{selectedItem=passwordQuality[1].first}) + RadioButtonItem(passwordQuality[2].second,{selectedItem==passwordQuality[2].first},{selectedItem=passwordQuality[2].first}) + RadioButtonItem(passwordQuality[3].second,{selectedItem==passwordQuality[3].first},{selectedItem=passwordQuality[3].first}) + RadioButtonItem(passwordQuality[4].second,{selectedItem==passwordQuality[4].first},{selectedItem=passwordQuality[4].first}) + RadioButtonItem(passwordQuality[5].second,{selectedItem==passwordQuality[5].first},{selectedItem=passwordQuality[5].first}) + RadioButtonItem(passwordQuality[6].second,{selectedItem==passwordQuality[6].first},{selectedItem=passwordQuality[6].first}) + RadioButtonItem(passwordQuality[7].second,{selectedItem==passwordQuality[7].first},{selectedItem=passwordQuality[7].first}) + Text(text = "连续性:密码重复(6666)或密码递增递减(4321、2468)", modifier = Modifier.padding(vertical = 3.dp)) + Button( + onClick = { + myDpm.setPasswordQuality(myComponent,selectedItem) + Toast.makeText(myContext, "成功", Toast.LENGTH_SHORT).show() + }, + enabled = isDeviceOwner(myDpm) || isProfileOwner(myDpm) + ) { + Text("应用") + } + } + Spacer(Modifier.padding(vertical = 20.dp)) } } diff --git a/app/src/main/java/com/binbin/androidowner/Permissions.kt b/app/src/main/java/com/binbin/androidowner/Permissions.kt index 7154e6e..d805fff 100644 --- a/app/src/main/java/com/binbin/androidowner/Permissions.kt +++ b/app/src/main/java/com/binbin/androidowner/Permissions.kt @@ -16,6 +16,8 @@ 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.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.text.selection.SelectionContainer import androidx.compose.foundation.verticalScroll import androidx.compose.material3.Button @@ -33,6 +35,8 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.focus.FocusManager 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.unit.dp import androidx.core.content.ContextCompat.startActivity import androidx.navigation.NavGraph.Companion.findStartDestination @@ -79,9 +83,9 @@ fun DpmPermissions(myDpm: DevicePolicyManager, myComponent: ComponentName, myCon Text("撤销") } }else{ - /*Button(onClick = { activateDeviceAdmin(myContext,myComponent) }) { + Button(onClick = { activateDeviceAdmin(myContext,myComponent) }) { Text("激活") - }*/ + } } } if(!isda){ @@ -220,18 +224,96 @@ fun DpmPermissions(myDpm: DevicePolicyManager, myComponent: ComponentName, myCon modifier = Modifier .fillMaxWidth() .padding(vertical = 5.dp) - .clip(RoundedCornerShape(15)) + .clip(RoundedCornerShape(15.dp)) .background(color = MaterialTheme.colorScheme.primaryContainer) .padding(8.dp) ) { Text(text = "设备信息", style = MaterialTheme.typography.titleLarge) val orgDevice = myDpm.isOrganizationOwnedDeviceWithManagedProfile Text("由组织拥有的受管理资料设备:$orgDevice") - Text("Managed profile: ${myDpm.isManagedProfile(myComponent)}") + if(isDeviceOwner(myDpm)|| isProfileOwner(myDpm)){ + Text("Managed profile: ${myDpm.isManagedProfile(myComponent)}") + } if(VERSION.SDK_INT>=34&&(isDeviceOwner(myDpm)||(isProfileOwner(myDpm)&&myDpm.isOrganizationOwnedDeviceWithManagedProfile))){ val financed = myDpm.isDeviceFinanced Text("企业资产 : $financed") } + if(VERSION.SDK_INT>=33){ + Text("最小WiFi安全等级:${myDpm.minimumRequiredWifiSecurityLevel}") + Text("设备策略管理器角色:${myDpm.devicePolicyManagementRoleHolderPackage}") + } + } + } + if(VERSION.SDK_INT>=31&&(isProfileOwner(myDpm)|| isDeviceOwner(myDpm))){ + Column( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 5.dp) + .clip(RoundedCornerShape(15.dp)) + .background(color = MaterialTheme.colorScheme.primaryContainer) + .padding(8.dp) + ) { + val specificId = myDpm.enrollmentSpecificId + Text(text = "设备唯一标识码", style = MaterialTheme.typography.titleLarge) + Text("(恢复出厂设置不变)") + Text("(如果下面没有一长串数字字母组合,说明你的设备不支持)") + Text(specificId) + Button(onClick = {myDpm.setOrganizationId(specificId)}, enabled = specificId!="") { + Text("设置为组织ID") + } + } + } + if(isDeviceOwner(myDpm) || isProfileOwner(myDpm)){ + Column( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 5.dp) + .clip(RoundedCornerShape(15.dp)) + .background(color = MaterialTheme.colorScheme.primaryContainer) + .padding(8.dp) + ) { + Text(text = "不受控制的账号类型", style = MaterialTheme.typography.titleLarge) + Text("作用未知") + var noManageAccount = myDpm.accountTypesWithManagementDisabled?.toMutableList() + var accountlist by remember{ mutableStateOf("") } + val refreshList = { + accountlist = "" + if (noManageAccount != null) { + for(eachAccount in noManageAccount!!){ + accountlist+="$eachAccount \n" + } + } + + } + refreshList() + if(accountlist!=""){ + Text(accountlist) + }else{ + Text("列表为空 \n") + } + var inputText by remember{ mutableStateOf("") } + TextField( + value = inputText, + onValueChange = {inputText=it}, + label = {Text("账号类型")}, + modifier = Modifier.padding(bottom = 4.dp), + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), + keyboardActions = KeyboardActions(onDone = {focusManager.clearFocus()}) + ) + Button(onClick={focusManager.clearFocus() + myDpm.setAccountManagementDisabled(myComponent,inputText,true) + noManageAccount=myDpm.accountTypesWithManagementDisabled?.toMutableList() + refreshList() + }){ + Text("添加至列表") + } + Button(onClick={focusManager.clearFocus() + myDpm.setAccountManagementDisabled(myComponent,inputText,false) + noManageAccount=myDpm.accountTypesWithManagementDisabled?.toMutableList() + refreshList() + }){ + Text("从列表中移除") + } } } diff --git a/app/src/main/java/com/binbin/androidowner/User.kt b/app/src/main/java/com/binbin/androidowner/User.kt index 81bab3d..027b246 100644 --- a/app/src/main/java/com/binbin/androidowner/User.kt +++ b/app/src/main/java/com/binbin/androidowner/User.kt @@ -1,10 +1,13 @@ package com.binbin.androidowner -import android.app.ActivityManager +import android.app.Activity import android.app.admin.DevicePolicyManager import android.content.ComponentName import android.content.Context +import android.content.Intent +import android.content.Intent.FLAG_ACTIVITY_NEW_TASK import android.os.Build.VERSION +import android.os.Build.VERSION_CODES import android.os.UserHandle import android.os.UserManager import android.widget.Toast @@ -34,8 +37,11 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.unit.dp +import androidx.core.app.ActivityCompat.startActivityForResult +import androidx.core.content.ContextCompat.startActivity import androidx.core.os.UserManagerCompat + @Composable fun UserManage(myDpm:DevicePolicyManager,myComponent:ComponentName,myContext: Context){ Column( @@ -66,8 +72,10 @@ fun UserManage(myDpm:DevicePolicyManager,myComponent:ComponentName,myContext: Co if (VERSION.SDK_INT >= 28) { val logoutable = myDpm.isLogoutEnabled Text(text = "用户可以退出 : $logoutable") - val ephemeralUser = myDpm.isEphemeralUser(myComponent) - Text(text = "临时用户: $ephemeralUser") + if(isDeviceOwner(myDpm)|| isProfileOwner(myDpm)){ + val ephemeralUser = myDpm.isEphemeralUser(myComponent) + Text(text = "临时用户: $ephemeralUser") + } val affiliatedUser = myDpm.isAffiliatedUser Text(text = "次级用户: $affiliatedUser") } @@ -94,16 +102,17 @@ fun UserManage(myDpm:DevicePolicyManager,myComponent:ComponentName,myContext: Co if(resultForLogout!=-1){ Text(userOperationResultCode(resultForLogout)) } + Button(onClick = {myDpm.switchUser(myComponent,currentUser)}, enabled = isDeviceOwner(myDpm)) { + Text("切换用户") + } Button(onClick = {resultForStop = myDpm.stopUser(myComponent,currentUser)}, enabled = isDeviceOwner(myDpm)) { Text("停止用户") } if(resultForStop!=-1){ Text(userOperationResultCode(resultForStop)) } - if(isProfileOwner(myDpm)){ - Button(onClick = {myDpm.setProfileEnabled(myComponent)}) { - Text(text = "启用资料") - } + Button(onClick = {myDpm.setProfileEnabled(myComponent)}, enabled = isProfileOwner(myDpm)||isDeviceOwner(myDpm)) { + Text(text = "启用资料") } } Button( @@ -119,6 +128,10 @@ fun UserManage(myDpm:DevicePolicyManager,myComponent:ComponentName,myContext: Co ) { Text("移除用户") } + Button(onClick = { createWorkProfile(myContext,myComponent) }) { + Text("创建工作资料") + } + Text("可能无法创建工作资料") } if(VERSION.SDK_INT>=24){ @@ -250,3 +263,21 @@ fun userOperationResultCode(result:Int): String { else->"Unknown" } } + +private fun createWorkProfile(myContext: Context,myComponent: ComponentName) { + val intent = Intent(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE) + intent.putExtra(DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME, "com.binbin.androidowner") + intent.putExtra(DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME, myComponent) + if (VERSION.SDK_INT >= VERSION_CODES.TIRAMISU) { + intent.putExtra(DevicePolicyManager.EXTRA_PROVISIONING_ALLOW_OFFLINE, true) + } + intent.putExtra(DevicePolicyManager.EXTRA_ADD_EXPLANATION,"hello") + /* + intent.putExtra(DevicePolicyManager.EXTRA_PROVISIONING_SKIP_USER_CONSENT, false) + val adminExtras = PersistableBundle() + if (adminExtras.size() > 0) { + intent.putExtra(DevicePolicyManager.EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE, adminExtras) + }*/ + intent.setFlags(FLAG_ACTIVITY_NEW_TASK) + startActivity(myContext,intent,null) +}