install/uninstall app

This commit is contained in:
BinTianqi
2024-02-10 14:10:32 +08:00
parent 8dedd63f61
commit aaaf1a75aa
7 changed files with 161 additions and 25 deletions

View File

@@ -27,13 +27,14 @@
- 关闭USB信号需设备支持 - 关闭USB信号需设备支持
- 设置时间 - 设置时间
- 管理系统更新策略 - 管理系统更新策略
- 清除数据 - 恢复出厂设置
- 应用管理 - 应用管理
- 隐藏应用 - 隐藏应用
- 停用应用 - 停用应用
- 禁止卸载应用 - 禁止卸载应用
- 应用权限管理 - 应用权限管理
- 设置许可的输入法 - 清除应用数据
- 安装、卸载应用
- 用户限制 - 用户限制
- 网络和互联网禁止使用流量、WiFi、VPN、私人DNS - 网络和互联网禁止使用流量、WiFi、VPN、私人DNS
- 其他连接禁止使用蓝牙、位置信息、NFC、USB(MTP) - 其他连接禁止使用蓝牙、位置信息、NFC、USB(MTP)
@@ -50,15 +51,15 @@
- 最大密码错误次数 - 最大密码错误次数
- 密码失效超时时间 - 密码失效超时时间
- 设置密码复杂度要求 - 设置密码复杂度要求
- 修改锁屏可用功能
### 这个应用十分危险!!! ### 这个应用十分危险!!!
在使用各个功能之前,请仔细阅读相应的说明。红色的按钮一定要谨慎使用! 在使用各个功能之前,请仔细阅读相应的说明。红色的按钮一定要谨慎使用!
如果操作不慎,可能会意外地丢失数据或者让你无法解锁你的设备! 如果操作不慎,可能会意外地丢失数据或者让你无法解锁你的设备!
### 即将加入的功能 ### 正在开发的功能
- ~~应用管理:安装/卸载应用~~(暂不考虑)
- 应用管理:包选择器(目前只能手动输入包名) - 应用管理:包选择器(目前只能手动输入包名)
- 应用管理:应用权限选择器 - 应用管理:应用权限选择器
- 用户管理:用户选择器(目前只能手动输入用户序列号) - 用户管理:用户选择器(目前只能手动输入用户序列号)

View File

@@ -16,6 +16,8 @@
<uses-permission android:name="android.permission.MANAGE_DEVICE_POLICY_RUNTIME_PERMISSIONS"/> <uses-permission android:name="android.permission.MANAGE_DEVICE_POLICY_RUNTIME_PERMISSIONS"/>
<uses-permission android:name="android.permission.MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY"/> <uses-permission android:name="android.permission.MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY"/>
<uses-permission android:name="android.permission.MANAGE_DEVICE_POLICY_PROFILE_INTERACTION"/> <uses-permission android:name="android.permission.MANAGE_DEVICE_POLICY_PROFILE_INTERACTION"/>
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
<uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES"/>
<application <application
android:dataExtractionRules="@xml/data_extraction_rules" android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules" android:fullBackupContent="@xml/backup_rules"
@@ -60,5 +62,14 @@
<action android:name="android.app.action.DEVICE_ADMIN_DISABLED"/> <action android:name="android.app.action.DEVICE_ADMIN_DISABLED"/>
</intent-filter> </intent-filter>
</receiver> </receiver>
<receiver
android:name="PackageInstallerReceiver"
android:description="@string/app_name"
android:permission="android.permission.BIND_DEVICE_ADMIN"
android:exported="true">
<intent-filter>
<action android:name="com.binbin.androidowner.PKG_INSTALL_RESULT"/>
</intent-filter>
</receiver>
</application> </application>
</manifest> </manifest>

View File

@@ -1,5 +1,6 @@
package com.binbin.androidowner package com.binbin.androidowner
import android.app.PendingIntent
import android.app.admin.DevicePolicyManager import android.app.admin.DevicePolicyManager
import android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT 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_DENIED
@@ -11,6 +12,7 @@ import android.app.admin.PackagePolicy.PACKAGE_POLICY_BLOCKLIST
import android.content.ComponentName import android.content.ComponentName
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.pm.PackageInstaller
import android.content.pm.PackageManager.NameNotFoundException import android.content.pm.PackageManager.NameNotFoundException
import android.net.Uri import android.net.Uri
import android.os.Build.VERSION 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.text.style.TextAlign
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.core.content.ContextCompat.startActivity import androidx.core.content.ContextCompat.startActivity
import java.io.IOException
import java.io.InputStream
import java.util.concurrent.Executors import java.util.concurrent.Executors
private var credentialList = mutableSetOf<String>() private var credentialList = mutableSetOf<String>()
@@ -527,6 +531,71 @@ fun ApplicationManage(){
Text("设为默认拨号应用") 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)) 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)
}

View File

@@ -6,6 +6,7 @@ import android.app.admin.DevicePolicyManager
import android.content.ComponentName import android.content.ComponentName
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.net.Uri
import android.os.Build.VERSION import android.os.Build.VERSION
import android.os.Bundle import android.os.Bundle
import android.os.UserManager import android.os.UserManager
@@ -42,10 +43,14 @@ import androidx.navigation.compose.rememberNavController
import com.binbin.androidowner.ui.theme.AndroidOwnerTheme import com.binbin.androidowner.ui.theme.AndroidOwnerTheme
import java.io.FileNotFoundException import java.io.FileNotFoundException
import java.io.IOException import java.io.IOException
import java.io.InputStream
lateinit var getCaCert: ActivityResultLauncher<Intent> lateinit var getCaCert: ActivityResultLauncher<Intent>
lateinit var createUser:ActivityResultLauncher<Intent> lateinit var createUser:ActivityResultLauncher<Intent>
lateinit var createManagedProfile:ActivityResultLauncher<Intent> lateinit var createManagedProfile:ActivityResultLauncher<Intent>
lateinit var getApk:ActivityResultLauncher<Intent>
lateinit var apkUri: Uri
var caCert = byteArrayOf() var caCert = byteArrayOf()
@ExperimentalMaterial3Api @ExperimentalMaterial3Api
@@ -53,20 +58,16 @@ class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
WindowCompat.setDecorFitsSystemWindows(window, false) WindowCompat.setDecorFitsSystemWindows(window, false)
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
getCaCert = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { getApk = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
val uri = it.data?.data val uri = it.data?.data
if(uri!=null){ if(uri!=null){ apkUri = uri }
try{ else{ Toast.makeText(applicationContext, "空URI", Toast.LENGTH_SHORT).show() }
val stream = contentResolver.openInputStream(uri) }
if(stream!=null) { getCaCert = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
caCert = stream.readBytes() uriToStream(applicationContext,it.data?.data){stream->
if(caCert.size>50000){ Toast.makeText(applicationContext, "太大了", Toast.LENGTH_SHORT).show(); caCert = byteArrayOf() } caCert = stream.readBytes()
}else{ Toast.makeText(applicationContext, "空的流", Toast.LENGTH_SHORT).show() } if(caCert.size>50000){ Toast.makeText(applicationContext, "太大了", Toast.LENGTH_SHORT).show(); caCert = byteArrayOf() }
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() }
} }
createUser = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { createUser = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
when(it.resultCode){ when(it.resultCode){
@@ -340,3 +341,21 @@ fun sections(bgColor:Color=MaterialTheme.colorScheme.primaryContainer):Modifier{
.padding(vertical = 2.dp, horizontal = 3.dp) .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() }
}

View File

@@ -65,7 +65,7 @@ fun ManagedProfile() {
Text("跳转至个人应用") Text("跳转至个人应用")
} }
}else{ }else{
if(!myDpm.isProvisioningAllowed(ACTION_PROVISION_MANAGED_PROFILE)){ if(!myDpm.isProvisioningAllowed(ACTION_PROVISION_MANAGED_PROFILE)&&!isDeviceOwner(myDpm)){
Button( Button(
onClick = { myContext.startActivity(Intent("com.binbin.androidowner.MAIN_ACTION")) }, modifier = Modifier.fillMaxWidth() onClick = { myContext.startActivity(Intent("com.binbin.androidowner.MAIN_ACTION")) }, modifier = Modifier.fillMaxWidth()
){ ){

View File

@@ -231,11 +231,7 @@ fun DpmPermissions(navCtrl:NavHostController){
if((VERSION.SDK_INT>=26&&isDeviceOwner(myDpm))||(VERSION.SDK_INT>=24&&isProfileOwner(myDpm))){ if((VERSION.SDK_INT>=26&&isDeviceOwner(myDpm))||(VERSION.SDK_INT>=24&&isProfileOwner(myDpm))){
Column(modifier = sections()){ Column(modifier = sections()){
var orgName by remember{ var orgName by remember{mutableStateOf(try{myDpm.getOrganizationName(myComponent).toString()}catch(e:SecurityException){""})}
mutableStateOf(
if(myDpm.getOrganizationName(myComponent).toString()=="null"){ "" }else{ myDpm.getOrganizationName(myComponent).toString() }
)
}
Text(text = "组织名称", style = typography.titleLarge) Text(text = "组织名称", style = typography.titleLarge)
TextField( TextField(
value = orgName, onValueChange = {orgName=it}, modifier = Modifier.fillMaxWidth().padding(vertical = 3.dp), value = orgName, onValueChange = {orgName=it}, modifier = Modifier.fillMaxWidth().padding(vertical = 3.dp),

View File

@@ -2,10 +2,12 @@ package com.binbin.androidowner
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.admin.DeviceAdminReceiver import android.app.admin.DeviceAdminReceiver
import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Build import android.content.pm.PackageInstaller.*
import android.os.Build.VERSION import android.os.Build.VERSION
import android.util.Log
import android.widget.Toast import android.widget.Toast
class MyDeviceAdminReceiver : DeviceAdminReceiver() { class MyDeviceAdminReceiver : DeviceAdminReceiver() {
@@ -34,3 +36,22 @@ class MyDeviceAdminReceiver : DeviceAdminReceiver() {
Toast.makeText(context, "新的系统更新!", Toast.LENGTH_SHORT).show() Toast.makeText(context, "新的系统更新!", Toast.LENGTH_SHORT).show()
} }
} }
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")
}
}