diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 712b992..1a68182 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -47,7 +47,7 @@ @@ -65,7 +65,7 @@ diff --git a/app/src/main/java/com/binbin/androidowner/ApplicationManage.kt b/app/src/main/java/com/binbin/androidowner/ApplicationManage.kt deleted file mode 100644 index 2162102..0000000 --- a/app/src/main/java/com/binbin/androidowner/ApplicationManage.kt +++ /dev/null @@ -1,727 +0,0 @@ -package com.binbin.androidowner - -import android.app.PendingIntent -import android.app.admin.DevicePolicyManager -import android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT -import android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED -import android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED -import android.app.admin.PackagePolicy -import android.app.admin.PackagePolicy.PACKAGE_POLICY_ALLOWLIST -import android.app.admin.PackagePolicy.PACKAGE_POLICY_ALLOWLIST_AND_SYSTEM -import android.app.admin.PackagePolicy.PACKAGE_POLICY_BLOCKLIST -import android.content.ComponentName -import android.content.Context -import android.content.Intent -import android.content.pm.PackageInstaller -import android.content.pm.PackageManager.NameNotFoundException -import android.net.Uri -import android.os.Build.VERSION -import android.os.Looper -import android.provider.Settings -import android.widget.Toast -import androidx.activity.ComponentActivity -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.animateContentSize -import androidx.compose.foundation.focusable -import androidx.compose.foundation.horizontalScroll -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 -import androidx.compose.material3.MaterialTheme.typography -import androidx.compose.runtime.* -import androidx.compose.runtime.saveable.rememberSaveable -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 -import androidx.compose.ui.unit.dp -import androidx.core.content.ContextCompat.startActivity -import kotlinx.coroutines.delay -import java.io.IOException -import java.io.InputStream -import java.util.concurrent.Executors - -private var credentialList = mutableSetOf() -private var crossProfilePkg = mutableSetOf() -private var keepUninstallPkg = mutableListOf() -private var permittedIme = mutableListOf() -private var permittedAccessibility = mutableListOf() -@Composable -fun ApplicationManage(){ - val myContext = LocalContext.current - val focusMgr = LocalFocusManager.current - val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager - val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) - var pkgName by rememberSaveable{ mutableStateOf("") } - val sharedPref = LocalContext.current.getSharedPreferences("data", Context.MODE_PRIVATE) - val isWear = sharedPref.getBoolean("isWear",false) - val bodyTextStyle = if(isWear){ typography.bodyMedium }else{ typography.bodyLarge } - val titleColor = colorScheme.onPrimaryContainer - Column{ - if(!isWear){ - TextField( - value = pkgName, - onValueChange = { pkgName = it }, - label = { Text(stringResource(R.string.package_name)) }, - modifier = Modifier.fillMaxWidth().padding(horizontal = 4.dp), - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Ascii, imeAction = ImeAction.Done), - keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}) - ) - } - Column(modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState())) { - if(isWear){ - TextField( - value = pkgName, - onValueChange = { pkgName = it }, - label = { Text(stringResource(R.string.package_name)) }, - modifier = Modifier.fillMaxWidth().padding(horizontal = 2.dp,vertical = 2.dp), - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Ascii, imeAction = ImeAction.Done), - keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}) - ) - }else{Spacer(Modifier.padding(vertical = 2.dp))} - if(VERSION.SDK_INT>=24&&isProfileOwner(myDpm)&&myDpm.isManagedProfile(myComponent)){ - Text(text = stringResource(R.string.scope_is_work_profile), style = bodyTextStyle, textAlign = TextAlign.Center,modifier = Modifier.fillMaxWidth().padding(vertical = 2.dp)) - } - - Button( - onClick = { - val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS) - intent.setData(Uri.parse("package:$pkgName")) - startActivity(myContext,intent,null) - }, - modifier = Modifier.fillMaxWidth().padding(horizontal = 8.dp) - ){ - Text(stringResource(R.string.app_info)) - } - - if(VERSION.SDK_INT>=24&&(isDeviceOwner(myDpm)||isProfileOwner(myDpm))){ - AppManageItem( - R.string.suspend,R.string.place_holder, - {try{ myDpm.isPackageSuspended(myComponent,pkgName) } - catch(e:NameNotFoundException){ false } - catch(e:IllegalArgumentException){ false }} - ) { b -> myDpm.setPackagesSuspended(myComponent, arrayOf(pkgName), b) } - } - if(isDeviceOwner(myDpm)||isProfileOwner(myDpm)){ - AppManageItem(R.string.hide,R.string.isapphidden_desc, {myDpm.isApplicationHidden(myComponent,pkgName)}) {b-> myDpm.setApplicationHidden(myComponent, pkgName, b)} - } - if(VERSION.SDK_INT>=24&&(isDeviceOwner(myDpm)||isProfileOwner(myDpm))){ - AppManageItem( - 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) { - Toast.makeText(myContext, myContext.getString(R.string.unsupported), Toast.LENGTH_SHORT).show() - } catch(e: NameNotFoundException) { - Toast.makeText(myContext, myContext.getString(R.string.not_installed), Toast.LENGTH_SHORT).show() - } - } - } - - if(isDeviceOwner(myDpm)||isProfileOwner(myDpm)){ - Column(modifier = sections()){ - var state by remember{mutableStateOf(myDpm.isUninstallBlocked(myComponent,pkgName))} - Text(text = stringResource(R.string.block_uninstall), style = typography.titleLarge, color = titleColor) - Text(stringResource(R.string.current_state, stringResource(if(state){R.string.enabled}else{R.string.disabled}))) - Text(text = stringResource(R.string.sometimes_get_wrong_block_uninstall_state), style = bodyTextStyle) - Row(horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.fillMaxWidth()) { - Button( - onClick = { - focusMgr.clearFocus() - myDpm.setUninstallBlocked(myComponent,pkgName,true) - Toast.makeText(myContext, myContext.getString(R.string.success), Toast.LENGTH_SHORT).show() - state = myDpm.isUninstallBlocked(myComponent,pkgName) - }, - modifier = Modifier.fillMaxWidth(0.49F) - ) { - Text(stringResource(R.string.enable)) - } - Button( - onClick = { - focusMgr.clearFocus() - myDpm.setUninstallBlocked(myComponent,pkgName,false) - Toast.makeText(myContext, myContext.getString(R.string.success), Toast.LENGTH_SHORT).show() - state = myDpm.isUninstallBlocked(myComponent,pkgName) - }, - modifier = Modifier.fillMaxWidth(0.96F) - ){ - Text(stringResource(R.string.disable)) - } - } - } - } - - if(VERSION.SDK_INT>=30&&(isDeviceOwner(myDpm)||isProfileOwner(myDpm))){ - Column(modifier = sections()){ - var pkgList = myDpm.getUserControlDisabledPackages(myComponent) - var listText by remember{mutableStateOf("")} - val refresh = { - pkgList = myDpm.getUserControlDisabledPackages(myComponent) - listText = "" - var count = pkgList.size - for(pkg in pkgList){ count-=1; listText+=pkg; if(count>0){listText+="\n"} } - } - var inited by remember{mutableStateOf(false)} - if(!inited){refresh();inited=true} - Text(text = stringResource(R.string.ucd), style = typography.titleLarge, color = titleColor) - Text(text = stringResource(R.string.ucd_desc), style = bodyTextStyle) - Text(text = stringResource(R.string.app_list_is)) - SelectionContainer(modifier = Modifier.horizontalScroll(rememberScrollState()).animateContentSize(scrollAnim())){ - Text(text = if(listText==""){stringResource(R.string.none)}else{listText}, style = bodyTextStyle, color = titleColor) - } - Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween){ - Button( - onClick = { - if(pkgName!=""){ - pkgList.add(pkgName) - myDpm.setUserControlDisabledPackages(myComponent,pkgList) - refresh() - }else{ - Toast.makeText(myContext, myContext.getString(R.string.fail), Toast.LENGTH_SHORT).show() - } - }, - modifier = Modifier.fillMaxWidth(0.49F) - ){ - Text(stringResource(R.string.add)) - } - Button( - onClick = { - val result = if(pkgName!=""){pkgList.remove(pkgName)}else{false} - if(result){ - myDpm.setUserControlDisabledPackages(myComponent,pkgList) - refresh() - }else{ - Toast.makeText(myContext, myContext.getString(R.string.not_exist), Toast.LENGTH_SHORT).show() - } - }, - modifier = Modifier.fillMaxWidth(0.96F) - ){ - Text(stringResource(R.string.remove)) - } - } - Button( - onClick = { myDpm.setUserControlDisabledPackages(myComponent, listOf()); refresh() }, - modifier = Modifier.fillMaxWidth() - ){ - Text(stringResource(R.string.clear_list)) - } - } - } - - if(VERSION.SDK_INT>=23&&(isDeviceOwner(myDpm)||isProfileOwner(myDpm))){ - val grantState = mapOf( - PERMISSION_GRANT_STATE_DEFAULT to stringResource(R.string.decide_by_user), - PERMISSION_GRANT_STATE_GRANTED to stringResource(R.string.granted), - PERMISSION_GRANT_STATE_DENIED to stringResource(R.string.denied) - ) - Column(modifier = sections()){ - var inputPermission by remember{mutableStateOf("android.permission.")} - var currentState by remember{mutableStateOf(grantState[myDpm.getPermissionGrantState(myComponent,pkgName,inputPermission)])} - Text(text = stringResource(R.string.permission_manage), style = typography.titleLarge, color = titleColor) - OutlinedTextField( - value = inputPermission, - label = { Text(stringResource(R.string.permission))}, - onValueChange = {inputPermission = it}, - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Ascii, imeAction = ImeAction.Done), - keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}), - modifier = Modifier.focusable().fillMaxWidth().padding(vertical = 2.dp) - ) - Text(stringResource(R.string.current_state, currentState?:stringResource(R.string.unknown)), style = bodyTextStyle) - Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween){ - Button( - onClick = { - myDpm.setPermissionGrantState(myComponent,pkgName,inputPermission, PERMISSION_GRANT_STATE_GRANTED) - currentState = grantState[myDpm.getPermissionGrantState(myComponent,pkgName,inputPermission)] - }, - modifier = Modifier.fillMaxWidth(0.49F) - ) { - Text(stringResource(R.string.grant)) - } - Button( - onClick = { - myDpm.setPermissionGrantState(myComponent,pkgName,inputPermission, PERMISSION_GRANT_STATE_DENIED) - currentState = grantState[myDpm.getPermissionGrantState(myComponent,pkgName,inputPermission)] - }, - Modifier.fillMaxWidth(0.96F) - ) { - Text(stringResource(R.string.deny)) - } - } - Button( - onClick = { - myDpm.setPermissionGrantState(myComponent,pkgName,inputPermission, PERMISSION_GRANT_STATE_DEFAULT) - currentState = grantState[myDpm.getPermissionGrantState(myComponent,pkgName,inputPermission)] - }, - modifier = Modifier.fillMaxWidth() - ) { - Text(stringResource(R.string.decide_by_user)) - } - } - } - - if(VERSION.SDK_INT>=30&&isProfileOwner(myDpm)&&myDpm.isManagedProfile(myComponent)){ - Column(modifier = sections()){ - Text(text = stringResource(R.string.cross_profile_package), style = typography.titleLarge, color = titleColor) - var list by remember{mutableStateOf("")} - val refresh = { - crossProfilePkg = myDpm.getCrossProfilePackages(myComponent) - list = "" - var count = crossProfilePkg.size - for(each in crossProfilePkg){ count-=1; list+=each; if(count>0){list+="\n"} } - } - var inited by remember{mutableStateOf(false)} - if(!inited){refresh();inited=true} - SelectionContainer(modifier = Modifier.horizontalScroll(rememberScrollState()).animateContentSize(scrollAnim())){ - Text(text = if(list==""){stringResource(R.string.none)}else{list}, style = bodyTextStyle, color = titleColor) - } - Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween){ - Button( - onClick = { - if(pkgName!=""){crossProfilePkg.add(pkgName)} - myDpm.setCrossProfilePackages(myComponent, crossProfilePkg) - refresh() - }, - modifier = Modifier.fillMaxWidth(0.49F) - ) { - Text(stringResource(R.string.add)) - } - Button( - onClick = { - if(pkgName!=""){crossProfilePkg.remove(pkgName)} - myDpm.setCrossProfilePackages(myComponent, crossProfilePkg) - refresh() - }, - modifier = Modifier.fillMaxWidth(0.96F) - ) { - Text(stringResource(R.string.remove)) - } - } - } - } - - if(isProfileOwner(myDpm)){ - Column(modifier = sections()){ - var pkgList: MutableList - var list by remember{mutableStateOf("")} - val refresh = { - pkgList = myDpm.getCrossProfileWidgetProviders(myComponent) - list = "" - var count = pkgList.size - for(each in pkgList){ count-=1; list+=each; if(count>0){list+="\n"}} - } - var inited by remember{mutableStateOf(false)} - if(!inited){refresh();inited=true} - Text(text = stringResource(R.string.cross_profile_widget), style = typography.titleLarge, color = titleColor) - Text(text = stringResource(R.string.cross_profile_widget_desc), style = bodyTextStyle) - SelectionContainer(modifier = Modifier.horizontalScroll(rememberScrollState()).animateContentSize(scrollAnim())){ - Text(text = if(list==""){stringResource(R.string.none)}else{list}, style = bodyTextStyle, color = titleColor) - } - Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween){ - Button( - onClick = { - if(pkgName!=""){myDpm.addCrossProfileWidgetProvider(myComponent,pkgName)} - refresh() - }, - modifier = Modifier.fillMaxWidth(0.49F) - ){ - Text(stringResource(R.string.add)) - } - Button( - onClick = { - if(pkgName!=""){myDpm.removeCrossProfileWidgetProvider(myComponent,pkgName)} - refresh() - }, - modifier = Modifier.fillMaxWidth(0.96F) - ){ - Text(stringResource(R.string.remove)) - } - } - } - } - - if(VERSION.SDK_INT>=34&&isDeviceOwner(myDpm)){ - var policy:PackagePolicy? - var policyType by remember{mutableIntStateOf(-1)} - var credentialListText by remember{mutableStateOf("")} - val refreshPolicy = { - policy = myDpm.credentialManagerPolicy - policyType = policy?.policyType ?: -1 - credentialList = policy?.packageNames ?: mutableSetOf() - credentialList = credentialList.toMutableSet() - } - val refreshText = { - credentialListText = "" - var count = credentialList.size - for(item in credentialList){ count-=1; credentialListText+=item; if(count>0){credentialListText+="\n"} } - } - var inited by remember{mutableStateOf(false)} - if(!inited){refreshPolicy(); refreshText(); inited = true} - Column(modifier = sections()){ - Text(text = stringResource(R.string.credential_manage_policy), style = typography.titleLarge, color = titleColor) - RadioButtonItem(stringResource(R.string.none),{policyType==-1},{policyType=-1}) - RadioButtonItem(stringResource(R.string.blacklist),{policyType==PACKAGE_POLICY_BLOCKLIST},{policyType=PACKAGE_POLICY_BLOCKLIST}) - RadioButtonItem(stringResource(R.string.whitelist),{policyType==PACKAGE_POLICY_ALLOWLIST},{policyType=PACKAGE_POLICY_ALLOWLIST}) - RadioButtonItem(stringResource(R.string.whitelist_and_system_app),{policyType==PACKAGE_POLICY_ALLOWLIST_AND_SYSTEM},{policyType=PACKAGE_POLICY_ALLOWLIST_AND_SYSTEM}) - AnimatedVisibility(policyType!=-1) { - Column { - Text("应用列表") - SelectionContainer(modifier = Modifier.horizontalScroll(rememberScrollState()).animateContentSize(scrollAnim())){ - Text(text = if(credentialListText!=""){ credentialListText }else{ stringResource(R.string.none) }, style = bodyTextStyle, color = titleColor) - } - Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween){ - Button( - onClick = { - if(pkgName!=""){credentialList.add(pkgName)} - refreshText() - }, - modifier = Modifier.fillMaxWidth(0.49F) - ) { - Text(stringResource(R.string.add)) - } - Button( - onClick = { - if(pkgName!=""){credentialList.remove(pkgName)} - refreshText() - }, - modifier = Modifier.fillMaxWidth(0.96F) - ) { - Text(stringResource(R.string.remove)) - } - } - } - } - Button( - onClick = { - focusMgr.clearFocus() - try{ - if(policyType!=-1&&credentialList.isNotEmpty()){ - myDpm.credentialManagerPolicy = PackagePolicy(policyType,credentialList) - }else{ - myDpm.credentialManagerPolicy = null - } - Toast.makeText(myContext, myContext.getString(R.string.success), Toast.LENGTH_SHORT).show() - }catch(e:java.lang.IllegalArgumentException){ - Toast.makeText(myContext, myContext.getString(R.string.fail), Toast.LENGTH_SHORT).show() - }finally { - refreshPolicy() - refreshText() - } - }, - modifier = Modifier.fillMaxWidth() - ) { - Text(stringResource(R.string.apply)) - } - } - } - - if(isProfileOwner(myDpm)||isDeviceOwner(myDpm)){ - Column(modifier = sections()) { - Text(text = stringResource(R.string.permitted_accessibility_app), style = typography.titleLarge, color = titleColor) - var listText by remember{ mutableStateOf("") } - val refreshList = { - listText = "" - var count = permittedAccessibility.size - for(eachAccessibility in permittedAccessibility){ count-=1; listText+=eachAccessibility; if(count>0){listText+="\n"} } - } - var inited by remember{mutableStateOf(false)} - if(!inited){ - val getList = myDpm.getPermittedAccessibilityServices(myComponent) - if(getList!=null){ permittedAccessibility = getList } - refreshList(); inited=true - } - SelectionContainer(modifier = Modifier.horizontalScroll(rememberScrollState()).animateContentSize(scrollAnim())){ - Text(text = if(listText==""){stringResource(R.string.none)}else{listText}, style = bodyTextStyle, color = titleColor) - } - Row(modifier = Modifier.fillMaxWidth(),horizontalArrangement = Arrangement.SpaceBetween){ - Button( - onClick = { permittedAccessibility.add(pkgName); refreshList()}, - modifier = Modifier.fillMaxWidth(0.49F) - ) { - Text(stringResource(R.string.add)) - } - Button( - onClick = { permittedAccessibility.remove(pkgName); refreshList() }, - modifier = Modifier.fillMaxWidth(0.96F) - ) { - Text(stringResource(R.string.remove)) - } - } - Button( - onClick = { - focusMgr.clearFocus() - Toast.makeText(myContext, if(myDpm.setPermittedAccessibilityServices(myComponent, permittedAccessibility)){"成功"}else{"失败"}, Toast.LENGTH_SHORT).show() - val getList = myDpm.getPermittedAccessibilityServices(myComponent) - if(getList!=null){ permittedAccessibility = getList } - refreshList() - }, - modifier = Modifier.fillMaxWidth() - ) { - Text(text = stringResource(R.string.apply)) - } - } - } - - if(isDeviceOwner(myDpm)||isProfileOwner(myDpm)){ - Column(modifier = sections()) { - Text(text = stringResource(R.string.permitted_ime), style = typography.titleLarge, color = titleColor) - var imeListText by remember{ mutableStateOf("") } - val refreshList = { - imeListText = "" - for(eachIme in permittedIme){ imeListText += "$eachIme \n" } - } - var inited by remember{mutableStateOf(false)} - if(!inited){ - val getList = myDpm.getPermittedInputMethods(myComponent) - if(getList!=null){ permittedIme = getList } - refreshList();inited=true - } - SelectionContainer(modifier = Modifier.horizontalScroll(rememberScrollState()).animateContentSize(scrollAnim())){ - Text(text = if(imeListText==""){stringResource(R.string.none)}else{imeListText}, style = bodyTextStyle, color = titleColor) - } - Row(modifier = Modifier.fillMaxWidth(),horizontalArrangement = Arrangement.SpaceBetween){ - Button( - onClick = { permittedIme.add(pkgName); refreshList() }, - modifier = Modifier.fillMaxWidth(0.49F) - ) { - Text(stringResource(R.string.add)) - } - Button( - onClick = { permittedIme.remove(pkgName); refreshList()}, - modifier = Modifier.fillMaxWidth(0.96F) - ) { - Text(stringResource(R.string.remove)) - } - } - Button( - onClick = { - Toast.makeText(myContext, if(myDpm.setPermittedInputMethods(myComponent, permittedIme)){"成功"}else{"失败"}, Toast.LENGTH_SHORT).show() - val getList = myDpm.getPermittedInputMethods(myComponent) - if(getList!=null){ permittedIme = getList } - refreshList() - }, - modifier = Modifier.fillMaxWidth() - ) { - Text(stringResource(R.string.apply)) - } - } - } - - if(VERSION.SDK_INT>=28&&isDeviceOwner(myDpm)){ - Column(modifier = sections()){ - Text(text = stringResource(R.string.keep_uninstalled_pkgs), style = typography.titleLarge, color = titleColor) - var listText by remember{mutableStateOf("")} - val refresh = { - listText = "" - var count = keepUninstallPkg.size - for(each in keepUninstallPkg){ count-=1; listText+=each; if(count>0){listText+="\n"} } - } - var inited by remember{mutableStateOf(false)} - if(!inited){ - val getList = myDpm.getKeepUninstalledPackages(myComponent) - if(getList!=null){ keepUninstallPkg = getList } - refresh(); inited=true - } - SelectionContainer(modifier = Modifier.horizontalScroll(rememberScrollState()).animateContentSize(scrollAnim())){ - Text(text = if(listText==""){stringResource(R.string.none)}else{listText}, style = bodyTextStyle, color = titleColor) - } - Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween){ - Button( - onClick = { - keepUninstallPkg.add(pkgName) - refresh() - }, - modifier = Modifier.fillMaxWidth(0.49F) - ){ - Text(stringResource(R.string.add)) - } - Button( - onClick = { - keepUninstallPkg.remove(pkgName) - refresh() - }, - modifier = Modifier.fillMaxWidth(0.96F) - ){ - Text(stringResource(R.string.remove)) - } - } - Button( - onClick = { - focusMgr.clearFocus() - myDpm.setKeepUninstalledPackages(myComponent, keepUninstallPkg) - val getList = myDpm.getKeepUninstalledPackages(myComponent) - if(getList!=null){ keepUninstallPkg = getList } - Toast.makeText(myContext, myContext.getString(R.string.success), Toast.LENGTH_SHORT).show() - }, - modifier = Modifier.fillMaxWidth() - ){ - Text(stringResource(R.string.apply)) - } - } - } - - if(VERSION.SDK_INT>=28){ - Button( - onClick = { - val executor = Executors.newCachedThreadPool() - val onClear = DevicePolicyManager.OnClearApplicationUserDataListener { pkg: String, succeed: Boolean -> - Looper.prepare() - focusMgr.clearFocus() - val toastText = if(pkg!=""){"$pkg\n"}else{""} + myContext.getString(R.string.clear_data) + myContext.getString(if(succeed){R.string.success}else{R.string.fail}) - Toast.makeText(myContext, toastText, Toast.LENGTH_SHORT).show() - Looper.loop() - } - myDpm.clearApplicationUserData(myComponent,pkgName,executor,onClear) - }, - enabled = isDeviceOwner(myDpm)||isProfileOwner(myDpm), - modifier = Modifier.fillMaxWidth().padding(horizontal = 10.dp) - ) { - Text(stringResource(R.string.clear_app_data)) - } - } - - if(VERSION.SDK_INT>=34){ - Button( - onClick = { - try{ - myDpm.setDefaultDialerApplication(pkgName) - Toast.makeText(myContext, myContext.getString(R.string.success), Toast.LENGTH_SHORT).show() - }catch(e:IllegalArgumentException){ - Toast.makeText(myContext, myContext.getString(R.string.fail), Toast.LENGTH_SHORT).show() - } - }, - enabled = isDeviceOwner(myDpm)||isProfileOwner(myDpm), - modifier = Modifier.fillMaxWidth().padding(horizontal = 10.dp) - ) { - Text(stringResource(R.string.set_default_dialer)) - } - } - - Column(modifier = sections()){ - Text(text = stringResource(R.string.uninstall_app), style = typography.titleLarge, color = titleColor) - Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween){ - Button( - onClick = { - val intent = Intent(myContext,PackageInstallerReceiver::class.java) - val intentSender = PendingIntent.getBroadcast(myContext, 8, intent, PendingIntent.FLAG_IMMUTABLE).intentSender - val pkgInstaller = myContext.packageManager.packageInstaller - pkgInstaller.uninstall(pkgName, intentSender) - }, - modifier = Modifier.fillMaxWidth(0.49F) - ) { - Text(stringResource(R.string.silent_uninstall)) - } - Button( - onClick = { - val intent = Intent(Intent.ACTION_UNINSTALL_PACKAGE) - intent.setData(Uri.parse("package:$pkgName")) - myContext.startActivity(intent) - }, - modifier = Modifier.fillMaxWidth(0.96F) - ) { - Text(stringResource(R.string.request_uninstall)) - } - } - } - - Column(modifier = sections()){ - Text(text = stringResource(R.string.install_app), style = typography.titleLarge, color = titleColor) - Button( - onClick = { - focusMgr.clearFocus() - val installApkIntent = Intent(Intent.ACTION_GET_CONTENT) - installApkIntent.setType("application/vnd.android.package-archive") - installApkIntent.addCategory(Intent.CATEGORY_OPENABLE) - getApk.launch(installApkIntent) - }, - modifier = Modifier.fillMaxWidth() - ) { - Text(stringResource(R.string.select_apk)) - } - var selected by remember{mutableStateOf(false)} - LaunchedEffect(selected){apkSelected{selected = apkUri!=null}} - AnimatedVisibility(selected) { - Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween){ - Button( - onClick = { uriToStream(myContext, apkUri){stream -> installPackage(myContext,stream)} }, - modifier = Modifier.fillMaxWidth(0.49F) - ) { - Text(stringResource(R.string.silent_install)) - } - Button( - onClick = { - val intent = Intent(Intent.ACTION_INSTALL_PACKAGE) - intent.setData(apkUri) - intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) - myContext.startActivity(intent) - }, - modifier = Modifier.fillMaxWidth(0.96F) - ) { - Text(stringResource(R.string.request_install)) - } - } - } - } - - Spacer(Modifier.padding(30.dp)) - } - } -} - -@Composable -private fun AppManageItem( - itemName:Int, - itemDesc:Int, - getMethod:()->Boolean, - setMethod:(b:Boolean)->Unit -){ - val sharedPref = LocalContext.current.getSharedPreferences("data", Context.MODE_PRIVATE) - Row( - modifier = sections(), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically - ) { - var enabled by remember{mutableStateOf(getMethod())} - enabled = getMethod() - Column(modifier = if(sharedPref.getBoolean("isWear",false)){Modifier.fillMaxWidth(0.65F)}else{Modifier}){ - Text(text = stringResource(itemName), style = typography.titleLarge, color = colorScheme.onPrimaryContainer) - if(itemDesc!=R.string.place_holder){ Text(stringResource(itemDesc)) } - } - Switch( - checked = enabled, - onCheckedChange = { setMethod(!enabled); enabled=getMethod() } - ) - } -} - -@Throws(IOException::class) -private fun installPackage(context: Context, inputStream: InputStream){ - val packageInstaller = context.packageManager.packageInstaller - val params = PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL) - val sessionId = packageInstaller.createSession(params) - val session = packageInstaller.openSession(sessionId) - val out = session.openWrite("COSU", 0, -1) - val buffer = ByteArray(65536) - var c: Int - while(inputStream.read(buffer).also{c = it}!=-1) { out.write(buffer, 0, c) } - session.fsync(out) - inputStream.close() - out.close() - val pendingIntent = PendingIntent.getBroadcast(context, sessionId, Intent(context,PackageInstallerReceiver::class.java), PendingIntent.FLAG_IMMUTABLE).intentSender - session.commit(pendingIntent) -} - -private suspend fun apkSelected(operation:()->Unit){ - while(true){ - delay(500) - operation() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/binbin/androidowner/DeviceControl.kt b/app/src/main/java/com/binbin/androidowner/DeviceControl.kt deleted file mode 100644 index ba60421..0000000 --- a/app/src/main/java/com/binbin/androidowner/DeviceControl.kt +++ /dev/null @@ -1,666 +0,0 @@ -package com.binbin.androidowner - -import android.app.admin.DevicePolicyManager -import android.app.admin.DevicePolicyManager.* -import android.content.ComponentName -import android.content.Context -import android.content.Intent -import android.os.Binder -import android.os.Build.VERSION -import android.os.UserManager -import android.util.Log -import android.widget.Toast -import androidx.activity.ComponentActivity -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.foundation.focusable -import androidx.compose.foundation.isSystemInDarkTheme -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 -import androidx.compose.material3.MaterialTheme.typography -import androidx.compose.runtime.* -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.painterResource -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.input.ImeAction -import androidx.compose.ui.text.input.KeyboardType -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.unit.dp -import kotlinx.coroutines.delay - -@Composable -fun SystemManage(){ - val myContext = LocalContext.current - val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager - val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) - val sharedPref = LocalContext.current.getSharedPreferences("data", Context.MODE_PRIVATE) - val isWear = sharedPref.getBoolean("isWear",false) - val titleColor = colorScheme.onPrimaryContainer - val userManager = myContext.getSystemService(Context.USER_SERVICE) as UserManager - val bodyTextStyle = if(isWear){typography.bodyMedium}else{typography.bodyLarge} - val focusMgr = LocalFocusManager.current - Column(modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState())) { - if(isDeviceOwner(myDpm)||isProfileOwner(myDpm)){ - DeviceCtrlItem(R.string.disable_cam,R.string.place_holder, R.drawable.photo_camera_fill0, - {myDpm.getCameraDisabled(null)},{b -> myDpm.setCameraDisabled(myComponent,b)} - ) - } - if(isDeviceOwner(myDpm)||isProfileOwner(myDpm)){ - DeviceCtrlItem(R.string.disable_scrcap,R.string.aosp_scrrec_also_work,R.drawable.screenshot_fill0, - {myDpm.getScreenCaptureDisabled(null)},{b -> myDpm.setScreenCaptureDisabled(myComponent,b) } - ) - } - if(VERSION.SDK_INT>=34&&(isDeviceOwner(myDpm)|| (isProfileOwner(myDpm)&&myDpm.isAffiliatedUser))){ - DeviceCtrlItem(R.string.disable_status_bar,R.string.place_holder,R.drawable.notifications_fill0, - {myDpm.isStatusBarDisabled},{b -> myDpm.setStatusBarDisabled(myComponent,b) } - ) - } - if(isDeviceOwner(myDpm)||(VERSION.SDK_INT>=30&&isProfileOwner(myDpm)&&myDpm.isOrganizationOwnedDeviceWithManagedProfile)){ - if(VERSION.SDK_INT>=30){ - DeviceCtrlItem(R.string.auto_time,R.string.place_holder,R.drawable.schedule_fill0, - {myDpm.getAutoTimeEnabled(myComponent)},{b -> myDpm.setAutoTimeEnabled(myComponent,b) } - ) - DeviceCtrlItem(R.string.auto_timezone,R.string.place_holder,R.drawable.globe_fill0, - {myDpm.getAutoTimeZoneEnabled(myComponent)},{b -> myDpm.setAutoTimeZoneEnabled(myComponent,b) } - ) - }else{ - DeviceCtrlItem(R.string.auto_time,R.string.place_holder,R.drawable.schedule_fill0,{myDpm.autoTimeRequired},{b -> myDpm.setAutoTimeRequired(myComponent,b)}) - } - } - if(isDeviceOwner(myDpm)|| isProfileOwner(myDpm)){ - DeviceCtrlItem(R.string.master_mute,R.string.place_holder,R.drawable.volume_up_fill0, - {myDpm.isMasterVolumeMuted(myComponent)},{b -> myDpm.setMasterVolumeMuted(myComponent,b) } - ) - } - if(VERSION.SDK_INT>=26&&(isDeviceOwner(myDpm)|| isProfileOwner(myDpm))){ - DeviceCtrlItem(R.string.backup_service,R.string.place_holder,R.drawable.backup_fill0, - {myDpm.isBackupServiceEnabled(myComponent)},{b -> myDpm.setBackupServiceEnabled(myComponent,b) } - ) - } - if(VERSION.SDK_INT>=23&&(isDeviceOwner(myDpm)|| isProfileOwner(myDpm))){ - DeviceCtrlItem(R.string.disable_bt_contact_share,R.string.place_holder,R.drawable.account_circle_fill0, - {myDpm.getBluetoothContactSharingDisabled(myComponent)},{b -> myDpm.setBluetoothContactSharingDisabled(myComponent,b)} - ) - } - if(VERSION.SDK_INT>=30&&isDeviceOwner(myDpm)){ - DeviceCtrlItem(R.string.common_criteria_mode,R.string.common_criteria_mode_desc,R.drawable.security_fill0, - {myDpm.isCommonCriteriaModeEnabled(myComponent)},{b -> myDpm.setCommonCriteriaModeEnabled(myComponent,b)} - ) - } - if(VERSION.SDK_INT>=31&&(isDeviceOwner(myDpm)||(VERSION.SDK_INT>=30&&isProfileOwner(myDpm)&&myDpm.isOrganizationOwnedDeviceWithManagedProfile))){ - if(myDpm.canUsbDataSignalingBeDisabled()){ - DeviceCtrlItem(R.string.usb_signal,R.string.place_holder,R.drawable.usb_fill0, - {myDpm.isUsbDataSignalingEnabled},{b -> myDpm.isUsbDataSignalingEnabled = b } - ) - }else{ - Text(text = stringResource(R.string.turn_off_usb_not_support),modifier = Modifier.fillMaxWidth(), style = bodyTextStyle, textAlign = TextAlign.Center) - } - } - Column(modifier = sections()) { - Text(text = stringResource(R.string.keyguard), style = typography.titleLarge,color = colorScheme.onPrimaryContainer) - if(VERSION.SDK_INT>=23){ - Text(text = stringResource(R.string.require_no_password_to_disable),style=bodyTextStyle) - Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) { - Button( - onClick = { - Toast.makeText(myContext, - myContext.getString(if(myDpm.setKeyguardDisabled(myComponent,true)){R.string.success}else{R.string.fail}), Toast.LENGTH_SHORT).show() - }, - enabled = isDeviceOwner(myDpm)|| (VERSION.SDK_INT>=28&&isProfileOwner(myDpm)&&myDpm.isAffiliatedUser), - modifier = Modifier.fillMaxWidth(0.49F) - ) { - Text(stringResource(R.string.disable)) - } - Button( - onClick = { - Toast.makeText(myContext, - myContext.getString(if(myDpm.setKeyguardDisabled(myComponent,false)){R.string.success}else{R.string.fail}), Toast.LENGTH_SHORT).show() - }, - enabled = isDeviceOwner(myDpm)|| (VERSION.SDK_INT>=28&&isProfileOwner(myDpm)&&myDpm.isAffiliatedUser), - modifier = Modifier.fillMaxWidth(0.96F) - ) { - Text(stringResource(R.string.enable)) - } - } - } - var flag by remember{mutableIntStateOf(0)} - Button( - onClick = {myDpm.lockNow()}, - enabled = myDpm.isAdminActive(myComponent), - modifier = Modifier.fillMaxWidth() - ) { - Text(stringResource(R.string.lock_now)) - } - if(VERSION.SDK_INT>=26){ CheckBoxItem(stringResource(R.string.require_enter_password_again),{flag==FLAG_EVICT_CREDENTIAL_ENCRYPTION_KEY},{flag = if(flag==0){1}else{0} }) } - } - - if(VERSION.SDK_INT>=24){ - Column(modifier = sections()){ - Button( - onClick = { - val result = myDpm.requestBugreport(myComponent) - Toast.makeText(myContext, if(result){"成功"}else{"失败"}, Toast.LENGTH_SHORT).show() - }, - modifier = Modifier.fillMaxWidth(), - enabled = isDeviceOwner(myDpm) - ) { - Text(stringResource(R.string.request_bug_report)) - } - Button( - onClick = {myDpm.reboot(myComponent)}, - enabled = isDeviceOwner(myDpm), - modifier = Modifier.fillMaxWidth() - ) { - Text(stringResource(R.string.reboot)) - } - } - } - - if(VERSION.SDK_INT>=28){ - Column(modifier = sections()){ - Text(text = stringResource(R.string.edit_time), style = typography.titleLarge, color = titleColor) - var inputTime by remember{mutableStateOf("")} - Text(text = stringResource(R.string.from_epoch_to_target_time), style = bodyTextStyle) - OutlinedTextField( - value = inputTime, - label = { Text(stringResource(R.string.time_unit_ms))}, - onValueChange = {inputTime = it}, - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number, imeAction = ImeAction.Done), - keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}), - enabled = isDeviceOwner(myDpm)||(VERSION.SDK_INT>=30&&isProfileOwner(myDpm)&&myDpm.isOrganizationOwnedDeviceWithManagedProfile), - modifier = Modifier.focusable().fillMaxWidth().padding(vertical = 2.dp) - ) - if(isWear){ - Button( - onClick = {inputTime = System.currentTimeMillis().toString()}, - modifier = Modifier.fillMaxWidth() - ) { - Text(stringResource(R.string.get_current_time)) - } - } - Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween){ - Button( - onClick = {myDpm.setTime(myComponent,inputTime.toLong())}, - modifier = Modifier.fillMaxWidth(if(isWear){1F}else{0.35F}), - enabled = inputTime!=""&&(isDeviceOwner(myDpm)||(VERSION.SDK_INT>=30&&isProfileOwner(myDpm)&&myDpm.isOrganizationOwnedDeviceWithManagedProfile)) - ) { - Text("应用") - } - if(!isWear){ - Button( - onClick = {inputTime = System.currentTimeMillis().toString()}, - modifier = Modifier.fillMaxWidth(0.98F) - ) { - Text(stringResource(R.string.get_current_time)) - } - } - } - } - } - - if(VERSION.SDK_INT>=23&&(isDeviceOwner(myDpm)||isProfileOwner(myDpm))){ - Column(modifier = sections()){ - var selectedPolicy by remember{mutableIntStateOf(myDpm.getPermissionPolicy(myComponent))} - Text(text = stringResource(R.string.permission_policy), style = typography.titleLarge, color = titleColor) - RadioButtonItem(stringResource(R.string.default_stringres), {selectedPolicy==PERMISSION_POLICY_PROMPT}, {selectedPolicy= PERMISSION_POLICY_PROMPT}) - RadioButtonItem(stringResource(R.string.auto_grant), {selectedPolicy==PERMISSION_POLICY_AUTO_GRANT}, {selectedPolicy= PERMISSION_POLICY_AUTO_GRANT}) - RadioButtonItem(stringResource(R.string.auto_deny), {selectedPolicy==PERMISSION_POLICY_AUTO_DENY}, {selectedPolicy= PERMISSION_POLICY_AUTO_DENY}) - Button( - onClick = { - myDpm.setPermissionPolicy(myComponent,selectedPolicy) - Toast.makeText(myContext, myContext.getString(R.string.success), Toast.LENGTH_SHORT).show() - }, - modifier = Modifier.fillMaxWidth() - ) { - Text(stringResource(R.string.apply)) - } - } - } - - if(VERSION.SDK_INT>=34&&isDeviceOwner(myDpm)){ - Column(modifier = sections()){ - Text(text = stringResource(R.string.mte_policy), style = typography.titleLarge, color = titleColor) - Text(stringResource(R.string.mte_policy_desc)) - var selectedMtePolicy by remember{mutableIntStateOf(myDpm.mtePolicy)} - RadioButtonItem(stringResource(R.string.decide_by_user), {selectedMtePolicy==MTE_NOT_CONTROLLED_BY_POLICY}, {selectedMtePolicy= MTE_NOT_CONTROLLED_BY_POLICY}) - RadioButtonItem(stringResource(R.string.enabled), {selectedMtePolicy==MTE_ENABLED}, {selectedMtePolicy=MTE_ENABLED}) - RadioButtonItem(stringResource(R.string.disabled), {selectedMtePolicy==MTE_DISABLED}, {selectedMtePolicy=MTE_DISABLED}) - Button( - onClick = { - try { - myDpm.mtePolicy = selectedMtePolicy - Toast.makeText(myContext, myContext.getString(R.string.success), Toast.LENGTH_SHORT).show() - }catch(e:java.lang.UnsupportedOperationException){ - Toast.makeText(myContext, myContext.getString(R.string.unsupported), Toast.LENGTH_SHORT).show() - } - selectedMtePolicy = myDpm.mtePolicy - }, - modifier = Modifier.fillMaxWidth() - ) { - Text(stringResource(R.string.apply)) - } - } - } - - if(VERSION.SDK_INT>=31&&(isDeviceOwner(myDpm)||isProfileOwner(myDpm))){ - Column(modifier = sections()){ - var appPolicy by remember{mutableIntStateOf(myDpm.nearbyAppStreamingPolicy)} - Text(text = stringResource(R.string.nearby_app_streaming), style = typography.titleLarge, color = titleColor) - RadioButtonItem(stringResource(R.string.decide_by_user),{appPolicy == NEARBY_STREAMING_NOT_CONTROLLED_BY_POLICY},{appPolicy = NEARBY_STREAMING_NOT_CONTROLLED_BY_POLICY}) - RadioButtonItem(stringResource(R.string.enabled),{appPolicy == NEARBY_STREAMING_ENABLED},{appPolicy = NEARBY_STREAMING_ENABLED}) - RadioButtonItem(stringResource(R.string.disabled),{appPolicy == NEARBY_STREAMING_DISABLED},{appPolicy = NEARBY_STREAMING_DISABLED}) - RadioButtonItem(stringResource(R.string.enable_if_secure_enough),{appPolicy == NEARBY_STREAMING_SAME_MANAGED_ACCOUNT_ONLY},{appPolicy = NEARBY_STREAMING_SAME_MANAGED_ACCOUNT_ONLY}) - Button( - onClick = { - myDpm.nearbyAppStreamingPolicy = appPolicy - Toast.makeText(myContext, "成功", Toast.LENGTH_SHORT).show() - }, - modifier = Modifier.fillMaxWidth() - ) { - Text("应用") - } - Spacer(Modifier.padding(vertical = 3.dp)) - var notificationPolicy by remember{mutableIntStateOf(myDpm.nearbyNotificationStreamingPolicy)} - Text(text = stringResource(R.string.nearby_notifi_streaming), style = typography.titleLarge, color = titleColor) - RadioButtonItem(stringResource(R.string.decide_by_user),{notificationPolicy == NEARBY_STREAMING_NOT_CONTROLLED_BY_POLICY},{notificationPolicy = NEARBY_STREAMING_NOT_CONTROLLED_BY_POLICY}) - RadioButtonItem(stringResource(R.string.enabled),{notificationPolicy == NEARBY_STREAMING_ENABLED},{notificationPolicy = NEARBY_STREAMING_ENABLED}) - RadioButtonItem(stringResource(R.string.disabled),{notificationPolicy == NEARBY_STREAMING_DISABLED},{notificationPolicy = NEARBY_STREAMING_DISABLED}) - RadioButtonItem(stringResource(R.string.enable_if_secure_enough),{notificationPolicy == NEARBY_STREAMING_SAME_MANAGED_ACCOUNT_ONLY},{notificationPolicy = NEARBY_STREAMING_SAME_MANAGED_ACCOUNT_ONLY}) - Button( - onClick = { - myDpm.nearbyNotificationStreamingPolicy = notificationPolicy - Toast.makeText(myContext, myContext.getString(R.string.success), Toast.LENGTH_SHORT).show() - }, - modifier = Modifier.fillMaxWidth() - ) { - Text(stringResource(R.string.apply)) - } - } - } - - if(VERSION.SDK_INT>=28&&isDeviceOwner(myDpm)){ - Column(modifier = sections()){ - val lockTaskPolicyList = mutableListOf( - LOCK_TASK_FEATURE_NONE, - LOCK_TASK_FEATURE_SYSTEM_INFO, - LOCK_TASK_FEATURE_NOTIFICATIONS, - LOCK_TASK_FEATURE_HOME, - LOCK_TASK_FEATURE_OVERVIEW, - LOCK_TASK_FEATURE_GLOBAL_ACTIONS, - LOCK_TASK_FEATURE_KEYGUARD - ) - var sysInfo by remember{mutableStateOf(false)} - var notifications by remember{mutableStateOf(false)} - var home by remember{mutableStateOf(false)} - var overview by remember{mutableStateOf(false)} - var globalAction by remember{mutableStateOf(false)} - var keyGuard by remember{mutableStateOf(false)} - var blockAct by remember{mutableStateOf(false)} - if(VERSION.SDK_INT>=30){lockTaskPolicyList.add(LOCK_TASK_FEATURE_BLOCK_ACTIVITY_START_IN_TASK)} - var inited by remember{mutableStateOf(false)} - var custom by remember{mutableStateOf(false)} - val refreshFeature = { - var calculate = myDpm.getLockTaskFeatures(myComponent) - if(calculate!=0){ - if(VERSION.SDK_INT>=30&&calculate-lockTaskPolicyList[7]>=0){blockAct=true;calculate-=lockTaskPolicyList[7]} - if(calculate-lockTaskPolicyList[6]>=0){keyGuard=true;calculate-=lockTaskPolicyList[6]} - if(calculate-lockTaskPolicyList[5]>=0){globalAction=true;calculate-=lockTaskPolicyList[5]} - if(calculate-lockTaskPolicyList[4]>=0){overview=true;calculate-=lockTaskPolicyList[4]} - if(calculate-lockTaskPolicyList[3]>=0){home=true;calculate-=lockTaskPolicyList[3]} - if(calculate-lockTaskPolicyList[2]>=0){notifications=true;calculate-=lockTaskPolicyList[2]} - if(calculate-lockTaskPolicyList[1]>=0){sysInfo=true;calculate-=lockTaskPolicyList[1]} - }else{ - custom = false - } - } - Text(text = stringResource(R.string.lock_task_feature), style = typography.titleLarge, color = titleColor) - if(!inited){ refreshFeature();custom=myDpm.getLockTaskFeatures(myComponent)!=0;inited=true } - RadioButtonItem(stringResource(R.string.disable_all),{!custom},{custom=false}) - RadioButtonItem(stringResource(R.string.custom),{custom},{custom=true}) - AnimatedVisibility(custom) { - Column { - CheckBoxItem(stringResource(R.string.ltf_sys_info),{sysInfo},{sysInfo=!sysInfo}) - CheckBoxItem(stringResource(R.string.ltf_notifications),{notifications},{notifications=!notifications}) - CheckBoxItem(stringResource(R.string.ltf_home),{home},{home=!home}) - CheckBoxItem(stringResource(R.string.ltf_overview),{overview},{overview=!overview}) - CheckBoxItem(stringResource(R.string.ltf_global_actions),{globalAction},{globalAction=!globalAction}) - CheckBoxItem(stringResource(R.string.ltf_keyguard),{keyGuard},{keyGuard=!keyGuard}) - if(VERSION.SDK_INT>=30){ CheckBoxItem(stringResource(R.string.ltf_block_activity_start_in_task),{blockAct},{blockAct=!blockAct}) } - } - } - Button( - modifier = Modifier.fillMaxWidth(), - onClick = { - var result = lockTaskPolicyList[0] - if(custom){ - if(blockAct&&VERSION.SDK_INT>=30){result+=lockTaskPolicyList[7]} - if(keyGuard){result+=lockTaskPolicyList[6]} - if(globalAction){result+=lockTaskPolicyList[5]} - if(overview){result+=lockTaskPolicyList[4]} - if(home){result+=lockTaskPolicyList[3]} - if(notifications){result+=lockTaskPolicyList[2]} - if(sysInfo){result+=lockTaskPolicyList[1]} - } - myDpm.setLockTaskFeatures(myComponent,result) - refreshFeature() - Toast.makeText(myContext, myContext.getString(R.string.success), Toast.LENGTH_SHORT).show() - } - ) { - Text(stringResource(R.string.apply)) - } - Spacer(Modifier.padding(vertical = 4.dp)) - val whitelist = myDpm.getLockTaskPackages(myComponent).toMutableList() - var listText by remember{mutableStateOf("")} - var inputPkg by remember{mutableStateOf("")} - val refreshWhitelist = { - inputPkg="" - listText="" - var currentItem = whitelist.size - for(each in whitelist){ - currentItem-=1 - listText += each - if(currentItem>0){listText += "\n"} - } - } - refreshWhitelist() - Text(text = stringResource(R.string.whitelist_app), style = typography.titleLarge, color = titleColor) - if(listText!=""){ - SelectionContainer { - Text(text = listText, style = bodyTextStyle) - } - }else{ - Text(text = stringResource(R.string.none), style = bodyTextStyle) - } - OutlinedTextField( - value = inputPkg, - onValueChange = {inputPkg=it}, - label = {Text(stringResource(R.string.package_name))}, - keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), - keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}), - modifier = Modifier.focusable().fillMaxWidth().padding(vertical = 3.dp) - ) - Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween){ - Button( - onClick = { - focusMgr.clearFocus() - whitelist.add(inputPkg) - myDpm.setLockTaskPackages(myComponent,whitelist.toTypedArray()) - Toast.makeText(myContext, myContext.getString(R.string.success), Toast.LENGTH_SHORT).show() - refreshWhitelist() - }, - modifier = Modifier.fillMaxWidth(0.49F) - ) { - Text(stringResource(R.string.add)) - } - Button( - onClick = { - focusMgr.clearFocus() - if(inputPkg in whitelist){ - whitelist.remove(inputPkg) - myDpm.setLockTaskPackages(myComponent,whitelist.toTypedArray()) - Toast.makeText(myContext, myContext.getString(R.string.success), Toast.LENGTH_SHORT).show() - }else{ - Toast.makeText(myContext, myContext.getString(R.string.not_exist), Toast.LENGTH_SHORT).show() - } - refreshWhitelist() - }, - modifier = Modifier.fillMaxWidth(0.96F) - ) { - Text(stringResource(R.string.remove)) - } - } - } - } - - if(isDeviceOwner(myDpm)||isProfileOwner(myDpm)){ - var exist by remember{mutableStateOf(false)} - var isEmpty by remember{mutableStateOf(false)} - val refresh = { - isEmpty = caCert.isEmpty() - exist = if(!isEmpty){ myDpm.hasCaCertInstalled(myComponent, caCert) }else{ false } - } - LaunchedEffect(exist){ isCaCertSelected(600){refresh()} } - Column(modifier = sections()){ - Text(text = stringResource(R.string.ca_cert), style = typography.titleLarge, color = titleColor) - if(isEmpty){ Text(text = stringResource(R.string.please_select_ca_cert)) }else{ Text(text = stringResource(R.string.cacert_installed, exist)) } - Button( - onClick = { - val caCertIntent = Intent(Intent.ACTION_GET_CONTENT) - caCertIntent.setType("*/*") - caCertIntent.addCategory(Intent.CATEGORY_OPENABLE) - getCaCert.launch(caCertIntent) - }, - modifier = Modifier.fillMaxWidth() - ) { - Text(stringResource(R.string.select_ca_cert)) - } - AnimatedVisibility(!isEmpty) { - Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween){ - Button( - onClick = { - val result = myDpm.installCaCert(myComponent, caCert) - Toast.makeText(myContext, myContext.getString(if(result){R.string.success}else{R.string.fail}), Toast.LENGTH_SHORT).show() - refresh() - }, - modifier = Modifier.fillMaxWidth(0.49F) - ) { - Text(stringResource(R.string.install)) - } - Button( - onClick = { - if(exist){ - myDpm.uninstallCaCert(myComponent, caCert) - exist = myDpm.hasCaCertInstalled(myComponent, caCert) - Toast.makeText(myContext, myContext.getString(if(exist){R.string.fail}else{R.string.success}), Toast.LENGTH_SHORT).show() - }else{ Toast.makeText(myContext, myContext.getString(R.string.not_exist), Toast.LENGTH_SHORT).show() } - }, - modifier = Modifier.fillMaxWidth(0.96F) - ) { - Text(stringResource(R.string.uninstall)) - } - } - } - Button( - onClick = { - myDpm.uninstallAllUserCaCerts(myComponent) - Toast.makeText(myContext, myContext.getString(R.string.success), Toast.LENGTH_SHORT).show() - }, - modifier = Modifier.fillMaxWidth() - ){ - Text(stringResource(R.string.uninstall_all_user_ca_cert)) - } - } - } - - if(VERSION.SDK_INT>=26&&(isDeviceOwner(myDpm)||(VERSION.SDK_INT>=30&&isProfileOwner(myDpm)&&myDpm.isOrganizationOwnedDeviceWithManagedProfile))){ - Column(modifier = sections()){ - Text(text = stringResource(R.string.retrieve_security_logs), style = typography.titleLarge, color = titleColor) - Text(text = stringResource(R.string.developing), style = bodyTextStyle) - Row(modifier=Modifier.fillMaxWidth().padding(horizontal=8.dp),horizontalArrangement=Arrangement.SpaceBetween,verticalAlignment=Alignment.CenterVertically){ - var checked by remember{mutableStateOf(myDpm.isSecurityLoggingEnabled(myComponent))} - Text(text = stringResource(R.string.enabled), style = typography.titleLarge) - Switch( - checked = checked, - onCheckedChange = {myDpm.setSecurityLoggingEnabled(myComponent,!checked);checked=myDpm.isSecurityLoggingEnabled(myComponent)} - ) - } - Button( - onClick = { - val log = myDpm.retrieveSecurityLogs(myComponent) - if(log!=null){ - for(i in log){ Log.d("SecureLog",i.toString()) } - Toast.makeText(myContext,myContext.getString(R.string.success),Toast.LENGTH_SHORT).show() - }else{ - Log.d("SecureLog",myContext.getString(R.string.none)) - Toast.makeText(myContext, myContext.getString(R.string.no_logs),Toast.LENGTH_SHORT).show() - } - }, - modifier = Modifier.fillMaxWidth() - ) { - Text(stringResource(R.string.security_logs)) - } - Button( - onClick = { - val log = myDpm.retrievePreRebootSecurityLogs(myComponent) - if(log!=null){ - for(i in log){ Log.d("SecureLog",i.toString()) } - Toast.makeText(myContext,myContext.getString(R.string.success),Toast.LENGTH_SHORT).show() - }else{ - Log.d("SecureLog",myContext.getString(R.string.none)) - Toast.makeText(myContext,myContext.getString(R.string.no_logs),Toast.LENGTH_SHORT).show() - } - }, - modifier = Modifier.fillMaxWidth() - ) { - Text(stringResource(R.string.pre_reboot_security_logs)) - } - } - } - - if(isDeviceOwner(myDpm)){ - SysUpdatePolicy() - } - - Column(modifier = sections(if(isSystemInDarkTheme()){ colorScheme.errorContainer }else{ colorScheme.errorContainer.copy(alpha = 0.6F) })) { - var flag by remember{ mutableIntStateOf(0) } - var confirmed by remember{ mutableStateOf(false) } - var externalStorage by remember{mutableStateOf(false)} - var protectionData by remember{mutableStateOf(false)} - var euicc by remember{mutableStateOf(false)} - var silent by remember{mutableStateOf(false)} - var reason by remember{mutableStateOf("")} - Text(text = stringResource(R.string.wipe_data),style = typography.titleLarge,modifier = Modifier.padding(6.dp),color = colorScheme.onErrorContainer) - CheckBoxItem(stringResource(R.string.wipe_external_storage),{externalStorage},{externalStorage=!externalStorage;confirmed=false}, colorScheme.onErrorContainer) - if(VERSION.SDK_INT>=22&&isDeviceOwner(myDpm)){ - CheckBoxItem(stringResource(R.string.wipe_reset_protection_data),{protectionData},{protectionData=!protectionData;confirmed=false}, colorScheme.onErrorContainer) - } - if(VERSION.SDK_INT>=28){ CheckBoxItem(stringResource(R.string.wipe_euicc),{euicc},{euicc=!euicc;confirmed=false}, colorScheme.onErrorContainer) } - if(VERSION.SDK_INT>=29){ CheckBoxItem(stringResource(R.string.wipe_silently),{silent},{silent=!silent;confirmed=false}, colorScheme.onErrorContainer) } - AnimatedVisibility(!silent&&VERSION.SDK_INT>=28) { - OutlinedTextField( - value = reason, onValueChange = {reason=it}, - label = {Text(stringResource(R.string.reason))}, - enabled = !confirmed, - keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), - keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}), - modifier = Modifier.focusable().fillMaxWidth().padding(vertical = 3.dp) - ) - } - Button( - onClick = { - focusMgr.clearFocus() - flag = 0 - if(externalStorage){flag += WIPE_EXTERNAL_STORAGE} - if(protectionData&&VERSION.SDK_INT>=22){flag += WIPE_RESET_PROTECTION_DATA} - if(euicc&&VERSION.SDK_INT>=28){flag += WIPE_EUICC} - if(reason==""){silent = true} - if(silent&&VERSION.SDK_INT>=29){flag += WIPE_SILENTLY} - confirmed=!confirmed - }, - colors = ButtonDefaults.buttonColors( - containerColor = if(confirmed){ colorScheme.primary }else{ colorScheme.error }, - contentColor = if(confirmed){ colorScheme.onPrimary }else{ colorScheme.onError } - ), - enabled = myDpm.isAdminActive(myComponent), - modifier = Modifier.fillMaxWidth() - ) { - Text(text = stringResource(if(confirmed){ R.string.cancel }else{ R.string.confirm })) - } - Row(modifier = Modifier.fillMaxWidth(),horizontalArrangement = Arrangement.SpaceBetween) { - Button( - onClick = { - if(VERSION.SDK_INT>=28){myDpm.wipeData(flag,reason)} - else{myDpm.wipeData(flag)} - }, - colors = ButtonDefaults.buttonColors(containerColor = colorScheme.error, contentColor = colorScheme.onError), - enabled = confirmed&&(VERSION.SDK_INT<34||(VERSION.SDK_INT>=34&&!userManager.isSystemUser)), - modifier = Modifier.fillMaxWidth(if(VERSION.SDK_INT >= 34&&(isDeviceOwner(myDpm)||(isProfileOwner(myDpm)&&myDpm.isOrganizationOwnedDeviceWithManagedProfile))){0.49F}else{1F}) - ) { - Text("WipeData") - } - if (VERSION.SDK_INT >= 34&&(isDeviceOwner(myDpm)||(isProfileOwner(myDpm)&&myDpm.isOrganizationOwnedDeviceWithManagedProfile))) { - Button( - onClick = {myDpm.wipeDevice(flag)}, - colors = ButtonDefaults.buttonColors(containerColor = colorScheme.error, contentColor = colorScheme.onError), - enabled = confirmed, - modifier = Modifier.fillMaxWidth(0.96F) - ) { - Text("WipeDevice") - } - } - } - if(VERSION.SDK_INT>=24&&isProfileOwner(myDpm)&&myDpm.isManagedProfile(myComponent)){ - Text(text = stringResource(R.string.will_delete_work_profile), style = bodyTextStyle) - } - if(VERSION.SDK_INT>=34&&Binder.getCallingUid()/100000==0){ - Text(text = stringResource(R.string.api34_or_above_wipedata_cannot_in_system_user), style = bodyTextStyle) - } - } - Spacer(Modifier.padding(vertical = 30.dp)) - } -} - -@Composable -fun DeviceCtrlItem( - itemName:Int, - itemDesc:Int, - leadIcon:Int, - getMethod:()->Boolean, - setMethod:(b:Boolean)->Unit -){ - var isEnabled by remember{ mutableStateOf(false) } - val sharedPref = LocalContext.current.getSharedPreferences("data", Context.MODE_PRIVATE) - val isWear = sharedPref.getBoolean("isWear",false) - Row( - modifier = sections(), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically - ) { - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = if(isWear){Modifier.fillMaxWidth(0.65F)}else{Modifier.fillMaxWidth(0.8F)} - ){ - if(!isWear){ - Icon( - painter = painterResource(leadIcon), - contentDescription = null, - tint = colorScheme.onPrimaryContainer, - modifier = Modifier.padding(start = 5.dp, end = 9.dp) - )} - Column { - Text( - text = stringResource(itemName), - style = if(!isWear){typography.titleLarge}else{typography.titleMedium}, - color = colorScheme.onPrimaryContainer, - fontWeight = if(isWear){ FontWeight.SemiBold }else{ FontWeight.Medium } - ) - if(itemDesc!=R.string.place_holder){ Text(stringResource(itemDesc)) } - } - } - isEnabled = getMethod() - Switch( - checked = isEnabled, - onCheckedChange = { - setMethod(!isEnabled) - isEnabled=getMethod() - }, - modifier = Modifier.padding(end = 5.dp) - ) - } -} - -private suspend fun isCaCertSelected(delay:Long,operation:()->Unit){ - while(true){ - delay(delay) - operation() - } -} diff --git a/app/src/main/java/com/binbin/androidowner/MainActivity.kt b/app/src/main/java/com/binbin/androidowner/MainActivity.kt index babfa9f..0bc52ce 100644 --- a/app/src/main/java/com/binbin/androidowner/MainActivity.kt +++ b/app/src/main/java/com/binbin/androidowner/MainActivity.kt @@ -5,15 +5,12 @@ import android.app.Activity import android.app.admin.DevicePolicyManager import android.content.ComponentName import android.content.Context -import android.content.Intent -import android.net.Uri import android.os.Build.VERSION import android.os.Bundle import android.os.UserManager import android.widget.Toast import androidx.activity.ComponentActivity import androidx.activity.compose.setContent -import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.* @@ -40,27 +37,14 @@ import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.rememberNavController +import com.binbin.androidowner.dpm.* import com.binbin.androidowner.ui.theme.AndroidOwnerTheme import com.binbin.androidowner.ui.theme.Animations -import java.io.FileNotFoundException -import java.io.IOException -import java.io.InputStream -lateinit var getCaCert: ActivityResultLauncher -lateinit var createUser:ActivityResultLauncher -lateinit var createManagedProfile:ActivityResultLauncher -lateinit var getApk:ActivityResultLauncher -lateinit var getUserIcon:ActivityResultLauncher -var userIconUri:Uri? = null -var apkUri: Uri? = null -var caCert = byteArrayOf() - @ExperimentalMaterial3Api class MainActivity : ComponentActivity() { - override fun onCreate(savedInstanceState: Bundle?) { - WindowCompat.setDecorFitsSystemWindows(window, false) - super.onCreate(savedInstanceState) + private fun registerActivityResult(){ getUserIcon = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { userIconUri = it.data?.data if(userIconUri==null){ Toast.makeText(applicationContext, "空URI", Toast.LENGTH_SHORT).show() } @@ -87,6 +71,12 @@ class MainActivity : ComponentActivity() { createManagedProfile = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { if(it.resultCode==Activity.RESULT_CANCELED){Toast.makeText(applicationContext, "用户已取消", Toast.LENGTH_SHORT).show()} } + } + + override fun onCreate(savedInstanceState: Bundle?) { + WindowCompat.setDecorFitsSystemWindows(window, false) + super.onCreate(savedInstanceState) + registerActivityResult() setContent { AndroidOwnerTheme { MyScaffold() @@ -160,7 +150,7 @@ fun MyScaffold(){ popExitTransition = Animations(myContext).navHostPopExitTransition ){ composable(route = "HomePage", content = { HomePage(navCtrl)}) - composable(route = "DeviceControl", content = { SystemManage()}) + composable(route = "SystemManage", content = { SystemManage() }) composable(route = "ManagedProfile", content = {ManagedProfile()}) composable(route = "Permissions", content = { DpmPermissions(navCtrl)}) composable(route = "ApplicationManage", content = { ApplicationManage()}) @@ -254,57 +244,6 @@ fun HomePageItem(name:Int, imgVector:Int, navTo:String, myNav:NavHostController) } } -@Composable -fun RadioButtonItem( - text:String, - selected:()->Boolean, - operation:()->Unit, - textColor:Color = colorScheme.onBackground -){ - val sharedPref = LocalContext.current.getSharedPreferences("data", Context.MODE_PRIVATE) - val isWear = sharedPref.getBoolean("isWear",false) - Row(verticalAlignment = Alignment.CenterVertically,modifier = Modifier - .fillMaxWidth() - .padding(vertical = if(isWear){3.dp}else{0.dp}) - .clip(RoundedCornerShape(25)) - .clickable(onClick = operation) - ) { - RadioButton(selected = selected(), onClick = operation,modifier=if(isWear){Modifier.size(28.dp)}else{Modifier}) - Text(text = text, style = if(!isWear){typography.bodyLarge}else{typography.bodyMedium}, color = textColor, - modifier = Modifier.padding(bottom = 2.dp)) - } -} -@Composable -fun CheckBoxItem( - text:String, - checked:()->Boolean, - operation:()->Unit, - textColor:Color = colorScheme.onBackground -){ - val sharedPref = LocalContext.current.getSharedPreferences("data", Context.MODE_PRIVATE) - val isWear = sharedPref.getBoolean("isWear",false) - Row(verticalAlignment = Alignment.CenterVertically,modifier = Modifier - .fillMaxWidth() - .padding(vertical = if(isWear){3.dp}else{0.dp}) - .clip(RoundedCornerShape(25)) - .clickable(onClick = operation) - ) { - Checkbox( - checked = checked(), - onCheckedChange = {operation()}, - modifier=if(isWear){Modifier.size(28.dp)}else{Modifier} - ) - Text(text = text, style = if(!isWear){typography.bodyLarge}else{typography.bodyMedium}, color = textColor, modifier = Modifier.padding(bottom = 2.dp)) - } -} - -fun isDeviceOwner(dpm:DevicePolicyManager): Boolean { - return dpm.isDeviceOwnerApp("com.binbin.androidowner") -} - -fun isProfileOwner(dpm:DevicePolicyManager): Boolean { - return dpm.isProfileOwnerApp("com.binbin.androidowner") -} @SuppressLint("ModifierFactoryExtensionFunction", "ComposableModifierFactory") @Composable @@ -330,20 +269,3 @@ fun sections(bgColor:Color=colorScheme.primaryContainer,onClick:()->Unit={},clic } } -fun uriToStream( - context: Context, - uri: Uri?, - operation:(stream:InputStream)->Unit -){ - if(uri!=null){ - apkUri = uri - try{ - val stream = context.contentResolver.openInputStream(uri) - if(stream!=null) { operation(stream) } - else{ Toast.makeText(context, "空的流", Toast.LENGTH_SHORT).show() } - stream?.close() - } - catch(e:FileNotFoundException){ Toast.makeText(context, "文件不存在", Toast.LENGTH_SHORT).show() } - catch(e:IOException){ Toast.makeText(context, "IO异常", Toast.LENGTH_SHORT).show() } - }else{ Toast.makeText(context, "空URI", Toast.LENGTH_SHORT).show() } -} diff --git a/app/src/main/java/com/binbin/androidowner/ManagedProfile.kt b/app/src/main/java/com/binbin/androidowner/ManagedProfile.kt deleted file mode 100644 index afb0434..0000000 --- a/app/src/main/java/com/binbin/androidowner/ManagedProfile.kt +++ /dev/null @@ -1,265 +0,0 @@ -package com.binbin.androidowner - -import android.app.admin.DevicePolicyManager -import android.app.admin.DevicePolicyManager.* -import android.content.* -import android.os.Binder -import android.os.Build.VERSION -import android.widget.Toast -import androidx.activity.ComponentActivity -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.animateContentSize -import androidx.compose.foundation.focusable -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 -import androidx.compose.material3.MaterialTheme.typography -import androidx.compose.material3.OutlinedTextField -import androidx.compose.material3.Switch -import androidx.compose.material3.Text -import androidx.compose.runtime.* -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.unit.dp -import androidx.navigation.NavHostController - -@Composable -fun ManagedProfile() { - val myContext = LocalContext.current - val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager - val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) - val focusMgr = LocalFocusManager.current - val sharedPref = LocalContext.current.getSharedPreferences("data", Context.MODE_PRIVATE) - val isWear = sharedPref.getBoolean("isWear",false) - val bodyTextStyle = if(isWear){ typography.bodyMedium}else{ typography.bodyLarge} - val titleColor = colorScheme.onPrimaryContainer - Column(modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState())){ - - Column(modifier = sections()){ - Text(text = stringResource(R.string.info), style = typography.titleLarge, color = titleColor) - if(VERSION.SDK_INT>=24){ - if(isProfileOwner(myDpm)&&myDpm.isManagedProfile(myComponent)){ - Text(text = stringResource(R.string.is_already_work_profile)) - }else{ - Text(text = stringResource(R.string.able_to_create_work_profile, myDpm.isProvisioningAllowed(ACTION_PROVISION_MANAGED_PROFILE)), style = bodyTextStyle) - if(isDeviceOwner(myDpm)){ - Text(text = stringResource(R.string.device_owner_cannot_create_work_profile), style = bodyTextStyle) - } - } - } - if(VERSION.SDK_INT>=30){ - Text(text = stringResource(R.string.is_org_owned_profile, myDpm.isOrganizationOwnedDeviceWithManagedProfile), style = bodyTextStyle) - } - if(VERSION.SDK_INT<24||(VERSION.SDK_INT>=24&&isProfileOwner(myDpm)&&myDpm.isManagedProfile(myComponent))){ - Button( - onClick = { myContext.startActivity(Intent("com.binbin.androidowner.MAIN_ACTION")) }, modifier = Modifier.fillMaxWidth() - ){ - Text("跳转至个人应用") - } - }else{ - if(!myDpm.isProvisioningAllowed(ACTION_PROVISION_MANAGED_PROFILE)&&!isDeviceOwner(myDpm)){ - Button( - onClick = { myContext.startActivity(Intent("com.binbin.androidowner.MAIN_ACTION")) }, modifier = Modifier.fillMaxWidth() - ){ - Text("跳转至工作资料") - } - } - } - } - - if(VERSION.SDK_INT>=30&&isProfileOwner(myDpm)&&myDpm.isManagedProfile(myComponent)&&!myDpm.isOrganizationOwnedDeviceWithManagedProfile){ - var expand by remember{mutableStateOf(false)} - Column(modifier = sections(colorScheme.tertiaryContainer,{expand=true},!expand).animateContentSize(animationSpec = scrollAnim())){ - if(expand){ - Text(text = stringResource(R.string.org_owned_work_profile), color = colorScheme.onTertiaryContainer, style = typography.titleLarge) - SelectionContainer { - Text(text = "使用ADB执行以下命令,或者使用Shizuku") - Text( - text = stringResource(R.string.activate_org_profile_command, Binder.getCallingUid()/100000), - color = colorScheme.onTertiaryContainer, style = bodyTextStyle - ) - } - }else{ - Text(text = stringResource(R.string.become_org_profile), color = colorScheme.onTertiaryContainer) - Text(text = stringResource(R.string.touch_to_view_command), style = bodyTextStyle) - } - } - } - if(VERSION.SDK_INT<24||(VERSION.SDK_INT>=24&&myDpm.isProvisioningAllowed(ACTION_PROVISION_MANAGED_PROFILE))){ - Column(modifier = sections()) { - Text(text = stringResource(R.string.work_profile), style = typography.titleLarge, color = titleColor) - var skipEncrypt by remember{mutableStateOf(false)} - if(VERSION.SDK_INT>=24){CheckBoxItem(stringResource(R.string.skip_encryption),{skipEncrypt},{skipEncrypt=!skipEncrypt})} - Button( - onClick = { - 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,myContext.getString(R.string.unsupported),Toast.LENGTH_SHORT).show() - } - }, - modifier = Modifier.fillMaxWidth() - ) { - Text(stringResource(R.string.create)) - } - } - } - - if(VERSION.SDK_INT>=30&&isProfileOwner(myDpm)&&myDpm.isManagedProfile(myComponent)&&myDpm.isOrganizationOwnedDeviceWithManagedProfile){ - Row(modifier = sections(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically){ - var suspended by remember{mutableStateOf(false)} - suspended = myDpm.getPersonalAppsSuspendedReasons(myComponent)!=PERSONAL_APPS_NOT_SUSPENDED - Text(text = stringResource(R.string.suspend_personal_app), style = typography.titleLarge, color = titleColor) - Switch( - checked = suspended, - onCheckedChange ={ - myDpm.setPersonalAppsSuspended(myComponent,!suspended) - suspended = myDpm.getPersonalAppsSuspendedReasons(myComponent)!=PERSONAL_APPS_NOT_SUSPENDED - } - ) - } - } - - if(VERSION.SDK_INT>=30&&isProfileOwner(myDpm)&&myDpm.isManagedProfile(myComponent)&&myDpm.isOrganizationOwnedDeviceWithManagedProfile){ - Column(modifier = sections()){ - var time by remember{mutableStateOf("")} - time = myDpm.getManagedProfileMaximumTimeOff(myComponent).toString() - Text(text = stringResource(R.string.profile_max_time_off), style = typography.titleLarge, color = titleColor) - Text(text = stringResource(R.string.profile_max_time_out_desc), style = bodyTextStyle) - Text(text = stringResource(R.string.personal_app_suspended_because_timeout, myDpm.getPersonalAppsSuspendedReasons(myComponent)==PERSONAL_APPS_SUSPENDED_PROFILE_TIMEOUT)) - OutlinedTextField( - value = time, onValueChange = {time=it}, modifier = Modifier.focusable().fillMaxWidth().padding(vertical = 2.dp), - label = {Text(stringResource(R.string.time_unit_ms))}, - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number, imeAction = ImeAction.Done), - keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}) - ) - Text(text = stringResource(R.string.cannot_less_than_72_hours), style = bodyTextStyle) - Button( - onClick = { - myDpm.setManagedProfileMaximumTimeOff(myComponent,time.toLong()) - Toast.makeText(myContext, myContext.getString(R.string.success), Toast.LENGTH_SHORT).show() - }, - modifier = Modifier.fillMaxWidth() - ) { - Text(stringResource(R.string.apply)) - } - } - } - - if(isProfileOwner(myDpm)&&(VERSION.SDK_INT<24||(VERSION.SDK_INT>=24&&myDpm.isManagedProfile(myComponent)))){ - Column(modifier = sections()){ - var action by remember{mutableStateOf("")} - Text(text = stringResource(R.string.intent_filter), style = typography.titleLarge, color = titleColor) - OutlinedTextField( - value = action, onValueChange = {action = it}, - label = {Text("Action")}, - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Ascii, imeAction = ImeAction.Done), - keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}), - modifier = Modifier.focusable().fillMaxWidth().padding(vertical = 2.dp) - ) - Button( - onClick = { - myDpm.addCrossProfileIntentFilter(myComponent, IntentFilter(action), FLAG_PARENT_CAN_ACCESS_MANAGED) - Toast.makeText(myContext, myContext.getString(R.string.success),Toast.LENGTH_SHORT).show() - }, - modifier = Modifier.fillMaxWidth() - ) { - Text(stringResource(R.string.add_intent_filter_work_to_personal)) - } - Button( - onClick = { - myDpm.addCrossProfileIntentFilter(myComponent, IntentFilter(action), FLAG_MANAGED_CAN_ACCESS_PARENT) - Toast.makeText(myContext, myContext.getString(R.string.success),Toast.LENGTH_SHORT).show() - }, - modifier = Modifier.fillMaxWidth() - ) { - Text(stringResource(R.string.add_intent_filter_personal_to_work)) - } - Button( - onClick = { - myDpm.clearCrossProfileIntentFilters(myComponent) - myDpm.addCrossProfileIntentFilter(myComponent, IntentFilter("com.binbin.androidowner.MAIN_ACTION"), FLAG_MANAGED_CAN_ACCESS_PARENT) - myDpm.addCrossProfileIntentFilter(myComponent, IntentFilter("com.binbin.androidowner.MAIN_ACTION"), FLAG_PARENT_CAN_ACCESS_MANAGED) - Toast.makeText(myContext, myContext.getString(R.string.success),Toast.LENGTH_SHORT).show() - }, - modifier = Modifier.fillMaxWidth() - ){ - Text(stringResource(R.string.clear_cross_profile_filters)) - } - } - } - - if(VERSION.SDK_INT>=31&&(isProfileOwner(myDpm)&&myDpm.isManagedProfile(myComponent))){ - Column(modifier = sections()){ - var orgId by remember{mutableStateOf("")} - Text(text = stringResource(R.string.org_id), style = typography.titleLarge, color = titleColor) - OutlinedTextField( - value = orgId, onValueChange = {orgId=it}, - label = {Text(stringResource(R.string.org_id))}, - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Ascii, imeAction = ImeAction.Done), - keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}), - modifier = Modifier.focusable().fillMaxWidth().padding(vertical = 2.dp) - ) - AnimatedVisibility(orgId.length !in 6..64) { - Text(text = stringResource(R.string.length_6_to_64), style = bodyTextStyle) - } - Button( - onClick = { - myDpm.setOrganizationId(orgId) - Toast.makeText(myContext, myContext.getString(R.string.success),Toast.LENGTH_SHORT).show() - }, - enabled = orgId.length in 6..64, - modifier = Modifier.fillMaxWidth() - ){ - Text(stringResource(R.string.apply)) - } - Text(text = stringResource(R.string.get_specific_id_after_set_org_id), style = bodyTextStyle) - } - } - - Spacer(Modifier.padding(vertical = 30.dp)) - } -} - -@Composable -fun ActivateManagedProfile(navCtrl: NavHostController){ - val myContext = LocalContext.current - val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager - val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) - val sharedPref = myContext.getSharedPreferences("data", Context.MODE_PRIVATE) - myDpm.addCrossProfileIntentFilter(myComponent, IntentFilter("com.binbin.androidowner.MAIN_ACTION"), FLAG_MANAGED_CAN_ACCESS_PARENT) - myDpm.addCrossProfileIntentFilter(myComponent, IntentFilter("com.binbin.androidowner.MAIN_ACTION"), FLAG_PARENT_CAN_ACCESS_MANAGED) - Column(modifier = Modifier.verticalScroll(rememberScrollState()), horizontalAlignment = Alignment.CenterHorizontally){ - Text(text = stringResource(R.string.activate_managed_profile), style = typography.titleLarge) - Text(text = stringResource(R.string.activate_managed_profile_desc)) - Button( - onClick = { - myDpm.setProfileEnabled(myComponent) - navCtrl.popBackStack("HomePage",false) - sharedPref.edit().putBoolean("ManagedProfileActivated",true).apply() - Toast.makeText(myContext, myContext.getString(R.string.success), Toast.LENGTH_SHORT).show() - }, - modifier = Modifier.fillMaxWidth().padding(8.dp) - ) { - Text(stringResource(R.string.activate)) - } - } -} diff --git a/app/src/main/java/com/binbin/androidowner/Network.kt b/app/src/main/java/com/binbin/androidowner/Network.kt deleted file mode 100644 index e30acea..0000000 --- a/app/src/main/java/com/binbin/androidowner/Network.kt +++ /dev/null @@ -1,682 +0,0 @@ -package com.binbin.androidowner - -import android.app.admin.DevicePolicyManager -import android.app.admin.WifiSsidPolicy -import android.app.admin.WifiSsidPolicy.WIFI_SSID_POLICY_TYPE_ALLOWLIST -import android.app.admin.WifiSsidPolicy.WIFI_SSID_POLICY_TYPE_DENYLIST -import android.content.ComponentName -import android.content.Context -import android.net.wifi.WifiSsid -import android.os.Build.VERSION -import android.telephony.TelephonyManager -import android.telephony.TelephonyManager.UNKNOWN_CARRIER_ID -import android.telephony.data.ApnSetting.* -import android.util.Log -import android.widget.Toast -import androidx.activity.ComponentActivity -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.animateContentSize -import androidx.compose.foundation.focusable -import androidx.compose.foundation.horizontalScroll -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 -import androidx.compose.material3.MaterialTheme.typography -import androidx.compose.material3.OutlinedTextField -import androidx.compose.material3.Switch -import androidx.compose.material3.Text -import androidx.compose.material3.TextField -import androidx.compose.runtime.* -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 -import androidx.compose.ui.unit.dp -import androidx.core.net.toUri - -var ssidSet = mutableSetOf() -@Composable -fun Network(){ - Column(modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState())){ - val myContext = LocalContext.current - val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager - val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) - val sharedPref = LocalContext.current.getSharedPreferences("data", Context.MODE_PRIVATE) - val isWear = sharedPref.getBoolean("isWear",false) - val bodyTextStyle = if(isWear){ typography.bodyMedium }else{ typography.bodyLarge } - val focusMgr = LocalFocusManager.current - val titleColor = colorScheme.onPrimaryContainer - - if(VERSION.SDK_INT>=24){ - val wifimac = try { myDpm.getWifiMacAddress(myComponent).toString() }catch(e:SecurityException){ "没有权限" } - Text(text = "WiFi MAC: $wifimac",modifier=Modifier.fillMaxWidth(), textAlign = TextAlign.Center,style=bodyTextStyle) - } - - if(VERSION.SDK_INT>=33&&isDeviceOwner(myDpm)){ - DeviceCtrlItem(R.string.preferential_network_service,R.string.developing,R.drawable.globe_fill0, - {myDpm.isPreferentialNetworkServiceEnabled},{b -> myDpm.isPreferentialNetworkServiceEnabled = b} - ) - } - if(VERSION.SDK_INT>=30&&(isDeviceOwner(myDpm)||(isProfileOwner(myDpm)&&myDpm.isOrganizationOwnedDeviceWithManagedProfile))){ - DeviceCtrlItem(R.string.wifi_lockdown,R.string.place_holder,R.drawable.wifi_password_fill0, - {myDpm.hasLockdownAdminConfiguredNetworks(myComponent)},{b -> myDpm.setConfiguredNetworksLockdownState(myComponent,b)} - ) - } - if(VERSION.SDK_INT>=33){ - Column(modifier = sections()){ - var selectedWifiSecLevel by remember{mutableIntStateOf(myDpm.minimumRequiredWifiSecurityLevel)} - Text(text = stringResource(R.string.min_wifi_security_level), style = typography.titleLarge, color = titleColor) - RadioButtonItem(stringResource(R.string.wifi_security_level_open), {selectedWifiSecLevel==DevicePolicyManager.WIFI_SECURITY_OPEN}, {selectedWifiSecLevel= DevicePolicyManager.WIFI_SECURITY_OPEN}) - RadioButtonItem("WEP, WPA(2)-PSK", {selectedWifiSecLevel==DevicePolicyManager.WIFI_SECURITY_PERSONAL}, {selectedWifiSecLevel= DevicePolicyManager.WIFI_SECURITY_PERSONAL}) - RadioButtonItem("WPA-EAP", {selectedWifiSecLevel==DevicePolicyManager.WIFI_SECURITY_ENTERPRISE_EAP}, {selectedWifiSecLevel= DevicePolicyManager.WIFI_SECURITY_ENTERPRISE_EAP}) - RadioButtonItem("WPA3-192bit", {selectedWifiSecLevel==DevicePolicyManager.WIFI_SECURITY_ENTERPRISE_192}, {selectedWifiSecLevel= DevicePolicyManager.WIFI_SECURITY_ENTERPRISE_192}) - Button( - enabled = isDeviceOwner(myDpm)||(isProfileOwner(myDpm)&&myDpm.isOrganizationOwnedDeviceWithManagedProfile), - onClick = { - myDpm.minimumRequiredWifiSecurityLevel=selectedWifiSecLevel - Toast.makeText(myContext, myContext.getString(R.string.success), Toast.LENGTH_SHORT).show() - }, - modifier = Modifier.fillMaxWidth() - ){ - Text(stringResource(R.string.apply)) - } - } - } - - if(VERSION.SDK_INT>=33&&(isDeviceOwner(myDpm)||(isProfileOwner(myDpm)&&myDpm.isOrganizationOwnedDeviceWithManagedProfile))){ - Column(modifier = sections()){ - var policy = myDpm.wifiSsidPolicy - var selectedPolicyType by remember{mutableIntStateOf(policy?.policyType ?: -1)} - var inputSsid by remember{mutableStateOf("")} - var ssidList by remember{mutableStateOf("")} - val refreshPolicy = { - policy = myDpm.wifiSsidPolicy - selectedPolicyType = policy?.policyType ?: -1 - ssidSet = policy?.ssids ?: mutableSetOf() - } - val refreshList = { - ssidList = "" - var count = ssidSet.size - for(ssid in ssidSet){ count-=1; ssidList+=ssid; if(count>0){ssidList+="\n"} } - } - var inited by remember{mutableStateOf(false)} - if(!inited){ refreshPolicy(); refreshList(); inited=true } - Text(text = stringResource(R.string.wifi_ssid_policy), style = typography.titleLarge, color = titleColor) - RadioButtonItem(stringResource(R.string.none),{selectedPolicyType==-1},{selectedPolicyType=-1}) - RadioButtonItem(stringResource(R.string.whitelist),{selectedPolicyType==WIFI_SSID_POLICY_TYPE_ALLOWLIST},{selectedPolicyType=WIFI_SSID_POLICY_TYPE_ALLOWLIST}) - RadioButtonItem(stringResource(R.string.blacklist),{selectedPolicyType==WIFI_SSID_POLICY_TYPE_DENYLIST},{selectedPolicyType=WIFI_SSID_POLICY_TYPE_DENYLIST}) - Column(modifier = Modifier.animateContentSize(scrollAnim()).horizontalScroll(rememberScrollState())){ - if(ssidList!=""){ - Text(stringResource(R.string.ssid_list_is)) - SelectionContainer{ - Text(text = ssidList, style = bodyTextStyle, color = colorScheme.onPrimaryContainer) - } - } - } - OutlinedTextField( - value = inputSsid, - label = { Text("SSID")}, - onValueChange = {inputSsid = it}, - keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), - keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}), - modifier = Modifier.focusable().fillMaxWidth().padding(vertical = 2.dp) - ) - Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween){ - Button( - onClick = { - if(inputSsid==""){ - Toast.makeText(myContext, myContext.getString(R.string.cannot_be_empty), Toast.LENGTH_SHORT).show() - }else if(WifiSsid.fromBytes(inputSsid.toByteArray()) in ssidSet){ - Toast.makeText(myContext, myContext.getString(R.string.already_exist), Toast.LENGTH_SHORT).show() - }else{ - ssidSet.add(WifiSsid.fromBytes(inputSsid.toByteArray())) - refreshList() - } - inputSsid = "" - }, - modifier = Modifier.fillMaxWidth(0.49F) - ) { - Text(stringResource(R.string.add)) - } - Button( - onClick = { - if(inputSsid==""){ - Toast.makeText(myContext, myContext.getString(R.string.cannot_be_empty), Toast.LENGTH_SHORT).show() - }else if(WifiSsid.fromBytes(inputSsid.toByteArray()) in ssidSet){ - ssidSet.remove(WifiSsid.fromBytes(inputSsid.toByteArray())) - inputSsid = "" - refreshList() - }else{ - Toast.makeText(myContext, myContext.getString(R.string.not_exist), Toast.LENGTH_SHORT).show() - } - }, - modifier = Modifier.fillMaxWidth(0.96F) - ) { - Text(stringResource(R.string.remove)) - } - } - Button( - onClick = { - focusMgr.clearFocus() - if(selectedPolicyType==-1){ - if(policy==null&&ssidSet.isNotEmpty()){ - Toast.makeText(myContext, myContext.getString(R.string.please_select_a_policy), Toast.LENGTH_SHORT).show() - }else{ - myDpm.wifiSsidPolicy = null - refreshPolicy() - Toast.makeText(myContext, myContext.getString(R.string.success), Toast.LENGTH_SHORT).show() - } - }else{ - myDpm.wifiSsidPolicy = if(ssidSet.size==0){ null }else{ WifiSsidPolicy(selectedPolicyType, ssidSet) } - refreshPolicy() - Toast.makeText(myContext, myContext.getString(R.string.success), Toast.LENGTH_SHORT).show() - } - }, - modifier = Modifier.fillMaxWidth() - ) { - Text(stringResource(R.string.apply)) - } - } - } - if(VERSION.SDK_INT>=29&&isDeviceOwner(myDpm)){ - Column(modifier = sections()){ - Text(text = stringResource(R.string.private_dns), style = typography.titleLarge, color = titleColor) - val dnsStatus = mapOf( - DevicePolicyManager.PRIVATE_DNS_MODE_UNKNOWN to stringResource(R.string.unknown), - DevicePolicyManager.PRIVATE_DNS_MODE_OFF to stringResource(R.string.disabled), - DevicePolicyManager.PRIVATE_DNS_MODE_OPPORTUNISTIC to stringResource(R.string.auto), - DevicePolicyManager.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME to stringResource(R.string.dns_provide_hostname) - ) - val operationResult = mapOf( - DevicePolicyManager.PRIVATE_DNS_SET_NO_ERROR to stringResource(R.string.success), - DevicePolicyManager.PRIVATE_DNS_SET_ERROR_HOST_NOT_SERVING to stringResource(R.string.host_not_serving_dns_tls), - DevicePolicyManager.PRIVATE_DNS_SET_ERROR_FAILURE_SETTING to stringResource(R.string.fail) - ) - var status by remember{mutableStateOf(dnsStatus[myDpm.getGlobalPrivateDnsMode(myComponent)])} - Text(text = stringResource(R.string.current_state, status?:stringResource(R.string.unknown))) - Button( - onClick = { - val result = myDpm.setGlobalPrivateDnsModeOpportunistic(myComponent) - Toast.makeText(myContext, operationResult[result], Toast.LENGTH_SHORT).show() - status = dnsStatus[myDpm.getGlobalPrivateDnsMode(myComponent)] - }, - modifier = Modifier.fillMaxWidth() - ) { - Text(stringResource(R.string.set_to_auto)) - } - Spacer(Modifier.padding(vertical = 3.dp)) - var inputHost by remember{mutableStateOf(myDpm.getGlobalPrivateDnsHost(myComponent) ?: "")} - OutlinedTextField( - value = inputHost, - onValueChange = {inputHost=it}, - label = {Text(stringResource(R.string.dns_hostname))}, - keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), - keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}), - modifier = Modifier.focusable().fillMaxWidth().padding(vertical = 2.dp) - ) - Button( - onClick = { - focusMgr.clearFocus() - val result: Int - try{ - result = myDpm.setGlobalPrivateDnsModeSpecifiedHost(myComponent,inputHost) - Toast.makeText(myContext, operationResult[result], Toast.LENGTH_SHORT).show() - }catch(e:IllegalArgumentException){ - Toast.makeText(myContext, myContext.getString(R.string.invalid_hostname), Toast.LENGTH_SHORT).show() - }catch(e:SecurityException){ - Toast.makeText(myContext, myContext.getString(R.string.security_exception), Toast.LENGTH_SHORT).show() - }finally { - status = dnsStatus[myDpm.getGlobalPrivateDnsMode(myComponent)] - } - }, - modifier = Modifier.fillMaxWidth() - ) { - Text(stringResource(R.string.set_dns_host)) - } - } - } - - if(VERSION.SDK_INT>=26&&(isDeviceOwner(myDpm)||(isProfileOwner(myDpm)&&myDpm.isManagedProfile(myComponent)))){ - Column(modifier = sections()){ - Text(text = stringResource(R.string.retrieve_net_logs), style = typography.titleLarge, color = titleColor) - Text(text = stringResource(R.string.developing), style = bodyTextStyle) - Row(modifier=Modifier.fillMaxWidth().padding(horizontal=8.dp),horizontalArrangement=Arrangement.SpaceBetween,verticalAlignment=Alignment.CenterVertically){ - var checked by remember{mutableStateOf(myDpm.isNetworkLoggingEnabled(myComponent))} - Text(text = stringResource(R.string.enabled), style = typography.titleLarge) - Switch( - checked = checked, - onCheckedChange = {myDpm.setNetworkLoggingEnabled(myComponent,!checked);checked = myDpm.isNetworkLoggingEnabled(myComponent)} - ) - } - Button( - onClick = { - val log = myDpm.retrieveNetworkLogs(myComponent,1234567890) - if(log!=null){ - for(i in log){ Log.d("NetLog",i.toString()) } - Toast.makeText(myContext, myContext.getString(R.string.success),Toast.LENGTH_SHORT).show() - }else{ - Log.d("NetLog",myContext.getString(R.string.none)) - Toast.makeText(myContext, myContext.getString(R.string.none),Toast.LENGTH_SHORT).show() - } - }, - modifier = Modifier.fillMaxWidth() - ) { - Text(stringResource(R.string.retrieve)) - } - } - } - - if(VERSION.SDK_INT>=31&&(isDeviceOwner(myDpm)||isProfileOwner(myDpm))){ - Column(modifier = sections()){ - var keyPair by remember{mutableStateOf("")} - Text(text = stringResource(R.string.wifi_keypair), style = typography.titleLarge, color = titleColor) - OutlinedTextField( - value = keyPair, - label = { Text(stringResource(R.string.keypair))}, - onValueChange = {keyPair = it}, - keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), - keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}), - modifier = Modifier.focusable().fillMaxWidth().padding(vertical = 2.dp) - ) - val isExist = try{myDpm.isKeyPairGrantedToWifiAuth(keyPair)}catch(e:java.lang.IllegalArgumentException){false} - Text(stringResource(R.string.already_exist)+":$isExist") - Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween){ - Button( - onClick = { - val result = myDpm.grantKeyPairToWifiAuth(keyPair) - Toast.makeText(myContext, myContext.getString(if(result){R.string.success}else{R.string.fail}), Toast.LENGTH_SHORT).show() - }, - modifier = Modifier.fillMaxWidth(0.49F) - ) { - Text(stringResource(R.string.add)) - } - Button( - onClick = { - val result = myDpm.revokeKeyPairFromWifiAuth(keyPair) - Toast.makeText(myContext, myContext.getString(if(result){R.string.success}else{R.string.fail}), Toast.LENGTH_SHORT).show() - }, - modifier = Modifier.fillMaxWidth(0.96F) - ) { - Text(stringResource(R.string.remove)) - } - } - } - } - - if(VERSION.SDK_INT>=28&&isDeviceOwner(myDpm)){ - Column(modifier = sections()){ - val setting = myDpm.getOverrideApns(myComponent) - var inputNum by remember{mutableStateOf("0")} - var nextStep by remember{mutableStateOf(false)} - val builder = Builder() - Text(text = stringResource(R.string.apn_settings), style = typography.titleLarge, color = titleColor) - Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically){ - Text(text = stringResource(R.string.enable), style = typography.titleLarge) - Switch(checked = myDpm.isOverrideApnEnabled(myComponent), onCheckedChange = {myDpm.setOverrideApnsEnabled(myComponent,it)}) - } - Text(text = stringResource(R.string.total_apn_amount, setting.size), style = bodyTextStyle) - if(setting.size>0){ - Text(text = stringResource(R.string.select_a_apn_or_create, setting.size), style = bodyTextStyle) - TextField( - value = inputNum, - label = { Text("APN")}, - onValueChange = {inputNum = it}, - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number, imeAction = ImeAction.Done), - keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}), - modifier = Modifier.focusable().fillMaxWidth().padding(vertical = 2.dp), - enabled = !nextStep - ) - }else{ - Text(text = stringResource(R.string.no_apn_you_should_create_one), style = bodyTextStyle) - } - Button( - onClick = {focusMgr.clearFocus(); nextStep=!nextStep}, - modifier = Modifier.fillMaxWidth(), - enabled = inputNum!=""&&(nextStep||inputNum=="0"||setting[inputNum.toInt()-1]!=null) - ) { - Text(stringResource(if(nextStep){R.string.previous_step}else{R.string.next_step})) - } - var result = Builder().build() - AnimatedVisibility(nextStep) { - var carrierEnabled by remember{mutableStateOf(false)} - var inputApnName by remember{mutableStateOf("")} - var user by remember{mutableStateOf("")} - var profileId by remember{mutableStateOf("")} - var selectedAuthType by remember{mutableIntStateOf(AUTH_TYPE_NONE)} - var carrierId by remember{mutableStateOf("$UNKNOWN_CARRIER_ID")} - var apnTypeBitmask by remember{mutableStateOf("")} - var entryName by remember{mutableStateOf("")} - var mmsProxyAddress by remember{mutableStateOf("")} - var mmsProxyPort by remember{mutableStateOf("")} - var proxyAddress by remember{mutableStateOf("")} - var proxyPort by remember{mutableStateOf("")} - var mmsc by remember{mutableStateOf("")} - var mtuV4 by remember{mutableStateOf("")} - var mtuV6 by remember{mutableStateOf("")} - var mvnoType by remember{mutableIntStateOf(-1)} - var networkTypeBitmask by remember{mutableStateOf("")} - var operatorNumeric by remember{mutableStateOf("")} - var password by remember{mutableStateOf("")} - var persistent by remember{mutableStateOf(false)} - var protocol by remember{mutableIntStateOf(-1)} - var roamingProtocol by remember{mutableIntStateOf(-1)} - var id by remember{mutableIntStateOf(0)} - - if(inputNum!="0"){ - val current = setting[inputNum.toInt()-1] - id = current.id - carrierEnabled = current.isEnabled - inputApnName = current.apnName - user = current.user - if(VERSION.SDK_INT>=33){profileId = current.profileId.toString()} - selectedAuthType = current.authType - apnTypeBitmask = current.apnTypeBitmask.toString() - entryName = current.entryName - if(VERSION.SDK_INT>=29){mmsProxyAddress = current.mmsProxyAddressAsString} - mmsProxyPort = current.mmsProxyPort.toString() - if(VERSION.SDK_INT>=29){proxyAddress = current.proxyAddressAsString} - proxyPort = current.proxyPort.toString() - mmsc = current.mmsc.toString() - if(VERSION.SDK_INT>=33){ mtuV4 = current.mtuV4.toString(); mtuV6 = current.mtuV6.toString() } - mvnoType = current.mvnoType - networkTypeBitmask = current.networkTypeBitmask.toString() - operatorNumeric = current.operatorNumeric - password = current.password - if(VERSION.SDK_INT>=33){persistent = current.isPersistent} - protocol = current.protocol - roamingProtocol = current.roamingProtocol - } - - Column { - - Text(text = "APN", style = typography.titleLarge) - TextField( - value = inputApnName, - onValueChange = {inputApnName=it}, - label = {Text(stringResource(R.string.name))}, - keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), - keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}), - modifier = Modifier.focusable().fillMaxWidth().padding(top = 2.dp, bottom = 4.dp) - ) - Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically){ - Text(text = stringResource(R.string.enable), style = typography.titleLarge) - Switch(checked = carrierEnabled, onCheckedChange = {carrierEnabled=it}) - } - - Text(text = stringResource(R.string.user_name), style = typography.titleLarge) - TextField( - value = user, - onValueChange = {user=it}, - label = {Text(stringResource(R.string.user_name))}, - keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), - keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}), - modifier = Modifier.focusable().fillMaxWidth().padding(top = 2.dp, bottom = 4.dp) - ) - - if(VERSION.SDK_INT>=33){ - Text(text = stringResource(R.string.profile_id), style = typography.titleLarge) - TextField( - value = profileId, - onValueChange = {profileId=it}, - label = {Text("ID")}, - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number, imeAction = ImeAction.Done), - keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}), - modifier = Modifier.focusable().fillMaxWidth().padding(top = 2.dp, bottom = 4.dp) - ) - } - - Text(text = stringResource(R.string.auth_type), style = typography.titleLarge) - RadioButtonItem("无",{selectedAuthType==AUTH_TYPE_NONE},{selectedAuthType=AUTH_TYPE_NONE}) - RadioButtonItem("CHAP",{selectedAuthType==AUTH_TYPE_CHAP},{selectedAuthType=AUTH_TYPE_CHAP}) - RadioButtonItem("PAP",{selectedAuthType==AUTH_TYPE_PAP},{selectedAuthType=AUTH_TYPE_PAP}) - RadioButtonItem("PAP/CHAP",{selectedAuthType==AUTH_TYPE_PAP_OR_CHAP},{selectedAuthType=AUTH_TYPE_PAP_OR_CHAP}) - - if(VERSION.SDK_INT>=29){ - val ts = myContext.getSystemService(ComponentActivity.TELEPHONY_SERVICE) as TelephonyManager - carrierId = ts.simCarrierId.toString() - Text(text = "CarrierID", style = typography.titleLarge) - TextField( - value = carrierId, - onValueChange = {carrierId=it}, - label = {Text("ID")}, - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number, imeAction = ImeAction.Done), - keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}), - modifier = Modifier.focusable().fillMaxWidth().padding(top = 2.dp, bottom = 4.dp) - ) - } - - Text(text = stringResource(R.string.apn_type), style = typography.titleLarge) - TextField( - value = apnTypeBitmask, - onValueChange = {apnTypeBitmask=it}, - label = {Text(stringResource(R.string.bitmask))}, - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number, imeAction = ImeAction.Done), - keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}), - modifier = Modifier.focusable().fillMaxWidth().padding(top = 2.dp, bottom = 4.dp) - ) - - Text(text = stringResource(R.string.description), style = typography.titleLarge) - TextField( - value = entryName, - onValueChange = {entryName=it}, - label = {Text(stringResource(R.string.description))}, - keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), - keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}), - modifier = Modifier.focusable().fillMaxWidth().padding(top = 2.dp, bottom = 4.dp) - ) - - Text(text = stringResource(R.string.mms_proxy), style = typography.titleLarge) - if(VERSION.SDK_INT>=29){ - TextField( - value = mmsProxyAddress, - onValueChange = {mmsProxyAddress=it}, - label = {Text(stringResource(R.string.address))}, - keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), - keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}), - modifier = Modifier.focusable().fillMaxWidth().padding(top = 2.dp, bottom = 4.dp) - ) - } - TextField( - value = mmsProxyPort, - onValueChange = {mmsProxyPort=it}, - label = {Text(stringResource(R.string.port))}, - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number, imeAction = ImeAction.Done), - keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}), - modifier = Modifier.focusable().fillMaxWidth().padding(top = 2.dp, bottom = 4.dp) - ) - - Text(text = stringResource(R.string.proxy), style = typography.titleLarge) - if(VERSION.SDK_INT>=29){ - TextField( - value = proxyAddress, - onValueChange = {proxyAddress=it}, - label = {Text(stringResource(R.string.address))}, - keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), - keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}), - modifier = Modifier.focusable().fillMaxWidth().padding(top = 2.dp, bottom = 4.dp) - ) - } - TextField( - value = proxyPort, - onValueChange = {proxyPort=it}, - label = {Text(stringResource(R.string.port))}, - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number, imeAction = ImeAction.Done), - keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}), - modifier = Modifier.focusable().fillMaxWidth().padding(top = 2.dp, bottom = 4.dp) - ) - - Text(text = "MMSC", style = typography.titleLarge) - TextField( - value = mmsc, - onValueChange = {mmsc=it}, - label = {Text("Uri")}, - keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), - keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}), - modifier = Modifier.focusable().fillMaxWidth().padding(top = 2.dp, bottom = 4.dp) - ) - - if(VERSION.SDK_INT>=33){ - Text(text = "MTU", style = typography.titleLarge) - TextField( - value = mtuV4, - onValueChange = {mtuV4=it}, - label = {Text("IPV4")}, - keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), - keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}), - modifier = Modifier.focusable().fillMaxWidth().padding(top = 2.dp, bottom = 4.dp) - ) - TextField( - value = mtuV6, - onValueChange = {mtuV6=it}, - label = {Text("IPV6")}, - keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), - keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}), - modifier = Modifier.focusable().fillMaxWidth().padding(top = 2.dp, bottom = 4.dp) - ) - } - - Text(text = "MVNO", style = typography.titleLarge) - RadioButtonItem("SPN",{mvnoType==MVNO_TYPE_SPN},{mvnoType=MVNO_TYPE_SPN}) - RadioButtonItem("IMSI",{mvnoType==MVNO_TYPE_IMSI},{mvnoType=MVNO_TYPE_IMSI}) - RadioButtonItem("GID",{mvnoType==MVNO_TYPE_GID},{mvnoType=MVNO_TYPE_GID}) - RadioButtonItem("ICCID",{mvnoType==MVNO_TYPE_ICCID},{mvnoType=MVNO_TYPE_ICCID}) - - Text(text = stringResource(R.string.network_type), style = typography.titleLarge) - TextField( - value = networkTypeBitmask, - onValueChange = {networkTypeBitmask=it}, - label = {Text(stringResource(R.string.bitmask))}, - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number, imeAction = ImeAction.Done), - keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}), - modifier = Modifier.focusable().fillMaxWidth().padding(top = 2.dp, bottom = 4.dp) - ) - - Text(text = "OperatorNumeric", style = typography.titleLarge) - TextField( - value = operatorNumeric, - onValueChange = {operatorNumeric=it}, - label = {Text("ID")}, - keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), - keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}), - modifier = Modifier.focusable().fillMaxWidth().padding(top = 2.dp, bottom = 4.dp) - ) - - Text(text = stringResource(R.string.password), style = typography.titleLarge) - TextField( - value = password, - onValueChange = {password=it}, - label = {Text(stringResource(R.string.password))}, - keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), - keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}), - modifier = Modifier.focusable().fillMaxWidth().padding(top = 2.dp, bottom = 4.dp) - ) - - if(VERSION.SDK_INT>=33){ - Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically){ - Text(text = stringResource(R.string.persistent), style = typography.titleLarge) - Switch(checked = persistent, onCheckedChange = {persistent=it}) - } - } - - Text(text = stringResource(R.string.protocol), style = typography.titleLarge) - RadioButtonItem("IPV4",{protocol==PROTOCOL_IP},{protocol=PROTOCOL_IP}) - RadioButtonItem("IPV6",{protocol==PROTOCOL_IPV6},{protocol=PROTOCOL_IPV6}) - RadioButtonItem("IPV4/IPV6",{protocol==PROTOCOL_IPV4V6},{protocol=PROTOCOL_IPV4V6}) - RadioButtonItem("PPP",{protocol==PROTOCOL_PPP},{protocol=PROTOCOL_PPP}) - if(VERSION.SDK_INT>=29){ - RadioButtonItem("non-IP",{protocol==PROTOCOL_NON_IP},{protocol=PROTOCOL_NON_IP}) - RadioButtonItem("Unstructured",{protocol==PROTOCOL_UNSTRUCTURED},{protocol=PROTOCOL_UNSTRUCTURED}) - } - - Text(text = stringResource(R.string.roaming_protocol), style = typography.titleLarge) - RadioButtonItem("IPV4",{roamingProtocol==PROTOCOL_IP},{roamingProtocol=PROTOCOL_IP}) - RadioButtonItem("IPV6",{roamingProtocol==PROTOCOL_IPV6},{roamingProtocol=PROTOCOL_IPV6}) - RadioButtonItem("IPV4/IPV6",{roamingProtocol==PROTOCOL_IPV4V6},{roamingProtocol=PROTOCOL_IPV4V6}) - RadioButtonItem("PPP",{roamingProtocol==PROTOCOL_PPP},{roamingProtocol=PROTOCOL_PPP}) - if(VERSION.SDK_INT>=29){ - RadioButtonItem("non-IP",{roamingProtocol==PROTOCOL_NON_IP},{roamingProtocol=PROTOCOL_NON_IP}) - RadioButtonItem("Unstructured",{roamingProtocol==PROTOCOL_UNSTRUCTURED},{roamingProtocol=PROTOCOL_UNSTRUCTURED}) - } - - var finalStep by remember{mutableStateOf(false)} - Button( - onClick = { - if(!finalStep){ - builder.setCarrierEnabled(carrierEnabled) - builder.setApnName(inputApnName) - builder.setUser(user) - if(VERSION.SDK_INT>=33){builder.setProfileId(profileId.toInt())} - builder.setAuthType(selectedAuthType) - if(VERSION.SDK_INT>=29){builder.setCarrierId(carrierId.toInt())} - builder.setApnTypeBitmask(apnTypeBitmask.toInt()) - builder.setEntryName(entryName) - if(VERSION.SDK_INT>=29){builder.setMmsProxyAddress(mmsProxyAddress)} - builder.setMmsProxyPort(mmsProxyPort.toInt()) - if(VERSION.SDK_INT>=29){builder.setProxyAddress(proxyAddress)} - builder.setProxyPort(proxyPort.toInt()) - builder.setMmsc(mmsc.toUri()) - if(VERSION.SDK_INT>=33){ builder.setMtuV4(mtuV4.toInt()); builder.setMtuV6(mtuV6.toInt()) } - builder.setMvnoType(mvnoType) - builder.setNetworkTypeBitmask(networkTypeBitmask.toInt()) - builder.setOperatorNumeric(operatorNumeric) - builder.setPassword(password) - if(VERSION.SDK_INT>=33){builder.setPersistent(persistent)} - builder.setProtocol(protocol) - builder.setRoamingProtocol(roamingProtocol) - result = builder.build() - } - - finalStep=!finalStep - }, - modifier = Modifier.fillMaxWidth() - ) { - Text(stringResource(if(finalStep){R.string.previous_step}else{R.string.next_step})) - } - AnimatedVisibility(finalStep) { - if(inputNum=="0"){ - Button( - onClick = {myDpm.addOverrideApn(myComponent,result)}, - modifier = Modifier.fillMaxWidth() - ) { - Text(stringResource(R.string.create)) - } - }else{ - Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween){ - Button( - onClick = { - val success = myDpm.updateOverrideApn(myComponent,id,result) - Toast.makeText(myContext, myContext.getString(if(success){R.string.success}else{R.string.fail}), Toast.LENGTH_SHORT).show() - }, - Modifier.fillMaxWidth(0.49F) - ){ - Text(stringResource(R.string.update)) - } - Button( - onClick = { - val success = myDpm.removeOverrideApn(myComponent,id) - Toast.makeText(myContext, if(success){"成功"}else{"失败"}, Toast.LENGTH_SHORT).show() - }, - Modifier.fillMaxWidth(0.96F) - ){ - Text(stringResource(R.string.remove)) - } - } - } - } - } - } - 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/Password.kt b/app/src/main/java/com/binbin/androidowner/Password.kt deleted file mode 100644 index 226133d..0000000 --- a/app/src/main/java/com/binbin/androidowner/Password.kt +++ /dev/null @@ -1,476 +0,0 @@ -package com.binbin.androidowner - -import android.app.KeyguardManager -import android.app.admin.DevicePolicyManager -import android.app.admin.DevicePolicyManager.* -import android.content.ComponentName -import android.content.Context -import android.content.Intent -import android.os.Build.VERSION -import android.widget.Toast -import androidx.activity.ComponentActivity -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.animateContentSize -import androidx.compose.foundation.* -import androidx.compose.foundation.layout.* -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.foundation.text.KeyboardActions -import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.Check -import androidx.compose.material3.* -import androidx.compose.material3.MaterialTheme.colorScheme -import androidx.compose.material3.MaterialTheme.typography -import androidx.compose.runtime.* -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.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 kotlinx.coroutines.delay - -@Composable -fun Password(){ - val myContext = LocalContext.current - val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager - val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) - val sharedPref = LocalContext.current.getSharedPreferences("data", Context.MODE_PRIVATE) - var newPwd by remember{ mutableStateOf("") } - val focusMgr = LocalFocusManager.current - val isWear = sharedPref.getBoolean("isWear",false) - val titleColor = colorScheme.onPrimaryContainer - val bodyTextStyle = if(isWear){typography.bodyMedium}else{typography.bodyLarge} - val scrollState = rememberScrollState() - Column( - horizontalAlignment = Alignment.CenterHorizontally, - modifier = Modifier.fillMaxSize().verticalScroll(scrollState) - ) { - val myByteArray by remember{ mutableStateOf(byteArrayOf(1,1,4,5,1,4,1,9,1,9,8,1,0,1,1,4,5,1,4,1,9,1,9,8,1,0,1,1,4,5,1,4,1,9,1,9,8,1,0)) } - Text( - text = stringResource(R.string.password_warning), - color = colorScheme.onErrorContainer, - modifier = sections(colorScheme.errorContainer), - style=bodyTextStyle - ) - if(isWear){ - Text( - text = stringResource(R.string.password_wearos_warning), - color = colorScheme.onErrorContainer, - modifier = sections(colorScheme.errorContainer), - style = typography.bodyMedium - ) - } - if(myDpm.isDeviceOwnerApp("com.binbin.androidowner")){ - Column(modifier = sections()) { - if(VERSION.SDK_INT>=29){ - val passwordComplexity = mapOf( - PASSWORD_COMPLEXITY_NONE to stringResource(R.string.password_complexity_none), - PASSWORD_COMPLEXITY_LOW to stringResource(R.string.password_complexity_low), - PASSWORD_COMPLEXITY_MEDIUM to stringResource(R.string.password_complexity_medium), - PASSWORD_COMPLEXITY_HIGH to stringResource(R.string.password_complexity_high) - ) - val pwdComplex = passwordComplexity[myDpm.passwordComplexity] - Text(text = stringResource(R.string.current_password_complexity_is, pwdComplex?:stringResource(R.string.unknown)),style=bodyTextStyle) - } - if(isDeviceOwner(myDpm)|| isProfileOwner(myDpm)){ - Text(stringResource(R.string.is_password_sufficient, myDpm.isActivePasswordSufficient),style=bodyTextStyle) - } - val pwdFailedAttempts = myDpm.currentFailedPasswordAttempts - Text(text = stringResource(R.string.password_failed_attempts_is, pwdFailedAttempts),style=bodyTextStyle) - if(VERSION.SDK_INT>=28&&isProfileOwner(myDpm)&&myDpm.isManagedProfile(myComponent)){ - val unifiedPwd = myDpm.isUsingUnifiedPassword(myComponent) - Text(stringResource(R.string.is_using_unified_password, unifiedPwd),style=bodyTextStyle) - } - } - } - if(VERSION.SDK_INT>=26){ - Column(horizontalAlignment = Alignment.Start, modifier = sections()) { - Text(text = stringResource(R.string.reset_password_token), style = typography.titleLarge,color = titleColor) - Row( - modifier = if(!isWear){Modifier.fillMaxWidth()}else{Modifier.horizontalScroll(rememberScrollState())}, - horizontalArrangement = Arrangement.SpaceBetween - ){ - Button( - onClick = { - if(myDpm.clearResetPasswordToken(myComponent)){ Toast.makeText(myContext, myContext.getString(R.string.success), Toast.LENGTH_SHORT).show() - }else{ Toast.makeText(myContext, myContext.getString(R.string.fail), Toast.LENGTH_SHORT).show() } - }, - modifier = if(isWear){Modifier}else{Modifier.fillMaxWidth(0.32F)}, - enabled = isDeviceOwner(myDpm)||isProfileOwner(myDpm) - ) { - Text(stringResource(R.string.clear)) - } - if(isWear){Spacer(Modifier.padding(horizontal = 2.dp))} - Button( - onClick = { - try { - if(myDpm.setResetPasswordToken(myComponent, myByteArray)){ - Toast.makeText(myContext, myContext.getString(R.string.success), Toast.LENGTH_SHORT).show() - }else{ - Toast.makeText(myContext, myContext.getString(R.string.fail), Toast.LENGTH_SHORT).show() - } - }catch(e:SecurityException){ - Toast.makeText(myContext, myContext.getString(R.string.security_exception), Toast.LENGTH_SHORT).show() - } - }, - enabled = isDeviceOwner(myDpm)||isProfileOwner(myDpm), - modifier = if(isWear){Modifier}else{Modifier.fillMaxWidth(0.47F)} - ) { - Text(stringResource(R.string.set)) - } - if(isWear){Spacer(Modifier.padding(horizontal = 2.dp))} - Button( - onClick = { - if(!myDpm.isResetPasswordTokenActive(myComponent)){ - try{ activateToken(myContext) } - catch(e:NullPointerException){ Toast.makeText(myContext, myContext.getString(R.string.please_set_a_token), Toast.LENGTH_SHORT).show() } - }else{ Toast.makeText(myContext, "已经激活", Toast.LENGTH_SHORT).show() } - }, - enabled = isDeviceOwner(myDpm)||isProfileOwner(myDpm), - modifier = if(isWear){Modifier}else{Modifier.fillMaxWidth(0.88F)} - ) { - Text(stringResource(R.string.activate)) - } - } - Text(stringResource(R.string.activate_token_not_required_when_no_password),style=bodyTextStyle) - } - } - Column( - modifier = sections() - ) { - var confirmed by remember{ mutableStateOf(false) } - Text(text = stringResource(R.string.reset_password),style = typography.titleLarge,color = titleColor) - OutlinedTextField( - value = newPwd, - onValueChange = {newPwd=it}, - enabled = !confirmed&&(isDeviceOwner(myDpm)||isProfileOwner(myDpm)||myDpm.isAdminActive(myComponent)), - label = { Text(stringResource(R.string.password))}, - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password, imeAction = ImeAction.Done), - keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}), - modifier = Modifier.focusable().padding(vertical = if(isWear){0.dp}else{5.dp}).fillMaxWidth() - ) - Text(text = stringResource(R.string.reset_pwd_desc), modifier = Modifier.padding(vertical = 3.dp),style=bodyTextStyle) - var resetPwdFlag by remember{ mutableIntStateOf(0) } - if(VERSION.SDK_INT>=23){ - RadioButtonItem( - stringResource(R.string.do_not_ask_credentials_on_boot), - {resetPwdFlag==RESET_PASSWORD_DO_NOT_ASK_CREDENTIALS_ON_BOOT}, {resetPwdFlag=RESET_PASSWORD_DO_NOT_ASK_CREDENTIALS_ON_BOOT} - ) - } - RadioButtonItem(stringResource(R.string.reset_password_require_entry),{resetPwdFlag==RESET_PASSWORD_REQUIRE_ENTRY}, {resetPwdFlag=RESET_PASSWORD_REQUIRE_ENTRY}) - RadioButtonItem(stringResource(R.string.none),{resetPwdFlag==0},{resetPwdFlag=0}) - Button( - onClick = { - if(newPwd.length>=4||newPwd.isEmpty()){ confirmed=!confirmed - }else{ Toast.makeText(myContext, myContext.getString(R.string.require_4_digit_password), Toast.LENGTH_SHORT).show() } - }, - enabled = isDeviceOwner(myDpm) || isProfileOwner(myDpm) || myDpm.isAdminActive(myComponent), - modifier = Modifier.fillMaxWidth(), - colors = ButtonDefaults.buttonColors( - containerColor = if(confirmed){ colorScheme.primary }else{ colorScheme.error }, - contentColor = if(confirmed){ colorScheme.onPrimary }else{ colorScheme.onError } - ) - ) { - Text(text = stringResource(if(confirmed){R.string.cancel}else{R.string.confirm})) - } - if(VERSION.SDK_INT>=26){ - Button( - onClick = { - val resetSuccess = myDpm.resetPasswordWithToken(myComponent,newPwd,myByteArray,resetPwdFlag) - if(resetSuccess){ Toast.makeText(myContext, myContext.getString(R.string.success), Toast.LENGTH_SHORT).show();newPwd=""} - else{ Toast.makeText(myContext, myContext.getString(R.string.fail), Toast.LENGTH_SHORT).show() } - confirmed=false - }, - colors = ButtonDefaults.buttonColors(containerColor = colorScheme.error, contentColor = colorScheme.onError), - enabled = confirmed&&(isDeviceOwner(myDpm)||isProfileOwner(myDpm)), - modifier = Modifier.fillMaxWidth() - ) { - Text(stringResource(R.string.reset_password_with_token)) - } - } - Button( - onClick = { - val resetSuccess = myDpm.resetPassword(newPwd,resetPwdFlag) - if(resetSuccess){ Toast.makeText(myContext, myContext.getString(R.string.success), Toast.LENGTH_SHORT).show(); newPwd=""} - else{ Toast.makeText(myContext, myContext.getString(R.string.fail), Toast.LENGTH_SHORT).show() } - confirmed=false - }, - enabled = confirmed, - colors = ButtonDefaults.buttonColors(containerColor = colorScheme.error, contentColor = colorScheme.onError), - modifier = Modifier.fillMaxWidth() - ) { - Text(stringResource(R.string.reset_password_deprecated)) - } - } - - PasswordItem(R.string.max_pwd_fail,R.string.max_pwd_fail_desc,R.string.max_pwd_fail_textfield, 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,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,true, - {myDpm.getPasswordHistoryLength(null).toString()},{ic -> myDpm.setPasswordHistoryLength(myComponent, ic.toInt()) }) - PasswordItem(R.string.max_time_to_lock,R.string.max_time_to_lock_desc,R.string.time_unit_ms,true, - {myDpm.getMaximumTimeToLock(myComponent).toString()},{ic -> myDpm.setMaximumTimeToLock(myComponent,ic.toLong())}) - - if(VERSION.SDK_INT>=31){ - Column(modifier = sections()) { - val passwordComplexity = mapOf( - PASSWORD_COMPLEXITY_NONE to stringResource(R.string.password_complexity_none), - PASSWORD_COMPLEXITY_LOW to stringResource(R.string.password_complexity_low), - PASSWORD_COMPLEXITY_MEDIUM to stringResource(R.string.password_complexity_medium), - PASSWORD_COMPLEXITY_HIGH to stringResource(R.string.password_complexity_high) - ).toList() - var selectedItem by remember{ mutableIntStateOf(passwordComplexity[0].first) } - if(isDeviceOwner(myDpm) || isProfileOwner(myDpm)){ - selectedItem=myDpm.requiredPasswordComplexity - } - Text(text = stringResource(R.string.required_password_complexity), style = typography.titleLarge,color = titleColor) - 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}, - if(isWear){ - colorScheme.error}else{ - colorScheme.onBackground}) - Text(text = stringResource(R.string.password_ordered_desc), modifier = Modifier.padding(vertical = 3.dp), style = bodyTextStyle) - Row(modifier = Modifier.fillMaxWidth(),horizontalArrangement = Arrangement.SpaceBetween){ - Button( - onClick = { - myDpm.requiredPasswordComplexity = selectedItem - Toast.makeText(myContext, myContext.getString(R.string.success), Toast.LENGTH_SHORT).show() - }, - enabled = isDeviceOwner(myDpm)|| isProfileOwner(myDpm), - modifier = if(isWear){Modifier.fillMaxWidth()}else{Modifier.fillMaxWidth(0.4F)} - ) { - Text(text = stringResource(R.string.apply)) - } - if(!isWear){ - Button( - onClick = {myContext.startActivity(Intent(ACTION_SET_NEW_PASSWORD))}, - modifier = Modifier.fillMaxWidth(0.95F) - ){ - Text(stringResource(R.string.require_set_new_password)) - } - } - } - if(isWear){ - Button( - onClick = {myContext.startActivity(Intent(ACTION_SET_NEW_PASSWORD))}, - modifier = Modifier.fillMaxWidth() - ){ - Text(stringResource(R.string.require_set_new_password)) - } - } - } - } - - Column(modifier = sections()){ - var state by remember{mutableIntStateOf(-1)} - var shortcuts by remember{mutableStateOf(false)} - var biometrics by remember{mutableStateOf(false)} - var iris by remember{mutableStateOf(false)} - var face by remember{mutableStateOf(false)} - var remote by remember{mutableStateOf(false)} - var fingerprint by remember{mutableStateOf(false)} - var agents by remember{mutableStateOf(false)} - var unredacted by remember{mutableStateOf(false)} - var notification by remember{mutableStateOf(false)} - var camera by remember{mutableStateOf(false)} - var widgets by remember{mutableStateOf(false)} - val calculateCustomFeature = { - var calculate = myDpm.getKeyguardDisabledFeatures(myComponent) - if(calculate==0){state=0} - else{ - if(calculate-KEYGUARD_DISABLE_SHORTCUTS_ALL>=0 && VERSION.SDK_INT>=34){shortcuts=true;calculate-=KEYGUARD_DISABLE_SHORTCUTS_ALL} - if(calculate-KEYGUARD_DISABLE_BIOMETRICS>=0&&VERSION.SDK_INT>=28){biometrics=true;calculate -= KEYGUARD_DISABLE_BIOMETRICS} - if(calculate-KEYGUARD_DISABLE_IRIS>=0&&VERSION.SDK_INT>=28){iris=true;calculate -=KEYGUARD_DISABLE_IRIS } - if(calculate-KEYGUARD_DISABLE_FACE>=0&&VERSION.SDK_INT>=28){face=true;calculate -= KEYGUARD_DISABLE_FACE} - if(calculate-KEYGUARD_DISABLE_REMOTE_INPUT>=0&&VERSION.SDK_INT>=24){remote=true;calculate -= KEYGUARD_DISABLE_REMOTE_INPUT} - if(calculate-KEYGUARD_DISABLE_FINGERPRINT>=0){fingerprint=true;calculate -= KEYGUARD_DISABLE_FINGERPRINT} - if(calculate-KEYGUARD_DISABLE_TRUST_AGENTS>=0){agents=true;calculate -= KEYGUARD_DISABLE_TRUST_AGENTS} - if(calculate-KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS>=0){unredacted=true;calculate -= KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS} - if(calculate-KEYGUARD_DISABLE_SECURE_NOTIFICATIONS>=0){notification=true;calculate -= KEYGUARD_DISABLE_SECURE_NOTIFICATIONS} - if(calculate-KEYGUARD_DISABLE_SECURE_CAMERA>=0){camera=true;calculate -= KEYGUARD_DISABLE_SECURE_CAMERA} - if(calculate-KEYGUARD_DISABLE_WIDGETS_ALL>=0){widgets=true;calculate -= KEYGUARD_DISABLE_WIDGETS_ALL} - } - } - if(state==-1){ - state = when(myDpm.getKeyguardDisabledFeatures(myComponent)){ - KEYGUARD_DISABLE_FEATURES_NONE->0 - KEYGUARD_DISABLE_FEATURES_ALL->1 - else->2 - } - calculateCustomFeature() - } - Text(text = stringResource(R.string.keyguard_disabled_features), style = typography.titleLarge) - RadioButtonItem(stringResource(R.string.enable_all),{state==0},{state=0}) - RadioButtonItem(stringResource(R.string.disable_all),{state==1},{state=1}) - RadioButtonItem(stringResource(R.string.custom),{state==2},{state=2}) - AnimatedVisibility(state==2) { - Column { - CheckBoxItem(stringResource(R.string.keyguard_disabled_features_widgets),{widgets},{widgets=!widgets}) - CheckBoxItem(stringResource(R.string.keyguard_disabled_features_camera),{camera},{camera=!camera}) - CheckBoxItem(stringResource(R.string.keyguard_disabled_features_notification),{notification},{notification=!notification}) - CheckBoxItem(stringResource(R.string.keyguard_disabled_features_unredacted_notification),{unredacted},{unredacted=!unredacted}) - CheckBoxItem(stringResource(R.string.keyguard_disabled_features_trust_agents),{agents},{agents=!agents}) - CheckBoxItem(stringResource(R.string.keyguard_disabled_features_fingerprint),{fingerprint},{fingerprint=!fingerprint}) - if(VERSION.SDK_INT>=24){ CheckBoxItem(stringResource(R.string.keyguard_disabled_features_remote_input),{remote}, {remote=!remote}) } - if(VERSION.SDK_INT>=28){ - CheckBoxItem(stringResource(R.string.keyguard_disabled_features_face),{face},{face=!face}) - CheckBoxItem(stringResource(R.string.keyguard_disabled_features_iris),{iris},{iris=!iris}) - CheckBoxItem(stringResource(R.string.keyguard_disabled_features_biometrics),{biometrics},{biometrics=!biometrics}) - } - if(VERSION.SDK_INT>=34){ CheckBoxItem(stringResource(R.string.keyguard_disabled_features_shortcuts),{shortcuts},{shortcuts=!shortcuts}) } - } - } - Button( - onClick = { - var result = 0 - if(state==0){ result = 0 } - else if(state==1){ result = KEYGUARD_DISABLE_FEATURES_ALL } - else{ - if(shortcuts&&VERSION.SDK_INT>=34){result+=KEYGUARD_DISABLE_SHORTCUTS_ALL} - if(biometrics&&VERSION.SDK_INT>=28){result+=KEYGUARD_DISABLE_BIOMETRICS} - if(iris&&VERSION.SDK_INT>=28){result+=KEYGUARD_DISABLE_IRIS} - if(face&&VERSION.SDK_INT>=28){result+=KEYGUARD_DISABLE_FACE} - if(remote&&VERSION.SDK_INT>=24){result+=KEYGUARD_DISABLE_REMOTE_INPUT} - if(fingerprint){result+=KEYGUARD_DISABLE_FINGERPRINT} - if(agents){result+=KEYGUARD_DISABLE_TRUST_AGENTS} - if(unredacted){result+=KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS} - if(notification){result+=KEYGUARD_DISABLE_SECURE_NOTIFICATIONS} - if(camera){result+=KEYGUARD_DISABLE_SECURE_CAMERA} - if(widgets){result+=KEYGUARD_DISABLE_WIDGETS_ALL} - } - myDpm.setKeyguardDisabledFeatures(myComponent,result) - Toast.makeText(myContext, myContext.getString(R.string.success), Toast.LENGTH_SHORT).show() - calculateCustomFeature() - }, - enabled = isDeviceOwner(myDpm)||isProfileOwner(myDpm), - modifier = Modifier.fillMaxWidth() - ) { - Text(text = stringResource(R.string.apply)) - } - } - - var passwordQualityExpand by remember{mutableStateOf(VERSION.SDK_INT < 31)} - var launchScrollDown by remember{mutableStateOf(false)} - LaunchedEffect(launchScrollDown){ - if(launchScrollDown){ - delay(10) - scrollState.animateScrollTo((scrollState.value+myContext.resources.displayMetrics.heightPixels*0.4).toInt(), scrollAnim()) - launchScrollDown=false - } - } - Column(modifier = sections(onClick = {passwordQualityExpand=true;launchScrollDown=true}, clickable = !passwordQualityExpand).animateContentSize(animationSpec = scrollAnim())) { - val passwordQuality = mapOf( - PASSWORD_QUALITY_UNSPECIFIED to stringResource(R.string.password_quality_unspecified), - PASSWORD_QUALITY_SOMETHING to stringResource(R.string.password_quality_something), - PASSWORD_QUALITY_ALPHABETIC to stringResource(R.string.password_quality_alphabetic), - PASSWORD_QUALITY_NUMERIC to stringResource(R.string.password_quality_numeric), - PASSWORD_QUALITY_ALPHANUMERIC to stringResource(R.string.password_quality_alphanumeric), - PASSWORD_QUALITY_BIOMETRIC_WEAK to stringResource(R.string.password_quality_biometrics_weak), - PASSWORD_QUALITY_NUMERIC_COMPLEX to stringResource(R.string.password_quality_numeric_complex), - PASSWORD_QUALITY_COMPLEX to stringResource(R.string.custom)+"(${stringResource(R.string.unsupported)})", - ).toList() - var selectedItem by remember{ mutableIntStateOf(passwordQuality[0].first) } - if(isDeviceOwner(myDpm) || isProfileOwner(myDpm)){ selectedItem=myDpm.getPasswordQuality(myComponent) } - Text(text = stringResource(R.string.required_password_quality), style = typography.titleLarge,color = titleColor) - if(passwordQualityExpand){ - Text(text = stringResource(R.string.password_complexity_instead_password_quality), style = bodyTextStyle) - } - if(VERSION.SDK_INT>=31){ Text(text = stringResource(R.string.password_quality_deprecated_desc), color = colorScheme.error, style = bodyTextStyle) } - if(passwordQualityExpand){ - 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 = stringResource(R.string.password_ordered_desc), modifier = Modifier.padding(vertical = 3.dp), style = bodyTextStyle) - Button( - onClick = { - myDpm.setPasswordQuality(myComponent,selectedItem) - Toast.makeText(myContext, myContext.getString(R.string.success), Toast.LENGTH_SHORT).show() - }, - enabled = isDeviceOwner(myDpm) || isProfileOwner(myDpm), - modifier = Modifier.fillMaxWidth() - ) { - Text(stringResource(R.string.apply)) - } - if(VERSION.SDK_INT<31){ - Button(onClick = {myContext.startActivity(Intent(ACTION_SET_NEW_PASSWORD))}){ - Text(stringResource(R.string.require_set_new_password)) - }} - } - } - Spacer(Modifier.padding(vertical = 30.dp)) - } -} - -@Composable -private fun PasswordItem( - itemName:Int, - itemDesc:Int, - textFieldLabel:Int, - allowZero:Boolean, - getMethod:()->String, - setMethod:(ic:String)->Unit -){ - val myContext = LocalContext.current - val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager - val sharedPref = LocalContext.current.getSharedPreferences("data", Context.MODE_PRIVATE) - val isWear = sharedPref.getBoolean("isWear",false) - val focusMgr = LocalFocusManager.current - Column(modifier = sections()) { - var inputContent by remember{ mutableStateOf(if(isDeviceOwner(myDpm)){getMethod()}else{""}) } - var ableToApply by remember{ mutableStateOf(inputContent!=""&&((inputContent=="0"&&allowZero)||inputContent!="0")) } - Text(text = stringResource(itemName), style = typography.titleLarge,color = colorScheme.onPrimaryContainer) - Text(text= stringResource(itemDesc),modifier=Modifier.padding(vertical = 2.dp), style = if(!isWear){typography.bodyLarge}else{typography.bodyMedium}) - if(!isWear){Spacer(Modifier.padding(vertical = 2.dp))} - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.SpaceBetween, - modifier = Modifier.fillMaxWidth() - ){ - OutlinedTextField( - value = inputContent, - label = { Text(stringResource(textFieldLabel))}, - onValueChange = { - inputContent = it - ableToApply = inputContent!=""&&((inputContent=="0"&&allowZero)||inputContent!="0") - }, - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number, imeAction = ImeAction.Done), - keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}), - enabled = isDeviceOwner(myDpm), - modifier = Modifier.focusable().fillMaxWidth(), - trailingIcon = { - Icon( - imageVector = Icons.Outlined.Check, contentDescription = "OK", - modifier = Modifier - .clip(RoundedCornerShape(50)) - .clickable(onClick = {focusMgr.clearFocus() ; setMethod(inputContent)}, enabled = isDeviceOwner(myDpm)&&ableToApply) - .padding(2.dp) - ) - } - ) - } - } -} - -fun activateToken(myContext: Context){ - val desc = myContext.getString(R.string.activate_reset_password_token_here) - val keyguardManager = myContext.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager - val confirmIntent = keyguardManager.createConfirmDeviceCredentialIntent(myContext.getString(R.string.app_name), desc) - if (confirmIntent != null) { - startActivity(myContext,confirmIntent, null) - } else { - Toast.makeText(myContext, myContext.getString(R.string.fail), Toast.LENGTH_SHORT).show() - } -} diff --git a/app/src/main/java/com/binbin/androidowner/Permissions.kt b/app/src/main/java/com/binbin/androidowner/Permissions.kt deleted file mode 100644 index be395e2..0000000 --- a/app/src/main/java/com/binbin/androidowner/Permissions.kt +++ /dev/null @@ -1,460 +0,0 @@ -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 -import android.os.Build.VERSION -import android.widget.Toast -import androidx.activity.ComponentActivity -import androidx.compose.animation.animateContentSize -import androidx.compose.foundation.focusable -import androidx.compose.foundation.horizontalScroll -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.material.icons.Icons -import androidx.compose.material.icons.filled.KeyboardArrowRight -import androidx.compose.material3.* -import androidx.compose.material3.MaterialTheme.colorScheme -import androidx.compose.material3.MaterialTheme.typography -import androidx.compose.runtime.* -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.focus.FocusDirection -import androidx.compose.ui.focus.FocusManager -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.unit.dp -import androidx.compose.ui.unit.sp -import androidx.core.content.ContextCompat.startActivity -import androidx.navigation.NavHostController - - -@Composable -fun DpmPermissions(navCtrl:NavHostController){ - val myContext = LocalContext.current - val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager - val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) - val isda = myDpm.isAdminActive(myComponent) - val focusManager = LocalFocusManager.current - val sharedPref = LocalContext.current.getSharedPreferences("data", Context.MODE_PRIVATE) - val isWear = sharedPref.getBoolean("isWear",false) - val titleColor = colorScheme.onPrimaryContainer - val bodyTextStyle = if(isWear){typography.bodyMedium}else{typography.bodyLarge} - var expandCommandBlock by remember{mutableStateOf("")} - Column( - modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()), - horizontalAlignment = Alignment.CenterHorizontally - ) { - Row( - modifier = sections(onClick = {navCtrl.navigate("ShizukuActivate")}, clickable = true), - horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically - ){ - Text(text = "Shizuku", style = typography.titleLarge, color = titleColor, modifier = Modifier.padding(vertical = 2.dp)) - Icon(imageVector = Icons.Default.KeyboardArrowRight,contentDescription = null, tint = colorScheme.onPrimaryContainer) - } - if(!myDpm.isAdminActive(myComponent)&&isWear){ - Button(onClick = { activateDeviceAdmin(myContext,myComponent) },modifier = Modifier.padding(horizontal = 3.dp).fillMaxWidth()) { - Text(stringResource(R.string.activate_device_admin)) - } - } - Row( - modifier = sections(), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically - ) { - Column { - Text(text = "Device Admin", fontSize = if(!isWear){22.sp}else{20.sp},color = titleColor) - Text(text = stringResource(if(isda){R.string.activated}else{R.string.deactivated})) - } - if(!isWear) - if(isda){ - if(!isDeviceOwner(myDpm)&&!isProfileOwner(myDpm)){ - Button( - onClick = { - myDpm.removeActiveAdmin(myComponent) - navCtrl.navigateUp() - } - ) { - Text(stringResource(R.string.deactivate)) - } - } - }else{ - Button(onClick = { activateDeviceAdmin(myContext,myComponent) }) { - Text(stringResource(R.string.activate)) - } - } - } - if(!isda&&!isDeviceOwner(myDpm)&&!isProfileOwner(myDpm)){ - SelectionContainer( - modifier = sections(colorScheme.tertiaryContainer,{expandCommandBlock="admin"},expandCommandBlock!="admin").animateContentSize(animationSpec = scrollAnim()) - ){ - if(expandCommandBlock=="admin"){ - Text( - text = stringResource(R.string.activate_device_admin_command), - color = colorScheme.onTertiaryContainer, style = bodyTextStyle - ) - }else{ - Text(text = stringResource(R.string.touch_to_view_command), style = bodyTextStyle) - } - } - } - - if(!isDeviceOwner(myDpm)){ - Row( - modifier = sections(), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically - ) { - Column { - Text(text = "Profile Owner", fontSize = if(!isWear){22.sp}else{20.sp},color = titleColor) - Text(stringResource(if(isProfileOwner(myDpm)){R.string.activated}else{R.string.deactivated})) - } - if(isProfileOwner(myDpm)&&VERSION.SDK_INT>=24&&!isWear&&!myDpm.isManagedProfile(myComponent)){ - Button( - onClick = { - myDpm.clearProfileOwner(myComponent) - navCtrl.navigateUp() - } - ) { - Text(stringResource(R.string.deactivate)) - } - } - } - } - if(!isDeviceOwner(myDpm)&&!isProfileOwner(myDpm)){ - SelectionContainer( - modifier = sections(colorScheme.tertiaryContainer,{expandCommandBlock="profile"},expandCommandBlock!="profile").animateContentSize(animationSpec = scrollAnim()) - ){ - if(expandCommandBlock=="profile"){ - Text( - text = stringResource(R.string.activate_profile_owner_command), - color = colorScheme.onTertiaryContainer, style = bodyTextStyle - ) - }else{ - Text(text = stringResource(R.string.touch_to_view_command), style = bodyTextStyle) - } - } - } - - if(!isProfileOwner(myDpm)){ - Row( - modifier = sections(), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically - ) { - Column { - Text(text = "Device Owner", fontSize = if(!isWear){22.sp}else{20.sp},color = titleColor) - Text(stringResource(if(isDeviceOwner(myDpm)){R.string.activated}else{R.string.deactivated})) - } - if(isDeviceOwner(myDpm)&&!isWear){ - Button( - onClick = { - myDpm.clearDeviceOwnerApp(myContext.packageName) - navCtrl.navigateUp() - } - ) { - Text(stringResource(R.string.deactivate)) - } - } - } - } - - if(!isDeviceOwner(myDpm)&&!isProfileOwner(myDpm)){ - SelectionContainer( - modifier = sections(colorScheme.tertiaryContainer,{expandCommandBlock="device"},expandCommandBlock!="device").animateContentSize(animationSpec = scrollAnim()) - ){ - if(expandCommandBlock=="device"){ - Text( - text = stringResource(R.string.activate_device_owner_command), - color = colorScheme.onTertiaryContainer, style = bodyTextStyle - ) - }else{ - Text(text = stringResource(R.string.touch_to_view_command), style = bodyTextStyle) - } - } - } - if(VERSION.SDK_INT>=30){ - Column( - modifier = sections() - ) { - Text(text = stringResource(R.string.device_info), style = typography.titleLarge,color = titleColor) - if(VERSION.SDK_INT>=34&&(isDeviceOwner(myDpm)||(isProfileOwner(myDpm)&&myDpm.isOrganizationOwnedDeviceWithManagedProfile))){ - val financed = myDpm.isDeviceFinanced - Text(stringResource(R.string.is_device_financed, financed),style=bodyTextStyle) - } - if(VERSION.SDK_INT>=33){ - val dpmRole = myDpm.devicePolicyManagementRoleHolderPackage - Text(stringResource(R.string.dpmrh, if(dpmRole==null) { stringResource(R.string.none) } else { "" }),style=bodyTextStyle) - if(dpmRole!=null){ - SelectionContainer(modifier = Modifier.horizontalScroll(rememberScrollState())){ - Text(text = dpmRole, style = bodyTextStyle, color = colorScheme.onPrimaryContainer) - } - } - } - val encryptionStatus = mapOf( - DevicePolicyManager.ENCRYPTION_STATUS_INACTIVE to stringResource(R.string.es_inactive), - DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE to stringResource(R.string.es_active), - DevicePolicyManager.ENCRYPTION_STATUS_UNSUPPORTED to stringResource(R.string.es_unsupported), - DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE_DEFAULT_KEY to stringResource(R.string.es_active_default_key), - DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE_PER_USER to stringResource(R.string.es_active_per_user), - DevicePolicyManager.ENCRYPTION_STATUS_ACTIVATING to stringResource(R.string.unknown) - ) - Text("加密状态:${encryptionStatus[myDpm.storageEncryptionStatus]}",style=bodyTextStyle) - val adminList = myDpm.activeAdmins - if(adminList!=null){ - var adminListText = "" - Text(text = stringResource(R.string.activated_device_admin, adminList.size), style = bodyTextStyle) - var count = adminList.size - for(each in adminList){ - count -= 1 - adminListText += "$each" - if(count>0){adminListText += "\n"} - } - SelectionContainer(modifier = Modifier.fillMaxWidth().padding(vertical = 2.dp).horizontalScroll(rememberScrollState())){ - Text(text = adminListText, style = bodyTextStyle, color = titleColor) - } - } - } - } - if(VERSION.SDK_INT>=31&&(isProfileOwner(myDpm)|| isDeviceOwner(myDpm))){ - Column(modifier = sections()) { - val specificId = myDpm.enrollmentSpecificId - Text(text = stringResource(R.string.enrollment_specific_id), style = typography.titleLarge,color = titleColor) - if(specificId!=""){ - SelectionContainer(modifier = Modifier.horizontalScroll(rememberScrollState())){ Text(specificId, style = bodyTextStyle, softWrap = false) } - }else{ - Text(stringResource(R.string.require_set_org_id),style=bodyTextStyle) - } - } - } - - if((VERSION.SDK_INT>=26&&isDeviceOwner(myDpm))||(VERSION.SDK_INT>=24&&isProfileOwner(myDpm))){ - Column(modifier = sections()){ - var orgName by remember{mutableStateOf(try{myDpm.getOrganizationName(myComponent).toString()}catch(e:SecurityException){""})} - Text(text = stringResource(R.string.org_name), style = typography.titleLarge, color = titleColor) - OutlinedTextField( - value = orgName, onValueChange = {orgName=it}, modifier = Modifier.focusable().fillMaxWidth().padding(vertical = 3.dp), - label = {Text(stringResource(R.string.org_name))}, - keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), - keyboardActions = KeyboardActions(onDone = {focusManager.clearFocus()}) - ) - Button( - onClick = { - focusManager.clearFocus() - myDpm.setOrganizationName(myComponent,orgName) - Toast.makeText(myContext, myContext.getString(R.string.success), Toast.LENGTH_SHORT).show() - }, - modifier = Modifier.fillMaxWidth() - ){ - Text(stringResource(R.string.apply)) - } - } - } - - if(isDeviceOwner(myDpm) || isProfileOwner(myDpm)){ - Column(modifier = sections()) { - Text(text = stringResource(R.string.account_types_management_disabled), style = typography.titleLarge,color = titleColor) - Text(stringResource(R.string.developing),style=bodyTextStyle) - var noManageAccount = myDpm.accountTypesWithManagementDisabled - var accountlist by remember{ mutableStateOf("") } - val refreshList = { - accountlist = "" - if (noManageAccount != null) { - var count = noManageAccount!!.size - for(each in noManageAccount!!){ count -= 1; accountlist += each; if(count>0){accountlist += "\n"} } - } - } - var inited by remember{mutableStateOf(false)} - if(!inited){ refreshList(); inited=true } - Text(text = if(accountlist==""){stringResource(R.string.none)}else{accountlist}, style = bodyTextStyle) - var inputText by remember{ mutableStateOf("") } - OutlinedTextField( - value = inputText, - onValueChange = {inputText=it}, - label = {Text(stringResource(R.string.account_types))}, - modifier = Modifier.focusable().fillMaxWidth().padding(bottom = 4.dp), - keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), - keyboardActions = KeyboardActions(onDone = {focusManager.clearFocus()}) - ) - Row(modifier = Modifier.fillMaxWidth(),horizontalArrangement = Arrangement.SpaceBetween){ - Button( - onClick={ - focusManager.clearFocus() - myDpm.setAccountManagementDisabled(myComponent,inputText,true) - noManageAccount=myDpm.accountTypesWithManagementDisabled - refreshList() - }, - modifier = Modifier.fillMaxWidth(0.49f) - ){ - Text(stringResource(R.string.add)) - } - Button( - onClick={focusManager.clearFocus() - myDpm.setAccountManagementDisabled(myComponent,inputText,false) - noManageAccount=myDpm.accountTypesWithManagementDisabled - refreshList() - }, - modifier = Modifier.fillMaxWidth(0.96F) - ){ - Text(stringResource(R.string.remove)) - } - } - } - } - - if(VERSION.SDK_INT>=24&&isDeviceOwner(myDpm)){ - DeviceOwnerInfo(R.string.owner_lockscr_info,R.string.place_holder,R.string.owner_lockscr_info,focusManager,myContext, - {myDpm.deviceOwnerLockScreenInfo},{content -> myDpm.setDeviceOwnerLockScreenInfo(myComponent,content)}) - } - if((isDeviceOwner(myDpm)||isProfileOwner(myDpm))&&VERSION.SDK_INT>=24){ - DeviceOwnerInfo(R.string.support_msg,R.string.support_msg_desc,R.string.message,focusManager,myContext, - {myDpm.getShortSupportMessage(myComponent)},{content -> myDpm.setShortSupportMessage(myComponent,content)}) - DeviceOwnerInfo(R.string.long_support_msg,R.string.long_support_msg_desc,R.string.message,focusManager,myContext, - {myDpm.getLongSupportMessage(myComponent)},{content -> myDpm.setLongSupportMessage(myComponent,content)}) - } - - if(VERSION.SDK_INT>=28&&(isDeviceOwner(myDpm)||isProfileOwner(myDpm))){ - Column(modifier = sections()){ - var pkg by remember{mutableStateOf("")} - var cls by remember{mutableStateOf("")} - Text(text = stringResource(R.string.transform_ownership), style = typography.titleLarge, color = titleColor) - Text(text = stringResource(R.string.transform_ownership_desc), style = bodyTextStyle) - OutlinedTextField( - value = pkg, onValueChange = {pkg = it}, label = {Text(stringResource(R.string.target_package_name))}, - modifier = Modifier.focusable().fillMaxWidth().padding(vertical = 2.dp), - keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next), - keyboardActions = KeyboardActions(onNext = {focusManager.moveFocus(FocusDirection.Down)}) - ) - OutlinedTextField( - value = cls, onValueChange = {cls = it}, label = {Text(stringResource(R.string.target_class_name))}, - modifier = Modifier.focusable().fillMaxWidth().padding(vertical = 2.dp), - keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), - keyboardActions = KeyboardActions(onDone = {focusManager.clearFocus()}) - ) - Button( - onClick = { - try { - myDpm.transferOwnership(myComponent,ComponentName(pkg, cls),null) - Toast.makeText(myContext, myContext.getString(R.string.success), Toast.LENGTH_SHORT).show() - }catch(e:IllegalArgumentException){ - Toast.makeText(myContext, myContext.getString(R.string.fail), Toast.LENGTH_SHORT).show() - } - }, - modifier = Modifier.fillMaxWidth().padding(top = 2.dp) - ) { - Text(stringResource(R.string.transform)) - } - } - } - - if(isWear&&(myDpm.isAdminActive(myComponent)||isProfileOwner(myDpm)||isDeviceOwner(myDpm))){ - Column(modifier = sections(), horizontalAlignment = Alignment.CenterHorizontally) { - Button( - onClick = { - myDpm.removeActiveAdmin(myComponent) - navCtrl.navigateUp() - }, - colors = ButtonDefaults.buttonColors(contentColor = colorScheme.onError, containerColor = colorScheme.error), - enabled = myDpm.isAdminActive(myComponent) - ) { - Text(stringResource(R.string.deactivate_da)) - } - if(VERSION.SDK_INT>=24){ - Button( - onClick = { - myDpm.clearProfileOwner(myComponent) - navCtrl.navigateUp() - }, - colors = ButtonDefaults.buttonColors(contentColor = colorScheme.onError, containerColor = colorScheme.error), - enabled = isProfileOwner(myDpm) - ) { - Text(stringResource(R.string.deactivate_po)) - } - } - Button( - onClick = { - myDpm.clearDeviceOwnerApp(myContext.packageName) - navCtrl.navigateUp() - }, - colors = ButtonDefaults.buttonColors(contentColor = colorScheme.onError, containerColor = colorScheme.error), - enabled = isDeviceOwner(myDpm) - ) { - Text(stringResource(R.string.deactivate_do)) - } - } - } - Spacer(Modifier.padding(vertical = 30.dp)) - } -} - -@Composable -fun DeviceOwnerInfo( - name:Int, - desc:Int, - textfield:Int, - fm:FocusManager, - myContext:Context, - input:()->CharSequence?, - output:(content:String?)->Unit -){ - Column(modifier = sections()) { - val sharedPref = LocalContext.current.getSharedPreferences("data", Context.MODE_PRIVATE) - val isWear = sharedPref.getBoolean("isWear",false) - Text(text = stringResource(name), style = typography.titleLarge, softWrap = false, color = colorScheme.onPrimaryContainer) - if(desc!=R.string.place_holder){ - Text( - text = stringResource(desc),modifier = Modifier.padding(top = 6.dp), - style = if(!isWear){typography.bodyLarge}else{typography.bodyMedium}) - } - var inputContent by remember{ mutableStateOf(input()) } - OutlinedTextField( - value = if(inputContent!=null){ inputContent.toString() }else{""}, - label = {Text(stringResource(textfield))}, - onValueChange = { inputContent=it }, - modifier = Modifier.focusable().fillMaxWidth().padding(vertical = 4.dp) - ) - Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) { - Button( - onClick = { - output(inputContent.toString()) - inputContent= input() - fm.clearFocus() - Toast.makeText(myContext, myContext.getString(R.string.success), Toast.LENGTH_SHORT).show() - }, - modifier = if(isWear){Modifier.fillMaxWidth(0.49F)}else{Modifier.fillMaxWidth(0.6F)} - ) { - Text(text = stringResource(R.string.apply)) - } - Button( - onClick = { - output(null) - inputContent = input() - fm.clearFocus() - Toast.makeText(myContext, myContext.getString(R.string.success), Toast.LENGTH_SHORT).show() - }, - modifier = Modifier.fillMaxWidth(0.96F) - ) { - Text(text = stringResource(R.string.reset)) - } - } - } -} - -fun activateDeviceAdmin(inputContext:Context,inputComponent:ComponentName){ - try { - val intent = Intent(DevicePolicyManager.ACTION_ADD_DEVICE_ADMIN) - intent.putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN, inputComponent) - intent.putExtra(DevicePolicyManager.EXTRA_ADD_EXPLANATION, inputContext.getString(R.string.activate_android_owner_here)) - startActivity(inputContext,intent,null) - }catch(e:ActivityNotFoundException){ - Toast.makeText(inputContext,inputContext.getString(R.string.unsupported),Toast.LENGTH_SHORT).show() - } -} diff --git a/app/src/main/java/com/binbin/androidowner/SystemUpdatePolicy.kt b/app/src/main/java/com/binbin/androidowner/SystemUpdatePolicy.kt deleted file mode 100644 index 3d1fcc6..0000000 --- a/app/src/main/java/com/binbin/androidowner/SystemUpdatePolicy.kt +++ /dev/null @@ -1,145 +0,0 @@ -package com.binbin.androidowner - -import android.app.admin.DevicePolicyManager -import android.app.admin.SystemUpdateInfo -import android.app.admin.SystemUpdatePolicy -import android.content.ComponentName -import android.content.Context -import android.os.Build.VERSION -import android.widget.Toast -import androidx.activity.ComponentActivity -import androidx.compose.foundation.focusable -import androidx.compose.foundation.layout.* -import androidx.compose.foundation.text.KeyboardActions -import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.material3.Button -import androidx.compose.material3.MaterialTheme.colorScheme -import androidx.compose.material3.MaterialTheme.typography -import androidx.compose.material3.OutlinedTextField -import androidx.compose.material3.Text -import androidx.compose.runtime.* -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.unit.dp -import java.util.Date - -@Composable -fun SysUpdatePolicy(){ - val myContext = LocalContext.current - val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager - val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) - val focusMgr = LocalFocusManager.current - val sharedPref = myContext.getSharedPreferences("data", Context.MODE_PRIVATE) - val isWear = sharedPref.getBoolean("isWear",false) - val bodyTextStyle = if(isWear){ typography.bodyMedium}else{typography.bodyLarge} - Column { - if(VERSION.SDK_INT>=26&&isDeviceOwner(myDpm)){ - val sysUpdateInfo = myDpm.getPendingSystemUpdate(myComponent) - Column(modifier = sections()) { - if(sysUpdateInfo!=null){ - Text(text = "Update first available: ${Date(sysUpdateInfo.receivedTime)}", style = bodyTextStyle) - Text(text = "Hash code: ${sysUpdateInfo.hashCode()}", style = bodyTextStyle) - val securityStateDesc = when(sysUpdateInfo.securityPatchState){ - SystemUpdateInfo.SECURITY_PATCH_STATE_UNKNOWN-> stringResource(R.string.unknown) - SystemUpdateInfo.SECURITY_PATCH_STATE_TRUE->"true" - else->"false" - } - Text(text = stringResource(R.string.is_security_patch, securityStateDesc), style = bodyTextStyle) - }else{ - Text(text = stringResource(R.string.no_system_update), style = bodyTextStyle) - } - } - } - if(VERSION.SDK_INT>=23){ - Column(modifier = sections()) { - var selectedPolicy by remember{ mutableStateOf(myDpm.systemUpdatePolicy?.policyType) } - Text(text = stringResource(R.string.system_update_policy), style = typography.titleLarge, color = colorScheme.onPrimaryContainer) - RadioButtonItem(stringResource(R.string.system_update_policy_automatic),{selectedPolicy==SystemUpdatePolicy.TYPE_INSTALL_AUTOMATIC},{selectedPolicy=SystemUpdatePolicy.TYPE_INSTALL_AUTOMATIC}) - RadioButtonItem(stringResource(R.string.system_update_policy_install_windowed),{selectedPolicy==SystemUpdatePolicy.TYPE_INSTALL_WINDOWED},{selectedPolicy=SystemUpdatePolicy.TYPE_INSTALL_WINDOWED}) - RadioButtonItem(stringResource(R.string.system_update_policy_postpone),{selectedPolicy==SystemUpdatePolicy.TYPE_POSTPONE},{selectedPolicy=SystemUpdatePolicy.TYPE_POSTPONE}) - RadioButtonItem(stringResource(R.string.none),{selectedPolicy == null},{selectedPolicy=null}) - var windowedPolicyStart by remember{ mutableStateOf("") } - var windowedPolicyEnd by remember{ mutableStateOf("") } - if(selectedPolicy==2){ - Spacer(Modifier.padding(vertical = 3.dp)) - Row(modifier = Modifier.fillMaxWidth(),horizontalArrangement = Arrangement.Center) { - OutlinedTextField( - value = windowedPolicyStart, - label = { Text(stringResource(R.string.start_time))}, - onValueChange = {windowedPolicyStart=it}, - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number, imeAction = ImeAction.Done), - keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}), - modifier = Modifier.focusable().fillMaxWidth(0.5F) - ) - Spacer(Modifier.padding(horizontal = 3.dp)) - OutlinedTextField( - value = windowedPolicyEnd, - onValueChange = {windowedPolicyEnd=it}, - label = {Text(stringResource(R.string.end_time))}, - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number, imeAction = ImeAction.Done), - keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}), - modifier = Modifier.focusable().fillMaxWidth() - ) - } - Spacer(Modifier.padding(vertical = 3.dp)) - Text(text = stringResource(R.string.minutes_in_one_day), style = bodyTextStyle) - } - val policy = - when(selectedPolicy){ - SystemUpdatePolicy.TYPE_INSTALL_AUTOMATIC->SystemUpdatePolicy.createAutomaticInstallPolicy() - SystemUpdatePolicy.TYPE_INSTALL_WINDOWED->SystemUpdatePolicy.createWindowedInstallPolicy(windowedPolicyStart.toInt(),windowedPolicyEnd.toInt()) - SystemUpdatePolicy.TYPE_POSTPONE->SystemUpdatePolicy.createPostponeInstallPolicy() - else->null - } - Button( - onClick = {myDpm.setSystemUpdatePolicy(myComponent,policy);Toast.makeText(myContext, "成功!", Toast.LENGTH_SHORT).show()}, - enabled = isDeviceOwner(myDpm), - modifier = Modifier.fillMaxWidth() - ) { - Text(stringResource(R.string.apply)) - } - }} - /*if(VERSION.SDK_INT>=29){ - Column(modifier = sections()){ - var resultUri by remember{mutableStateOf(otaUri)} - Text(text = "安装系统更新", style = typography.titleLarge) - Button( - onClick = { - val getUri = Intent(Intent.ACTION_GET_CONTENT) - getUri.setType("application/zip") - getUri.addCategory(Intent.CATEGORY_OPENABLE) - getOtaPackage.launch(getUri) - }, - modifier = Modifier.fillMaxWidth(), - enabled = isDeviceOwner(myDpm)||isProfileOwner(myDpm) - ) { - Text("选择OTA包") - } - Button( - onClick = {resultUri = otaUri}, - modifier = Modifier.fillMaxWidth(), - enabled = isDeviceOwner(myDpm)||isProfileOwner(myDpm) - ) { - Text("查看OTA包详情") - } - Text("URI: $resultUri") - if(installOta){ - Button( - onClick = { - val sysUpdateExecutor = Executors.newCachedThreadPool() - val sysUpdateCallback:InstallSystemUpdateCallback = InstallSystemUpdateCallback - myDpm.installSystemUpdate(myComponent,resultUri,sysUpdateExecutor,sysUpdateCallback) - }, - enabled = isDeviceOwner(myDpm)||isProfileOwner(myDpm) - ){ - Text("安装") - } - } - } - }*/ - } -} diff --git a/app/src/main/java/com/binbin/androidowner/User.kt b/app/src/main/java/com/binbin/androidowner/User.kt deleted file mode 100644 index 5d22145..0000000 --- a/app/src/main/java/com/binbin/androidowner/User.kt +++ /dev/null @@ -1,380 +0,0 @@ -package com.binbin.androidowner - -import android.app.admin.DevicePolicyManager -import android.content.ComponentName -import android.content.Context -import android.content.Intent -import android.graphics.BitmapFactory -import android.os.Binder -import android.os.Build.VERSION -import android.os.UserHandle -import android.os.UserManager -import android.provider.MediaStore -import android.widget.Toast -import androidx.activity.ComponentActivity -import androidx.compose.foundation.focusable -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 -import androidx.compose.material3.MaterialTheme.typography -import androidx.compose.material3.Text -import androidx.compose.material3.TextField -import androidx.compose.runtime.* -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.platform.LocalFocusManager -import androidx.compose.ui.text.input.ImeAction -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.fillMaxSize().verticalScroll(rememberScrollState())) { - val myContext = LocalContext.current - val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager - val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) - val focusMgr = LocalFocusManager.current - val userManager = myContext.getSystemService(Context.USER_SERVICE) as UserManager - val sharedPref = LocalContext.current.getSharedPreferences("data", Context.MODE_PRIVATE) - val isWear = sharedPref.getBoolean("isWear",false) - val bodyTextStyle = if(isWear){ typography.bodyMedium}else{ typography.bodyLarge} - val titleColor = colorScheme.onPrimaryContainer - Column(modifier = sections()) { - Text(text = "用户信息", style = typography.titleLarge, color = titleColor) - Text("用户已解锁:${UserManagerCompat.isUserUnlocked(myContext)}",style = bodyTextStyle) - if(VERSION.SDK_INT>=24){ Text("支持多用户:${UserManager.supportsMultipleUsers()}",style = bodyTextStyle) } - if(VERSION.SDK_INT>=23){ Text(text = "系统用户:${userManager.isSystemUser}", style = bodyTextStyle) } - if(VERSION.SDK_INT>=34){ Text(text = "管理员用户:${userManager.isAdminUser}", style = bodyTextStyle) } - if(VERSION.SDK_INT>=31){ Text(text = "无头系统用户: ${UserManager.isHeadlessSystemUserMode()}",style = bodyTextStyle) } - Spacer(Modifier.padding(vertical = if(isWear){2.dp}else{5.dp})) - if (VERSION.SDK_INT >= 28) { - val logoutable = myDpm.isLogoutEnabled - Text(text = "用户可以退出 : $logoutable",style = bodyTextStyle) - if(isDeviceOwner(myDpm)|| isProfileOwner(myDpm)){ - val ephemeralUser = myDpm.isEphemeralUser(myComponent) - Text(text = "临时用户: $ephemeralUser",style = bodyTextStyle) - } - Text(text = "附属用户: ${myDpm.isAffiliatedUser}",style = bodyTextStyle) - } - Spacer(Modifier.padding(vertical = if(isWear){2.dp}else{5.dp})) - Text(text = "当前UserID:${Binder.getCallingUid()/100000}",style = bodyTextStyle) - Text(text = "当前用户序列号:${userManager.getSerialNumberForUser(android.os.Process.myUserHandle())}",style = bodyTextStyle) - } - - Column(modifier = sections()) { - Text(text = "用户操作", style = typography.titleLarge,color = titleColor) - var idInput by remember{ mutableStateOf("") } - var userHandleById:UserHandle by remember{ mutableStateOf(android.os.Process.myUserHandle()) } - var useUid by remember{ mutableStateOf(false) } - TextField( - value = idInput, - onValueChange = { - idInput=it - if(useUid){ - if(idInput!=""&&VERSION.SDK_INT>=24){ - userHandleById = UserHandle.getUserHandleForUid(idInput.toInt()) - } - }else{ - val userHandleBySerial = userManager.getUserForSerialNumber(idInput.toLong()) - userHandleById = userHandleBySerial ?: android.os.Process.myUserHandle() - } - }, - label = {Text(if(useUid){"UID"}else{"序列号"})}, - enabled = isDeviceOwner(myDpm), - modifier = Modifier.focusable().fillMaxWidth().padding(vertical = 3.dp), - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number, imeAction = ImeAction.Done), - keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}) - ) - 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){ - Button( - onClick = { - val result = myDpm.logoutUser(myComponent) - Toast.makeText(myContext, userOperationResultCode(result), Toast.LENGTH_SHORT).show() - }, - enabled = isProfileOwner(myDpm), - modifier = Modifier.fillMaxWidth() - ) { - Text("登出当前用户") - } - } - } - Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween){ - Button( - onClick = { - focusMgr.clearFocus() - if(VERSION.SDK_INT>=28){ - val result = myDpm.startUserInBackground(myComponent,userHandleById) - Toast.makeText(myContext, userOperationResultCode(result), Toast.LENGTH_SHORT).show() - } - }, - enabled = isDeviceOwner(myDpm)&&VERSION.SDK_INT>=28, - modifier = Modifier.fillMaxWidth(0.49F) - ){ - Text(if(isWear){"启动"}else{"在后台启动"}) - } - Button( - onClick = { - focusMgr.clearFocus() - if(myDpm.switchUser(myComponent,userHandleById)){ - Toast.makeText(myContext, "成功", Toast.LENGTH_SHORT).show() - }else{ - Toast.makeText(myContext, "失败", Toast.LENGTH_SHORT).show() - } - }, - enabled = isDeviceOwner(myDpm), - modifier = Modifier.fillMaxWidth(0.96F) - ) { - Text("切换") - } - } - Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween){ - Button( - onClick = { - focusMgr.clearFocus() - try{ - if(VERSION.SDK_INT>=28){ - val result = myDpm.stopUser(myComponent,userHandleById) - Toast.makeText(myContext, userOperationResultCode(result), Toast.LENGTH_SHORT).show() - } - }catch(e:IllegalArgumentException){ - Toast.makeText(myContext, "失败", Toast.LENGTH_SHORT).show() - } - }, - enabled = isDeviceOwner(myDpm)&&VERSION.SDK_INT>=28, - modifier = Modifier.fillMaxWidth(0.49F) - ) { - Text("停止") - } - Button( - onClick = { - focusMgr.clearFocus() - if(myDpm.removeUser(myComponent,userHandleById)){ - Toast.makeText(myContext, "成功", Toast.LENGTH_SHORT).show() - idInput="" - }else{ - Toast.makeText(myContext, "失败", Toast.LENGTH_SHORT).show() - } - }, - enabled = isDeviceOwner(myDpm), - modifier = Modifier.fillMaxWidth(0.96F) - ) { - Text("移除") - } - } - if(VERSION.SDK_INT<28){ - Text(text = "停止用户需API28", style = bodyTextStyle) - } - } - - if(VERSION.SDK_INT>=24){ - Column(modifier = sections()) { - var userName by remember{ mutableStateOf("") } - Text(text = "创建用户", style = typography.titleLarge, color = titleColor) - TextField( - value = userName, - onValueChange = {userName=it}, - label = {Text("用户名")}, - modifier = Modifier.focusable().fillMaxWidth().padding(vertical = 4.dp), - keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), - keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}) - ) - var selectedFlag by remember{ mutableIntStateOf(0) } - RadioButtonItem("无",{selectedFlag==0},{selectedFlag=0}) - RadioButtonItem("跳过创建用户向导",{selectedFlag==DevicePolicyManager.SKIP_SETUP_WIZARD},{selectedFlag=DevicePolicyManager.SKIP_SETUP_WIZARD}) - if(VERSION.SDK_INT>=28){ - RadioButtonItem("临时用户",{selectedFlag==DevicePolicyManager.MAKE_USER_EPHEMERAL},{selectedFlag=DevicePolicyManager.MAKE_USER_EPHEMERAL}) - RadioButtonItem("启用所有系统应用",{selectedFlag==DevicePolicyManager.LEAVE_ALL_SYSTEM_APPS_ENABLED},{selectedFlag=DevicePolicyManager.LEAVE_ALL_SYSTEM_APPS_ENABLED}) - } - var newUserHandle: UserHandle? by remember{ mutableStateOf(null) } - Button( - onClick = { - newUserHandle=myDpm.createAndManageUser(myComponent,userName,myComponent,null,selectedFlag) - focusMgr.clearFocus() - Toast.makeText(myContext, if(newUserHandle!=null){"成功"}else{"失败"}, Toast.LENGTH_SHORT).show() - }, - enabled = isDeviceOwner(myDpm), - modifier = Modifier.fillMaxWidth() - ) { - Text("创建(Owner)") - } - if(newUserHandle!=null){ Text(text = "新用户的序列号:${userManager.getSerialNumberForUser(newUserHandle)}", style = bodyTextStyle) } - } - } - if(VERSION.SDK_INT>=26&&(isDeviceOwner(myDpm)||isProfileOwner(myDpm))){ - Column(modifier = sections()){ - var input by remember{mutableStateOf("")} - var list by remember{mutableStateOf("")} - val refresh = { - list = "" - var count = affiliationID.size - for(item in affiliationID){ count-=1; list+=item; if(count>0){list+="\n"} } - } - var inited by remember{mutableStateOf(false)} - if(!inited){affiliationID = myDpm.getAffiliationIds(myComponent);refresh();inited=true} - Text(text = "附属用户ID", style = typography.titleLarge, color = titleColor) - TextField( - value = input, - onValueChange = {input = it}, - label = {Text("ID")}, - modifier = Modifier.focusable().fillMaxWidth().padding(vertical = 2.dp), - keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), - keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}) - ) - 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() }, - modifier = Modifier.fillMaxWidth(0.49F) - ){ - Text("添加") - } - Button( - onClick = { affiliationID.remove(input); refresh() }, - modifier = Modifier.fillMaxWidth(0.96F) - ){ - Text("移除") - } - } - Button( - onClick = { - if("" in affiliationID) { - Toast.makeText(myContext, "有空字符串", Toast.LENGTH_SHORT).show() - }else if(affiliationID.isEmpty()){ - Toast.makeText(myContext, "不能为空", Toast.LENGTH_SHORT).show() - }else{ - myDpm.setAffiliationIds(myComponent, affiliationID) - affiliationID = myDpm.getAffiliationIds(myComponent) - refresh() - Toast.makeText(myContext,"成功",Toast.LENGTH_SHORT).show() - } - }, - modifier = Modifier.fillMaxWidth() - ) { - Text("应用") - } - Text(text = "如果多用户,附属用户ID相同时可以让其他用户附属于主用户", style = bodyTextStyle) - } - } - - UserSessionMessage("用户名", "用户名", true, {null}) {msg-> myDpm.setProfileName(myComponent, msg.toString())} - - if(VERSION.SDK_INT>=23&&(isDeviceOwner(myDpm)||isProfileOwner(myDpm))){ - Column(modifier = sections()){ - var getContent by remember{mutableStateOf(false)} - Text(text = "用户图标", style = typography.titleLarge, color = titleColor) - Text(text = "尽量选择正方形的图片,以免产生问题", style = bodyTextStyle) - CheckBoxItem("使用文件选择器而不是相册",{getContent},{getContent=!getContent}) - Button( - onClick = { - val intent = Intent(if(getContent){Intent.ACTION_GET_CONTENT}else{Intent.ACTION_PICK}) - if(getContent){intent.addCategory(Intent.CATEGORY_OPENABLE)} - intent.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/*") - getUserIcon.launch(intent) - }, - modifier = Modifier.fillMaxWidth() - ) { - Text("选择图片...") - } - Button( - onClick = { - if(userIconUri!=null){ - uriToStream(myContext, userIconUri){stream -> - val bitmap = BitmapFactory.decodeStream(stream) - myDpm.setUserIcon(myComponent,bitmap) - Toast.makeText(myContext, "成功", Toast.LENGTH_SHORT).show() - } - }else{ - Toast.makeText(myContext, "请先选择图片", Toast.LENGTH_SHORT).show() - } - }, - modifier = Modifier.fillMaxWidth() - ) { - Text("应用") - } - } - } - - if(VERSION.SDK_INT>=28){ - UserSessionMessage("用户会话开始消息", "消息", false, {myDpm.getStartUserSessionMessage(myComponent)}) {msg-> myDpm.setStartUserSessionMessage(myComponent, msg)} - UserSessionMessage("用户会话结束消息", "消息", false, {myDpm.getEndUserSessionMessage(myComponent)}) {msg-> myDpm.setEndUserSessionMessage(myComponent, msg)} - } - Spacer(Modifier.padding(vertical = 30.dp)) - } -} - -@Composable -private fun UserSessionMessage(text:String, textField:String, profileOwner:Boolean, get: ()->CharSequence?, setMsg:(msg: CharSequence?)->Unit){ - Column( - modifier = sections() - ) { - val myContext = LocalContext.current - val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager - val focusMgr = LocalFocusManager.current - var msg by remember{ mutableStateOf(if(isDeviceOwner(myDpm)||(isProfileOwner(myDpm)&&profileOwner)){ if(get()==null){""}else{get().toString()} }else{""}) } - val sharedPref = myContext.getSharedPreferences("data", Context.MODE_PRIVATE) - val isWear = sharedPref.getBoolean("isWear",false) - Text(text = text, style = typography.titleLarge, color = colorScheme.onPrimaryContainer) - TextField( - value = msg, - onValueChange = {msg=it}, - label = {Text(textField)}, - keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), - keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}), - modifier = Modifier.focusable().fillMaxWidth().padding(vertical = 6.dp), - enabled = isDeviceOwner(myDpm)||(isProfileOwner(myDpm)&&profileOwner) - ) - Row(modifier = Modifier.fillMaxWidth(),horizontalArrangement = Arrangement.SpaceBetween) { - Button( - onClick = { - focusMgr.clearFocus() - setMsg(msg) - msg = if(get()==null){""}else{get().toString()} - Toast.makeText(myContext, "成功", Toast.LENGTH_SHORT).show() - }, - enabled = isDeviceOwner(myDpm)||(isProfileOwner(myDpm)&&profileOwner), - modifier = Modifier.fillMaxWidth(if(isWear){0.49F}else{0.65F}) - ) { - Text("应用") - } - Button( - onClick = { - focusMgr.clearFocus() - setMsg(null) - msg = get()?.toString() ?: "" - Toast.makeText(myContext, "成功", Toast.LENGTH_SHORT).show() - }, - enabled = isDeviceOwner(myDpm)||(isProfileOwner(myDpm)&&profileOwner), - modifier = Modifier.fillMaxWidth(0.96F) - ) { - Text("默认") - } - } - } -} - -private fun userOperationResultCode(result:Int): String { - return when(result){ - UserManager.USER_OPERATION_SUCCESS->"成功" - UserManager.USER_OPERATION_ERROR_UNKNOWN->"未知结果(失败)" - UserManager.USER_OPERATION_ERROR_MANAGED_PROFILE->"失败:受管理的资料" - UserManager.USER_OPERATION_ERROR_CURRENT_USER->"失败:当前用户" - else->"未知" - } -} diff --git a/app/src/main/java/com/binbin/androidowner/Utils.kt b/app/src/main/java/com/binbin/androidowner/Utils.kt new file mode 100644 index 0000000..16f8975 --- /dev/null +++ b/app/src/main/java/com/binbin/androidowner/Utils.kt @@ -0,0 +1,26 @@ +package com.binbin.androidowner + +import android.content.Context +import android.net.Uri +import android.widget.Toast +import java.io.FileNotFoundException +import java.io.IOException +import java.io.InputStream + + +fun uriToStream( + context: Context, + uri: Uri?, + operation:(stream: InputStream)->Unit +){ + if(uri!=null){ + try{ + val stream = context.contentResolver.openInputStream(uri) + if(stream!=null) { operation(stream) } + else{ Toast.makeText(context, "空的流", Toast.LENGTH_SHORT).show() } + stream?.close() + } + catch(e: FileNotFoundException){ Toast.makeText(context, "文件不存在", Toast.LENGTH_SHORT).show() } + catch(e: IOException){ Toast.makeText(context, "IO异常", Toast.LENGTH_SHORT).show() } + }else{ Toast.makeText(context, "空URI", Toast.LENGTH_SHORT).show() } +} diff --git a/app/src/main/java/com/binbin/androidowner/Receiver.kt b/app/src/main/java/com/binbin/androidowner/dpm/AdminReceiver.kt similarity index 99% rename from app/src/main/java/com/binbin/androidowner/Receiver.kt rename to app/src/main/java/com/binbin/androidowner/dpm/AdminReceiver.kt index 97c90ab..3963060 100644 --- a/app/src/main/java/com/binbin/androidowner/Receiver.kt +++ b/app/src/main/java/com/binbin/androidowner/dpm/AdminReceiver.kt @@ -1,4 +1,4 @@ -package com.binbin.androidowner +package com.binbin.androidowner.dpm import android.annotation.SuppressLint import android.app.admin.DeviceAdminReceiver diff --git a/app/src/main/java/com/binbin/androidowner/dpm/ApplicationManage.kt b/app/src/main/java/com/binbin/androidowner/dpm/ApplicationManage.kt new file mode 100644 index 0000000..14ffd3e --- /dev/null +++ b/app/src/main/java/com/binbin/androidowner/dpm/ApplicationManage.kt @@ -0,0 +1,798 @@ +package com.binbin.androidowner.dpm + +import android.annotation.SuppressLint +import android.app.PendingIntent +import android.app.admin.DevicePolicyManager +import android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT +import android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED +import android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED +import android.app.admin.PackagePolicy +import android.app.admin.PackagePolicy.PACKAGE_POLICY_ALLOWLIST +import android.app.admin.PackagePolicy.PACKAGE_POLICY_ALLOWLIST_AND_SYSTEM +import android.app.admin.PackagePolicy.PACKAGE_POLICY_BLOCKLIST +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.content.pm.PackageInstaller +import android.content.pm.PackageManager.NameNotFoundException +import android.net.Uri +import android.os.Build.VERSION +import android.os.Looper +import android.provider.Settings +import android.widget.Toast +import androidx.activity.ComponentActivity +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.animateContentSize +import androidx.compose.foundation.focusable +import androidx.compose.foundation.horizontalScroll +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 +import androidx.compose.material3.MaterialTheme.typography +import androidx.compose.runtime.* +import androidx.compose.runtime.saveable.rememberSaveable +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 +import androidx.compose.ui.unit.dp +import androidx.core.content.ContextCompat.startActivity +import com.binbin.androidowner.R +import com.binbin.androidowner.ui.RadioButtonItem +import com.binbin.androidowner.uriToStream +import kotlinx.coroutines.delay +import java.io.IOException +import java.io.InputStream +import java.util.concurrent.Executors + +private var credentialList = mutableSetOf() +private var crossProfilePkg = mutableSetOf() +private var keepUninstallPkg = mutableListOf() +private var permittedIme = mutableListOf() +private var permittedAccessibility = mutableListOf() +@Composable +fun ApplicationManage(){ + val myContext = LocalContext.current + val focusMgr = LocalFocusManager.current + val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager + val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) + var pkgName by rememberSaveable{ mutableStateOf("") } + val sharedPref = LocalContext.current.getSharedPreferences("data", Context.MODE_PRIVATE) + val isWear = sharedPref.getBoolean("isWear",false) + val bodyTextStyle = if(isWear){ typography.bodyMedium }else{ typography.bodyLarge } + val titleColor = colorScheme.onPrimaryContainer + Column{ + TextField( + value = pkgName, + onValueChange = { pkgName = it }, + label = { Text(stringResource(R.string.package_name)) }, + modifier = Modifier.fillMaxWidth().padding(horizontal = 4.dp), + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Ascii, imeAction = ImeAction.Done), + keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}) + ) + Column(modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState())) { + if(VERSION.SDK_INT>=24&&isProfileOwner(myDpm)&&myDpm.isManagedProfile(myComponent)){ + Text(text = stringResource(R.string.scope_is_work_profile), style = bodyTextStyle, textAlign = TextAlign.Center,modifier = Modifier.fillMaxWidth().padding(vertical = 2.dp)) + } + + Button( + onClick = { + val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS) + intent.setData(Uri.parse("package:$pkgName")) + startActivity(myContext,intent,null) + }, + modifier = Modifier.fillMaxWidth().padding(horizontal = 8.dp) + ){ + Text(stringResource(R.string.app_info)) + } + + if(VERSION.SDK_INT>=24&&(isDeviceOwner(myDpm)||isProfileOwner(myDpm))){ + AppManageItem( + R.string.suspend,R.string.place_holder, + {try{ myDpm.isPackageSuspended(myComponent,pkgName) } + catch(e:NameNotFoundException){ false } + catch(e:IllegalArgumentException){ false }} + ) { b -> myDpm.setPackagesSuspended(myComponent, arrayOf(pkgName), b) } + } + if(isDeviceOwner(myDpm)||isProfileOwner(myDpm)){ + AppManageItem(R.string.hide,R.string.isapphidden_desc, {myDpm.isApplicationHidden(myComponent,pkgName)}) {b-> myDpm.setApplicationHidden(myComponent, pkgName, b)} + } + if(VERSION.SDK_INT>=24&&(isDeviceOwner(myDpm)||isProfileOwner(myDpm))){ + AppManageItem( + 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) { + Toast.makeText(myContext, myContext.getString(R.string.unsupported), Toast.LENGTH_SHORT).show() + } catch(e: NameNotFoundException) { + Toast.makeText(myContext, myContext.getString(R.string.not_installed), Toast.LENGTH_SHORT).show() + } + } + } + + if(isDeviceOwner(myDpm)||isProfileOwner(myDpm)){ + Column{ + var state by remember{mutableStateOf(myDpm.isUninstallBlocked(myComponent,pkgName))} + Text(text = stringResource(R.string.block_uninstall), style = typography.titleLarge, color = titleColor) + Text(stringResource(R.string.current_state, stringResource(if(state){R.string.enabled}else{R.string.disabled}))) + Text(text = stringResource(R.string.sometimes_get_wrong_block_uninstall_state), style = bodyTextStyle) + Row(horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.fillMaxWidth()) { + Button( + onClick = { + focusMgr.clearFocus() + myDpm.setUninstallBlocked(myComponent,pkgName,true) + Toast.makeText(myContext, myContext.getString(R.string.success), Toast.LENGTH_SHORT).show() + state = myDpm.isUninstallBlocked(myComponent,pkgName) + }, + modifier = Modifier.fillMaxWidth(0.49F) + ) { + Text(stringResource(R.string.enable)) + } + Button( + onClick = { + focusMgr.clearFocus() + myDpm.setUninstallBlocked(myComponent,pkgName,false) + Toast.makeText(myContext, myContext.getString(R.string.success), Toast.LENGTH_SHORT).show() + state = myDpm.isUninstallBlocked(myComponent,pkgName) + }, + modifier = Modifier.fillMaxWidth(0.96F) + ){ + Text(stringResource(R.string.disable)) + } + } + } + } + + if(VERSION.SDK_INT>=30&&(isDeviceOwner(myDpm)||isProfileOwner(myDpm))){ + UserCtrlDisabledPkg(pkgName) + } + + if(VERSION.SDK_INT>=23&&(isDeviceOwner(myDpm)||isProfileOwner(myDpm))){ + PermissionManage(pkgName) + } + + if(VERSION.SDK_INT>=30&&isProfileOwner(myDpm)&&myDpm.isManagedProfile(myComponent)){ + CrossProfilePkg(pkgName) + } + + if(isProfileOwner(myDpm)){ + CrossProfileWidget(pkgName) + } + + if(VERSION.SDK_INT>=34&&isDeviceOwner(myDpm)){ + CredentialManagePolicy(pkgName) + } + + if(isProfileOwner(myDpm)||isDeviceOwner(myDpm)){ + PermittedAccessibility(pkgName) + } + + if(isDeviceOwner(myDpm)||isProfileOwner(myDpm)){ + PermittedIME(pkgName) + } + + if(VERSION.SDK_INT>=28&&isDeviceOwner(myDpm)){ + KeepUninstalledApp(pkgName) + } + + if(VERSION.SDK_INT>=28){ + Button( + onClick = { + val executor = Executors.newCachedThreadPool() + val onClear = DevicePolicyManager.OnClearApplicationUserDataListener { pkg: String, succeed: Boolean -> + Looper.prepare() + focusMgr.clearFocus() + val toastText = if(pkg!=""){"$pkg\n"}else{""} + myContext.getString(R.string.clear_data) + myContext.getString(if(succeed){R.string.success}else{R.string.fail}) + Toast.makeText(myContext, toastText, Toast.LENGTH_SHORT).show() + Looper.loop() + } + myDpm.clearApplicationUserData(myComponent,pkgName,executor,onClear) + }, + enabled = isDeviceOwner(myDpm)||isProfileOwner(myDpm), + modifier = Modifier.fillMaxWidth().padding(horizontal = 10.dp) + ) { + Text(stringResource(R.string.clear_app_data)) + } + } + + if(VERSION.SDK_INT>=34){ + Button( + onClick = { + try{ + myDpm.setDefaultDialerApplication(pkgName) + Toast.makeText(myContext, myContext.getString(R.string.success), Toast.LENGTH_SHORT).show() + }catch(e:IllegalArgumentException){ + Toast.makeText(myContext, myContext.getString(R.string.fail), Toast.LENGTH_SHORT).show() + } + }, + enabled = isDeviceOwner(myDpm)||isProfileOwner(myDpm), + modifier = Modifier.fillMaxWidth().padding(horizontal = 10.dp) + ) { + Text(stringResource(R.string.set_default_dialer)) + } + } + + UninstallApp(pkgName) + + InstallApp() + + Spacer(Modifier.padding(30.dp)) + } + } +} + +@Composable +private fun AppManageItem( + itemName:Int, + itemDesc:Int, + getMethod:()->Boolean, + setMethod:(b:Boolean)->Unit +){ + val sharedPref = LocalContext.current.getSharedPreferences("data", Context.MODE_PRIVATE) + Row( + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + var enabled by remember{mutableStateOf(getMethod())} + enabled = getMethod() + Column(modifier = if(sharedPref.getBoolean("isWear",false)){Modifier.fillMaxWidth(0.65F)}else{Modifier}){ + Text(text = stringResource(itemName), style = typography.titleLarge, color = colorScheme.onPrimaryContainer) + if(itemDesc!=R.string.place_holder){ Text(stringResource(itemDesc)) } + } + Switch( + checked = enabled, + onCheckedChange = { setMethod(!enabled); enabled=getMethod() } + ) + } +} + +@SuppressLint("NewApi") +@Composable +fun UserCtrlDisabledPkg(pkgName:String){ + val myContext = LocalContext.current + val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager + val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) + Column{ + var pkgList = myDpm.getUserControlDisabledPackages(myComponent) + var listText by remember{mutableStateOf("")} + val refresh = { + pkgList = myDpm.getUserControlDisabledPackages(myComponent) + listText = "" + var count = pkgList.size + for(pkg in pkgList){ count-=1; listText+=pkg; if(count>0){listText+="\n"} } + } + var inited by remember{mutableStateOf(false)} + if(!inited){refresh();inited=true} + Text(text = stringResource(R.string.ucd), style = typography.titleLarge) + Text(text = stringResource(R.string.ucd_desc)) + Text(text = stringResource(R.string.app_list_is)) + SelectionContainer(modifier = Modifier.horizontalScroll(rememberScrollState()).animateContentSize(scrollAnim())){ + Text(text = if(listText==""){stringResource(R.string.none)}else{listText}) + } + Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween){ + Button( + onClick = { + if(pkgName!=""){ + pkgList.add(pkgName) + myDpm.setUserControlDisabledPackages(myComponent,pkgList) + refresh() + }else{ + Toast.makeText(myContext, myContext.getString(R.string.fail), Toast.LENGTH_SHORT).show() + } + }, + modifier = Modifier.fillMaxWidth(0.49F) + ){ + Text(stringResource(R.string.add)) + } + Button( + onClick = { + val result = if(pkgName!=""){pkgList.remove(pkgName)}else{false} + if(result){ + myDpm.setUserControlDisabledPackages(myComponent,pkgList) + refresh() + }else{ + Toast.makeText(myContext, myContext.getString(R.string.not_exist), Toast.LENGTH_SHORT).show() + } + }, + modifier = Modifier.fillMaxWidth(0.96F) + ){ + Text(stringResource(R.string.remove)) + } + } + Button( + onClick = { myDpm.setUserControlDisabledPackages(myComponent, listOf()); refresh() }, + modifier = Modifier.fillMaxWidth() + ){ + Text(stringResource(R.string.clear_list)) + } + } +} + +@SuppressLint("NewApi") +@Composable +fun PermissionManage(pkgName: String){ + val myContext = LocalContext.current + val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager + val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) + val focusMgr = LocalFocusManager.current + val grantState = mapOf( + PERMISSION_GRANT_STATE_DEFAULT to stringResource(R.string.decide_by_user), + PERMISSION_GRANT_STATE_GRANTED to stringResource(R.string.granted), + PERMISSION_GRANT_STATE_DENIED to stringResource(R.string.denied) + ) + Column{ + var inputPermission by remember{mutableStateOf("android.permission.")} + var currentState by remember{mutableStateOf(grantState[myDpm.getPermissionGrantState(myComponent,pkgName,inputPermission)])} + Text(text = stringResource(R.string.permission_manage), style = typography.titleLarge) + OutlinedTextField( + value = inputPermission, + label = { Text(stringResource(R.string.permission))}, + onValueChange = {inputPermission = it}, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Ascii, imeAction = ImeAction.Done), + keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}), + modifier = Modifier.focusable().fillMaxWidth().padding(vertical = 2.dp) + ) + Text(stringResource(R.string.current_state, currentState?:stringResource(R.string.unknown))) + Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween){ + Button( + onClick = { + myDpm.setPermissionGrantState(myComponent,pkgName,inputPermission, PERMISSION_GRANT_STATE_GRANTED) + currentState = grantState[myDpm.getPermissionGrantState(myComponent,pkgName,inputPermission)] + }, + modifier = Modifier.fillMaxWidth(0.49F) + ) { + Text(stringResource(R.string.grant)) + } + Button( + onClick = { + myDpm.setPermissionGrantState(myComponent,pkgName,inputPermission, PERMISSION_GRANT_STATE_DENIED) + currentState = grantState[myDpm.getPermissionGrantState(myComponent,pkgName,inputPermission)] + }, + Modifier.fillMaxWidth(0.96F) + ) { + Text(stringResource(R.string.deny)) + } + } + Button( + onClick = { + myDpm.setPermissionGrantState(myComponent,pkgName,inputPermission, PERMISSION_GRANT_STATE_DEFAULT) + currentState = grantState[myDpm.getPermissionGrantState(myComponent,pkgName,inputPermission)] + }, + modifier = Modifier.fillMaxWidth() + ) { + Text(stringResource(R.string.decide_by_user)) + } + } +} + +@SuppressLint("NewApi") +@Composable +fun CrossProfilePkg(pkgName: String){ + val myContext = LocalContext.current + val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager + val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) + Column{ + Text(text = stringResource(R.string.cross_profile_package), style = typography.titleLarge) + var list by remember{mutableStateOf("")} + val refresh = { + crossProfilePkg = myDpm.getCrossProfilePackages(myComponent) + list = "" + var count = crossProfilePkg.size + for(each in crossProfilePkg){ count-=1; list+=each; if(count>0){list+="\n"} } + } + var inited by remember{mutableStateOf(false)} + if(!inited){refresh();inited=true} + SelectionContainer(modifier = Modifier.horizontalScroll(rememberScrollState()).animateContentSize(scrollAnim())){ + Text(text = if(list==""){stringResource(R.string.none)}else{list}) + } + Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween){ + Button( + onClick = { + if(pkgName!=""){crossProfilePkg.add(pkgName)} + myDpm.setCrossProfilePackages(myComponent, crossProfilePkg) + refresh() + }, + modifier = Modifier.fillMaxWidth(0.49F) + ) { + Text(stringResource(R.string.add)) + } + Button( + onClick = { + if(pkgName!=""){crossProfilePkg.remove(pkgName)} + myDpm.setCrossProfilePackages(myComponent, crossProfilePkg) + refresh() + }, + modifier = Modifier.fillMaxWidth(0.96F) + ) { + Text(stringResource(R.string.remove)) + } + } + } +} + +@Composable +fun CrossProfileWidget(pkgName: String){ + val myContext = LocalContext.current + val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager + val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) + Column{ + var pkgList: MutableList + var list by remember{mutableStateOf("")} + val refresh = { + pkgList = myDpm.getCrossProfileWidgetProviders(myComponent) + list = "" + var count = pkgList.size + for(each in pkgList){ count-=1; list+=each; if(count>0){list+="\n"}} + } + var inited by remember{mutableStateOf(false)} + if(!inited){refresh();inited=true} + Text(text = stringResource(R.string.cross_profile_widget), style = typography.titleLarge) + Text(text = stringResource(R.string.cross_profile_widget_desc)) + SelectionContainer(modifier = Modifier.horizontalScroll(rememberScrollState()).animateContentSize(scrollAnim())){ + Text(text = if(list==""){stringResource(R.string.none)}else{list}) + } + Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween){ + Button( + onClick = { + if(pkgName!=""){myDpm.addCrossProfileWidgetProvider(myComponent,pkgName)} + refresh() + }, + modifier = Modifier.fillMaxWidth(0.49F) + ){ + Text(stringResource(R.string.add)) + } + Button( + onClick = { + if(pkgName!=""){myDpm.removeCrossProfileWidgetProvider(myComponent,pkgName)} + refresh() + }, + modifier = Modifier.fillMaxWidth(0.96F) + ){ + Text(stringResource(R.string.remove)) + } + } + } +} + +@SuppressLint("NewApi") +@Composable +fun CredentialManagePolicy(pkgName: String){ + val myContext = LocalContext.current + val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager + val focusMgr = LocalFocusManager.current + var policy:PackagePolicy? + var policyType by remember{mutableIntStateOf(-1)} + var credentialListText by remember{mutableStateOf("")} + val refreshPolicy = { + policy = myDpm.credentialManagerPolicy + policyType = policy?.policyType ?: -1 + credentialList = policy?.packageNames ?: mutableSetOf() + credentialList = credentialList.toMutableSet() + } + val refreshText = { + credentialListText = "" + var count = credentialList.size + for(item in credentialList){ count-=1; credentialListText+=item; if(count>0){credentialListText+="\n"} } + } + var inited by remember{mutableStateOf(false)} + if(!inited){refreshPolicy(); refreshText(); inited = true} + Column{ + Text(text = stringResource(R.string.credential_manage_policy), style = typography.titleLarge) + RadioButtonItem(stringResource(R.string.none),{policyType==-1},{policyType=-1}) + RadioButtonItem(stringResource(R.string.blacklist),{policyType==PACKAGE_POLICY_BLOCKLIST},{policyType=PACKAGE_POLICY_BLOCKLIST}) + RadioButtonItem(stringResource(R.string.whitelist),{policyType==PACKAGE_POLICY_ALLOWLIST},{policyType=PACKAGE_POLICY_ALLOWLIST}) + RadioButtonItem(stringResource(R.string.whitelist_and_system_app),{policyType==PACKAGE_POLICY_ALLOWLIST_AND_SYSTEM},{policyType=PACKAGE_POLICY_ALLOWLIST_AND_SYSTEM}) + AnimatedVisibility(policyType!=-1) { + Column { + Text("应用列表") + SelectionContainer(modifier = Modifier.horizontalScroll(rememberScrollState()).animateContentSize(scrollAnim())){ + Text(text = if(credentialListText!=""){ credentialListText }else{ stringResource(R.string.none) }) + } + Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween){ + Button( + onClick = { + if(pkgName!=""){credentialList.add(pkgName)} + refreshText() + }, + modifier = Modifier.fillMaxWidth(0.49F) + ) { + Text(stringResource(R.string.add)) + } + Button( + onClick = { + if(pkgName!=""){credentialList.remove(pkgName)} + refreshText() + }, + modifier = Modifier.fillMaxWidth(0.96F) + ) { + Text(stringResource(R.string.remove)) + } + } + } + } + Button( + onClick = { + focusMgr.clearFocus() + try{ + if(policyType!=-1&&credentialList.isNotEmpty()){ + myDpm.credentialManagerPolicy = PackagePolicy(policyType,credentialList) + }else{ + myDpm.credentialManagerPolicy = null + } + Toast.makeText(myContext, myContext.getString(R.string.success), Toast.LENGTH_SHORT).show() + }catch(e:java.lang.IllegalArgumentException){ + Toast.makeText(myContext, myContext.getString(R.string.fail), Toast.LENGTH_SHORT).show() + }finally { + refreshPolicy() + refreshText() + } + }, + modifier = Modifier.fillMaxWidth() + ) { + Text(stringResource(R.string.apply)) + } + } +} + +@Composable +fun PermittedAccessibility(pkgName: String){ + val myContext = LocalContext.current + val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager + val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) + val focusMgr = LocalFocusManager.current + Column { + Text(text = stringResource(R.string.permitted_accessibility_app), style = typography.titleLarge) + var listText by remember{ mutableStateOf("") } + val refreshList = { + listText = "" + var count = permittedAccessibility.size + for(eachAccessibility in permittedAccessibility){ count-=1; listText+=eachAccessibility; if(count>0){listText+="\n"} } + } + var inited by remember{mutableStateOf(false)} + if(!inited){ + val getList = myDpm.getPermittedAccessibilityServices(myComponent) + if(getList!=null){ permittedAccessibility = getList } + refreshList(); inited=true + } + SelectionContainer(modifier = Modifier.horizontalScroll(rememberScrollState()).animateContentSize(scrollAnim())){ + Text(text = if(listText==""){stringResource(R.string.none)}else{listText}) + } + Row(modifier = Modifier.fillMaxWidth(),horizontalArrangement = Arrangement.SpaceBetween){ + Button( + onClick = { permittedAccessibility.add(pkgName); refreshList()}, + modifier = Modifier.fillMaxWidth(0.49F) + ) { + Text(stringResource(R.string.add)) + } + Button( + onClick = { permittedAccessibility.remove(pkgName); refreshList() }, + modifier = Modifier.fillMaxWidth(0.96F) + ) { + Text(stringResource(R.string.remove)) + } + } + Button( + onClick = { + focusMgr.clearFocus() + Toast.makeText(myContext, if(myDpm.setPermittedAccessibilityServices(myComponent, permittedAccessibility)){"成功"}else{"失败"}, Toast.LENGTH_SHORT).show() + val getList = myDpm.getPermittedAccessibilityServices(myComponent) + if(getList!=null){ permittedAccessibility = getList } + refreshList() + }, + modifier = Modifier.fillMaxWidth() + ) { + Text(text = stringResource(R.string.apply)) + } + } +} + +@Composable +fun PermittedIME(pkgName: String){ + val myContext = LocalContext.current + val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager + val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) + val focusMgr = LocalFocusManager.current + Column { + Text(text = stringResource(R.string.permitted_ime), style = typography.titleLarge) + var imeListText by remember{ mutableStateOf("") } + val refreshList = { + imeListText = "" + for(eachIme in permittedIme){ imeListText += "$eachIme \n" } + } + var inited by remember{mutableStateOf(false)} + if(!inited){ + val getList = myDpm.getPermittedInputMethods(myComponent) + if(getList!=null){ permittedIme = getList } + refreshList();inited=true + } + SelectionContainer(modifier = Modifier.horizontalScroll(rememberScrollState()).animateContentSize(scrollAnim())){ + Text(text = if(imeListText==""){stringResource(R.string.none)}else{imeListText}) + } + Row(modifier = Modifier.fillMaxWidth(),horizontalArrangement = Arrangement.SpaceBetween){ + Button( + onClick = { permittedIme.add(pkgName); refreshList() }, + modifier = Modifier.fillMaxWidth(0.49F) + ) { + Text(stringResource(R.string.add)) + } + Button( + onClick = { permittedIme.remove(pkgName); refreshList()}, + modifier = Modifier.fillMaxWidth(0.96F) + ) { + Text(stringResource(R.string.remove)) + } + } + Button( + onClick = { + focusMgr.clearFocus() + Toast.makeText(myContext, if(myDpm.setPermittedInputMethods(myComponent, permittedIme)){"成功"}else{"失败"}, Toast.LENGTH_SHORT).show() + val getList = myDpm.getPermittedInputMethods(myComponent) + if(getList!=null){ permittedIme = getList } + refreshList() + }, + modifier = Modifier.fillMaxWidth() + ) { + Text(stringResource(R.string.apply)) + } + } +} + +@SuppressLint("NewApi") +@Composable +fun KeepUninstalledApp(pkgName: String){ + val myContext = LocalContext.current + val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager + val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) + val focusMgr = LocalFocusManager.current + Column{ + Text(text = stringResource(R.string.keep_uninstalled_pkgs), style = typography.titleLarge) + var listText by remember{mutableStateOf("")} + val refresh = { + listText = "" + var count = keepUninstallPkg.size + for(each in keepUninstallPkg){ count-=1; listText+=each; if(count>0){listText+="\n"} } + } + var inited by remember{mutableStateOf(false)} + if(!inited){ + val getList = myDpm.getKeepUninstalledPackages(myComponent) + if(getList!=null){ keepUninstallPkg = getList } + refresh(); inited=true + } + SelectionContainer(modifier = Modifier.horizontalScroll(rememberScrollState()).animateContentSize(scrollAnim())){ + Text(text = if(listText==""){stringResource(R.string.none)}else{listText}) + } + Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween){ + Button( + onClick = { + keepUninstallPkg.add(pkgName) + refresh() + }, + modifier = Modifier.fillMaxWidth(0.49F) + ){ + Text(stringResource(R.string.add)) + } + Button( + onClick = { + keepUninstallPkg.remove(pkgName) + refresh() + }, + modifier = Modifier.fillMaxWidth(0.96F) + ){ + Text(stringResource(R.string.remove)) + } + } + Button( + onClick = { + focusMgr.clearFocus() + myDpm.setKeepUninstalledPackages(myComponent, keepUninstallPkg) + val getList = myDpm.getKeepUninstalledPackages(myComponent) + if(getList!=null){ keepUninstallPkg = getList } + Toast.makeText(myContext, myContext.getString(R.string.success), Toast.LENGTH_SHORT).show() + }, + modifier = Modifier.fillMaxWidth() + ){ + Text(stringResource(R.string.apply)) + } + } +} + +@Composable +fun UninstallApp(pkgName: String){ + val myContext = LocalContext.current + Column{ + Text(text = stringResource(R.string.uninstall_app), style = typography.titleLarge) + Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween){ + Button( + onClick = { + val intent = Intent(myContext,PackageInstallerReceiver::class.java) + val intentSender = PendingIntent.getBroadcast(myContext, 8, intent, PendingIntent.FLAG_IMMUTABLE).intentSender + val pkgInstaller = myContext.packageManager.packageInstaller + pkgInstaller.uninstall(pkgName, intentSender) + }, + modifier = Modifier.fillMaxWidth(0.49F) + ) { + Text(stringResource(R.string.silent_uninstall)) + } + Button( + onClick = { + val intent = Intent(Intent.ACTION_UNINSTALL_PACKAGE) + intent.setData(Uri.parse("package:$pkgName")) + myContext.startActivity(intent) + }, + modifier = Modifier.fillMaxWidth(0.96F) + ) { + Text(stringResource(R.string.request_uninstall)) + } + } + } +} + +@Composable +fun InstallApp(){ + val myContext = LocalContext.current + val focusMgr = LocalFocusManager.current + Column{ + Text(text = stringResource(R.string.install_app), style = typography.titleLarge) + Button( + onClick = { + focusMgr.clearFocus() + val installApkIntent = Intent(Intent.ACTION_GET_CONTENT) + installApkIntent.setType("application/vnd.android.package-archive") + installApkIntent.addCategory(Intent.CATEGORY_OPENABLE) + getApk.launch(installApkIntent) + }, + modifier = Modifier.fillMaxWidth() + ) { + Text(stringResource(R.string.select_apk)) + } + var selected by remember{mutableStateOf(false)} + LaunchedEffect(selected){while(true){ delay(500); selected = apkUri!=null}} + AnimatedVisibility(selected) { + Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween){ + Button( + onClick = { uriToStream(myContext, apkUri){stream -> installPackage(myContext,stream)} }, + modifier = Modifier.fillMaxWidth(0.49F) + ) { + Text(stringResource(R.string.silent_install)) + } + Button( + onClick = { + val intent = Intent(Intent.ACTION_INSTALL_PACKAGE) + intent.setData(apkUri) + intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + myContext.startActivity(intent) + }, + modifier = Modifier.fillMaxWidth(0.96F) + ) { + Text(stringResource(R.string.request_install)) + } + } + } + } +} + +@Throws(IOException::class) +private fun installPackage(context: Context, inputStream: InputStream){ + val packageInstaller = context.packageManager.packageInstaller + val params = PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL) + val sessionId = packageInstaller.createSession(params) + val session = packageInstaller.openSession(sessionId) + val out = session.openWrite("COSU", 0, -1) + val buffer = ByteArray(65536) + var c: Int + while(inputStream.read(buffer).also{c = it}!=-1) { out.write(buffer, 0, c) } + session.fsync(out) + inputStream.close() + out.close() + val pendingIntent = PendingIntent.getBroadcast(context, sessionId, Intent(context,PackageInstallerReceiver::class.java), PendingIntent.FLAG_IMMUTABLE).intentSender + session.commit(pendingIntent) +} diff --git a/app/src/main/java/com/binbin/androidowner/dpm/DPM.kt b/app/src/main/java/com/binbin/androidowner/dpm/DPM.kt new file mode 100644 index 0000000..8ce03d7 --- /dev/null +++ b/app/src/main/java/com/binbin/androidowner/dpm/DPM.kt @@ -0,0 +1,26 @@ +package com.binbin.androidowner.dpm + +import android.app.admin.DevicePolicyManager +import android.content.Intent +import android.net.Uri +import androidx.activity.result.ActivityResultLauncher + + +lateinit var getCaCert: ActivityResultLauncher +lateinit var createUser: ActivityResultLauncher +lateinit var createManagedProfile: ActivityResultLauncher +lateinit var getApk: ActivityResultLauncher +lateinit var getUserIcon: ActivityResultLauncher + +var userIconUri: Uri? = null +var apkUri: Uri? = null +var caCert = byteArrayOf() + +fun isDeviceOwner(dpm: DevicePolicyManager): Boolean { + return dpm.isDeviceOwnerApp("com.binbin.androidowner") +} + +fun isProfileOwner(dpm: DevicePolicyManager): Boolean { + return dpm.isProfileOwnerApp("com.binbin.androidowner") +} + diff --git a/app/src/main/java/com/binbin/androidowner/dpm/ManagedProfile.kt b/app/src/main/java/com/binbin/androidowner/dpm/ManagedProfile.kt new file mode 100644 index 0000000..2fd2b33 --- /dev/null +++ b/app/src/main/java/com/binbin/androidowner/dpm/ManagedProfile.kt @@ -0,0 +1,282 @@ +package com.binbin.androidowner.dpm + +import android.annotation.SuppressLint +import android.app.admin.DevicePolicyManager +import android.app.admin.DevicePolicyManager.* +import android.content.* +import android.os.Binder +import android.os.Build.VERSION +import android.widget.Toast +import androidx.activity.ComponentActivity +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.foundation.focusable +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 +import androidx.compose.material3.MaterialTheme.typography +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Switch +import androidx.compose.material3.Text +import androidx.compose.runtime.* +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.unit.dp +import androidx.navigation.NavHostController +import com.binbin.androidowner.R +import com.binbin.androidowner.ui.CheckBoxItem + +@Composable +fun ManagedProfile() { + val myContext = LocalContext.current + val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager + val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) + val sharedPref = LocalContext.current.getSharedPreferences("data", Context.MODE_PRIVATE) + val isWear = sharedPref.getBoolean("isWear",false) + val bodyTextStyle = if(isWear){ typography.bodyMedium}else{ typography.bodyLarge} + val titleColor = colorScheme.onPrimaryContainer + Column(modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState())){ + Column{ + Text(text = stringResource(R.string.info), style = typography.titleLarge, color = titleColor) + if(VERSION.SDK_INT>=24){ + if(isProfileOwner(myDpm)&&myDpm.isManagedProfile(myComponent)){ + Text(text = stringResource(R.string.is_already_work_profile)) + }else{ + Text(text = stringResource(R.string.able_to_create_work_profile, myDpm.isProvisioningAllowed(ACTION_PROVISION_MANAGED_PROFILE)), style = bodyTextStyle) + if(isDeviceOwner(myDpm)){ + Text(text = stringResource(R.string.device_owner_cannot_create_work_profile), style = bodyTextStyle) + } + } + } + if(VERSION.SDK_INT>=30){ + Text(text = stringResource(R.string.is_org_owned_profile, myDpm.isOrganizationOwnedDeviceWithManagedProfile), style = bodyTextStyle) + } + } + + if(VERSION.SDK_INT>=30&&isProfileOwner(myDpm)&&myDpm.isManagedProfile(myComponent)&&!myDpm.isOrganizationOwnedDeviceWithManagedProfile){ + OrgOwnedProfile() + + } + if(VERSION.SDK_INT<24||(VERSION.SDK_INT>=24&&myDpm.isProvisioningAllowed(ACTION_PROVISION_MANAGED_PROFILE))){ + CreateWorkProfile() + } + + if(VERSION.SDK_INT>=30&&isProfileOwner(myDpm)&&myDpm.isManagedProfile(myComponent)&&myDpm.isOrganizationOwnedDeviceWithManagedProfile){ + SuspendPersonalApp() + } + + if(isProfileOwner(myDpm)&&(VERSION.SDK_INT<24||(VERSION.SDK_INT>=24&&myDpm.isManagedProfile(myComponent)))){ + IntentFilter() + } + + if(VERSION.SDK_INT>=31&&(isProfileOwner(myDpm)&&myDpm.isManagedProfile(myComponent))){ + OrgID() + } + + Spacer(Modifier.padding(vertical = 30.dp)) + } +} + +@Composable +fun CreateWorkProfile(){ + val myContext = LocalContext.current + val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) + Column{ + Text(text = stringResource(R.string.work_profile), style = typography.titleLarge) + var skipEncrypt by remember{mutableStateOf(false)} + if(VERSION.SDK_INT>=24){CheckBoxItem(stringResource(R.string.skip_encryption),{skipEncrypt},{skipEncrypt=!skipEncrypt})} + Button( + onClick = { + 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,myContext.getString(R.string.unsupported),Toast.LENGTH_SHORT).show() + } + }, + modifier = Modifier.fillMaxWidth() + ) { + Text(stringResource(R.string.create)) + } + } +} + +@Composable +fun OrgOwnedProfile(){ + Column{ + Text(text = stringResource(R.string.org_owned_work_profile), color = colorScheme.onTertiaryContainer, style = typography.titleLarge) + SelectionContainer { + Text(text = "使用ADB执行以下命令,或者使用Shizuku") + Text( + text = stringResource(R.string.activate_org_profile_command, Binder.getCallingUid()/100000), + color = colorScheme.onTertiaryContainer + ) + } + } +} + +@SuppressLint("NewApi") +@Composable +fun OrgID(){ + val myContext = LocalContext.current + val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager + val focusMgr = LocalFocusManager.current + Column{ + var orgId by remember{mutableStateOf("")} + Text(text = stringResource(R.string.org_id), style = typography.titleLarge) + OutlinedTextField( + value = orgId, onValueChange = {orgId=it}, + label = {Text(stringResource(R.string.org_id))}, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Ascii, imeAction = ImeAction.Done), + keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}), + modifier = Modifier.focusable().fillMaxWidth().padding(vertical = 2.dp) + ) + AnimatedVisibility(orgId.length !in 6..64) { + Text(text = stringResource(R.string.length_6_to_64)) + } + Button( + onClick = { + myDpm.setOrganizationId(orgId) + Toast.makeText(myContext, myContext.getString(R.string.success),Toast.LENGTH_SHORT).show() + }, + enabled = orgId.length in 6..64, + modifier = Modifier.fillMaxWidth() + ){ + Text(stringResource(R.string.apply)) + } + Text(text = stringResource(R.string.get_specific_id_after_set_org_id)) + } +} + +@SuppressLint("NewApi") +@Composable +fun SuspendPersonalApp(){ + val myContext = LocalContext.current + val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager + val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) + val focusMgr = LocalFocusManager.current + Column{ + Row(horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically){ + var suspended by remember{mutableStateOf(false)} + suspended = myDpm.getPersonalAppsSuspendedReasons(myComponent)!=PERSONAL_APPS_NOT_SUSPENDED + Text(text = stringResource(R.string.suspend_personal_app), style = typography.titleLarge) + Switch( + checked = suspended, + onCheckedChange ={ + myDpm.setPersonalAppsSuspended(myComponent,!suspended) + suspended = myDpm.getPersonalAppsSuspendedReasons(myComponent)!=PERSONAL_APPS_NOT_SUSPENDED + } + ) + } + var time by remember{mutableStateOf("")} + time = myDpm.getManagedProfileMaximumTimeOff(myComponent).toString() + Text(text = stringResource(R.string.profile_max_time_off), style = typography.titleLarge) + Text(text = stringResource(R.string.profile_max_time_out_desc)) + Text(text = stringResource(R.string.personal_app_suspended_because_timeout, myDpm.getPersonalAppsSuspendedReasons(myComponent)==PERSONAL_APPS_SUSPENDED_PROFILE_TIMEOUT)) + OutlinedTextField( + value = time, onValueChange = {time=it}, modifier = Modifier.focusable().fillMaxWidth().padding(vertical = 2.dp), + label = {Text(stringResource(R.string.time_unit_ms))}, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number, imeAction = ImeAction.Done), + keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}) + ) + Text(text = stringResource(R.string.cannot_less_than_72_hours)) + Button( + onClick = { + myDpm.setManagedProfileMaximumTimeOff(myComponent,time.toLong()) + Toast.makeText(myContext, myContext.getString(R.string.success), Toast.LENGTH_SHORT).show() + }, + modifier = Modifier.fillMaxWidth() + ) { + Text(stringResource(R.string.apply)) + } + } +} + +@Composable +fun IntentFilter(){ + val myContext = LocalContext.current + val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager + val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) + val focusMgr = LocalFocusManager.current + Column{ + var action by remember{mutableStateOf("")} + Text(text = stringResource(R.string.intent_filter), style = typography.titleLarge) + OutlinedTextField( + value = action, onValueChange = {action = it}, + label = {Text("Action")}, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Ascii, imeAction = ImeAction.Done), + keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}), + modifier = Modifier.focusable().fillMaxWidth().padding(vertical = 2.dp) + ) + Button( + onClick = { + myDpm.addCrossProfileIntentFilter(myComponent, IntentFilter(action), FLAG_PARENT_CAN_ACCESS_MANAGED) + Toast.makeText(myContext, myContext.getString(R.string.success),Toast.LENGTH_SHORT).show() + }, + modifier = Modifier.fillMaxWidth() + ) { + Text(stringResource(R.string.add_intent_filter_work_to_personal)) + } + Button( + onClick = { + myDpm.addCrossProfileIntentFilter(myComponent, IntentFilter(action), FLAG_MANAGED_CAN_ACCESS_PARENT) + Toast.makeText(myContext, myContext.getString(R.string.success),Toast.LENGTH_SHORT).show() + }, + modifier = Modifier.fillMaxWidth() + ) { + Text(stringResource(R.string.add_intent_filter_personal_to_work)) + } + Button( + onClick = { + myDpm.clearCrossProfileIntentFilters(myComponent) + myDpm.addCrossProfileIntentFilter(myComponent, IntentFilter("com.binbin.androidowner.MAIN_ACTION"), FLAG_MANAGED_CAN_ACCESS_PARENT) + myDpm.addCrossProfileIntentFilter(myComponent, IntentFilter("com.binbin.androidowner.MAIN_ACTION"), FLAG_PARENT_CAN_ACCESS_MANAGED) + Toast.makeText(myContext, myContext.getString(R.string.success),Toast.LENGTH_SHORT).show() + }, + modifier = Modifier.fillMaxWidth() + ){ + Text(stringResource(R.string.clear_cross_profile_filters)) + } + } +} + +@Composable +fun ActivateManagedProfile(navCtrl: NavHostController){ + val myContext = LocalContext.current + val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager + val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) + val sharedPref = myContext.getSharedPreferences("data", Context.MODE_PRIVATE) + myDpm.addCrossProfileIntentFilter(myComponent, IntentFilter("com.binbin.androidowner.MAIN_ACTION"), FLAG_MANAGED_CAN_ACCESS_PARENT) + myDpm.addCrossProfileIntentFilter(myComponent, IntentFilter("com.binbin.androidowner.MAIN_ACTION"), FLAG_PARENT_CAN_ACCESS_MANAGED) + Column(modifier = Modifier.verticalScroll(rememberScrollState()), horizontalAlignment = Alignment.CenterHorizontally){ + Text(text = stringResource(R.string.activate_managed_profile), style = typography.titleLarge) + Text(text = stringResource(R.string.activate_managed_profile_desc)) + Button( + onClick = { + myDpm.setProfileEnabled(myComponent) + navCtrl.popBackStack("HomePage",false) + sharedPref.edit().putBoolean("ManagedProfileActivated",true).apply() + Toast.makeText(myContext, myContext.getString(R.string.success), Toast.LENGTH_SHORT).show() + }, + modifier = Modifier.fillMaxWidth().padding(8.dp) + ) { + Text(stringResource(R.string.activate)) + } + } +} diff --git a/app/src/main/java/com/binbin/androidowner/dpm/Network.kt b/app/src/main/java/com/binbin/androidowner/dpm/Network.kt new file mode 100644 index 0000000..badce23 --- /dev/null +++ b/app/src/main/java/com/binbin/androidowner/dpm/Network.kt @@ -0,0 +1,741 @@ +package com.binbin.androidowner.dpm + +import android.annotation.SuppressLint +import android.app.admin.DevicePolicyManager +import android.app.admin.WifiSsidPolicy +import android.app.admin.WifiSsidPolicy.WIFI_SSID_POLICY_TYPE_ALLOWLIST +import android.app.admin.WifiSsidPolicy.WIFI_SSID_POLICY_TYPE_DENYLIST +import android.content.ComponentName +import android.content.Context +import android.net.wifi.WifiSsid +import android.os.Build.VERSION +import android.telephony.TelephonyManager +import android.telephony.TelephonyManager.UNKNOWN_CARRIER_ID +import android.telephony.data.ApnSetting.* +import android.util.Log +import android.widget.Toast +import androidx.activity.ComponentActivity +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.animateContentSize +import androidx.compose.foundation.focusable +import androidx.compose.foundation.horizontalScroll +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 +import androidx.compose.material3.MaterialTheme.typography +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Switch +import androidx.compose.material3.Text +import androidx.compose.material3.TextField +import androidx.compose.runtime.* +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 +import androidx.compose.ui.unit.dp +import androidx.core.net.toUri +import com.binbin.androidowner.R +import com.binbin.androidowner.ui.RadioButtonItem + +var ssidSet = mutableSetOf() +@Composable +fun Network(){ + val myContext = LocalContext.current + val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager + val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) + val sharedPref = LocalContext.current.getSharedPreferences("data", Context.MODE_PRIVATE) + val isWear = sharedPref.getBoolean("isWear",false) + val bodyTextStyle = if(isWear){ typography.bodyMedium }else{ typography.bodyLarge } + Column(modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState())){ + + if(VERSION.SDK_INT>=24){ + val wifimac = try { myDpm.getWifiMacAddress(myComponent).toString() }catch(e:SecurityException){ "没有权限" } + Text(text = "WiFi MAC: $wifimac",modifier=Modifier.fillMaxWidth(), textAlign = TextAlign.Center,style=bodyTextStyle) + } + + if(VERSION.SDK_INT>=33&&isDeviceOwner(myDpm)){ + DeviceCtrlItem( + R.string.preferential_network_service,R.string.developing,R.drawable.globe_fill0, + {myDpm.isPreferentialNetworkServiceEnabled},{b -> myDpm.isPreferentialNetworkServiceEnabled = b} + ) + } + if(VERSION.SDK_INT>=30&&(isDeviceOwner(myDpm)||(isProfileOwner(myDpm)&&myDpm.isOrganizationOwnedDeviceWithManagedProfile))){ + DeviceCtrlItem(R.string.wifi_lockdown,R.string.place_holder,R.drawable.wifi_password_fill0, + {myDpm.hasLockdownAdminConfiguredNetworks(myComponent)},{b -> myDpm.setConfiguredNetworksLockdownState(myComponent,b)} + ) + } + if(VERSION.SDK_INT>=33){ + WifiSecLevel() + } + + if(VERSION.SDK_INT>=33&&(isDeviceOwner(myDpm)||(isProfileOwner(myDpm)&&myDpm.isOrganizationOwnedDeviceWithManagedProfile))){ + WifiSsidPolicy() + } + + if(VERSION.SDK_INT>=29&&isDeviceOwner(myDpm)){ + PrivateDNS() + } + + if(VERSION.SDK_INT>=26&&(isDeviceOwner(myDpm)||(isProfileOwner(myDpm)&&myDpm.isManagedProfile(myComponent)))){ + NetLog() + } + + if(VERSION.SDK_INT>=31&&(isDeviceOwner(myDpm)||isProfileOwner(myDpm))){ + WifiKeypair() + } + + if(VERSION.SDK_INT>=28&&isDeviceOwner(myDpm)){ + APN() + } + + Spacer(Modifier.padding(vertical = 30.dp)) + } +} + +@SuppressLint("NewApi") +@Composable +fun WifiSecLevel(){ + val myContext = LocalContext.current + val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager + Column{ + var selectedWifiSecLevel by remember{mutableIntStateOf(myDpm.minimumRequiredWifiSecurityLevel)} + Text(text = stringResource(R.string.min_wifi_security_level), style = typography.titleLarge) + RadioButtonItem(stringResource(R.string.wifi_security_level_open), {selectedWifiSecLevel==DevicePolicyManager.WIFI_SECURITY_OPEN}, {selectedWifiSecLevel= DevicePolicyManager.WIFI_SECURITY_OPEN}) + RadioButtonItem("WEP, WPA(2)-PSK", {selectedWifiSecLevel==DevicePolicyManager.WIFI_SECURITY_PERSONAL}, {selectedWifiSecLevel= DevicePolicyManager.WIFI_SECURITY_PERSONAL}) + RadioButtonItem("WPA-EAP", {selectedWifiSecLevel==DevicePolicyManager.WIFI_SECURITY_ENTERPRISE_EAP}, {selectedWifiSecLevel= DevicePolicyManager.WIFI_SECURITY_ENTERPRISE_EAP}) + RadioButtonItem("WPA3-192bit", {selectedWifiSecLevel==DevicePolicyManager.WIFI_SECURITY_ENTERPRISE_192}, {selectedWifiSecLevel= DevicePolicyManager.WIFI_SECURITY_ENTERPRISE_192}) + Button( + enabled = isDeviceOwner(myDpm)||(isProfileOwner(myDpm)&&myDpm.isOrganizationOwnedDeviceWithManagedProfile), + onClick = { + myDpm.minimumRequiredWifiSecurityLevel=selectedWifiSecLevel + Toast.makeText(myContext, myContext.getString(R.string.success), Toast.LENGTH_SHORT).show() + }, + modifier = Modifier.fillMaxWidth() + ){ + Text(stringResource(R.string.apply)) + } + } +} + +@SuppressLint("NewApi") +@Composable +fun WifiSsidPolicy(){ + val myContext = LocalContext.current + val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager + val focusMgr = LocalFocusManager.current + Column{ + var policy = myDpm.wifiSsidPolicy + var selectedPolicyType by remember{mutableIntStateOf(policy?.policyType ?: -1)} + var inputSsid by remember{mutableStateOf("")} + var ssidList by remember{mutableStateOf("")} + val refreshPolicy = { + policy = myDpm.wifiSsidPolicy + selectedPolicyType = policy?.policyType ?: -1 + ssidSet = policy?.ssids ?: mutableSetOf() + } + val refreshList = { + ssidList = "" + var count = ssidSet.size + for(ssid in ssidSet){ count-=1; ssidList+=ssid; if(count>0){ssidList+="\n"} } + } + var inited by remember{mutableStateOf(false)} + if(!inited){ refreshPolicy(); refreshList(); inited=true } + Text(text = stringResource(R.string.wifi_ssid_policy), style = typography.titleLarge) + RadioButtonItem(stringResource(R.string.none),{selectedPolicyType==-1},{selectedPolicyType=-1}) + RadioButtonItem(stringResource(R.string.whitelist),{selectedPolicyType==WIFI_SSID_POLICY_TYPE_ALLOWLIST},{selectedPolicyType=WIFI_SSID_POLICY_TYPE_ALLOWLIST}) + RadioButtonItem(stringResource(R.string.blacklist),{selectedPolicyType==WIFI_SSID_POLICY_TYPE_DENYLIST},{selectedPolicyType=WIFI_SSID_POLICY_TYPE_DENYLIST}) + Column(modifier = Modifier.animateContentSize(scrollAnim()).horizontalScroll(rememberScrollState())){ + if(ssidList!=""){ + Text(stringResource(R.string.ssid_list_is)) + SelectionContainer{ + Text(text = ssidList, color = colorScheme.onPrimaryContainer) + } + } + } + OutlinedTextField( + value = inputSsid, + label = { Text("SSID")}, + onValueChange = {inputSsid = it}, + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), + keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}), + modifier = Modifier.focusable().fillMaxWidth().padding(vertical = 2.dp) + ) + Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween){ + Button( + onClick = { + if(inputSsid==""){ + Toast.makeText(myContext, myContext.getString(R.string.cannot_be_empty), Toast.LENGTH_SHORT).show() + }else if(WifiSsid.fromBytes(inputSsid.toByteArray()) in ssidSet){ + Toast.makeText(myContext, myContext.getString(R.string.already_exist), Toast.LENGTH_SHORT).show() + }else{ + ssidSet.add(WifiSsid.fromBytes(inputSsid.toByteArray())) + refreshList() + } + inputSsid = "" + }, + modifier = Modifier.fillMaxWidth(0.49F) + ) { + Text(stringResource(R.string.add)) + } + Button( + onClick = { + if(inputSsid==""){ + Toast.makeText(myContext, myContext.getString(R.string.cannot_be_empty), Toast.LENGTH_SHORT).show() + }else if(WifiSsid.fromBytes(inputSsid.toByteArray()) in ssidSet){ + ssidSet.remove(WifiSsid.fromBytes(inputSsid.toByteArray())) + inputSsid = "" + refreshList() + }else{ + Toast.makeText(myContext, myContext.getString(R.string.not_exist), Toast.LENGTH_SHORT).show() + } + }, + modifier = Modifier.fillMaxWidth(0.96F) + ) { + Text(stringResource(R.string.remove)) + } + } + Button( + onClick = { + focusMgr.clearFocus() + if(selectedPolicyType==-1){ + if(policy==null&&ssidSet.isNotEmpty()){ + Toast.makeText(myContext, myContext.getString(R.string.please_select_a_policy), Toast.LENGTH_SHORT).show() + }else{ + myDpm.wifiSsidPolicy = null + refreshPolicy() + Toast.makeText(myContext, myContext.getString(R.string.success), Toast.LENGTH_SHORT).show() + } + }else{ + myDpm.wifiSsidPolicy = if(ssidSet.size==0){ null }else{ WifiSsidPolicy(selectedPolicyType, ssidSet) } + refreshPolicy() + Toast.makeText(myContext, myContext.getString(R.string.success), Toast.LENGTH_SHORT).show() + } + }, + modifier = Modifier.fillMaxWidth() + ) { + Text(stringResource(R.string.apply)) + } + } +} + +@SuppressLint("NewApi") +@Composable +fun PrivateDNS(){ + val myContext = LocalContext.current + val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager + val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) + val focusMgr = LocalFocusManager.current + Column{ + Text(text = stringResource(R.string.private_dns), style = typography.titleLarge) + val dnsStatus = mapOf( + DevicePolicyManager.PRIVATE_DNS_MODE_UNKNOWN to stringResource(R.string.unknown), + DevicePolicyManager.PRIVATE_DNS_MODE_OFF to stringResource(R.string.disabled), + DevicePolicyManager.PRIVATE_DNS_MODE_OPPORTUNISTIC to stringResource(R.string.auto), + DevicePolicyManager.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME to stringResource(R.string.dns_provide_hostname) + ) + val operationResult = mapOf( + DevicePolicyManager.PRIVATE_DNS_SET_NO_ERROR to stringResource(R.string.success), + DevicePolicyManager.PRIVATE_DNS_SET_ERROR_HOST_NOT_SERVING to stringResource(R.string.host_not_serving_dns_tls), + DevicePolicyManager.PRIVATE_DNS_SET_ERROR_FAILURE_SETTING to stringResource(R.string.fail) + ) + var status by remember{mutableStateOf(dnsStatus[myDpm.getGlobalPrivateDnsMode(myComponent)])} + Text(text = stringResource(R.string.current_state, status?:stringResource(R.string.unknown))) + Button( + onClick = { + val result = myDpm.setGlobalPrivateDnsModeOpportunistic(myComponent) + Toast.makeText(myContext, operationResult[result], Toast.LENGTH_SHORT).show() + status = dnsStatus[myDpm.getGlobalPrivateDnsMode(myComponent)] + }, + modifier = Modifier.fillMaxWidth() + ) { + Text(stringResource(R.string.set_to_auto)) + } + Spacer(Modifier.padding(vertical = 3.dp)) + var inputHost by remember{mutableStateOf(myDpm.getGlobalPrivateDnsHost(myComponent) ?: "")} + OutlinedTextField( + value = inputHost, + onValueChange = {inputHost=it}, + label = {Text(stringResource(R.string.dns_hostname))}, + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), + keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}), + modifier = Modifier.focusable().fillMaxWidth().padding(vertical = 2.dp) + ) + Button( + onClick = { + focusMgr.clearFocus() + val result: Int + try{ + result = myDpm.setGlobalPrivateDnsModeSpecifiedHost(myComponent,inputHost) + Toast.makeText(myContext, operationResult[result], Toast.LENGTH_SHORT).show() + }catch(e:IllegalArgumentException){ + Toast.makeText(myContext, myContext.getString(R.string.invalid_hostname), Toast.LENGTH_SHORT).show() + }catch(e:SecurityException){ + Toast.makeText(myContext, myContext.getString(R.string.security_exception), Toast.LENGTH_SHORT).show() + }finally { + status = dnsStatus[myDpm.getGlobalPrivateDnsMode(myComponent)] + } + }, + modifier = Modifier.fillMaxWidth() + ) { + Text(stringResource(R.string.set_dns_host)) + } + } +} + +@SuppressLint("NewApi") +@Composable +fun NetLog(){ + val myContext = LocalContext.current + val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager + val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) + Column{ + Text(text = stringResource(R.string.retrieve_net_logs), style = typography.titleLarge) + Text(text = stringResource(R.string.developing)) + Row(modifier=Modifier.fillMaxWidth().padding(horizontal=8.dp),horizontalArrangement=Arrangement.SpaceBetween,verticalAlignment=Alignment.CenterVertically){ + var checked by remember{mutableStateOf(myDpm.isNetworkLoggingEnabled(myComponent))} + Text(text = stringResource(R.string.enabled), style = typography.titleLarge) + Switch( + checked = checked, + onCheckedChange = {myDpm.setNetworkLoggingEnabled(myComponent,!checked);checked = myDpm.isNetworkLoggingEnabled(myComponent)} + ) + } + Button( + onClick = { + val log = myDpm.retrieveNetworkLogs(myComponent,1234567890) + if(log!=null){ + for(i in log){ Log.d("NetLog",i.toString()) } + Toast.makeText(myContext, myContext.getString(R.string.success),Toast.LENGTH_SHORT).show() + }else{ + Log.d("NetLog",myContext.getString(R.string.none)) + Toast.makeText(myContext, myContext.getString(R.string.none),Toast.LENGTH_SHORT).show() + } + }, + modifier = Modifier.fillMaxWidth() + ) { + Text(stringResource(R.string.retrieve)) + } + } +} + +@SuppressLint("NewApi") +@Composable +fun WifiKeypair(){ + val myContext = LocalContext.current + val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager + val focusMgr = LocalFocusManager.current + Column{ + var keyPair by remember{mutableStateOf("")} + Text(text = stringResource(R.string.wifi_keypair), style = typography.titleLarge) + OutlinedTextField( + value = keyPair, + label = { Text(stringResource(R.string.keypair))}, + onValueChange = {keyPair = it}, + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), + keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}), + modifier = Modifier.focusable().fillMaxWidth().padding(vertical = 2.dp) + ) + val isExist = try{myDpm.isKeyPairGrantedToWifiAuth(keyPair)}catch(e:java.lang.IllegalArgumentException){false} + Text(stringResource(R.string.already_exist)+":$isExist") + Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween){ + Button( + onClick = { + val result = myDpm.grantKeyPairToWifiAuth(keyPair) + Toast.makeText(myContext, myContext.getString(if(result){R.string.success}else{R.string.fail}), Toast.LENGTH_SHORT).show() + }, + modifier = Modifier.fillMaxWidth(0.49F) + ) { + Text(stringResource(R.string.add)) + } + Button( + onClick = { + val result = myDpm.revokeKeyPairFromWifiAuth(keyPair) + Toast.makeText(myContext, myContext.getString(if(result){R.string.success}else{R.string.fail}), Toast.LENGTH_SHORT).show() + }, + modifier = Modifier.fillMaxWidth(0.96F) + ) { + Text(stringResource(R.string.remove)) + } + } + } +} + +@SuppressLint("NewApi") +@Composable +fun APN(){ + val myContext = LocalContext.current + val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager + val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) + val focusMgr = LocalFocusManager.current + Column{ + val setting = myDpm.getOverrideApns(myComponent) + var inputNum by remember{mutableStateOf("0")} + var nextStep by remember{mutableStateOf(false)} + val builder = Builder() + Text(text = stringResource(R.string.apn_settings), style = typography.titleLarge) + Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically){ + Text(text = stringResource(R.string.enable), style = typography.titleLarge) + Switch(checked = myDpm.isOverrideApnEnabled(myComponent), onCheckedChange = {myDpm.setOverrideApnsEnabled(myComponent,it)}) + } + Text(text = stringResource(R.string.total_apn_amount, setting.size)) + if(setting.size>0){ + Text(text = stringResource(R.string.select_a_apn_or_create, setting.size)) + TextField( + value = inputNum, + label = { Text("APN")}, + onValueChange = {inputNum = it}, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number, imeAction = ImeAction.Done), + keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}), + modifier = Modifier.focusable().fillMaxWidth().padding(vertical = 2.dp), + enabled = !nextStep + ) + }else{ + Text(text = stringResource(R.string.no_apn_you_should_create_one)) + } + Button( + onClick = {focusMgr.clearFocus(); nextStep=!nextStep}, + modifier = Modifier.fillMaxWidth(), + enabled = inputNum!=""&&(nextStep||inputNum=="0"||setting[inputNum.toInt()-1]!=null) + ) { + Text(stringResource(if(nextStep){R.string.previous_step}else{R.string.next_step})) + } + var result = Builder().build() + AnimatedVisibility(nextStep) { + var carrierEnabled by remember{mutableStateOf(false)} + var inputApnName by remember{mutableStateOf("")} + var user by remember{mutableStateOf("")} + var profileId by remember{mutableStateOf("")} + var selectedAuthType by remember{mutableIntStateOf(AUTH_TYPE_NONE)} + var carrierId by remember{mutableStateOf("$UNKNOWN_CARRIER_ID")} + var apnTypeBitmask by remember{mutableStateOf("")} + var entryName by remember{mutableStateOf("")} + var mmsProxyAddress by remember{mutableStateOf("")} + var mmsProxyPort by remember{mutableStateOf("")} + var proxyAddress by remember{mutableStateOf("")} + var proxyPort by remember{mutableStateOf("")} + var mmsc by remember{mutableStateOf("")} + var mtuV4 by remember{mutableStateOf("")} + var mtuV6 by remember{mutableStateOf("")} + var mvnoType by remember{mutableIntStateOf(-1)} + var networkTypeBitmask by remember{mutableStateOf("")} + var operatorNumeric by remember{mutableStateOf("")} + var password by remember{mutableStateOf("")} + var persistent by remember{mutableStateOf(false)} + var protocol by remember{mutableIntStateOf(-1)} + var roamingProtocol by remember{mutableIntStateOf(-1)} + var id by remember{mutableIntStateOf(0)} + + if(inputNum!="0"){ + val current = setting[inputNum.toInt()-1] + id = current.id + carrierEnabled = current.isEnabled + inputApnName = current.apnName + user = current.user + if(VERSION.SDK_INT>=33){profileId = current.profileId.toString()} + selectedAuthType = current.authType + apnTypeBitmask = current.apnTypeBitmask.toString() + entryName = current.entryName + if(VERSION.SDK_INT>=29){mmsProxyAddress = current.mmsProxyAddressAsString} + mmsProxyPort = current.mmsProxyPort.toString() + if(VERSION.SDK_INT>=29){proxyAddress = current.proxyAddressAsString} + proxyPort = current.proxyPort.toString() + mmsc = current.mmsc.toString() + if(VERSION.SDK_INT>=33){ mtuV4 = current.mtuV4.toString(); mtuV6 = current.mtuV6.toString() } + mvnoType = current.mvnoType + networkTypeBitmask = current.networkTypeBitmask.toString() + operatorNumeric = current.operatorNumeric + password = current.password + if(VERSION.SDK_INT>=33){persistent = current.isPersistent} + protocol = current.protocol + roamingProtocol = current.roamingProtocol + } + + Column { + + Text(text = "APN", style = typography.titleLarge) + TextField( + value = inputApnName, + onValueChange = {inputApnName=it}, + label = {Text(stringResource(R.string.name))}, + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), + keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}), + modifier = Modifier.focusable().fillMaxWidth().padding(top = 2.dp, bottom = 4.dp) + ) + Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically){ + Text(text = stringResource(R.string.enable), style = typography.titleLarge) + Switch(checked = carrierEnabled, onCheckedChange = {carrierEnabled=it}) + } + + Text(text = stringResource(R.string.user_name), style = typography.titleLarge) + TextField( + value = user, + onValueChange = {user=it}, + label = {Text(stringResource(R.string.user_name))}, + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), + keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}), + modifier = Modifier.focusable().fillMaxWidth().padding(top = 2.dp, bottom = 4.dp) + ) + + if(VERSION.SDK_INT>=33){ + Text(text = stringResource(R.string.profile_id), style = typography.titleLarge) + TextField( + value = profileId, + onValueChange = {profileId=it}, + label = {Text("ID")}, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number, imeAction = ImeAction.Done), + keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}), + modifier = Modifier.focusable().fillMaxWidth().padding(top = 2.dp, bottom = 4.dp) + ) + } + + Text(text = stringResource(R.string.auth_type), style = typography.titleLarge) + RadioButtonItem("无",{selectedAuthType==AUTH_TYPE_NONE},{selectedAuthType=AUTH_TYPE_NONE}) + RadioButtonItem("CHAP",{selectedAuthType==AUTH_TYPE_CHAP},{selectedAuthType=AUTH_TYPE_CHAP}) + RadioButtonItem("PAP",{selectedAuthType==AUTH_TYPE_PAP},{selectedAuthType=AUTH_TYPE_PAP}) + RadioButtonItem("PAP/CHAP",{selectedAuthType==AUTH_TYPE_PAP_OR_CHAP},{selectedAuthType=AUTH_TYPE_PAP_OR_CHAP}) + + if(VERSION.SDK_INT>=29){ + val ts = myContext.getSystemService(ComponentActivity.TELEPHONY_SERVICE) as TelephonyManager + carrierId = ts.simCarrierId.toString() + Text(text = "CarrierID", style = typography.titleLarge) + TextField( + value = carrierId, + onValueChange = {carrierId=it}, + label = {Text("ID")}, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number, imeAction = ImeAction.Done), + keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}), + modifier = Modifier.focusable().fillMaxWidth().padding(top = 2.dp, bottom = 4.dp) + ) + } + + Text(text = stringResource(R.string.apn_type), style = typography.titleLarge) + TextField( + value = apnTypeBitmask, + onValueChange = {apnTypeBitmask=it}, + label = {Text(stringResource(R.string.bitmask))}, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number, imeAction = ImeAction.Done), + keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}), + modifier = Modifier.focusable().fillMaxWidth().padding(top = 2.dp, bottom = 4.dp) + ) + + Text(text = stringResource(R.string.description), style = typography.titleLarge) + TextField( + value = entryName, + onValueChange = {entryName=it}, + label = {Text(stringResource(R.string.description))}, + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), + keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}), + modifier = Modifier.focusable().fillMaxWidth().padding(top = 2.dp, bottom = 4.dp) + ) + + Text(text = stringResource(R.string.mms_proxy), style = typography.titleLarge) + if(VERSION.SDK_INT>=29){ + TextField( + value = mmsProxyAddress, + onValueChange = {mmsProxyAddress=it}, + label = {Text(stringResource(R.string.address))}, + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), + keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}), + modifier = Modifier.focusable().fillMaxWidth().padding(top = 2.dp, bottom = 4.dp) + ) + } + TextField( + value = mmsProxyPort, + onValueChange = {mmsProxyPort=it}, + label = {Text(stringResource(R.string.port))}, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number, imeAction = ImeAction.Done), + keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}), + modifier = Modifier.focusable().fillMaxWidth().padding(top = 2.dp, bottom = 4.dp) + ) + + Text(text = stringResource(R.string.proxy), style = typography.titleLarge) + if(VERSION.SDK_INT>=29){ + TextField( + value = proxyAddress, + onValueChange = {proxyAddress=it}, + label = {Text(stringResource(R.string.address))}, + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), + keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}), + modifier = Modifier.focusable().fillMaxWidth().padding(top = 2.dp, bottom = 4.dp) + ) + } + TextField( + value = proxyPort, + onValueChange = {proxyPort=it}, + label = {Text(stringResource(R.string.port))}, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number, imeAction = ImeAction.Done), + keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}), + modifier = Modifier.focusable().fillMaxWidth().padding(top = 2.dp, bottom = 4.dp) + ) + + Text(text = "MMSC", style = typography.titleLarge) + TextField( + value = mmsc, + onValueChange = {mmsc=it}, + label = {Text("Uri")}, + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), + keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}), + modifier = Modifier.focusable().fillMaxWidth().padding(top = 2.dp, bottom = 4.dp) + ) + + if(VERSION.SDK_INT>=33){ + Text(text = "MTU", style = typography.titleLarge) + TextField( + value = mtuV4, + onValueChange = {mtuV4=it}, + label = {Text("IPV4")}, + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), + keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}), + modifier = Modifier.focusable().fillMaxWidth().padding(top = 2.dp, bottom = 4.dp) + ) + TextField( + value = mtuV6, + onValueChange = {mtuV6=it}, + label = {Text("IPV6")}, + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), + keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}), + modifier = Modifier.focusable().fillMaxWidth().padding(top = 2.dp, bottom = 4.dp) + ) + } + + Text(text = "MVNO", style = typography.titleLarge) + RadioButtonItem("SPN",{mvnoType==MVNO_TYPE_SPN},{mvnoType=MVNO_TYPE_SPN}) + RadioButtonItem("IMSI",{mvnoType==MVNO_TYPE_IMSI},{mvnoType=MVNO_TYPE_IMSI}) + RadioButtonItem("GID",{mvnoType==MVNO_TYPE_GID},{mvnoType=MVNO_TYPE_GID}) + RadioButtonItem("ICCID",{mvnoType==MVNO_TYPE_ICCID},{mvnoType=MVNO_TYPE_ICCID}) + + Text(text = stringResource(R.string.network_type), style = typography.titleLarge) + TextField( + value = networkTypeBitmask, + onValueChange = {networkTypeBitmask=it}, + label = {Text(stringResource(R.string.bitmask))}, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number, imeAction = ImeAction.Done), + keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}), + modifier = Modifier.focusable().fillMaxWidth().padding(top = 2.dp, bottom = 4.dp) + ) + + Text(text = "OperatorNumeric", style = typography.titleLarge) + TextField( + value = operatorNumeric, + onValueChange = {operatorNumeric=it}, + label = {Text("ID")}, + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), + keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}), + modifier = Modifier.focusable().fillMaxWidth().padding(top = 2.dp, bottom = 4.dp) + ) + + Text(text = stringResource(R.string.password), style = typography.titleLarge) + TextField( + value = password, + onValueChange = {password=it}, + label = {Text(stringResource(R.string.password))}, + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), + keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}), + modifier = Modifier.focusable().fillMaxWidth().padding(top = 2.dp, bottom = 4.dp) + ) + + if(VERSION.SDK_INT>=33){ + Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically){ + Text(text = stringResource(R.string.persistent), style = typography.titleLarge) + Switch(checked = persistent, onCheckedChange = {persistent=it}) + } + } + + Text(text = stringResource(R.string.protocol), style = typography.titleLarge) + RadioButtonItem("IPV4",{protocol==PROTOCOL_IP},{protocol=PROTOCOL_IP}) + RadioButtonItem("IPV6",{protocol==PROTOCOL_IPV6},{protocol=PROTOCOL_IPV6}) + RadioButtonItem("IPV4/IPV6",{protocol==PROTOCOL_IPV4V6},{protocol=PROTOCOL_IPV4V6}) + RadioButtonItem("PPP",{protocol==PROTOCOL_PPP},{protocol=PROTOCOL_PPP}) + if(VERSION.SDK_INT>=29){ + RadioButtonItem("non-IP",{protocol==PROTOCOL_NON_IP},{protocol=PROTOCOL_NON_IP}) + RadioButtonItem("Unstructured",{protocol==PROTOCOL_UNSTRUCTURED},{protocol=PROTOCOL_UNSTRUCTURED}) + } + + Text(text = stringResource(R.string.roaming_protocol), style = typography.titleLarge) + RadioButtonItem("IPV4",{roamingProtocol==PROTOCOL_IP},{roamingProtocol=PROTOCOL_IP}) + RadioButtonItem("IPV6",{roamingProtocol==PROTOCOL_IPV6},{roamingProtocol=PROTOCOL_IPV6}) + RadioButtonItem("IPV4/IPV6",{roamingProtocol==PROTOCOL_IPV4V6},{roamingProtocol=PROTOCOL_IPV4V6}) + RadioButtonItem("PPP",{roamingProtocol==PROTOCOL_PPP},{roamingProtocol=PROTOCOL_PPP}) + if(VERSION.SDK_INT>=29){ + RadioButtonItem("non-IP",{roamingProtocol==PROTOCOL_NON_IP},{roamingProtocol=PROTOCOL_NON_IP}) + RadioButtonItem("Unstructured",{roamingProtocol==PROTOCOL_UNSTRUCTURED},{roamingProtocol=PROTOCOL_UNSTRUCTURED}) + } + + var finalStep by remember{mutableStateOf(false)} + Button( + onClick = { + if(!finalStep){ + builder.setCarrierEnabled(carrierEnabled) + builder.setApnName(inputApnName) + builder.setUser(user) + if(VERSION.SDK_INT>=33){builder.setProfileId(profileId.toInt())} + builder.setAuthType(selectedAuthType) + if(VERSION.SDK_INT>=29){builder.setCarrierId(carrierId.toInt())} + builder.setApnTypeBitmask(apnTypeBitmask.toInt()) + builder.setEntryName(entryName) + if(VERSION.SDK_INT>=29){builder.setMmsProxyAddress(mmsProxyAddress)} + builder.setMmsProxyPort(mmsProxyPort.toInt()) + if(VERSION.SDK_INT>=29){builder.setProxyAddress(proxyAddress)} + builder.setProxyPort(proxyPort.toInt()) + builder.setMmsc(mmsc.toUri()) + if(VERSION.SDK_INT>=33){ builder.setMtuV4(mtuV4.toInt()); builder.setMtuV6(mtuV6.toInt()) } + builder.setMvnoType(mvnoType) + builder.setNetworkTypeBitmask(networkTypeBitmask.toInt()) + builder.setOperatorNumeric(operatorNumeric) + builder.setPassword(password) + if(VERSION.SDK_INT>=33){builder.setPersistent(persistent)} + builder.setProtocol(protocol) + builder.setRoamingProtocol(roamingProtocol) + result = builder.build() + } + + finalStep=!finalStep + }, + modifier = Modifier.fillMaxWidth() + ) { + Text(stringResource(if(finalStep){R.string.previous_step}else{R.string.next_step})) + } + AnimatedVisibility(finalStep) { + if(inputNum=="0"){ + Button( + onClick = {myDpm.addOverrideApn(myComponent,result)}, + modifier = Modifier.fillMaxWidth() + ) { + Text(stringResource(R.string.create)) + } + }else{ + Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween){ + Button( + onClick = { + val success = myDpm.updateOverrideApn(myComponent,id,result) + Toast.makeText(myContext, myContext.getString(if(success){R.string.success}else{R.string.fail}), Toast.LENGTH_SHORT).show() + }, + Modifier.fillMaxWidth(0.49F) + ){ + Text(stringResource(R.string.update)) + } + Button( + onClick = { + val success = myDpm.removeOverrideApn(myComponent,id) + Toast.makeText(myContext, if(success){"成功"}else{"失败"}, Toast.LENGTH_SHORT).show() + }, + Modifier.fillMaxWidth(0.96F) + ){ + Text(stringResource(R.string.remove)) + } + } + } + } + } + } + Text(text = stringResource(id = R.string.developing)) + } +} diff --git a/app/src/main/java/com/binbin/androidowner/dpm/Password.kt b/app/src/main/java/com/binbin/androidowner/dpm/Password.kt new file mode 100644 index 0000000..b10e684 --- /dev/null +++ b/app/src/main/java/com/binbin/androidowner/dpm/Password.kt @@ -0,0 +1,484 @@ +package com.binbin.androidowner.dpm + +import android.annotation.SuppressLint +import android.app.KeyguardManager +import android.app.admin.DevicePolicyManager +import android.app.admin.DevicePolicyManager.* +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.os.Build.VERSION +import android.widget.Toast +import androidx.activity.ComponentActivity +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.foundation.clickable +import androidx.compose.foundation.focusable +import androidx.compose.foundation.layout.* +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.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Check +import androidx.compose.material3.* +import androidx.compose.material3.MaterialTheme.colorScheme +import androidx.compose.material3.MaterialTheme.typography +import androidx.compose.runtime.* +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.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 com.binbin.androidowner.R +import com.binbin.androidowner.ui.CheckBoxItem +import com.binbin.androidowner.ui.RadioButtonItem + +@Composable +fun Password(){ + val myContext = LocalContext.current + val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager + val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) + val sharedPref = LocalContext.current.getSharedPreferences("data", Context.MODE_PRIVATE) + val isWear = sharedPref.getBoolean("isWear",false) + val bodyTextStyle = if(isWear){typography.bodyMedium}else{typography.bodyLarge} + val scrollState = rememberScrollState() + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier.fillMaxSize().verticalScroll(scrollState) + ) { + Text( + text = stringResource(R.string.password_warning), + color = colorScheme.onErrorContainer, + style=bodyTextStyle + ) + if(myDpm.isDeviceOwnerApp("com.binbin.androidowner")){ + Column{ + if(VERSION.SDK_INT>=29){ + val passwordComplexity = mapOf( + PASSWORD_COMPLEXITY_NONE to stringResource(R.string.password_complexity_none), + PASSWORD_COMPLEXITY_LOW to stringResource(R.string.password_complexity_low), + PASSWORD_COMPLEXITY_MEDIUM to stringResource(R.string.password_complexity_medium), + PASSWORD_COMPLEXITY_HIGH to stringResource(R.string.password_complexity_high) + ) + val pwdComplex = passwordComplexity[myDpm.passwordComplexity] + Text(text = stringResource(R.string.current_password_complexity_is, pwdComplex?:stringResource(R.string.unknown)),style=bodyTextStyle) + } + if(isDeviceOwner(myDpm)|| isProfileOwner(myDpm)){ + Text(stringResource(R.string.is_password_sufficient, myDpm.isActivePasswordSufficient),style=bodyTextStyle) + } + val pwdFailedAttempts = myDpm.currentFailedPasswordAttempts + Text(text = stringResource(R.string.password_failed_attempts_is, pwdFailedAttempts),style=bodyTextStyle) + if(VERSION.SDK_INT>=28&&isProfileOwner(myDpm)&&myDpm.isManagedProfile(myComponent)){ + val unifiedPwd = myDpm.isUsingUnifiedPassword(myComponent) + Text(stringResource(R.string.is_using_unified_password, unifiedPwd),style=bodyTextStyle) + } + } + } + if(VERSION.SDK_INT>=26){ + ResetPasswordToken() + } + + ResetPassword() + + PasswordItem(R.string.max_pwd_fail,R.string.max_pwd_fail_desc,R.string.max_pwd_fail_textfield, 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,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,true, + {myDpm.getPasswordHistoryLength(null).toString()},{ic -> myDpm.setPasswordHistoryLength(myComponent, ic.toInt()) }) + PasswordItem(R.string.max_time_to_lock,R.string.max_time_to_lock_desc,R.string.time_unit_ms,true, + {myDpm.getMaximumTimeToLock(myComponent).toString()},{ic -> myDpm.setMaximumTimeToLock(myComponent,ic.toLong())}) + + if(VERSION.SDK_INT>=31){ + PasswordComplexity() + } + + KeyguardDisabledFeatures() + + PasswordQuality() + + Spacer(Modifier.padding(vertical = 30.dp)) + } +} + +@Composable +private fun PasswordItem( + itemName:Int, + itemDesc:Int, + textFieldLabel:Int, + allowZero:Boolean, + getMethod:()->String, + setMethod:(ic:String)->Unit +){ + val myContext = LocalContext.current + val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager + val sharedPref = LocalContext.current.getSharedPreferences("data", Context.MODE_PRIVATE) + val isWear = sharedPref.getBoolean("isWear",false) + val focusMgr = LocalFocusManager.current + Column{ + var inputContent by remember{ mutableStateOf(if(isDeviceOwner(myDpm)){getMethod()}else{""}) } + var ableToApply by remember{ mutableStateOf(inputContent!=""&&((inputContent=="0"&&allowZero)||inputContent!="0")) } + Text(text = stringResource(itemName), style = typography.titleLarge,color = colorScheme.onPrimaryContainer) + Text(text= stringResource(itemDesc),modifier=Modifier.padding(vertical = 2.dp), style = if(!isWear){typography.bodyLarge}else{typography.bodyMedium}) + if(!isWear){Spacer(Modifier.padding(vertical = 2.dp))} + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween, + modifier = Modifier.fillMaxWidth() + ){ + OutlinedTextField( + value = inputContent, + label = { Text(stringResource(textFieldLabel))}, + onValueChange = { + inputContent = it + ableToApply = inputContent!=""&&((inputContent=="0"&&allowZero)||inputContent!="0") + }, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number, imeAction = ImeAction.Done), + keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}), + enabled = isDeviceOwner(myDpm), + modifier = Modifier.focusable().fillMaxWidth(), + trailingIcon = { + Icon( + imageVector = Icons.Outlined.Check, contentDescription = "OK", + modifier = Modifier + .clip(RoundedCornerShape(50)) + .clickable(onClick = {focusMgr.clearFocus() ; setMethod(inputContent)}, enabled = isDeviceOwner(myDpm)&&ableToApply) + .padding(2.dp) + ) + } + ) + } + } +} + +@SuppressLint("NewApi") +@Composable +fun ResetPasswordToken(){ + val myContext = LocalContext.current + val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager + val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) + val myByteArray by remember{ mutableStateOf(byteArrayOf(1,1,4,5,1,4,1,9,1,9,8,1,0,1,1,4,5,1,4,1,9,1,9,8,1,0,1,1,4,5,1,4,1,9,1,9,8,1,0)) } + Column(horizontalAlignment = Alignment.Start) { + Text(text = stringResource(R.string.reset_password_token), style = typography.titleLarge) + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween + ){ + Button( + onClick = { + if(myDpm.clearResetPasswordToken(myComponent)){ Toast.makeText(myContext, myContext.getString(R.string.success), Toast.LENGTH_SHORT).show() + }else{ Toast.makeText(myContext, myContext.getString(R.string.fail), Toast.LENGTH_SHORT).show() } + }, + modifier = Modifier.fillMaxWidth(0.32F), + enabled = isDeviceOwner(myDpm)||isProfileOwner(myDpm) + ) { + Text(stringResource(R.string.clear)) + } + Button( + onClick = { + try { + if(myDpm.setResetPasswordToken(myComponent, myByteArray)){ + Toast.makeText(myContext, myContext.getString(R.string.success), Toast.LENGTH_SHORT).show() + }else{ + Toast.makeText(myContext, myContext.getString(R.string.fail), Toast.LENGTH_SHORT).show() + } + }catch(e:SecurityException){ + Toast.makeText(myContext, myContext.getString(R.string.security_exception), Toast.LENGTH_SHORT).show() + } + }, + enabled = isDeviceOwner(myDpm)||isProfileOwner(myDpm), + modifier = Modifier.fillMaxWidth(0.47F) + ) { + Text(stringResource(R.string.set)) + } + Button( + onClick = { + if(!myDpm.isResetPasswordTokenActive(myComponent)){ + try{ activateToken(myContext) } + catch(e:NullPointerException){ Toast.makeText(myContext, myContext.getString(R.string.please_set_a_token), Toast.LENGTH_SHORT).show() } + }else{ Toast.makeText(myContext, "已经激活", Toast.LENGTH_SHORT).show() } + }, + enabled = isDeviceOwner(myDpm)||isProfileOwner(myDpm), + modifier = Modifier.fillMaxWidth(0.88F) + ) { + Text(stringResource(R.string.activate)) + } + } + Text(stringResource(R.string.activate_token_not_required_when_no_password)) + } +} + +@Composable +fun ResetPassword(){ + val myContext = LocalContext.current + val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager + val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) + val focusMgr = LocalFocusManager.current + var newPwd by remember{ mutableStateOf("") } + val myByteArray by remember{ mutableStateOf(byteArrayOf(1,1,4,5,1,4,1,9,1,9,8,1,0,1,1,4,5,1,4,1,9,1,9,8,1,0,1,1,4,5,1,4,1,9,1,9,8,1,0)) } + Column{ + var confirmed by remember{ mutableStateOf(false) } + Text(text = stringResource(R.string.reset_password),style = typography.titleLarge) + OutlinedTextField( + value = newPwd, + onValueChange = {newPwd=it}, + enabled = !confirmed&&(isDeviceOwner(myDpm)||isProfileOwner(myDpm)||myDpm.isAdminActive(myComponent)), + label = { Text(stringResource(R.string.password))}, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password, imeAction = ImeAction.Done), + keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}), + modifier = Modifier.focusable().padding(vertical = 5.dp).fillMaxWidth() + ) + Text(text = stringResource(R.string.reset_pwd_desc), modifier = Modifier.padding(vertical = 3.dp)) + var resetPwdFlag by remember{ mutableIntStateOf(0) } + if(VERSION.SDK_INT>=23){ + RadioButtonItem( + stringResource(R.string.do_not_ask_credentials_on_boot), + {resetPwdFlag==RESET_PASSWORD_DO_NOT_ASK_CREDENTIALS_ON_BOOT}, {resetPwdFlag=RESET_PASSWORD_DO_NOT_ASK_CREDENTIALS_ON_BOOT} + ) + } + RadioButtonItem(stringResource(R.string.reset_password_require_entry),{resetPwdFlag==RESET_PASSWORD_REQUIRE_ENTRY}, {resetPwdFlag=RESET_PASSWORD_REQUIRE_ENTRY}) + RadioButtonItem(stringResource(R.string.none),{resetPwdFlag==0},{resetPwdFlag=0}) + Button( + onClick = { + if(newPwd.length>=4||newPwd.isEmpty()){ confirmed=!confirmed + }else{ Toast.makeText(myContext, myContext.getString(R.string.require_4_digit_password), Toast.LENGTH_SHORT).show() } + }, + enabled = isDeviceOwner(myDpm) || isProfileOwner(myDpm) || myDpm.isAdminActive(myComponent), + modifier = Modifier.fillMaxWidth(), + colors = ButtonDefaults.buttonColors( + containerColor = if(confirmed){ colorScheme.primary }else{ colorScheme.error }, + contentColor = if(confirmed){ colorScheme.onPrimary }else{ colorScheme.onError } + ) + ) { + Text(text = stringResource(if(confirmed){R.string.cancel}else{R.string.confirm})) + } + if(VERSION.SDK_INT>=26){ + Button( + onClick = { + val resetSuccess = myDpm.resetPasswordWithToken(myComponent,newPwd,myByteArray,resetPwdFlag) + if(resetSuccess){ Toast.makeText(myContext, myContext.getString(R.string.success), Toast.LENGTH_SHORT).show();newPwd=""} + else{ Toast.makeText(myContext, myContext.getString(R.string.fail), Toast.LENGTH_SHORT).show() } + confirmed=false + }, + colors = ButtonDefaults.buttonColors(containerColor = colorScheme.error, contentColor = colorScheme.onError), + enabled = confirmed&&(isDeviceOwner(myDpm)||isProfileOwner(myDpm)), + modifier = Modifier.fillMaxWidth() + ) { + Text(stringResource(R.string.reset_password_with_token)) + } + } + Button( + onClick = { + val resetSuccess = myDpm.resetPassword(newPwd,resetPwdFlag) + if(resetSuccess){ Toast.makeText(myContext, myContext.getString(R.string.success), Toast.LENGTH_SHORT).show(); newPwd=""} + else{ Toast.makeText(myContext, myContext.getString(R.string.fail), Toast.LENGTH_SHORT).show() } + confirmed=false + }, + enabled = confirmed, + colors = ButtonDefaults.buttonColors(containerColor = colorScheme.error, contentColor = colorScheme.onError), + modifier = Modifier.fillMaxWidth() + ) { + Text(stringResource(R.string.reset_password_deprecated)) + } + } +} + +@SuppressLint("NewApi") +@Composable +fun PasswordComplexity(){ + val myContext = LocalContext.current + val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager + Column{ + val passwordComplexity = mapOf( + PASSWORD_COMPLEXITY_NONE to stringResource(R.string.password_complexity_none), + PASSWORD_COMPLEXITY_LOW to stringResource(R.string.password_complexity_low), + PASSWORD_COMPLEXITY_MEDIUM to stringResource(R.string.password_complexity_medium), + PASSWORD_COMPLEXITY_HIGH to stringResource(R.string.password_complexity_high) + ).toList() + var selectedItem by remember{ mutableIntStateOf(passwordComplexity[0].first) } + if(isDeviceOwner(myDpm) || isProfileOwner(myDpm)){ + selectedItem=myDpm.requiredPasswordComplexity + } + Text(text = stringResource(R.string.required_password_complexity), style = typography.titleLarge) + 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 = stringResource(R.string.password_ordered_desc), modifier = Modifier.padding(vertical = 3.dp)) + Row(modifier = Modifier.fillMaxWidth(),horizontalArrangement = Arrangement.SpaceBetween){ + Button( + onClick = { + myDpm.requiredPasswordComplexity = selectedItem + Toast.makeText(myContext, myContext.getString(R.string.success), Toast.LENGTH_SHORT).show() + }, + enabled = isDeviceOwner(myDpm)|| isProfileOwner(myDpm), + modifier = Modifier.fillMaxWidth(0.4F) + ) { + Text(text = stringResource(R.string.apply)) + } + Button( + onClick = {myContext.startActivity(Intent(ACTION_SET_NEW_PASSWORD))}, + modifier = Modifier.fillMaxWidth(0.95F) + ){ + Text(stringResource(R.string.require_set_new_password)) + } + } + } +} + +@Composable +fun KeyguardDisabledFeatures(){ + val myContext = LocalContext.current + val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager + val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) + Column{ + var state by remember{mutableIntStateOf(-1)} + var shortcuts by remember{mutableStateOf(false)} + var biometrics by remember{mutableStateOf(false)} + var iris by remember{mutableStateOf(false)} + var face by remember{mutableStateOf(false)} + var remote by remember{mutableStateOf(false)} + var fingerprint by remember{mutableStateOf(false)} + var agents by remember{mutableStateOf(false)} + var unredacted by remember{mutableStateOf(false)} + var notification by remember{mutableStateOf(false)} + var camera by remember{mutableStateOf(false)} + var widgets by remember{mutableStateOf(false)} + val calculateCustomFeature = { + var calculate = myDpm.getKeyguardDisabledFeatures(myComponent) + if(calculate==0){state=0} + else{ + if(calculate-KEYGUARD_DISABLE_SHORTCUTS_ALL>=0 && VERSION.SDK_INT>=34){shortcuts=true;calculate-= KEYGUARD_DISABLE_SHORTCUTS_ALL } + if(calculate-KEYGUARD_DISABLE_BIOMETRICS>=0&&VERSION.SDK_INT>=28){biometrics=true;calculate -= KEYGUARD_DISABLE_BIOMETRICS } + if(calculate-KEYGUARD_DISABLE_IRIS>=0&&VERSION.SDK_INT>=28){iris=true;calculate -= KEYGUARD_DISABLE_IRIS } + if(calculate-KEYGUARD_DISABLE_FACE>=0&&VERSION.SDK_INT>=28){face=true;calculate -= KEYGUARD_DISABLE_FACE } + if(calculate-KEYGUARD_DISABLE_REMOTE_INPUT>=0&&VERSION.SDK_INT>=24){remote=true;calculate -= KEYGUARD_DISABLE_REMOTE_INPUT } + if(calculate-KEYGUARD_DISABLE_FINGERPRINT>=0){fingerprint=true;calculate -= KEYGUARD_DISABLE_FINGERPRINT } + if(calculate-KEYGUARD_DISABLE_TRUST_AGENTS>=0){agents=true;calculate -= KEYGUARD_DISABLE_TRUST_AGENTS } + if(calculate-KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS>=0){unredacted=true;calculate -= KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS } + if(calculate-KEYGUARD_DISABLE_SECURE_NOTIFICATIONS>=0){notification=true;calculate -= KEYGUARD_DISABLE_SECURE_NOTIFICATIONS } + if(calculate-KEYGUARD_DISABLE_SECURE_CAMERA>=0){camera=true;calculate -= KEYGUARD_DISABLE_SECURE_CAMERA } + if(calculate-KEYGUARD_DISABLE_WIDGETS_ALL>=0){widgets=true;calculate -= KEYGUARD_DISABLE_WIDGETS_ALL } + } + } + if(state==-1){ + state = when(myDpm.getKeyguardDisabledFeatures(myComponent)){ + KEYGUARD_DISABLE_FEATURES_NONE->0 + KEYGUARD_DISABLE_FEATURES_ALL->1 + else->2 + } + calculateCustomFeature() + } + Text(text = stringResource(R.string.keyguard_disabled_features), style = typography.titleLarge) + RadioButtonItem(stringResource(R.string.enable_all),{state==0},{state=0}) + RadioButtonItem(stringResource(R.string.disable_all),{state==1},{state=1}) + RadioButtonItem(stringResource(R.string.custom),{state==2},{state=2}) + AnimatedVisibility(state==2) { + Column { + CheckBoxItem(stringResource(R.string.keyguard_disabled_features_widgets),{widgets},{widgets=!widgets}) + CheckBoxItem(stringResource(R.string.keyguard_disabled_features_camera),{camera},{camera=!camera}) + CheckBoxItem(stringResource(R.string.keyguard_disabled_features_notification),{notification},{notification=!notification}) + CheckBoxItem(stringResource(R.string.keyguard_disabled_features_unredacted_notification),{unredacted},{unredacted=!unredacted}) + CheckBoxItem(stringResource(R.string.keyguard_disabled_features_trust_agents),{agents},{agents=!agents}) + CheckBoxItem(stringResource(R.string.keyguard_disabled_features_fingerprint),{fingerprint},{fingerprint=!fingerprint}) + if(VERSION.SDK_INT>=24){ CheckBoxItem(stringResource(R.string.keyguard_disabled_features_remote_input),{remote}, {remote=!remote}) } + if(VERSION.SDK_INT>=28){ + CheckBoxItem(stringResource(R.string.keyguard_disabled_features_face),{face},{face=!face}) + CheckBoxItem(stringResource(R.string.keyguard_disabled_features_iris),{iris},{iris=!iris}) + CheckBoxItem(stringResource(R.string.keyguard_disabled_features_biometrics),{biometrics},{biometrics=!biometrics}) + } + if(VERSION.SDK_INT>=34){ CheckBoxItem(stringResource(R.string.keyguard_disabled_features_shortcuts),{shortcuts},{shortcuts=!shortcuts}) } + } + } + Button( + onClick = { + var result = 0 + if(state==0){ result = 0 } + else if(state==1){ result = KEYGUARD_DISABLE_FEATURES_ALL } + else{ + if(shortcuts&&VERSION.SDK_INT>=34){result+=KEYGUARD_DISABLE_SHORTCUTS_ALL} + if(biometrics&&VERSION.SDK_INT>=28){result+=KEYGUARD_DISABLE_BIOMETRICS} + if(iris&&VERSION.SDK_INT>=28){result+=KEYGUARD_DISABLE_IRIS} + if(face&&VERSION.SDK_INT>=28){result+=KEYGUARD_DISABLE_FACE} + if(remote&&VERSION.SDK_INT>=24){result+=KEYGUARD_DISABLE_REMOTE_INPUT} + if(fingerprint){result+=KEYGUARD_DISABLE_FINGERPRINT} + if(agents){result+=KEYGUARD_DISABLE_TRUST_AGENTS} + if(unredacted){result+=KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS} + if(notification){result+=KEYGUARD_DISABLE_SECURE_NOTIFICATIONS} + if(camera){result+=KEYGUARD_DISABLE_SECURE_CAMERA} + if(widgets){result+=KEYGUARD_DISABLE_WIDGETS_ALL} + } + myDpm.setKeyguardDisabledFeatures(myComponent,result) + Toast.makeText(myContext, myContext.getString(R.string.success), Toast.LENGTH_SHORT).show() + calculateCustomFeature() + }, + enabled = isDeviceOwner(myDpm)||isProfileOwner(myDpm), + modifier = Modifier.fillMaxWidth() + ) { + Text(text = stringResource(R.string.apply)) + } + } +} + +@Composable +fun PasswordQuality(){ + val myContext = LocalContext.current + val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager + val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) + Column{ + val passwordQuality = mapOf( + PASSWORD_QUALITY_UNSPECIFIED to stringResource(R.string.password_quality_unspecified), + PASSWORD_QUALITY_SOMETHING to stringResource(R.string.password_quality_something), + PASSWORD_QUALITY_ALPHABETIC to stringResource(R.string.password_quality_alphabetic), + PASSWORD_QUALITY_NUMERIC to stringResource(R.string.password_quality_numeric), + PASSWORD_QUALITY_ALPHANUMERIC to stringResource(R.string.password_quality_alphanumeric), + PASSWORD_QUALITY_BIOMETRIC_WEAK to stringResource(R.string.password_quality_biometrics_weak), + PASSWORD_QUALITY_NUMERIC_COMPLEX to stringResource(R.string.password_quality_numeric_complex), + PASSWORD_QUALITY_COMPLEX to stringResource(R.string.custom)+"(${stringResource(R.string.unsupported)})", + ).toList() + var selectedItem by remember{ mutableIntStateOf(passwordQuality[0].first) } + if(isDeviceOwner(myDpm) || isProfileOwner(myDpm)){ selectedItem=myDpm.getPasswordQuality(myComponent) } + Text(text = stringResource(R.string.required_password_quality), style = typography.titleLarge) + Text(text = stringResource(R.string.password_complexity_instead_password_quality)) + if(VERSION.SDK_INT>=31){ Text(text = stringResource(R.string.password_quality_deprecated_desc), color = 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 = stringResource(R.string.password_ordered_desc), modifier = Modifier.padding(vertical = 3.dp)) + Button( + onClick = { + myDpm.setPasswordQuality(myComponent,selectedItem) + Toast.makeText(myContext, myContext.getString(R.string.success), Toast.LENGTH_SHORT).show() + }, + enabled = isDeviceOwner(myDpm) || isProfileOwner(myDpm), + modifier = Modifier.fillMaxWidth() + ) { + Text(stringResource(R.string.apply)) + } + if(VERSION.SDK_INT<31){ + Button(onClick = {myContext.startActivity(Intent(ACTION_SET_NEW_PASSWORD))}){Text(stringResource(R.string.require_set_new_password))} + } + } +} + +fun activateToken(myContext: Context){ + val desc = myContext.getString(R.string.activate_reset_password_token_here) + val keyguardManager = myContext.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager + val confirmIntent = keyguardManager.createConfirmDeviceCredentialIntent(myContext.getString(R.string.app_name), desc) + if (confirmIntent != null) { + startActivity(myContext,confirmIntent, null) + } else { + Toast.makeText(myContext, myContext.getString(R.string.fail), Toast.LENGTH_SHORT).show() + } +} diff --git a/app/src/main/java/com/binbin/androidowner/dpm/Permissions.kt b/app/src/main/java/com/binbin/androidowner/dpm/Permissions.kt new file mode 100644 index 0000000..45c7603 --- /dev/null +++ b/app/src/main/java/com/binbin/androidowner/dpm/Permissions.kt @@ -0,0 +1,457 @@ +package com.binbin.androidowner.dpm + +import android.annotation.SuppressLint +import android.app.admin.DevicePolicyManager +import android.content.ActivityNotFoundException +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.os.Build.VERSION +import android.widget.Toast +import androidx.activity.ComponentActivity +import androidx.compose.foundation.* +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.foundation.text.selection.SelectionContainer +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.KeyboardArrowRight +import androidx.compose.material3.* +import androidx.compose.material3.MaterialTheme.colorScheme +import androidx.compose.material3.MaterialTheme.typography +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusDirection +import androidx.compose.ui.focus.FocusManager +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.unit.dp +import androidx.compose.ui.unit.sp +import androidx.core.content.ContextCompat.startActivity +import androidx.navigation.NavHostController +import com.binbin.androidowner.R + + +@Composable +fun DpmPermissions(navCtrl:NavHostController){ + val myContext = LocalContext.current + val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager + val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) + val focusManager = LocalFocusManager.current + val sharedPref = LocalContext.current.getSharedPreferences("data", Context.MODE_PRIVATE) + val isWear = sharedPref.getBoolean("isWear",false) + val titleColor = colorScheme.onPrimaryContainer + val bodyTextStyle = if(isWear){typography.bodyMedium}else{typography.bodyLarge} + val expandCommandBlock by remember{mutableStateOf("")} + Column( + modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Row( + modifier = Modifier.clickable {navCtrl.navigate("ShizukuActivate")}, + horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically + ){ + Text(text = "Shizuku", style = typography.titleLarge, color = titleColor, modifier = Modifier.padding(vertical = 2.dp)) + Icon(imageVector = Icons.Default.KeyboardArrowRight,contentDescription = null, tint = colorScheme.onPrimaryContainer) + } + DeviceAdmin() + ProfileOwner() + DeviceOwner() + if(VERSION.SDK_INT>=30){ + Column { + Text(text = stringResource(R.string.device_info), style = typography.titleLarge,color = titleColor) + if(VERSION.SDK_INT>=34&&(isDeviceOwner(myDpm)||(isProfileOwner(myDpm)&&myDpm.isOrganizationOwnedDeviceWithManagedProfile))){ + val financed = myDpm.isDeviceFinanced + Text(stringResource(R.string.is_device_financed, financed),style=bodyTextStyle) + } + if(VERSION.SDK_INT>=33){ + val dpmRole = myDpm.devicePolicyManagementRoleHolderPackage + Text(stringResource(R.string.dpmrh, if(dpmRole==null) { stringResource(R.string.none) } else { "" }),style=bodyTextStyle) + if(dpmRole!=null){ + SelectionContainer(modifier = Modifier.horizontalScroll(rememberScrollState())){ + Text(text = dpmRole, style = bodyTextStyle, color = colorScheme.onPrimaryContainer) + } + } + } + val encryptionStatus = mapOf( + DevicePolicyManager.ENCRYPTION_STATUS_INACTIVE to stringResource(R.string.es_inactive), + DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE to stringResource(R.string.es_active), + DevicePolicyManager.ENCRYPTION_STATUS_UNSUPPORTED to stringResource(R.string.es_unsupported), + DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE_DEFAULT_KEY to stringResource(R.string.es_active_default_key), + DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE_PER_USER to stringResource(R.string.es_active_per_user), + DevicePolicyManager.ENCRYPTION_STATUS_ACTIVATING to stringResource(R.string.unknown) + ) + Text("加密状态:${encryptionStatus[myDpm.storageEncryptionStatus]}",style=bodyTextStyle) + val adminList = myDpm.activeAdmins + if(adminList!=null){ + var adminListText = "" + Text(text = stringResource(R.string.activated_device_admin, adminList.size), style = bodyTextStyle) + var count = adminList.size + for(each in adminList){ + count -= 1 + adminListText += "$each" + if(count>0){adminListText += "\n"} + } + SelectionContainer(modifier = Modifier.fillMaxWidth().padding(vertical = 2.dp).horizontalScroll(rememberScrollState())){ + Text(text = adminListText, style = bodyTextStyle, color = titleColor) + } + } + } + } + + if(VERSION.SDK_INT>=31&&(isProfileOwner(myDpm)|| isDeviceOwner(myDpm))){ + SpecificID() + } + + if((VERSION.SDK_INT>=26&&isDeviceOwner(myDpm))||(VERSION.SDK_INT>=24&&isProfileOwner(myDpm))){ + OrgName() + } + + if(isDeviceOwner(myDpm) || isProfileOwner(myDpm)){ + NoManageAccount() + } + + if(VERSION.SDK_INT>=24&&isDeviceOwner(myDpm)){ + DeviceOwnerInfo(R.string.owner_lockscr_info,R.string.place_holder,R.string.owner_lockscr_info,focusManager,myContext, + {myDpm.deviceOwnerLockScreenInfo},{content -> myDpm.setDeviceOwnerLockScreenInfo(myComponent,content)}) + } + if(VERSION.SDK_INT>=24&&(isDeviceOwner(myDpm)||isProfileOwner(myDpm))){ + SupportMsg() + } + + if(VERSION.SDK_INT>=28&&(isDeviceOwner(myDpm)||isProfileOwner(myDpm))){ + TransformOwnership() + } + + Spacer(Modifier.padding(vertical = 30.dp)) + } +} + +@Composable +fun DeviceOwnerInfo( + name:Int, + desc:Int, + textfield:Int, + fm:FocusManager, + myContext:Context, + input:()->CharSequence?, + output:(content:String?)->Unit +){ + Column{ + val sharedPref = LocalContext.current.getSharedPreferences("data", Context.MODE_PRIVATE) + val isWear = sharedPref.getBoolean("isWear",false) + Text(text = stringResource(name), style = typography.titleLarge, softWrap = false, color = colorScheme.onPrimaryContainer) + if(desc!=R.string.place_holder){ + Text( + text = stringResource(desc),modifier = Modifier.padding(top = 6.dp), + style = if(!isWear){typography.bodyLarge}else{typography.bodyMedium}) + } + var inputContent by remember{ mutableStateOf(input()) } + OutlinedTextField( + value = if(inputContent!=null){ inputContent.toString() }else{""}, + label = {Text(stringResource(textfield))}, + onValueChange = { inputContent=it }, + modifier = Modifier.focusable().fillMaxWidth().padding(vertical = 4.dp) + ) + Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) { + Button( + onClick = { + output(inputContent.toString()) + inputContent= input() + fm.clearFocus() + Toast.makeText(myContext, myContext.getString(R.string.success), Toast.LENGTH_SHORT).show() + }, + modifier = if(isWear){Modifier.fillMaxWidth(0.49F)}else{Modifier.fillMaxWidth(0.6F)} + ) { + Text(text = stringResource(R.string.apply)) + } + Button( + onClick = { + output(null) + inputContent = input() + fm.clearFocus() + Toast.makeText(myContext, myContext.getString(R.string.success), Toast.LENGTH_SHORT).show() + }, + modifier = Modifier.fillMaxWidth(0.96F) + ) { + Text(text = stringResource(R.string.reset)) + } + } + } +} + +@Composable +fun DeviceAdmin(){ + val myContext = LocalContext.current + val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager + val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) + Column { + Row( + horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically + ) { + Column { + Text(text = "Device Admin") + Text( + text = stringResource( + if(myDpm.isAdminActive(myComponent)) { + R.string.activated + } else { + R.string.deactivated + } + ) + ) + } + if(myDpm.isAdminActive(myComponent)) { + if(!isDeviceOwner(myDpm)&&!isProfileOwner(myDpm)) { + Button(onClick = { + myDpm.removeActiveAdmin(myComponent) + }) { + Text(stringResource(R.string.deactivate)) + } + } + } else { + Button(onClick = {activateDeviceAdmin(myContext, myComponent)}) { + Text(stringResource(R.string.activate)) + } + } + } + if(!myDpm.isAdminActive(myComponent)) { + SelectionContainer { + Text(text = stringResource(R.string.activate_device_admin_command), color = colorScheme.onTertiaryContainer) + } + } + } +} + +@Composable +fun ProfileOwner(){ + val myContext = LocalContext.current + val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager + val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) + Column { + if(!isDeviceOwner(myDpm)){ + Row( + + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Column { + Text(text = "Profile Owner") + Text(stringResource(if(isProfileOwner(myDpm)){R.string.activated}else{R.string.deactivated})) + } + if(isProfileOwner(myDpm)&&VERSION.SDK_INT>=24&&!myDpm.isManagedProfile(myComponent)){ + Button(onClick = {myDpm.clearProfileOwner(myComponent)}) { + Text(stringResource(R.string.deactivate)) + } + } + } + } + if(!isDeviceOwner(myDpm)&&!isProfileOwner(myDpm)){ + SelectionContainer{ + Text(text = stringResource(R.string.activate_profile_owner_command), color = colorScheme.onTertiaryContainer) + } + } + } +} + +@Composable +fun DeviceOwner(){ + val myContext = LocalContext.current + val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager + Column { + if(!isProfileOwner(myDpm)){ + Row( + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Column { + Text(text = "Device Owner") + Text(stringResource(if(isDeviceOwner(myDpm)){R.string.activated}else{R.string.deactivated})) + } + if(isDeviceOwner(myDpm)){ + Button( + onClick = { + myDpm.clearDeviceOwnerApp(myContext.packageName) + } + ) { + Text(stringResource(R.string.deactivate)) + } + } + } + } + + if(!isDeviceOwner(myDpm)&&!isProfileOwner(myDpm)){ + SelectionContainer{ + Text(text = stringResource(R.string.activate_device_owner_command), color = colorScheme.onTertiaryContainer) + } + } + } +} + +@SuppressLint("NewApi") +@Composable +fun SpecificID(){ + val myContext = LocalContext.current + val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager + Column{ + val specificId = myDpm.enrollmentSpecificId + Text(text = stringResource(R.string.enrollment_specific_id), style = typography.titleLarge) + if(specificId!=""){ + SelectionContainer(modifier = Modifier.horizontalScroll(rememberScrollState())){ Text(specificId,softWrap = false) } + }else{ + Text(stringResource(R.string.require_set_org_id)) + } + } +} + +@SuppressLint("NewApi") +@Composable +fun OrgName(){ + val myContext = LocalContext.current + val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager + val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) + val focusMgr = LocalFocusManager.current + Column{ + var orgName by remember{mutableStateOf(try{myDpm.getOrganizationName(myComponent).toString()}catch(e:SecurityException){""})} + Text(text = stringResource(R.string.org_name), style = typography.titleLarge) + OutlinedTextField( + value = orgName, onValueChange = {orgName=it}, modifier = Modifier.focusable().fillMaxWidth().padding(vertical = 3.dp), + label = {Text(stringResource(R.string.org_name))}, + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), + keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}) + ) + Button( + onClick = { + focusMgr.clearFocus() + myDpm.setOrganizationName(myComponent,orgName) + Toast.makeText(myContext, myContext.getString(R.string.success), Toast.LENGTH_SHORT).show() + }, + modifier = Modifier.fillMaxWidth() + ){ + Text(stringResource(R.string.apply)) + } + } +} + +@SuppressLint("NewApi") +@Composable +fun SupportMsg(){ + val myContext = LocalContext.current + val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager + val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) + val focusMgr = LocalFocusManager.current + DeviceOwnerInfo(R.string.support_msg,R.string.support_msg_desc,R.string.message,focusMgr,myContext, + {myDpm.getShortSupportMessage(myComponent)},{content -> myDpm.setShortSupportMessage(myComponent,content)}) + DeviceOwnerInfo(R.string.long_support_msg,R.string.long_support_msg_desc,R.string.message,focusMgr,myContext, + {myDpm.getLongSupportMessage(myComponent)},{content -> myDpm.setLongSupportMessage(myComponent,content)}) +} + +@Composable +fun NoManageAccount(){ + val myContext = LocalContext.current + val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager + val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) + val focusMgr = LocalFocusManager.current + Column{ + Text(text = stringResource(R.string.account_types_management_disabled), style = typography.titleLarge) + Text(stringResource(R.string.developing)) + var noManageAccount = myDpm.accountTypesWithManagementDisabled + var accountlist by remember{ mutableStateOf("") } + val refreshList = { + accountlist = "" + if (noManageAccount != null) { + var count = noManageAccount!!.size + for(each in noManageAccount!!){ count -= 1; accountlist += each; if(count>0){accountlist += "\n"} } + } + } + var inited by remember{mutableStateOf(false)} + if(!inited){ refreshList(); inited=true } + Text(text = if(accountlist==""){stringResource(R.string.none)}else{accountlist}) + var inputText by remember{ mutableStateOf("") } + OutlinedTextField( + value = inputText, + onValueChange = {inputText=it}, + label = {Text(stringResource(R.string.account_types))}, + modifier = Modifier.focusable().fillMaxWidth().padding(bottom = 4.dp), + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), + keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}) + ) + Row(modifier = Modifier.fillMaxWidth(),horizontalArrangement = Arrangement.SpaceBetween){ + Button( + onClick={ + focusMgr.clearFocus() + myDpm.setAccountManagementDisabled(myComponent,inputText,true) + noManageAccount=myDpm.accountTypesWithManagementDisabled + refreshList() + }, + modifier = Modifier.fillMaxWidth(0.49f) + ){ + Text(stringResource(R.string.add)) + } + Button( + onClick={focusMgr.clearFocus() + myDpm.setAccountManagementDisabled(myComponent,inputText,false) + noManageAccount=myDpm.accountTypesWithManagementDisabled + refreshList() + }, + modifier = Modifier.fillMaxWidth(0.96F) + ){ + Text(stringResource(R.string.remove)) + } + } + } +} + +@SuppressLint("NewApi") +@Composable +fun TransformOwnership(){ + val myContext = LocalContext.current + val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager + val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) + val focusMgr = LocalFocusManager.current + Column{ + var pkg by remember{mutableStateOf("")} + var cls by remember{mutableStateOf("")} + Text(text = stringResource(R.string.transform_ownership), style = typography.titleLarge) + Text(text = stringResource(R.string.transform_ownership_desc)) + OutlinedTextField( + value = pkg, onValueChange = {pkg = it}, label = {Text(stringResource(R.string.target_package_name))}, + modifier = Modifier.focusable().fillMaxWidth().padding(vertical = 2.dp), + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next), + keyboardActions = KeyboardActions(onNext = {focusMgr.moveFocus(FocusDirection.Down)}) + ) + OutlinedTextField( + value = cls, onValueChange = {cls = it}, label = {Text(stringResource(R.string.target_class_name))}, + modifier = Modifier.focusable().fillMaxWidth().padding(vertical = 2.dp), + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), + keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}) + ) + Button( + onClick = { + try { + myDpm.transferOwnership(myComponent,ComponentName(pkg, cls),null) + Toast.makeText(myContext, myContext.getString(R.string.success), Toast.LENGTH_SHORT).show() + }catch(e:IllegalArgumentException){ + Toast.makeText(myContext, myContext.getString(R.string.fail), Toast.LENGTH_SHORT).show() + } + }, + modifier = Modifier.fillMaxWidth().padding(top = 2.dp) + ) { + Text(stringResource(R.string.transform)) + } + } +} + +fun activateDeviceAdmin(inputContext:Context,inputComponent:ComponentName){ + try { + val intent = Intent(DevicePolicyManager.ACTION_ADD_DEVICE_ADMIN) + intent.putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN, inputComponent) + intent.putExtra(DevicePolicyManager.EXTRA_ADD_EXPLANATION, inputContext.getString(R.string.activate_android_owner_here)) + startActivity(inputContext,intent,null) + }catch(e:ActivityNotFoundException){ + Toast.makeText(inputContext,inputContext.getString(R.string.unsupported),Toast.LENGTH_SHORT).show() + } +} diff --git a/app/src/main/java/com/binbin/androidowner/ShizukuActivate.kt b/app/src/main/java/com/binbin/androidowner/dpm/ShizukuActivate.kt similarity index 97% rename from app/src/main/java/com/binbin/androidowner/ShizukuActivate.kt rename to app/src/main/java/com/binbin/androidowner/dpm/ShizukuActivate.kt index bb14026..23e0d01 100644 --- a/app/src/main/java/com/binbin/androidowner/ShizukuActivate.kt +++ b/app/src/main/java/com/binbin/androidowner/dpm/ShizukuActivate.kt @@ -1,4 +1,4 @@ -package com.binbin.androidowner +package com.binbin.androidowner.dpm import android.app.admin.DevicePolicyManager import android.content.ComponentName @@ -35,6 +35,7 @@ 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 com.binbin.androidowner.R import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -58,7 +59,7 @@ fun ShizukuActivate(){ Column(modifier = Modifier.fillMaxSize().verticalScroll(scrollState), horizontalAlignment = Alignment.CenterHorizontally){ var outputText by remember{mutableStateOf("")} if(Binder.getCallingUid()/100000!=0){ - Row(modifier = sections(colorScheme.errorContainer), verticalAlignment = Alignment.CenterVertically){ + Row{ Icon(imageVector = Icons.Rounded.Warning, contentDescription = null, tint = colorScheme.onErrorContainer) Text(text = stringResource(R.string.not_primary_user_not_support_shizuku), style = bodyTextStyle, color = colorScheme.onErrorContainer) } @@ -86,7 +87,7 @@ fun ShizukuActivate(){ } if(!isDeviceOwner(myDpm)&&!isProfileOwner(myDpm)){ - Column(modifier = sections()){ + Column { Text(text = stringResource(R.string.activate), style = typography.titleLarge, color = colorScheme.onPrimaryContainer) if(!myDpm.isAdminActive(myComponent)){ @@ -131,7 +132,7 @@ fun ShizukuActivate(){ } if(VERSION.SDK_INT>=30&&!isDeviceOwner(myDpm)&&!myDpm.isProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE)&&!myDpm.isOrganizationOwnedDeviceWithManagedProfile){ - Column(modifier = sections()){ + Column { Text(text = stringResource(R.string.org_owned_work_profile), style = typography.titleLarge, color = colorScheme.onPrimaryContainer) Text(text = stringResource(R.string.input_userid_of_work_profile), style = bodyTextStyle) var inputUserID by remember{mutableStateOf("")} diff --git a/app/src/main/java/com/binbin/androidowner/dpm/SystemManage.kt b/app/src/main/java/com/binbin/androidowner/dpm/SystemManage.kt new file mode 100644 index 0000000..9843112 --- /dev/null +++ b/app/src/main/java/com/binbin/androidowner/dpm/SystemManage.kt @@ -0,0 +1,848 @@ +package com.binbin.androidowner.dpm + +import android.annotation.SuppressLint +import android.app.admin.DevicePolicyManager +import android.app.admin.DevicePolicyManager.* +import android.app.admin.SystemUpdateInfo +import android.app.admin.SystemUpdatePolicy +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.os.Binder +import android.os.Build.VERSION +import android.os.UserManager +import android.util.Log +import android.widget.Toast +import androidx.activity.ComponentActivity +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.foundation.focusable +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 +import androidx.compose.material3.MaterialTheme.typography +import androidx.compose.runtime.* +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.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import com.binbin.androidowner.R +import com.binbin.androidowner.sections +import com.binbin.androidowner.ui.CheckBoxItem +import com.binbin.androidowner.ui.RadioButtonItem +import kotlinx.coroutines.delay +import java.util.Date + +@Composable +fun SystemManage(){ + val myContext = LocalContext.current + val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager + val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) + Column(modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState())) { + if(isDeviceOwner(myDpm)||isProfileOwner(myDpm)){ + DeviceCtrlItem(R.string.disable_cam,R.string.place_holder, R.drawable.photo_camera_fill0, + {myDpm.getCameraDisabled(null)},{b -> myDpm.setCameraDisabled(myComponent,b)} + ) + } + if(isDeviceOwner(myDpm)||isProfileOwner(myDpm)){ + DeviceCtrlItem(R.string.disable_scrcap,R.string.aosp_scrrec_also_work,R.drawable.screenshot_fill0, + {myDpm.getScreenCaptureDisabled(null)},{b -> myDpm.setScreenCaptureDisabled(myComponent,b) } + ) + } + if(VERSION.SDK_INT>=34&&(isDeviceOwner(myDpm)|| (isProfileOwner(myDpm)&&myDpm.isAffiliatedUser))){ + DeviceCtrlItem(R.string.disable_status_bar,R.string.place_holder,R.drawable.notifications_fill0, + {myDpm.isStatusBarDisabled},{b -> myDpm.setStatusBarDisabled(myComponent,b) } + ) + } + if(isDeviceOwner(myDpm)||(VERSION.SDK_INT>=30&&isProfileOwner(myDpm)&&myDpm.isOrganizationOwnedDeviceWithManagedProfile)){ + if(VERSION.SDK_INT>=30){ + DeviceCtrlItem(R.string.auto_time,R.string.place_holder,R.drawable.schedule_fill0, + {myDpm.getAutoTimeEnabled(myComponent)},{b -> myDpm.setAutoTimeEnabled(myComponent,b) } + ) + DeviceCtrlItem(R.string.auto_timezone,R.string.place_holder,R.drawable.globe_fill0, + {myDpm.getAutoTimeZoneEnabled(myComponent)},{b -> myDpm.setAutoTimeZoneEnabled(myComponent,b) } + ) + }else{ + DeviceCtrlItem(R.string.auto_time,R.string.place_holder,R.drawable.schedule_fill0,{myDpm.autoTimeRequired},{b -> myDpm.setAutoTimeRequired(myComponent,b)}) + } + } + if(isDeviceOwner(myDpm)|| isProfileOwner(myDpm)){ + DeviceCtrlItem(R.string.master_mute,R.string.place_holder,R.drawable.volume_up_fill0, + {myDpm.isMasterVolumeMuted(myComponent)},{b -> myDpm.setMasterVolumeMuted(myComponent,b) } + ) + } + if(VERSION.SDK_INT>=26&&(isDeviceOwner(myDpm)|| isProfileOwner(myDpm))){ + DeviceCtrlItem(R.string.backup_service,R.string.place_holder,R.drawable.backup_fill0, + {myDpm.isBackupServiceEnabled(myComponent)},{b -> myDpm.setBackupServiceEnabled(myComponent,b) } + ) + } + if(VERSION.SDK_INT>=23&&(isDeviceOwner(myDpm)|| isProfileOwner(myDpm))){ + DeviceCtrlItem(R.string.disable_bt_contact_share,R.string.place_holder,R.drawable.account_circle_fill0, + {myDpm.getBluetoothContactSharingDisabled(myComponent)},{b -> myDpm.setBluetoothContactSharingDisabled(myComponent,b)} + ) + } + if(VERSION.SDK_INT>=30&&isDeviceOwner(myDpm)){ + DeviceCtrlItem(R.string.common_criteria_mode,R.string.common_criteria_mode_desc,R.drawable.security_fill0, + {myDpm.isCommonCriteriaModeEnabled(myComponent)},{b -> myDpm.setCommonCriteriaModeEnabled(myComponent,b)} + ) + } + if(VERSION.SDK_INT>=31&&(isDeviceOwner(myDpm)||(VERSION.SDK_INT>=30&&isProfileOwner(myDpm)&&myDpm.isOrganizationOwnedDeviceWithManagedProfile))){ + if(myDpm.canUsbDataSignalingBeDisabled()){ + DeviceCtrlItem(R.string.usb_signal,R.string.place_holder,R.drawable.usb_fill0, + {myDpm.isUsbDataSignalingEnabled},{b -> myDpm.isUsbDataSignalingEnabled = b } + ) + }else{ + Text(text = stringResource(R.string.turn_off_usb_not_support),modifier = Modifier.fillMaxWidth(), textAlign = TextAlign.Center) + } + } + + Keyguard() + + if(VERSION.SDK_INT>=24){ + Column{ + Button( + onClick = { + val result = myDpm.requestBugreport(myComponent) + Toast.makeText(myContext, if(result){"成功"}else{"失败"}, Toast.LENGTH_SHORT).show() + }, + modifier = Modifier.fillMaxWidth(), + enabled = isDeviceOwner(myDpm) + ) { + Text(stringResource(R.string.request_bug_report)) + } + Button( + onClick = {myDpm.reboot(myComponent)}, + enabled = isDeviceOwner(myDpm), + modifier = Modifier.fillMaxWidth() + ) { + Text(stringResource(R.string.reboot)) + } + } + } + + if(VERSION.SDK_INT>=28){ + EditTime() + } + + if(VERSION.SDK_INT>=23&&(isDeviceOwner(myDpm)||isProfileOwner(myDpm))){ + PermissionPolicy() + } + + if(VERSION.SDK_INT>=34&&isDeviceOwner(myDpm)){ + MTEPolicy() + } + + if(VERSION.SDK_INT>=31&&(isDeviceOwner(myDpm)||isProfileOwner(myDpm))){ + NearbyStreamingPolicy() + } + + if(VERSION.SDK_INT>=28&&isDeviceOwner(myDpm)){ + LockTaskFeatures() + } + + if(isDeviceOwner(myDpm)||isProfileOwner(myDpm)){ + CaCert() + } + + if(VERSION.SDK_INT>=26&&(isDeviceOwner(myDpm)||(VERSION.SDK_INT>=30&&isProfileOwner(myDpm)&&myDpm.isOrganizationOwnedDeviceWithManagedProfile))){ + SecurityLogs() + } + + if(isDeviceOwner(myDpm)){ + SysUpdatePolicy() + } + + WipeData() + + Spacer(Modifier.padding(vertical = 30.dp)) + } +} + +@Composable +fun DeviceCtrlItem( + itemName:Int, + itemDesc:Int, + leadIcon:Int, + getMethod:()->Boolean, + setMethod:(b:Boolean)->Unit +){ + var isEnabled by remember{ mutableStateOf(false) } + val sharedPref = LocalContext.current.getSharedPreferences("data", Context.MODE_PRIVATE) + val isWear = sharedPref.getBoolean("isWear",false) + Row( + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = if(isWear){Modifier.fillMaxWidth(0.65F)}else{Modifier.fillMaxWidth(0.8F)} + ){ + if(!isWear){ + Icon( + painter = painterResource(leadIcon), + contentDescription = null, + tint = colorScheme.onPrimaryContainer, + modifier = Modifier.padding(start = 5.dp, end = 9.dp) + )} + Column { + Text( + text = stringResource(itemName), + style = if(!isWear){typography.titleLarge}else{typography.titleMedium}, + color = colorScheme.onPrimaryContainer, + fontWeight = if(isWear){ FontWeight.SemiBold }else{ FontWeight.Medium } + ) + if(itemDesc!=R.string.place_holder){ Text(stringResource(itemDesc)) } + } + } + isEnabled = getMethod() + Switch( + checked = isEnabled, + onCheckedChange = { + setMethod(!isEnabled) + isEnabled=getMethod() + }, + modifier = Modifier.padding(end = 5.dp) + ) + } +} + +@Composable +fun Keyguard(){ + val myContext = LocalContext.current + val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager + val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) + Column{ + Text(text = stringResource(R.string.keyguard), style = typography.titleLarge,color = colorScheme.onPrimaryContainer) + if(VERSION.SDK_INT>=23){ + Text(text = stringResource(R.string.require_no_password_to_disable)) + Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) { + Button( + onClick = { + Toast.makeText(myContext, + myContext.getString(if(myDpm.setKeyguardDisabled(myComponent,true)){R.string.success}else{R.string.fail}), Toast.LENGTH_SHORT).show() + }, + enabled = isDeviceOwner(myDpm)|| (VERSION.SDK_INT>=28&&isProfileOwner(myDpm)&&myDpm.isAffiliatedUser), + modifier = Modifier.fillMaxWidth(0.49F) + ) { + Text(stringResource(R.string.disable)) + } + Button( + onClick = { + Toast.makeText(myContext, + myContext.getString(if(myDpm.setKeyguardDisabled(myComponent,false)){R.string.success}else{R.string.fail}), Toast.LENGTH_SHORT).show() + }, + enabled = isDeviceOwner(myDpm)|| (VERSION.SDK_INT>=28&&isProfileOwner(myDpm)&&myDpm.isAffiliatedUser), + modifier = Modifier.fillMaxWidth(0.96F) + ) { + Text(stringResource(R.string.enable)) + } + } + } + var flag by remember{mutableIntStateOf(0)} + Button( + onClick = {myDpm.lockNow()}, + enabled = myDpm.isAdminActive(myComponent), + modifier = Modifier.fillMaxWidth() + ) { + Text(stringResource(R.string.lock_now)) + } + if(VERSION.SDK_INT>=26){ CheckBoxItem(stringResource(R.string.require_enter_password_again),{flag==FLAG_EVICT_CREDENTIAL_ENCRYPTION_KEY},{flag = if(flag==0){1}else{0} }) } + } +} + +@SuppressLint("NewApi") +@Composable +fun EditTime(){ + val myContext = LocalContext.current + val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager + val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) + val focusMgr = LocalFocusManager.current + Column{ + Text(text = stringResource(R.string.edit_time), style = typography.titleLarge) + var inputTime by remember{mutableStateOf("")} + Text(text = stringResource(R.string.from_epoch_to_target_time)) + OutlinedTextField( + value = inputTime, + label = { Text(stringResource(R.string.time_unit_ms))}, + onValueChange = {inputTime = it}, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number, imeAction = ImeAction.Done), + keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}), + enabled = isDeviceOwner(myDpm)||(VERSION.SDK_INT>=30&&isProfileOwner(myDpm)&&myDpm.isOrganizationOwnedDeviceWithManagedProfile), + modifier = Modifier.focusable().fillMaxWidth().padding(vertical = 2.dp) + ) + Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween){ + Button( + onClick = {myDpm.setTime(myComponent,inputTime.toLong())}, + modifier = Modifier.fillMaxWidth(0.35F), + enabled = inputTime!=""&&(isDeviceOwner(myDpm)||(VERSION.SDK_INT>=30&&isProfileOwner(myDpm)&&myDpm.isOrganizationOwnedDeviceWithManagedProfile)) + ) { + Text("应用") + } + Button( + onClick = {inputTime = System.currentTimeMillis().toString()}, + modifier = Modifier.fillMaxWidth(0.98F) + ) { + Text(stringResource(R.string.get_current_time)) + } + } + } +} + +@SuppressLint("NewApi") +@Composable +fun PermissionPolicy(){ + val myContext = LocalContext.current + val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager + val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) + Column{ + var selectedPolicy by remember{mutableIntStateOf(myDpm.getPermissionPolicy(myComponent))} + Text(text = stringResource(R.string.permission_policy), style = typography.titleLarge) + RadioButtonItem(stringResource(R.string.default_stringres), {selectedPolicy==PERMISSION_POLICY_PROMPT}, {selectedPolicy= PERMISSION_POLICY_PROMPT}) + RadioButtonItem(stringResource(R.string.auto_grant), {selectedPolicy==PERMISSION_POLICY_AUTO_GRANT}, {selectedPolicy= PERMISSION_POLICY_AUTO_GRANT}) + RadioButtonItem(stringResource(R.string.auto_deny), {selectedPolicy==PERMISSION_POLICY_AUTO_DENY}, {selectedPolicy= PERMISSION_POLICY_AUTO_DENY}) + Button( + onClick = { + myDpm.setPermissionPolicy(myComponent,selectedPolicy) + Toast.makeText(myContext, myContext.getString(R.string.success), Toast.LENGTH_SHORT).show() + }, + modifier = Modifier.fillMaxWidth() + ) { + Text(stringResource(R.string.apply)) + } + } +} + +@SuppressLint("NewApi") +@Composable +fun MTEPolicy(){ + val myContext = LocalContext.current + val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager + Column{ + Text(text = stringResource(R.string.mte_policy), style = typography.titleLarge) + Text(stringResource(R.string.mte_policy_desc)) + var selectedMtePolicy by remember{mutableIntStateOf(myDpm.mtePolicy)} + RadioButtonItem(stringResource(R.string.decide_by_user), {selectedMtePolicy==MTE_NOT_CONTROLLED_BY_POLICY}, {selectedMtePolicy= MTE_NOT_CONTROLLED_BY_POLICY}) + RadioButtonItem(stringResource(R.string.enabled), {selectedMtePolicy==MTE_ENABLED}, {selectedMtePolicy=MTE_ENABLED}) + RadioButtonItem(stringResource(R.string.disabled), {selectedMtePolicy==MTE_DISABLED}, {selectedMtePolicy=MTE_DISABLED}) + Button( + onClick = { + try { + myDpm.mtePolicy = selectedMtePolicy + Toast.makeText(myContext, myContext.getString(R.string.success), Toast.LENGTH_SHORT).show() + }catch(e:java.lang.UnsupportedOperationException){ + Toast.makeText(myContext, myContext.getString(R.string.unsupported), Toast.LENGTH_SHORT).show() + } + selectedMtePolicy = myDpm.mtePolicy + }, + modifier = Modifier.fillMaxWidth() + ) { + Text(stringResource(R.string.apply)) + } + } +} + +@SuppressLint("NewApi") +@Composable +fun NearbyStreamingPolicy(){ + val myContext = LocalContext.current + val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager + Column{ + var appPolicy by remember{mutableIntStateOf(myDpm.nearbyAppStreamingPolicy)} + Text(text = stringResource(R.string.nearby_app_streaming), style = typography.titleLarge) + RadioButtonItem(stringResource(R.string.decide_by_user),{appPolicy == NEARBY_STREAMING_NOT_CONTROLLED_BY_POLICY},{appPolicy = NEARBY_STREAMING_NOT_CONTROLLED_BY_POLICY}) + RadioButtonItem(stringResource(R.string.enabled),{appPolicy == NEARBY_STREAMING_ENABLED},{appPolicy = NEARBY_STREAMING_ENABLED}) + RadioButtonItem(stringResource(R.string.disabled),{appPolicy == NEARBY_STREAMING_DISABLED},{appPolicy = NEARBY_STREAMING_DISABLED}) + RadioButtonItem(stringResource(R.string.enable_if_secure_enough),{appPolicy == NEARBY_STREAMING_SAME_MANAGED_ACCOUNT_ONLY},{appPolicy = NEARBY_STREAMING_SAME_MANAGED_ACCOUNT_ONLY}) + Button( + onClick = { + myDpm.nearbyAppStreamingPolicy = appPolicy + Toast.makeText(myContext, "成功", Toast.LENGTH_SHORT).show() + }, + modifier = Modifier.fillMaxWidth() + ) { + Text("应用") + } + Spacer(Modifier.padding(vertical = 3.dp)) + var notificationPolicy by remember{mutableIntStateOf(myDpm.nearbyNotificationStreamingPolicy)} + Text(text = stringResource(R.string.nearby_notifi_streaming), style = typography.titleLarge) + RadioButtonItem(stringResource(R.string.decide_by_user),{notificationPolicy == NEARBY_STREAMING_NOT_CONTROLLED_BY_POLICY},{notificationPolicy = NEARBY_STREAMING_NOT_CONTROLLED_BY_POLICY}) + RadioButtonItem(stringResource(R.string.enabled),{notificationPolicy == NEARBY_STREAMING_ENABLED},{notificationPolicy = NEARBY_STREAMING_ENABLED}) + RadioButtonItem(stringResource(R.string.disabled),{notificationPolicy == NEARBY_STREAMING_DISABLED},{notificationPolicy = NEARBY_STREAMING_DISABLED}) + RadioButtonItem(stringResource(R.string.enable_if_secure_enough),{notificationPolicy == NEARBY_STREAMING_SAME_MANAGED_ACCOUNT_ONLY},{notificationPolicy = NEARBY_STREAMING_SAME_MANAGED_ACCOUNT_ONLY}) + Button( + onClick = { + myDpm.nearbyNotificationStreamingPolicy = notificationPolicy + Toast.makeText(myContext, myContext.getString(R.string.success), Toast.LENGTH_SHORT).show() + }, + modifier = Modifier.fillMaxWidth() + ) { + Text(stringResource(R.string.apply)) + } + } +} + +@SuppressLint("NewApi") +@Composable +fun LockTaskFeatures(){ + val myContext = LocalContext.current + val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager + val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) + val focusMgr = LocalFocusManager.current + Column{ + val lockTaskPolicyList = mutableListOf( + LOCK_TASK_FEATURE_NONE, + LOCK_TASK_FEATURE_SYSTEM_INFO, + LOCK_TASK_FEATURE_NOTIFICATIONS, + LOCK_TASK_FEATURE_HOME, + LOCK_TASK_FEATURE_OVERVIEW, + LOCK_TASK_FEATURE_GLOBAL_ACTIONS, + LOCK_TASK_FEATURE_KEYGUARD + ) + var sysInfo by remember{mutableStateOf(false)} + var notifications by remember{mutableStateOf(false)} + var home by remember{mutableStateOf(false)} + var overview by remember{mutableStateOf(false)} + var globalAction by remember{mutableStateOf(false)} + var keyGuard by remember{mutableStateOf(false)} + var blockAct by remember{mutableStateOf(false)} + if(VERSION.SDK_INT>=30){lockTaskPolicyList.add(LOCK_TASK_FEATURE_BLOCK_ACTIVITY_START_IN_TASK)} + var inited by remember{mutableStateOf(false)} + var custom by remember{mutableStateOf(false)} + val refreshFeature = { + var calculate = myDpm.getLockTaskFeatures(myComponent) + if(calculate!=0){ + if(VERSION.SDK_INT>=30&&calculate-lockTaskPolicyList[7]>=0){blockAct=true;calculate-=lockTaskPolicyList[7]} + if(calculate-lockTaskPolicyList[6]>=0){keyGuard=true;calculate-=lockTaskPolicyList[6]} + if(calculate-lockTaskPolicyList[5]>=0){globalAction=true;calculate-=lockTaskPolicyList[5]} + if(calculate-lockTaskPolicyList[4]>=0){overview=true;calculate-=lockTaskPolicyList[4]} + if(calculate-lockTaskPolicyList[3]>=0){home=true;calculate-=lockTaskPolicyList[3]} + if(calculate-lockTaskPolicyList[2]>=0){notifications=true;calculate-=lockTaskPolicyList[2]} + if(calculate-lockTaskPolicyList[1]>=0){sysInfo=true;calculate-=lockTaskPolicyList[1]} + }else{ + custom = false + } + } + Text(text = stringResource(R.string.lock_task_feature), style = typography.titleLarge) + if(!inited){ refreshFeature();custom=myDpm.getLockTaskFeatures(myComponent)!=0;inited=true } + RadioButtonItem(stringResource(R.string.disable_all),{!custom},{custom=false}) + RadioButtonItem(stringResource(R.string.custom),{custom},{custom=true}) + AnimatedVisibility(custom) { + Column { + CheckBoxItem(stringResource(R.string.ltf_sys_info),{sysInfo},{sysInfo=!sysInfo}) + CheckBoxItem(stringResource(R.string.ltf_notifications),{notifications},{notifications=!notifications}) + CheckBoxItem(stringResource(R.string.ltf_home),{home},{home=!home}) + CheckBoxItem(stringResource(R.string.ltf_overview),{overview},{overview=!overview}) + CheckBoxItem(stringResource(R.string.ltf_global_actions),{globalAction},{globalAction=!globalAction}) + CheckBoxItem(stringResource(R.string.ltf_keyguard),{keyGuard},{keyGuard=!keyGuard}) + if(VERSION.SDK_INT>=30){ CheckBoxItem(stringResource(R.string.ltf_block_activity_start_in_task),{blockAct},{blockAct=!blockAct}) } + } + } + Button( + modifier = Modifier.fillMaxWidth(), + onClick = { + var result = lockTaskPolicyList[0] + if(custom){ + if(blockAct&&VERSION.SDK_INT>=30){result+=lockTaskPolicyList[7]} + if(keyGuard){result+=lockTaskPolicyList[6]} + if(globalAction){result+=lockTaskPolicyList[5]} + if(overview){result+=lockTaskPolicyList[4]} + if(home){result+=lockTaskPolicyList[3]} + if(notifications){result+=lockTaskPolicyList[2]} + if(sysInfo){result+=lockTaskPolicyList[1]} + } + myDpm.setLockTaskFeatures(myComponent,result) + refreshFeature() + Toast.makeText(myContext, myContext.getString(R.string.success), Toast.LENGTH_SHORT).show() + } + ) { + Text(stringResource(R.string.apply)) + } + Spacer(Modifier.padding(vertical = 4.dp)) + val whitelist = myDpm.getLockTaskPackages(myComponent).toMutableList() + var listText by remember{mutableStateOf("")} + var inputPkg by remember{mutableStateOf("")} + val refreshWhitelist = { + inputPkg="" + listText="" + var currentItem = whitelist.size + for(each in whitelist){ + currentItem-=1 + listText += each + if(currentItem>0){listText += "\n"} + } + } + refreshWhitelist() + Text(text = stringResource(R.string.whitelist_app), style = typography.titleLarge) + if(listText!=""){ + SelectionContainer { + Text(text = listText) + } + }else{ + Text(text = stringResource(R.string.none)) + } + OutlinedTextField( + value = inputPkg, + onValueChange = {inputPkg=it}, + label = {Text(stringResource(R.string.package_name))}, + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), + keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}), + modifier = Modifier.focusable().fillMaxWidth().padding(vertical = 3.dp) + ) + Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween){ + Button( + onClick = { + focusMgr.clearFocus() + whitelist.add(inputPkg) + myDpm.setLockTaskPackages(myComponent,whitelist.toTypedArray()) + Toast.makeText(myContext, myContext.getString(R.string.success), Toast.LENGTH_SHORT).show() + refreshWhitelist() + }, + modifier = Modifier.fillMaxWidth(0.49F) + ) { + Text(stringResource(R.string.add)) + } + Button( + onClick = { + focusMgr.clearFocus() + if(inputPkg in whitelist){ + whitelist.remove(inputPkg) + myDpm.setLockTaskPackages(myComponent,whitelist.toTypedArray()) + Toast.makeText(myContext, myContext.getString(R.string.success), Toast.LENGTH_SHORT).show() + }else{ + Toast.makeText(myContext, myContext.getString(R.string.not_exist), Toast.LENGTH_SHORT).show() + } + refreshWhitelist() + }, + modifier = Modifier.fillMaxWidth(0.96F) + ) { + Text(stringResource(R.string.remove)) + } + } + } +} + +@Composable +fun CaCert(){ + val myContext = LocalContext.current + val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager + val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) + var exist by remember{mutableStateOf(false)} + var isEmpty by remember{mutableStateOf(false)} + val refresh = { + isEmpty = caCert.isEmpty() + exist = if(!isEmpty){ myDpm.hasCaCertInstalled(myComponent, caCert) }else{ false } + } + LaunchedEffect(exist){ while(true){ delay(600); refresh() } } + Column{ + Text(text = stringResource(R.string.ca_cert), style = typography.titleLarge) + if(isEmpty){ Text(text = stringResource(R.string.please_select_ca_cert)) }else{ Text(text = stringResource(R.string.cacert_installed, exist)) } + Button( + onClick = { + val caCertIntent = Intent(Intent.ACTION_GET_CONTENT) + caCertIntent.setType("*/*") + caCertIntent.addCategory(Intent.CATEGORY_OPENABLE) + getCaCert.launch(caCertIntent) + }, + modifier = Modifier.fillMaxWidth() + ) { + Text(stringResource(R.string.select_ca_cert)) + } + AnimatedVisibility(!isEmpty) { + Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween){ + Button( + onClick = { + val result = myDpm.installCaCert(myComponent, caCert) + Toast.makeText(myContext, myContext.getString(if(result){R.string.success}else{R.string.fail}), Toast.LENGTH_SHORT).show() + refresh() + }, + modifier = Modifier.fillMaxWidth(0.49F) + ) { + Text(stringResource(R.string.install)) + } + Button( + onClick = { + if(exist){ + myDpm.uninstallCaCert(myComponent, caCert) + exist = myDpm.hasCaCertInstalled(myComponent, caCert) + Toast.makeText(myContext, myContext.getString(if(exist){R.string.fail}else{R.string.success}), Toast.LENGTH_SHORT).show() + }else{ Toast.makeText(myContext, myContext.getString(R.string.not_exist), Toast.LENGTH_SHORT).show() } + }, + modifier = Modifier.fillMaxWidth(0.96F) + ) { + Text(stringResource(R.string.uninstall)) + } + } + } + Button( + onClick = { + myDpm.uninstallAllUserCaCerts(myComponent) + Toast.makeText(myContext, myContext.getString(R.string.success), Toast.LENGTH_SHORT).show() + }, + modifier = Modifier.fillMaxWidth() + ){ + Text(stringResource(R.string.uninstall_all_user_ca_cert)) + } + } +} + +@SuppressLint("NewApi") +@Composable +fun SecurityLogs(){ + val myContext = LocalContext.current + val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager + val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) + Column{ + Text(text = stringResource(R.string.retrieve_security_logs), style = typography.titleLarge) + Text(text = stringResource(R.string.developing)) + Row(modifier=Modifier.fillMaxWidth().padding(horizontal=8.dp),horizontalArrangement=Arrangement.SpaceBetween,verticalAlignment=Alignment.CenterVertically){ + var checked by remember{mutableStateOf(myDpm.isSecurityLoggingEnabled(myComponent))} + Text(text = stringResource(R.string.enabled), style = typography.titleLarge) + Switch( + checked = checked, + onCheckedChange = {myDpm.setSecurityLoggingEnabled(myComponent,!checked);checked=myDpm.isSecurityLoggingEnabled(myComponent)} + ) + } + Button( + onClick = { + val log = myDpm.retrieveSecurityLogs(myComponent) + if(log!=null){ + for(i in log){ Log.d("SecureLog",i.toString()) } + Toast.makeText(myContext,myContext.getString(R.string.success),Toast.LENGTH_SHORT).show() + }else{ + Log.d("SecureLog",myContext.getString(R.string.none)) + Toast.makeText(myContext, myContext.getString(R.string.no_logs),Toast.LENGTH_SHORT).show() + } + }, + modifier = Modifier.fillMaxWidth() + ) { + Text(stringResource(R.string.security_logs)) + } + Button( + onClick = { + val log = myDpm.retrievePreRebootSecurityLogs(myComponent) + if(log!=null){ + for(i in log){ Log.d("SecureLog",i.toString()) } + Toast.makeText(myContext,myContext.getString(R.string.success),Toast.LENGTH_SHORT).show() + }else{ + Log.d("SecureLog",myContext.getString(R.string.none)) + Toast.makeText(myContext,myContext.getString(R.string.no_logs),Toast.LENGTH_SHORT).show() + } + }, + modifier = Modifier.fillMaxWidth() + ) { + Text(stringResource(R.string.pre_reboot_security_logs)) + } + } +} + +@Composable +fun WipeData(){ + val myContext = LocalContext.current + val userManager = myContext.getSystemService(Context.USER_SERVICE) as UserManager + val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager + val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) + val focusMgr = LocalFocusManager.current + Column{ + var flag by remember{ mutableIntStateOf(0) } + var confirmed by remember{ mutableStateOf(false) } + var externalStorage by remember{mutableStateOf(false)} + var protectionData by remember{mutableStateOf(false)} + var euicc by remember{mutableStateOf(false)} + var silent by remember{mutableStateOf(false)} + var reason by remember{mutableStateOf("")} + Text(text = stringResource(R.string.wipe_data),style = typography.titleLarge,modifier = Modifier.padding(6.dp),color = colorScheme.onErrorContainer) + CheckBoxItem(stringResource(R.string.wipe_external_storage),{externalStorage},{externalStorage=!externalStorage;confirmed=false}, colorScheme.onErrorContainer) + if(VERSION.SDK_INT>=22&&isDeviceOwner(myDpm)){ + CheckBoxItem(stringResource(R.string.wipe_reset_protection_data),{protectionData},{protectionData=!protectionData;confirmed=false}, colorScheme.onErrorContainer) + } + if(VERSION.SDK_INT>=28){ CheckBoxItem(stringResource(R.string.wipe_euicc),{euicc},{euicc=!euicc;confirmed=false}, colorScheme.onErrorContainer) } + if(VERSION.SDK_INT>=29){ CheckBoxItem(stringResource(R.string.wipe_silently),{silent},{silent=!silent;confirmed=false}, colorScheme.onErrorContainer) } + AnimatedVisibility(!silent&&VERSION.SDK_INT>=28) { + OutlinedTextField( + value = reason, onValueChange = {reason=it}, + label = {Text(stringResource(R.string.reason))}, + enabled = !confirmed, + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), + keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}), + modifier = Modifier.focusable().fillMaxWidth().padding(vertical = 3.dp) + ) + } + Button( + onClick = { + focusMgr.clearFocus() + flag = 0 + if(externalStorage){flag += WIPE_EXTERNAL_STORAGE} + if(protectionData&&VERSION.SDK_INT>=22){flag += WIPE_RESET_PROTECTION_DATA} + if(euicc&&VERSION.SDK_INT>=28){flag += WIPE_EUICC} + if(reason==""){silent = true} + if(silent&&VERSION.SDK_INT>=29){flag += WIPE_SILENTLY} + confirmed=!confirmed + }, + colors = ButtonDefaults.buttonColors( + containerColor = if(confirmed){ colorScheme.primary }else{ colorScheme.error }, + contentColor = if(confirmed){ colorScheme.onPrimary }else{ colorScheme.onError } + ), + enabled = myDpm.isAdminActive(myComponent), + modifier = Modifier.fillMaxWidth() + ) { + Text(text = stringResource(if(confirmed){ R.string.cancel }else{ R.string.confirm })) + } + Row(modifier = Modifier.fillMaxWidth(),horizontalArrangement = Arrangement.SpaceBetween) { + Button( + onClick = { + if(VERSION.SDK_INT>=28){myDpm.wipeData(flag,reason)} + else{myDpm.wipeData(flag)} + }, + colors = ButtonDefaults.buttonColors(containerColor = colorScheme.error, contentColor = colorScheme.onError), + enabled = confirmed&&(VERSION.SDK_INT<34||(VERSION.SDK_INT>=34&&!userManager.isSystemUser)), + modifier = Modifier.fillMaxWidth(if(VERSION.SDK_INT >= 34&&(isDeviceOwner(myDpm)||(isProfileOwner(myDpm)&&myDpm.isOrganizationOwnedDeviceWithManagedProfile))){0.49F}else{1F}) + ) { + Text("WipeData") + } + if (VERSION.SDK_INT >= 34&&(isDeviceOwner(myDpm)||(isProfileOwner(myDpm)&&myDpm.isOrganizationOwnedDeviceWithManagedProfile))) { + Button( + onClick = {myDpm.wipeDevice(flag)}, + colors = ButtonDefaults.buttonColors(containerColor = colorScheme.error, contentColor = colorScheme.onError), + enabled = confirmed, + modifier = Modifier.fillMaxWidth(0.96F) + ) { + Text("WipeDevice") + } + } + } + if(VERSION.SDK_INT>=24&&isProfileOwner(myDpm)&&myDpm.isManagedProfile(myComponent)){ + Text(text = stringResource(R.string.will_delete_work_profile)) + } + if(VERSION.SDK_INT>=34&&Binder.getCallingUid()/100000==0){ + Text(text = stringResource(R.string.api34_or_above_wipedata_cannot_in_system_user)) + } + } +} + +@Composable +fun SysUpdatePolicy(){ + val myContext = LocalContext.current + val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager + val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) + val focusMgr = LocalFocusManager.current + val sharedPref = myContext.getSharedPreferences("data", Context.MODE_PRIVATE) + val isWear = sharedPref.getBoolean("isWear",false) + val bodyTextStyle = if(isWear){ typography.bodyMedium}else{typography.bodyLarge} + Column { + if(VERSION.SDK_INT>=26&&isDeviceOwner(myDpm)){ + val sysUpdateInfo = myDpm.getPendingSystemUpdate(myComponent) + Column(modifier = sections()) { + if(sysUpdateInfo!=null){ + Text(text = "Update first available: ${Date(sysUpdateInfo.receivedTime)}", style = bodyTextStyle) + Text(text = "Hash code: ${sysUpdateInfo.hashCode()}", style = bodyTextStyle) + val securityStateDesc = when(sysUpdateInfo.securityPatchState){ + SystemUpdateInfo.SECURITY_PATCH_STATE_UNKNOWN-> stringResource(R.string.unknown) + SystemUpdateInfo.SECURITY_PATCH_STATE_TRUE->"true" + else->"false" + } + Text(text = stringResource(R.string.is_security_patch, securityStateDesc), style = bodyTextStyle) + }else{ + Text(text = stringResource(R.string.no_system_update), style = bodyTextStyle) + } + } + } + if(VERSION.SDK_INT>=23){ + Column(modifier = sections()) { + var selectedPolicy by remember{ mutableStateOf(myDpm.systemUpdatePolicy?.policyType) } + Text(text = stringResource(R.string.system_update_policy), style = typography.titleLarge, color = colorScheme.onPrimaryContainer) + RadioButtonItem(stringResource(R.string.system_update_policy_automatic),{selectedPolicy==SystemUpdatePolicy.TYPE_INSTALL_AUTOMATIC},{selectedPolicy= SystemUpdatePolicy.TYPE_INSTALL_AUTOMATIC}) + RadioButtonItem(stringResource(R.string.system_update_policy_install_windowed),{selectedPolicy==SystemUpdatePolicy.TYPE_INSTALL_WINDOWED},{selectedPolicy= SystemUpdatePolicy.TYPE_INSTALL_WINDOWED}) + RadioButtonItem(stringResource(R.string.system_update_policy_postpone),{selectedPolicy==SystemUpdatePolicy.TYPE_POSTPONE},{selectedPolicy= SystemUpdatePolicy.TYPE_POSTPONE}) + RadioButtonItem(stringResource(R.string.none),{selectedPolicy == null},{selectedPolicy=null}) + var windowedPolicyStart by remember{ mutableStateOf("") } + var windowedPolicyEnd by remember{ mutableStateOf("") } + if(selectedPolicy==2){ + Spacer(Modifier.padding(vertical = 3.dp)) + Row(modifier = Modifier.fillMaxWidth(),horizontalArrangement = Arrangement.Center) { + OutlinedTextField( + value = windowedPolicyStart, + label = { Text(stringResource(R.string.start_time))}, + onValueChange = {windowedPolicyStart=it}, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number, imeAction = ImeAction.Done), + keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}), + modifier = Modifier.focusable().fillMaxWidth(0.5F) + ) + Spacer(Modifier.padding(horizontal = 3.dp)) + OutlinedTextField( + value = windowedPolicyEnd, + onValueChange = {windowedPolicyEnd=it}, + label = {Text(stringResource(R.string.end_time))}, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number, imeAction = ImeAction.Done), + keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}), + modifier = Modifier.focusable().fillMaxWidth() + ) + } + Spacer(Modifier.padding(vertical = 3.dp)) + Text(text = stringResource(R.string.minutes_in_one_day), style = bodyTextStyle) + } + val policy = + when(selectedPolicy){ + SystemUpdatePolicy.TYPE_INSTALL_AUTOMATIC-> SystemUpdatePolicy.createAutomaticInstallPolicy() + SystemUpdatePolicy.TYPE_INSTALL_WINDOWED-> SystemUpdatePolicy.createWindowedInstallPolicy(windowedPolicyStart.toInt(),windowedPolicyEnd.toInt()) + SystemUpdatePolicy.TYPE_POSTPONE-> SystemUpdatePolicy.createPostponeInstallPolicy() + else->null + } + Button( + onClick = {myDpm.setSystemUpdatePolicy(myComponent,policy);Toast.makeText(myContext, "成功!", Toast.LENGTH_SHORT).show()}, + enabled = isDeviceOwner(myDpm), + modifier = Modifier.fillMaxWidth() + ) { + Text(stringResource(R.string.apply)) + } + }} + /*if(VERSION.SDK_INT>=29){ + Column(modifier = sections()){ + var resultUri by remember{mutableStateOf(otaUri)} + Text(text = "安装系统更新", style = typography.titleLarge) + Button( + onClick = { + val getUri = Intent(Intent.ACTION_GET_CONTENT) + getUri.setType("application/zip") + getUri.addCategory(Intent.CATEGORY_OPENABLE) + getOtaPackage.launch(getUri) + }, + modifier = Modifier.fillMaxWidth(), + enabled = isDeviceOwner(myDpm)||isProfileOwner(myDpm) + ) { + Text("选择OTA包") + } + Button( + onClick = {resultUri = otaUri}, + modifier = Modifier.fillMaxWidth(), + enabled = isDeviceOwner(myDpm)||isProfileOwner(myDpm) + ) { + Text("查看OTA包详情") + } + Text("URI: $resultUri") + if(installOta){ + Button( + onClick = { + val sysUpdateExecutor = Executors.newCachedThreadPool() + val sysUpdateCallback:InstallSystemUpdateCallback = InstallSystemUpdateCallback + myDpm.installSystemUpdate(myComponent,resultUri,sysUpdateExecutor,sysUpdateCallback) + }, + enabled = isDeviceOwner(myDpm)||isProfileOwner(myDpm) + ){ + Text("安装") + } + } + } + }*/ + } +} diff --git a/app/src/main/java/com/binbin/androidowner/dpm/UserManage.kt b/app/src/main/java/com/binbin/androidowner/dpm/UserManage.kt new file mode 100644 index 0000000..8c31110 --- /dev/null +++ b/app/src/main/java/com/binbin/androidowner/dpm/UserManage.kt @@ -0,0 +1,419 @@ +package com.binbin.androidowner.dpm + +import android.annotation.SuppressLint +import android.app.admin.DevicePolicyManager +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.graphics.BitmapFactory +import android.os.Binder +import android.os.Build.VERSION +import android.os.UserHandle +import android.os.UserManager +import android.provider.MediaStore +import android.widget.Toast +import androidx.activity.ComponentActivity +import androidx.compose.foundation.focusable +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 +import androidx.compose.material3.MaterialTheme.typography +import androidx.compose.material3.Text +import androidx.compose.material3.TextField +import androidx.compose.runtime.* +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalFocusManager +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.unit.dp +import androidx.core.os.UserManagerCompat +import com.binbin.androidowner.ui.CheckBoxItem +import com.binbin.androidowner.ui.RadioButtonItem +import com.binbin.androidowner.uriToStream + +var affiliationID = mutableSetOf() +@Composable +fun UserManage() { + Column(modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState())) { + val myContext = LocalContext.current + val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager + val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) + val userManager = myContext.getSystemService(Context.USER_SERVICE) as UserManager + val titleColor = colorScheme.onPrimaryContainer + Column{ + Text(text = "用户信息", style = typography.titleLarge, color = titleColor) + Text("用户已解锁:${UserManagerCompat.isUserUnlocked(myContext)}") + if(VERSION.SDK_INT>=24){ Text("支持多用户:${UserManager.supportsMultipleUsers()}") } + if(VERSION.SDK_INT>=23){ Text(text = "系统用户:${userManager.isSystemUser}") } + if(VERSION.SDK_INT>=34){ Text(text = "管理员用户:${userManager.isAdminUser}") } + if(VERSION.SDK_INT>=31){ Text(text = "无头系统用户: ${UserManager.isHeadlessSystemUserMode()}") } + Spacer(Modifier.padding(vertical = 5.dp)) + if (VERSION.SDK_INT >= 28) { + val logoutable = myDpm.isLogoutEnabled + Text(text = "用户可以退出 : $logoutable") + if(isDeviceOwner(myDpm)|| isProfileOwner(myDpm)){ + val ephemeralUser = myDpm.isEphemeralUser(myComponent) + Text(text = "临时用户: $ephemeralUser") + } + Text(text = "附属用户: ${myDpm.isAffiliatedUser}") + } + Spacer(Modifier.padding(vertical = 5.dp)) + Text(text = "当前UserID:${Binder.getCallingUid()/100000}") + Text(text = "当前用户序列号:${userManager.getSerialNumberForUser(android.os.Process.myUserHandle())}") + } + + UserOperation() + + if(VERSION.SDK_INT>=24&&isDeviceOwner(myDpm)){ + CreateUser() + } + + if(VERSION.SDK_INT>=26&&(isDeviceOwner(myDpm)||isProfileOwner(myDpm))){ + AffiliationID() + } + + UserSessionMessage("用户名", "用户名", true, {null}) {msg-> myDpm.setProfileName(myComponent, msg.toString())} + + if(VERSION.SDK_INT>=23&&(isDeviceOwner(myDpm)||isProfileOwner(myDpm))){ + UserIcon() + } + + if(VERSION.SDK_INT>=28){ + UserSessionMessage("用户会话开始消息", "消息", false, {myDpm.getStartUserSessionMessage(myComponent)}) {msg-> myDpm.setStartUserSessionMessage(myComponent, msg)} + UserSessionMessage("用户会话结束消息", "消息", false, {myDpm.getEndUserSessionMessage(myComponent)}) {msg-> myDpm.setEndUserSessionMessage(myComponent, msg)} + } + Spacer(Modifier.padding(vertical = 30.dp)) + } +} + +@Composable +private fun UserSessionMessage(text:String, textField:String, profileOwner:Boolean, get: ()->CharSequence?, setMsg:(msg: CharSequence?)->Unit){ + Column{ + val myContext = LocalContext.current + val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager + val focusMgr = LocalFocusManager.current + var msg by remember{ mutableStateOf(if(isDeviceOwner(myDpm)||(isProfileOwner(myDpm)&&profileOwner)){ if(get()==null){""}else{get().toString()} }else{""}) } + val sharedPref = myContext.getSharedPreferences("data", Context.MODE_PRIVATE) + val isWear = sharedPref.getBoolean("isWear",false) + Text(text = text, style = typography.titleLarge, color = colorScheme.onPrimaryContainer) + TextField( + value = msg, + onValueChange = {msg=it}, + label = {Text(textField)}, + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), + keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}), + modifier = Modifier.focusable().fillMaxWidth().padding(vertical = 6.dp), + enabled = isDeviceOwner(myDpm)||(isProfileOwner(myDpm)&&profileOwner) + ) + Row(modifier = Modifier.fillMaxWidth(),horizontalArrangement = Arrangement.SpaceBetween) { + Button( + onClick = { + focusMgr.clearFocus() + setMsg(msg) + msg = if(get()==null){""}else{get().toString()} + Toast.makeText(myContext, "成功", Toast.LENGTH_SHORT).show() + }, + enabled = isDeviceOwner(myDpm)||(isProfileOwner(myDpm)&&profileOwner), + modifier = Modifier.fillMaxWidth(if(isWear){0.49F}else{0.65F}) + ) { + Text("应用") + } + Button( + onClick = { + focusMgr.clearFocus() + setMsg(null) + msg = get()?.toString() ?: "" + Toast.makeText(myContext, "成功", Toast.LENGTH_SHORT).show() + }, + enabled = isDeviceOwner(myDpm)||(isProfileOwner(myDpm)&&profileOwner), + modifier = Modifier.fillMaxWidth(0.96F) + ) { + Text("默认") + } + } + } +} + +@Composable +fun UserOperation(){ + val myContext = LocalContext.current + val userManager = myContext.getSystemService(Context.USER_SERVICE) as UserManager + val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager + val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) + val focusMgr = LocalFocusManager.current + Column{ + Text(text = "用户操作", style = typography.titleLarge) + var idInput by remember{ mutableStateOf("") } + var userHandleById:UserHandle by remember{ mutableStateOf(android.os.Process.myUserHandle()) } + var useUid by remember{ mutableStateOf(false) } + TextField( + value = idInput, + onValueChange = { + idInput=it + if(useUid){ + if(idInput!=""&&VERSION.SDK_INT>=24){ + userHandleById = UserHandle.getUserHandleForUid(idInput.toInt()) + } + }else{ + val userHandleBySerial = userManager.getUserForSerialNumber(idInput.toLong()) + userHandleById = userHandleBySerial ?: android.os.Process.myUserHandle() + } + }, + label = {Text(if(useUid){"UID"}else{"序列号"})}, + enabled = isDeviceOwner(myDpm), + modifier = Modifier.focusable().fillMaxWidth().padding(vertical = 3.dp), + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number, imeAction = ImeAction.Done), + keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}) + ) + 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){ + Button( + onClick = { + val result = myDpm.logoutUser(myComponent) + Toast.makeText(myContext, userOperationResultCode(result), Toast.LENGTH_SHORT).show() + }, + enabled = isProfileOwner(myDpm), + modifier = Modifier.fillMaxWidth() + ) { + Text("登出当前用户") + } + } + } + Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween){ + Button( + onClick = { + focusMgr.clearFocus() + if(VERSION.SDK_INT>=28){ + val result = myDpm.startUserInBackground(myComponent,userHandleById) + Toast.makeText(myContext, userOperationResultCode(result), Toast.LENGTH_SHORT).show() + } + }, + enabled = isDeviceOwner(myDpm)&&VERSION.SDK_INT>=28, + modifier = Modifier.fillMaxWidth(0.49F) + ){ + Text("在后台启动") + } + Button( + onClick = { + focusMgr.clearFocus() + if(myDpm.switchUser(myComponent,userHandleById)){ + Toast.makeText(myContext, "成功", Toast.LENGTH_SHORT).show() + }else{ + Toast.makeText(myContext, "失败", Toast.LENGTH_SHORT).show() + } + }, + enabled = isDeviceOwner(myDpm), + modifier = Modifier.fillMaxWidth(0.96F) + ) { + Text("切换") + } + } + Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween){ + Button( + onClick = { + focusMgr.clearFocus() + try{ + if(VERSION.SDK_INT>=28){ + val result = myDpm.stopUser(myComponent,userHandleById) + Toast.makeText(myContext, userOperationResultCode(result), Toast.LENGTH_SHORT).show() + } + }catch(e:IllegalArgumentException){ + Toast.makeText(myContext, "失败", Toast.LENGTH_SHORT).show() + } + }, + enabled = isDeviceOwner(myDpm)&&VERSION.SDK_INT>=28, + modifier = Modifier.fillMaxWidth(0.49F) + ) { + Text("停止") + } + Button( + onClick = { + focusMgr.clearFocus() + if(myDpm.removeUser(myComponent,userHandleById)){ + Toast.makeText(myContext, "成功", Toast.LENGTH_SHORT).show() + idInput="" + }else{ + Toast.makeText(myContext, "失败", Toast.LENGTH_SHORT).show() + } + }, + enabled = isDeviceOwner(myDpm), + modifier = Modifier.fillMaxWidth(0.96F) + ) { + Text("移除") + } + } + if(VERSION.SDK_INT<28){ + Text(text = "停止用户需API28") + } + } +} + +@SuppressLint("NewApi") +@Composable +fun CreateUser(){ + val myContext = LocalContext.current + val userManager = myContext.getSystemService(Context.USER_SERVICE) as UserManager + val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager + val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) + val focusMgr = LocalFocusManager.current + Column{ + var userName by remember{ mutableStateOf("") } + Text(text = "创建用户", style = typography.titleLarge) + TextField( + value = userName, + onValueChange = {userName=it}, + label = {Text("用户名")}, + modifier = Modifier.focusable().fillMaxWidth().padding(vertical = 4.dp), + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), + keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}) + ) + var selectedFlag by remember{ mutableIntStateOf(0) } + RadioButtonItem("无",{selectedFlag==0},{selectedFlag=0}) + RadioButtonItem("跳过创建用户向导",{selectedFlag==DevicePolicyManager.SKIP_SETUP_WIZARD},{selectedFlag=DevicePolicyManager.SKIP_SETUP_WIZARD}) + if(VERSION.SDK_INT>=28){ + RadioButtonItem("临时用户",{selectedFlag==DevicePolicyManager.MAKE_USER_EPHEMERAL},{selectedFlag=DevicePolicyManager.MAKE_USER_EPHEMERAL}) + RadioButtonItem("启用所有系统应用",{selectedFlag==DevicePolicyManager.LEAVE_ALL_SYSTEM_APPS_ENABLED},{selectedFlag=DevicePolicyManager.LEAVE_ALL_SYSTEM_APPS_ENABLED}) + } + var newUserHandle: UserHandle? by remember{ mutableStateOf(null) } + Button( + onClick = { + newUserHandle=myDpm.createAndManageUser(myComponent,userName,myComponent,null,selectedFlag) + focusMgr.clearFocus() + Toast.makeText(myContext, if(newUserHandle!=null){"成功"}else{"失败"}, Toast.LENGTH_SHORT).show() + }, + enabled = isDeviceOwner(myDpm), + modifier = Modifier.fillMaxWidth() + ) { + Text("创建(Owner)") + } + if(newUserHandle!=null){ Text(text = "新用户的序列号:${userManager.getSerialNumberForUser(newUserHandle)}") } + } +} + +@SuppressLint("NewApi") +@Composable +fun AffiliationID(){ + val myContext = LocalContext.current + val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager + val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) + val focusMgr = LocalFocusManager.current + Column{ + var input by remember{mutableStateOf("")} + var list by remember{mutableStateOf("")} + val refresh = { + list = "" + var count = affiliationID.size + for(item in affiliationID){ count-=1; list+=item; if(count>0){list+="\n"} } + } + var inited by remember{mutableStateOf(false)} + if(!inited){affiliationID = myDpm.getAffiliationIds(myComponent);refresh();inited=true} + Text(text = "附属用户ID", style = typography.titleLarge) + TextField( + value = input, + onValueChange = {input = it}, + label = {Text("ID")}, + modifier = Modifier.focusable().fillMaxWidth().padding(vertical = 2.dp), + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), + keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}) + ) + if(list!=""){ + SelectionContainer { + Text(text = list) + } + }else{ + Text(text = "无") + } + Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween){ + Button( + onClick = { affiliationID.add(input); refresh() }, + modifier = Modifier.fillMaxWidth(0.49F) + ){ + Text("添加") + } + Button( + onClick = { affiliationID.remove(input); refresh() }, + modifier = Modifier.fillMaxWidth(0.96F) + ){ + Text("移除") + } + } + Button( + onClick = { + if("" in affiliationID) { + Toast.makeText(myContext, "有空字符串", Toast.LENGTH_SHORT).show() + }else if(affiliationID.isEmpty()){ + Toast.makeText(myContext, "不能为空", Toast.LENGTH_SHORT).show() + }else{ + myDpm.setAffiliationIds(myComponent, affiliationID) + affiliationID = myDpm.getAffiliationIds(myComponent) + refresh() + Toast.makeText(myContext,"成功",Toast.LENGTH_SHORT).show() + } + }, + modifier = Modifier.fillMaxWidth() + ) { + Text("应用") + } + Text(text = "如果多用户,附属用户ID相同时可以让其他用户附属于主用户") + } +} + +@SuppressLint("NewApi") +@Composable +fun UserIcon(){ + val myContext = LocalContext.current + val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager + val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) + Column{ + var getContent by remember{mutableStateOf(false)} + Text(text = "用户图标", style = typography.titleLarge) + Text(text = "尽量选择正方形的图片,以免产生问题") + CheckBoxItem("使用文件选择器而不是相册",{getContent},{getContent=!getContent}) + Button( + onClick = { + val intent = Intent(if(getContent){Intent.ACTION_GET_CONTENT}else{Intent.ACTION_PICK}) + if(getContent){intent.addCategory(Intent.CATEGORY_OPENABLE)} + intent.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/*") + getUserIcon.launch(intent) + }, + modifier = Modifier.fillMaxWidth() + ) { + Text("选择图片...") + } + Button( + onClick = { + if(userIconUri!=null){ + uriToStream(myContext, userIconUri){stream -> + val bitmap = BitmapFactory.decodeStream(stream) + myDpm.setUserIcon(myComponent,bitmap) + Toast.makeText(myContext, "成功", Toast.LENGTH_SHORT).show() + } + }else{ + Toast.makeText(myContext, "请先选择图片", Toast.LENGTH_SHORT).show() + } + }, + modifier = Modifier.fillMaxWidth() + ) { + Text("应用") + } + } +} + +private fun userOperationResultCode(result:Int): String { + return when(result){ + UserManager.USER_OPERATION_SUCCESS->"成功" + UserManager.USER_OPERATION_ERROR_UNKNOWN->"未知结果(失败)" + UserManager.USER_OPERATION_ERROR_MANAGED_PROFILE->"失败:受管理的资料" + UserManager.USER_OPERATION_ERROR_CURRENT_USER->"失败:当前用户" + else->"未知" + } +} diff --git a/app/src/main/java/com/binbin/androidowner/UserRestrict.kt b/app/src/main/java/com/binbin/androidowner/dpm/UserRestriction.kt similarity index 91% rename from app/src/main/java/com/binbin/androidowner/UserRestrict.kt rename to app/src/main/java/com/binbin/androidowner/dpm/UserRestriction.kt index 109e367..e6ae491 100644 --- a/app/src/main/java/com/binbin/androidowner/UserRestrict.kt +++ b/app/src/main/java/com/binbin/androidowner/dpm/UserRestriction.kt @@ -1,4 +1,4 @@ -package com.binbin.androidowner +package com.binbin.androidowner.dpm import android.app.admin.DevicePolicyManager import android.content.ComponentName @@ -28,9 +28,9 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp +import com.binbin.androidowner.R private data class Restriction( val restriction:String, @@ -50,22 +50,16 @@ fun UserRestriction(){ var mediaVisible by remember{ mutableStateOf(false) } var userVisible by remember{ mutableStateOf(false) } var otherVisible by remember{ mutableStateOf(false) } - val sharedPref = LocalContext.current.getSharedPreferences("data", Context.MODE_PRIVATE) - val isWear = sharedPref.getBoolean("isWear",false) - val bodyTextStyle = if(isWear){typography.bodyMedium}else{typography.bodyLarge} Column(modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()), horizontalAlignment = Alignment.CenterHorizontally){ - Text(text = "打开开关后会禁用对应的功能",style = bodyTextStyle) + Text(text = "打开开关后会禁用对应的功能") if(VERSION.SDK_INT<24){ - Text(text = "所有的用户限制都需要API24,你的设备低于API24,无法使用。", style = bodyTextStyle, color = colorScheme.error) + Text(text = "所有的用户限制都需要API24,你的设备低于API24,无法使用。", color = colorScheme.error) } if(isProfileOwner(myDpm)){ - Text(text = "Profile owner无法使用部分功能", style = bodyTextStyle) + Text(text = "Profile owner无法使用部分功能") } if(isProfileOwner(myDpm)&&(VERSION.SDK_INT<24||(VERSION.SDK_INT>=24&&myDpm.isManagedProfile(myComponent)))){ - Text(text = "工作资料中部分功能无效", style = bodyTextStyle) - } - if(isWear){ - Text(text = "部分功能在手表上无效", style = typography.bodyMedium) + Text(text = "工作资料中部分功能无效") } SectionTab("网络和互联网",{internetVisible}, { internetVisible=!internetVisible}) AnimatedVisibility(internetVisible) { @@ -148,33 +142,28 @@ private fun UserRestrictionItem( val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) var strictState by remember{ mutableStateOf(false) } - val sharedPref = LocalContext.current.getSharedPreferences("data", Context.MODE_PRIVATE) - val isWear = sharedPref.getBoolean("isWear",false) Row( - modifier = sections(colorScheme.secondaryContainer), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.SpaceBetween ){ Row( verticalAlignment = Alignment.CenterVertically, - modifier = if(isWear){Modifier.fillMaxWidth(0.65F)}else{Modifier.fillMaxWidth(0.8F)} + modifier = Modifier.fillMaxWidth(0.8F) ) { - if(!isWear){ Icon( painter = painterResource(leadIcon), contentDescription = null, modifier = Modifier.padding(start = 4.dp, end = 8.dp), tint = colorScheme.secondary - )} + ) Column{ Text( text = stringResource(itemName), - style = if(!isWear){typography.titleLarge}else{typography.titleMedium}, - color = colorScheme.onSecondaryContainer, - fontWeight = if(isWear){ FontWeight.SemiBold }else{ FontWeight.Medium } + style = typography.titleLarge, + color = colorScheme.onSecondaryContainer ) if(restrictionDescription!=""){ - Text(text = restrictionDescription, color = colorScheme.onSecondaryContainer, style = if(isWear){typography.bodyMedium}else{typography.bodyLarge}) + Text(text = restrictionDescription, color = colorScheme.onSecondaryContainer, style = typography.titleLarge) } } } @@ -200,7 +189,7 @@ private fun UserRestrictionItem( strictState = myDpm.getUserRestrictions(myComponent).getBoolean(restriction) }, enabled = isDeviceOwner(myDpm)|| isProfileOwner(myDpm), - modifier = Modifier.padding(end = if(!isWear){5.dp}else{0.dp}) + modifier = Modifier.padding(end = 5.dp) ) } } @@ -209,7 +198,7 @@ private fun UserRestrictionItem( private class RestrictionData{ fun internet():List{ val list:MutableList = mutableListOf() - list += Restriction(UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS,R.string.config_mobile_network,"",R.drawable.signal_cellular_alt_fill0) + list += Restriction(UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS, R.string.config_mobile_network,"",R.drawable.signal_cellular_alt_fill0) list += Restriction(UserManager.DISALLOW_CONFIG_WIFI,R.string.config_wifi,"",R.drawable.wifi_fill0) if(VERSION.SDK_INT>=24){list += Restriction(UserManager.DISALLOW_DATA_ROAMING,R.string.data_roaming,"",R.drawable.network_cell_fill0)} if(VERSION.SDK_INT>=34){ diff --git a/app/src/main/java/com/binbin/androidowner/ui/Components.kt b/app/src/main/java/com/binbin/androidowner/ui/Components.kt new file mode 100644 index 0000000..bef6de3 --- /dev/null +++ b/app/src/main/java/com/binbin/androidowner/ui/Components.kt @@ -0,0 +1,67 @@ +package com.binbin.androidowner.ui + +import android.content.Context +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Checkbox +import androidx.compose.material3.MaterialTheme.colorScheme +import androidx.compose.material3.MaterialTheme.typography +import androidx.compose.material3.RadioButton +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.unit.dp + + +@Composable +fun RadioButtonItem( + text:String, + selected:()->Boolean, + operation:()->Unit, + textColor: Color = colorScheme.onBackground +){ + val sharedPref = LocalContext.current.getSharedPreferences("data", Context.MODE_PRIVATE) + val isWear = sharedPref.getBoolean("isWear",false) + Row(verticalAlignment = Alignment.CenterVertically,modifier = Modifier + .fillMaxWidth() + .padding(vertical = if(isWear){3.dp}else{0.dp}) + .clip(RoundedCornerShape(25)) + .clickable(onClick = operation) + ) { + RadioButton(selected = selected(), onClick = operation,modifier=if(isWear){Modifier.size(28.dp)}else{Modifier}) + Text(text = text, style = if(!isWear){typography.bodyLarge}else{typography.bodyMedium}, color = textColor, + modifier = Modifier.padding(bottom = 2.dp)) + } +} + +@Composable +fun CheckBoxItem( + text:String, + checked:()->Boolean, + operation:()->Unit, + textColor:Color = colorScheme.onBackground +){ + val sharedPref = LocalContext.current.getSharedPreferences("data", Context.MODE_PRIVATE) + val isWear = sharedPref.getBoolean("isWear",false) + Row(verticalAlignment = Alignment.CenterVertically,modifier = Modifier + .fillMaxWidth() + .padding(vertical = if(isWear){3.dp}else{0.dp}) + .clip(RoundedCornerShape(25)) + .clickable(onClick = operation) + ) { + Checkbox( + checked = checked(), + onCheckedChange = {operation()}, + modifier=if(isWear){Modifier.size(28.dp)}else{Modifier} + ) + Text(text = text, style = if(!isWear){typography.bodyLarge}else{typography.bodyMedium}, color = textColor, modifier = Modifier.padding(bottom = 2.dp)) + } +} diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 4a7a202..668f403 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -85,11 +85,11 @@ 列出Owners 服务未启动 - dpm mark-profile-owner-on-organization-owned-device --user %1$s com.binbin.androidowner/com.binbin.androidowner.MyDeviceAdminReceiver + dpm mark-profile-owner-on-organization-owned-device --user %1$s com.binbin.androidowner/com.binbin.androidowner.dpm.MyDeviceAdminReceiver - dpm set-device-owner com.binbin.androidowner/com.binbin.androidowner.MyDeviceAdminReceiver - dpm set-profile-owner com.binbin.androidowner/com.binbin.androidowner.MyDeviceAdminReceiver - dpm set-active-admin com.binbin.androidowner/com.binbin.androidowner.MyDeviceAdminReceiver + dpm set-device-owner com.binbin.androidowner/com.binbin.androidowner.dpm.MyDeviceAdminReceiver + dpm set-profile-owner com.binbin.androidowner/com.binbin.androidowner.dpm.MyDeviceAdminReceiver + dpm set-active-admin com.binbin.androidowner/com.binbin.androidowner.dpm.MyDeviceAdminReceiver 已授权(Shell) 已授权(Root)