From aaaf1a75aaf6bdcd61b54fe02e05b97ae8bd2484 Mon Sep 17 00:00:00 2001 From: BinTianqi <1220958406@qq.com> Date: Sat, 10 Feb 2024 14:10:32 +0800 Subject: [PATCH] install/uninstall app --- Readme.md | 9 +- app/src/main/AndroidManifest.xml | 11 +++ .../binbin/androidowner/ApplicationManage.kt | 88 +++++++++++++++++++ .../com/binbin/androidowner/MainActivity.kt | 45 +++++++--- .../com/binbin/androidowner/ManagedProfile.kt | 2 +- .../com/binbin/androidowner/Permissions.kt | 6 +- .../java/com/binbin/androidowner/Receiver.kt | 25 +++++- 7 files changed, 161 insertions(+), 25 deletions(-) diff --git a/Readme.md b/Readme.md index e3d44e9..bcfd1ae 100644 --- a/Readme.md +++ b/Readme.md @@ -27,13 +27,14 @@ - 关闭USB信号(需设备支持) - 设置时间 - 管理系统更新策略 - - 清除数据 + - 恢复出厂设置 - 应用管理 - 隐藏应用 - 停用应用 - 禁止卸载应用 - 应用权限管理 - - 设置许可的输入法 + - 清除应用数据 + - 安装、卸载应用 - 用户限制 - 网络和互联网:禁止使用流量、WiFi、VPN、私人DNS - 其他连接:禁止使用蓝牙、位置信息、NFC、USB(MTP) @@ -50,15 +51,15 @@ - 最大密码错误次数 - 密码失效超时时间 - 设置密码复杂度要求 + - 修改锁屏可用功能 ### 这个应用十分危险!!! 在使用各个功能之前,请仔细阅读相应的说明。红色的按钮一定要谨慎使用! 如果操作不慎,可能会意外地丢失数据或者让你无法解锁你的设备! -### 即将加入的功能 +### 正在开发的功能 -- ~~应用管理:安装/卸载应用~~(暂不考虑) - 应用管理:包选择器(目前只能手动输入包名) - 应用管理:应用权限选择器 - 用户管理:用户选择器(目前只能手动输入用户序列号) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 933dcf7..991dbe7 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -16,6 +16,8 @@ + + + + + + + diff --git a/app/src/main/java/com/binbin/androidowner/ApplicationManage.kt b/app/src/main/java/com/binbin/androidowner/ApplicationManage.kt index c910033..f098f67 100644 --- a/app/src/main/java/com/binbin/androidowner/ApplicationManage.kt +++ b/app/src/main/java/com/binbin/androidowner/ApplicationManage.kt @@ -1,5 +1,6 @@ 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 @@ -11,6 +12,7 @@ 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 @@ -39,6 +41,8 @@ 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 java.io.IOException +import java.io.InputStream import java.util.concurrent.Executors private var credentialList = mutableSetOf() @@ -527,6 +531,71 @@ fun ApplicationManage(){ Text("设为默认拨号应用") } } + + Column(modifier = sections()){ + Text(text = "卸载应用", style = typography.titleLarge) + Text(text = "静默卸载需Device owner", style = bodyTextStyle) + 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("静默卸载") + } + Button( + onClick = { + val intent = Intent(Intent.ACTION_UNINSTALL_PACKAGE) + intent.setData(Uri.parse("package:$pkgName")) + myContext.startActivity(intent) + }, + modifier = Modifier.fillMaxWidth(0.96F) + ) { + Text("请求卸载") + } + } + } + + Column(modifier = sections()){ + Text(text = "安装应用", style = typography.titleLarge) + Text(text = "静默安装需Device owner", style = bodyTextStyle) + 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("选择APK...") + } + Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween){ + Button( + onClick = { uriToStream(myContext, apkUri){stream -> installPackage(myContext,stream)} }, + modifier = Modifier.fillMaxWidth(0.49F) + ) { + Text("静默安装") + } + 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("请求安装") + } + } + } + Spacer(Modifier.padding(30.dp)) } } @@ -557,3 +626,22 @@ private fun AppManageItem( ) } } + +@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/MainActivity.kt b/app/src/main/java/com/binbin/androidowner/MainActivity.kt index 1bd8c7c..0630bea 100644 --- a/app/src/main/java/com/binbin/androidowner/MainActivity.kt +++ b/app/src/main/java/com/binbin/androidowner/MainActivity.kt @@ -6,6 +6,7 @@ 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 @@ -42,10 +43,14 @@ import androidx.navigation.compose.rememberNavController import com.binbin.androidowner.ui.theme.AndroidOwnerTheme 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 apkUri: Uri var caCert = byteArrayOf() @ExperimentalMaterial3Api @@ -53,20 +58,16 @@ class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { WindowCompat.setDecorFitsSystemWindows(window, false) super.onCreate(savedInstanceState) - getCaCert = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { + getApk = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { val uri = it.data?.data - if(uri!=null){ - try{ - val stream = contentResolver.openInputStream(uri) - if(stream!=null) { - caCert = stream.readBytes() - if(caCert.size>50000){ Toast.makeText(applicationContext, "太大了", Toast.LENGTH_SHORT).show(); caCert = byteArrayOf() } - }else{ Toast.makeText(applicationContext, "空的流", Toast.LENGTH_SHORT).show() } - stream?.close() - } - catch(e:FileNotFoundException){ Toast.makeText(applicationContext, "文件不存在", Toast.LENGTH_SHORT).show() } - catch(e:IOException){ Toast.makeText(applicationContext, "IO异常", Toast.LENGTH_SHORT).show() } - }else{ Toast.makeText(applicationContext, "空URI", Toast.LENGTH_SHORT).show() } + if(uri!=null){ apkUri = uri } + else{ Toast.makeText(applicationContext, "空URI", Toast.LENGTH_SHORT).show() } + } + getCaCert = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { + uriToStream(applicationContext,it.data?.data){stream-> + caCert = stream.readBytes() + if(caCert.size>50000){ Toast.makeText(applicationContext, "太大了", Toast.LENGTH_SHORT).show(); caCert = byteArrayOf() } + } } createUser = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { when(it.resultCode){ @@ -340,3 +341,21 @@ fun sections(bgColor:Color=MaterialTheme.colorScheme.primaryContainer):Modifier{ .padding(vertical = 2.dp, horizontal = 3.dp) } } + +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 index 535bcff..abb2a10 100644 --- a/app/src/main/java/com/binbin/androidowner/ManagedProfile.kt +++ b/app/src/main/java/com/binbin/androidowner/ManagedProfile.kt @@ -65,7 +65,7 @@ fun ManagedProfile() { Text("跳转至个人应用") } }else{ - if(!myDpm.isProvisioningAllowed(ACTION_PROVISION_MANAGED_PROFILE)){ + if(!myDpm.isProvisioningAllowed(ACTION_PROVISION_MANAGED_PROFILE)&&!isDeviceOwner(myDpm)){ Button( onClick = { myContext.startActivity(Intent("com.binbin.androidowner.MAIN_ACTION")) }, modifier = Modifier.fillMaxWidth() ){ diff --git a/app/src/main/java/com/binbin/androidowner/Permissions.kt b/app/src/main/java/com/binbin/androidowner/Permissions.kt index dcb37e8..877a580 100644 --- a/app/src/main/java/com/binbin/androidowner/Permissions.kt +++ b/app/src/main/java/com/binbin/androidowner/Permissions.kt @@ -231,11 +231,7 @@ fun DpmPermissions(navCtrl:NavHostController){ if((VERSION.SDK_INT>=26&&isDeviceOwner(myDpm))||(VERSION.SDK_INT>=24&&isProfileOwner(myDpm))){ Column(modifier = sections()){ - var orgName by remember{ - mutableStateOf( - if(myDpm.getOrganizationName(myComponent).toString()=="null"){ "" }else{ myDpm.getOrganizationName(myComponent).toString() } - ) - } + var orgName by remember{mutableStateOf(try{myDpm.getOrganizationName(myComponent).toString()}catch(e:SecurityException){""})} Text(text = "组织名称", style = typography.titleLarge) TextField( value = orgName, onValueChange = {orgName=it}, modifier = Modifier.fillMaxWidth().padding(vertical = 3.dp), diff --git a/app/src/main/java/com/binbin/androidowner/Receiver.kt b/app/src/main/java/com/binbin/androidowner/Receiver.kt index ecaeac8..ba7aa5e 100644 --- a/app/src/main/java/com/binbin/androidowner/Receiver.kt +++ b/app/src/main/java/com/binbin/androidowner/Receiver.kt @@ -2,10 +2,12 @@ package com.binbin.androidowner import android.annotation.SuppressLint import android.app.admin.DeviceAdminReceiver +import android.content.BroadcastReceiver import android.content.Context import android.content.Intent -import android.os.Build +import android.content.pm.PackageInstaller.* import android.os.Build.VERSION +import android.util.Log import android.widget.Toast class MyDeviceAdminReceiver : DeviceAdminReceiver() { @@ -33,4 +35,23 @@ class MyDeviceAdminReceiver : DeviceAdminReceiver() { if (VERSION.SDK_INT < 26) { return } Toast.makeText(context, "新的系统更新!", Toast.LENGTH_SHORT).show() } -} \ No newline at end of file +} + +class PackageInstallerReceiver:BroadcastReceiver(){ + override fun onReceive(context: Context, intent: Intent) { + val toastText = when(intent.getIntExtra(EXTRA_STATUS,666)){ + STATUS_PENDING_USER_ACTION->"等待用户交互" + STATUS_SUCCESS->"成功" + STATUS_FAILURE->"失败" + STATUS_FAILURE_BLOCKED->"失败:被阻止" + STATUS_FAILURE_ABORTED->"失败:被打断" + STATUS_FAILURE_INVALID->"失败:无效" + STATUS_FAILURE_CONFLICT->"失败:冲突" + STATUS_FAILURE_STORAGE->"失败:空间不足" + STATUS_FAILURE_INCOMPATIBLE->"失败:不兼容" + STATUS_FAILURE_TIMEOUT->"失败:超时" + else->"未知" + } + Log.e("静默安装","${intent.getIntExtra(EXTRA_STATUS,666)}:$toastText") + } +}