package selector in AppManage

This commit is contained in:
BinTianqi
2024-04-04 23:42:02 +08:00
parent 3fabd81511
commit aa5fbd0b33
10 changed files with 244 additions and 13 deletions

View File

@@ -6,9 +6,7 @@
使用安卓的Device Admin和Device Owner特权完全掌控你的设备。 使用安卓的Device Admin和Device Owner特权完全掌控你的设备。
**从v4.2开始,重写代码,重新设计,界面会发生很大改变。** _我正在为这个App取一个新的名字......_
我正在为这个App取一个新的名字......
### 优点 ### 优点
@@ -19,7 +17,7 @@
### 缺点 ### 缺点
功能没有谷歌官方的 [TestDPC](https://github.com/googlesamples/android-testdpc) 那么 功能没有谷歌官方的 [TestDPC](https://github.com/googlesamples/android-testdpc) 那么
### 功能 ### 功能
@@ -33,14 +31,13 @@
### 正在开发的功能 ### 正在开发的功能
- 应用管理:选择器(目前只能手动输入包名 - 应用管理:应用权限选择器(现在只能手动输入权限名称
- 用管理:应用权限选择器 -管理:用户选择器(现在只能手动输入用户序列号)
- 用户管理:用户选择器(目前只能手动输入用户序列号)
- 安全日志和网络日志 - 安全日志和网络日志
### 许可证 ### 许可证
[License.md](License.md) [License.md](LICENSE.md)
> Copyright (C) 2024 BinTianqi > Copyright (C) 2024 BinTianqi
> >

View File

@@ -11,8 +11,8 @@ android {
applicationId = "com.binbin.androidowner" applicationId = "com.binbin.androidowner"
minSdk = 21 minSdk = 21
targetSdk = 34 targetSdk = 34
versionCode = 21 versionCode = 22
versionName = "4.4" versionName = "4.5"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables { vectorDrawables {
@@ -60,6 +60,7 @@ dependencies {
implementation(platform("androidx.compose:compose-bom:2024.02.01")) implementation(platform("androidx.compose:compose-bom:2024.02.01"))
implementation("androidx.compose.ui:ui") implementation("androidx.compose.ui:ui")
implementation("androidx.compose.ui:ui-graphics") implementation("androidx.compose.ui:ui-graphics")
implementation("com.google.accompanist:accompanist-drawablepainter:0.35.0-alpha")
debugImplementation("androidx.compose.ui:ui-tooling-preview") debugImplementation("androidx.compose.ui:ui-tooling-preview")
implementation("androidx.compose.material3:material3:1.2.0") implementation("androidx.compose.material3:material3:1.2.0")
implementation("androidx.navigation:navigation-compose:2.7.7") implementation("androidx.navigation:navigation-compose:2.7.7")

View File

@@ -18,7 +18,7 @@
<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_INSTALL_PACKAGES"/>
<uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES"/> <uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES"/>
<uses-sdk tools:overrideLibrary="rikka.shizuku.provider,rikka.shizuku.api,rikka.shizuku.shared,rikka.shizuku.aidl"/> <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" tools:ignore="QueryAllPackagesPermission" />
<application <application
android:dataExtractionRules="@xml/data_extraction_rules" android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules" android:fullBackupContent="@xml/backup_rules"

View File

@@ -107,6 +107,7 @@ fun MyScaffold(){
composable(route = "Password", content = { Password(navCtrl)}) composable(route = "Password", content = { Password(navCtrl)})
composable(route = "AppSetting", content = { AppSetting(navCtrl)}) composable(route = "AppSetting", content = { AppSetting(navCtrl)})
composable(route = "Network", content = {Network(navCtrl)}) composable(route = "Network", content = {Network(navCtrl)})
composable(route = "PackageSelector"){PackageSelector(navCtrl)}
} }
LaunchedEffect(Unit){ LaunchedEffect(Unit){
val profileInited = sharedPref.getBoolean("ManagedProfileActivated",false) val profileInited = sharedPref.getBoolean("ManagedProfileActivated",false)

View File

@@ -0,0 +1,190 @@
package com.binbin.androidowner
import android.graphics.drawable.Drawable
import android.widget.Toast
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.*
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.*
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.alpha
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.unit.dp
import androidx.navigation.NavHostController
import com.binbin.androidowner.dpm.applySelectedPackage
import com.binbin.androidowner.dpm.selectedPackage
import com.binbin.androidowner.ui.NavIcon
import com.binbin.androidowner.ui.theme.bgColor
import com.google.accompanist.drawablepainter.rememberDrawablePainter
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
private data class PkgInfo(
val pkgName: String,
val label: String,
val icon: Drawable,
val type:String
)
private val pkgs = mutableListOf<PkgInfo>()
@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
@Composable
fun PackageSelector(navCtrl:NavHostController){
val context = LocalContext.current
val pm = context.packageManager
val apps = pm.getInstalledApplications(0)
var progress by remember{mutableIntStateOf(0)}
var show by remember{mutableStateOf(true)}
var hideProgress by remember{mutableStateOf(true)}
var filter by remember{mutableStateOf("data")}
val scrollState = rememberLazyListState()
val co = rememberCoroutineScope()
val getPkgList:suspend ()->Unit = {
show = false
progress = 0
hideProgress = false
pkgs.clear()
for(pkg in apps){
val srcDir = pkg.sourceDir
pkgs+=PkgInfo(
pkg.packageName, pkg.loadLabel(pm).toString(), pkg.loadIcon(pm),
if(srcDir.contains("/data/")){ "data" }
else if(
srcDir.contains("system/priv-app")||srcDir.contains("product/priv-app")||
srcDir.contains("ext/priv-app")||srcDir.contains("vendor/priv-app")
){"priv"}
else if(srcDir.contains("apex")){"apex"}
else{"system"}
)
progress+=1
delay(1)
}
show = true
delay(300)
hideProgress = true
}
Scaffold(
topBar = {
TopAppBar(
title = {
Row(
horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.fillMaxWidth().padding(bottom = 2.dp),
verticalAlignment = Alignment.CenterVertically
){
Text(text = stringResource(R.string.pkg_selector))
Row {
Icon(
painter = painterResource(R.drawable.filter_alt_fill0),
contentDescription = "filter",
modifier = Modifier
.padding(horizontal = 6.dp)
.clip(RoundedCornerShape(50))
.combinedClickable(
onClick = {
when(filter){
"data"-> {
filter = "system"; co.launch {scrollState.scrollToItem(0)}
Toast.makeText(context, context.getString(R.string.show_system_app), Toast.LENGTH_SHORT).show()
}
"system"-> {
filter = "priv"; co.launch {scrollState.scrollToItem(0)}
Toast.makeText(context, context.getString(R.string.show_priv_app), Toast.LENGTH_SHORT).show()
}
else-> {
filter = "data"; co.launch {scrollState.scrollToItem(0)}
Toast.makeText(context, context.getString(R.string.show_user_app), Toast.LENGTH_SHORT).show()
}
}
},
onLongClick = {
filter = "apex"
Toast.makeText(context, context.getString(R.string.show_apex_app), Toast.LENGTH_SHORT).show()
}
)
.padding(5.dp)
)
Icon(
painter = painterResource(R.drawable.refresh_fill0),
contentDescription = "refresh",
modifier = Modifier
.padding(horizontal = 6.dp)
.clip(RoundedCornerShape(50))
.clickable{
co.launch{
delay(100)
getPkgList()
}
}
.padding(5.dp)
)
}
}
},
navigationIcon = {NavIcon{navCtrl.navigateUp()}},
colors = TopAppBarDefaults.topAppBarColors(containerColor = bgColor)
)
}
){paddingValues->
LazyColumn(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.fillMaxSize().background(bgColor).padding(top = paddingValues.calculateTopPadding()),
state = scrollState
){
items(1){
AnimatedVisibility(!hideProgress) {
LinearProgressIndicator(progress = {progress.toFloat()/apps.size}, modifier = Modifier.fillMaxWidth())
}
}
if(show) {
items(pkgs) {
if(filter==it.type){
PackageItem(it, navCtrl)
}
}
}else{
items(1){
Spacer(Modifier.padding(top = 5.dp))
Text(text = stringResource(R.string.loading), modifier = Modifier.alpha(0.8F))
}
}
}
LaunchedEffect(Unit){
delay(250)
if(pkgs.size==0){getPkgList()}
}
}
}
@Composable
private fun PackageItem(pkg:PkgInfo, navCtrl: NavHostController){
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
.clickable{selectedPackage=pkg.pkgName;applySelectedPackage=true;navCtrl.navigateUp()}
.padding(vertical = 3.dp)
){
Spacer(Modifier.padding(start = 15.dp))
Image(
painter = rememberDrawablePainter(pkg.icon), contentDescription = "App icon",
modifier = Modifier.size(50.dp)
)
Spacer(Modifier.padding(start = 15.dp))
Column {
Text(text = pkg.label, style = typography.titleLarge)
Text(text = pkg.pkgName, modifier = Modifier.alpha(0.8F))
Spacer(Modifier.padding(top = 3.dp))
}
}
}

View File

@@ -25,6 +25,7 @@ import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.animateContentSize import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.* import androidx.compose.foundation.*
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.text.selection.SelectionContainer import androidx.compose.foundation.text.selection.SelectionContainer
@@ -33,8 +34,10 @@ import androidx.compose.material3.MaterialTheme.typography
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.input.KeyboardType
@@ -93,6 +96,12 @@ fun ApplicationManage(navCtrl:NavHostController){
} }
){ paddingValues-> ){ paddingValues->
Column(modifier = Modifier.fillMaxSize().padding(top = paddingValues.calculateTopPadding())){ Column(modifier = Modifier.fillMaxSize().padding(top = paddingValues.calculateTopPadding())){
LaunchedEffect(Unit) {
while(true){
if(applySelectedPackage){pkgName = selectedPackage; applySelectedPackage = false}
delay(200)
}
}
if(backStackEntry?.destination?.route!="InstallApp"){ if(backStackEntry?.destination?.route!="InstallApp"){
TextField( TextField(
value = pkgName, value = pkgName,
@@ -100,7 +109,15 @@ fun ApplicationManage(navCtrl:NavHostController){
label = { Text(stringResource(R.string.package_name)) }, label = { Text(stringResource(R.string.package_name)) },
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Ascii, imeAction = ImeAction.Done), keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Ascii, imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}) keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}),
trailingIcon = {
Icon(painter = painterResource(R.drawable.checklist_fill0), contentDescription = null,
modifier = Modifier
.clip(RoundedCornerShape(50))
.clickable(onClick = {navCtrl.navigate("PackageSelector")})
.padding(3.dp))
},
singleLine = true
) )
} }
NavHost( NavHost(

View File

@@ -5,7 +5,8 @@ import android.content.Intent
import android.net.Uri import android.net.Uri
import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.ActivityResultLauncher
var selectedPackage = ""
var applySelectedPackage = false
lateinit var getCaCert: ActivityResultLauncher<Intent> lateinit var getCaCert: ActivityResultLauncher<Intent>
lateinit var createManagedProfile: ActivityResultLauncher<Intent> lateinit var createManagedProfile: ActivityResultLauncher<Intent>
lateinit var getApk: ActivityResultLauncher<Intent> lateinit var getApk: ActivityResultLauncher<Intent>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:fillColor="#FF000000"
android:pathData="M222,760 L80,618l56,-56 85,85 170,-170 56,57 -225,226ZM222,440L80,298l56,-56 85,85 170,-170 56,57 -225,226ZM520,680v-80h360v80L520,680ZM520,360v-80h360v80L520,360Z"/>
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:fillColor="#FF000000"
android:pathData="M480,800q-134,0 -227,-93t-93,-227q0,-134 93,-227t227,-93q69,0 132,28.5T720,270v-110h80v280L520,440v-80h168q-32,-56 -87.5,-88T480,240q-100,0 -170,70t-70,170q0,100 70,170t170,70q77,0 139,-44t87,-116h84q-28,106 -114,173t-196,67Z"/>
</vector>

View File

@@ -238,6 +238,12 @@
<!--AppManage--> <!--AppManage-->
<string name="app_manage">应用管理</string> <string name="app_manage">应用管理</string>
<string name="pkg_selector">应用选择器</string>
<string name="loading">加载中</string>
<string name="show_user_app">显示用户应用</string>
<string name="show_system_app">显示系统应用</string>
<string name="show_priv_app">显示priv-app</string>
<string name="show_apex_app">显示apex应用</string>
<string name="suspend">挂起</string> <string name="suspend">挂起</string>
<string name="hide">隐藏</string> <string name="hide">隐藏</string>
<string name="isapphidden_desc">如果隐藏,有可能是没安装</string> <string name="isapphidden_desc">如果隐藏,有可能是没安装</string>