Skip to content

Commit 0400c42

Browse files
committed
manager: refine webui package manager
use superuser viewmodel companion to get applist, no longer require QUERY_ALL_PACKAGES permission. Signed-off-by: KOWX712 <leecc0503@gmail.com>
1 parent c6137d3 commit 0400c42

6 files changed

Lines changed: 77 additions & 59 deletions

File tree

manager/app/src/main/AndroidManifest.xml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
xmlns:tools="http://schemas.android.com/tools">
44

55
<uses-permission android:name="android.permission.INTERNET" />
6-
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
76
<application
87
android:name=".KernelSUApplication"
98
android:allowBackup="true"

manager/app/src/main/java/me/weishu/kernelsu/KernelSUApplication.kt

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,17 @@ package me.weishu.kernelsu
22

33
import android.app.Application
44
import android.system.Os
5+
import androidx.lifecycle.ViewModelProvider
6+
import androidx.lifecycle.ViewModelStore
7+
import androidx.lifecycle.ViewModelStoreOwner
58
import coil.Coil
69
import coil.ImageLoader
710
import com.topjohnwu.superuser.Shell
11+
import kotlinx.coroutines.CoroutineScope
12+
import kotlinx.coroutines.Dispatchers
13+
import kotlinx.coroutines.launch
814
import me.weishu.kernelsu.ui.util.createRootShellBuilder
15+
import me.weishu.kernelsu.ui.viewmodel.SuperUserViewModel
916
import me.zhanghai.android.appiconloader.coil.AppIconFetcher
1017
import me.zhanghai.android.appiconloader.coil.AppIconKeyer
1118
import okhttp3.Cache
@@ -15,16 +22,22 @@ import java.util.Locale
1522

1623
lateinit var ksuApp: KernelSUApplication
1724

18-
class KernelSUApplication : Application() {
25+
class KernelSUApplication : Application(), ViewModelStoreOwner {
1926

2027
lateinit var okhttpClient: OkHttpClient
28+
private val appViewModelStore by lazy { ViewModelStore() }
2129

2230
override fun onCreate() {
2331
super.onCreate()
2432
ksuApp = this
2533
Shell.setDefaultBuilder(createRootShellBuilder(true))
2634
Shell.enableVerboseLogging = BuildConfig.DEBUG
2735

36+
val superUserViewModel = ViewModelProvider(this)[SuperUserViewModel::class.java]
37+
CoroutineScope(Dispatchers.Main).launch {
38+
superUserViewModel.fetchAppList()
39+
}
40+
2841
val context = this
2942
val iconSize = resources.getDimensionPixelSize(android.R.dimen.app_icon_size)
3043
Coil.setImageLoader(
@@ -55,5 +68,7 @@ class KernelSUApplication : Application() {
5568
}.build()
5669
}
5770

71+
override val viewModelStore: ViewModelStore
72+
get() = appViewModelStore
5873

5974
}

manager/app/src/main/java/me/weishu/kernelsu/ui/viewmodel/SuperUserViewModel.kt

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import android.content.Intent
66
import android.content.ServiceConnection
77
import android.content.pm.ApplicationInfo
88
import android.content.pm.PackageInfo
9+
import android.graphics.drawable.Drawable
910
import android.os.IBinder
1011
import android.os.Parcelable
1112
import android.os.SystemClock
@@ -30,13 +31,18 @@ import java.text.Collator
3031
import java.util.*
3132
import kotlin.coroutines.resume
3233
import kotlin.coroutines.suspendCoroutine
33-
import androidx.core.content.edit
3434

3535
class SuperUserViewModel : ViewModel() {
3636

3737
companion object {
3838
private const val TAG = "SuperUserViewModel"
39-
private var apps by mutableStateOf<List<AppInfo>>(emptyList())
39+
var apps by mutableStateOf<List<AppInfo>>(emptyList())
40+
41+
@JvmStatic
42+
fun getAppIconDrawable(context: Context, packageName: String): Drawable? {
43+
val appDetail = apps.find { it.packageName == packageName }
44+
return appDetail?.packageInfo?.applicationInfo?.loadIcon(context.packageManager)
45+
}
4046
}
4147

4248
@Parcelize

manager/app/src/main/java/me/weishu/kernelsu/ui/webui/AppIconUtil.java

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
11
package me.weishu.kernelsu.ui.webui;
22

33
import android.content.Context;
4-
import android.content.pm.ApplicationInfo;
5-
import android.content.pm.PackageManager;
64
import android.graphics.Bitmap;
75
import android.graphics.Canvas;
86
import android.graphics.drawable.BitmapDrawable;
97
import android.graphics.drawable.Drawable;
10-
118
import java.util.HashMap;
129
import java.util.Map;
10+
import me.weishu.kernelsu.ui.viewmodel.SuperUserViewModel;
1311

1412
public class AppIconUtil {
1513
private static final Map<String, Bitmap> iconCache = new HashMap<>();
@@ -19,9 +17,7 @@ public static Bitmap loadAppIconSync(Context context, String packageName, int si
1917
if (cached != null) return cached;
2018

2119
try {
22-
PackageManager pm = context.getPackageManager();
23-
ApplicationInfo appInfo = pm.getApplicationInfo(packageName, 0);
24-
Drawable drawable = pm.getApplicationIcon(appInfo);
20+
Drawable drawable = SuperUserViewModel.getAppIconDrawable(context, packageName);
2521
Bitmap raw = drawableToBitmap(drawable, sizePx);
2622
Bitmap icon = Bitmap.createScaledBitmap(raw, sizePx, sizePx, true);
2723
iconCache.put(packageName, icon);

manager/app/src/main/java/me/weishu/kernelsu/ui/webui/WebUIActivity.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,16 @@ import androidx.activity.ComponentActivity
2222
import androidx.activity.enableEdgeToEdge
2323
import androidx.activity.result.ActivityResultLauncher
2424
import androidx.activity.result.contract.ActivityResultContracts
25+
import androidx.activity.viewModels
2526
import androidx.annotation.RequiresApi
2627
import androidx.core.net.toUri
2728
import androidx.core.view.ViewCompat
2829
import androidx.core.view.WindowInsetsCompat
30+
import androidx.lifecycle.lifecycleScope
2931
import androidx.webkit.WebViewAssetLoader
32+
import kotlinx.coroutines.launch
3033
import me.weishu.kernelsu.ui.util.createRootShell
34+
import me.weishu.kernelsu.ui.viewmodel.SuperUserViewModel
3135
import org.json.JSONObject
3236
import java.io.File
3337
import java.text.SimpleDateFormat
@@ -45,6 +49,7 @@ class WebUIActivity : ComponentActivity() {
4549
private lateinit var saveFileLauncher: ActivityResultLauncher<Intent>
4650
private var pendingDownloadData: ByteArray? = null
4751
private var pendingDownloadSuggestedFilename: String? = null
52+
private val superUserViewModel: SuperUserViewModel by viewModels()
4853

4954
override fun onCreate(savedInstanceState: Bundle?) {
5055

@@ -56,6 +61,10 @@ class WebUIActivity : ComponentActivity() {
5661

5762
super.onCreate(savedInstanceState)
5863

64+
lifecycleScope.launch {
65+
superUserViewModel.fetchAppList()
66+
}
67+
5968
fileChooserLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
6069
if (result.resultCode == Activity.RESULT_OK) {
6170
val data = result.data

manager/app/src/main/java/me/weishu/kernelsu/ui/webui/WebViewInterface.kt

Lines changed: 42 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import com.topjohnwu.superuser.internal.UiThreadHandler
2525
import me.weishu.kernelsu.ui.util.createRootShell
2626
import me.weishu.kernelsu.ui.util.listModules
2727
import me.weishu.kernelsu.ui.util.withNewRootShell
28+
import me.weishu.kernelsu.ui.viewmodel.SuperUserViewModel
2829
import org.json.JSONArray
2930
import org.json.JSONObject
3031
import java.io.File
@@ -208,15 +209,11 @@ class WebViewInterface(
208209

209210
@JavascriptInterface
210211
fun listSystemPackages(): String {
211-
val pm = context.packageManager
212-
val packages = pm.getInstalledPackages(0)
213-
val packageNames = packages
214-
.mapNotNull { pkg ->
215-
val appInfo = pkg.applicationInfo
216-
if (appInfo != null && (appInfo.flags and ApplicationInfo.FLAG_SYSTEM) != 0) {
217-
pkg.packageName
218-
} else null
212+
val packageNames = SuperUserViewModel.apps
213+
.filter { appInfo ->
214+
appInfo.packageInfo.applicationInfo?.let { it.flags and ApplicationInfo.FLAG_SYSTEM != 0 } ?: false
219215
}
216+
.map { it.packageName }
220217
.sorted()
221218
val jsonArray = JSONArray()
222219
for (pkgName in packageNames) {
@@ -227,15 +224,11 @@ class WebViewInterface(
227224

228225
@JavascriptInterface
229226
fun listUserPackages(): String {
230-
val pm = context.packageManager
231-
val packages = pm.getInstalledPackages(0)
232-
val packageNames = packages
233-
.mapNotNull { pkg ->
234-
val appInfo = pkg.applicationInfo
235-
if (appInfo != null && (appInfo.flags and ApplicationInfo.FLAG_SYSTEM) == 0) {
236-
pkg.packageName
237-
} else null
227+
val packageNames = SuperUserViewModel.apps
228+
.filter { appInfo ->
229+
appInfo.packageInfo.applicationInfo?.let { it.flags and ApplicationInfo.FLAG_SYSTEM == 0 } ?: false
238230
}
231+
.map { it.packageName }
239232
.sorted()
240233
val jsonArray = JSONArray()
241234
for (pkgName in packageNames) {
@@ -246,9 +239,7 @@ class WebViewInterface(
246239

247240
@JavascriptInterface
248241
fun listAllPackages(): String {
249-
val pm = context.packageManager
250-
val packages = pm.getInstalledPackages(0)
251-
val packageNames = packages.map { it.packageName }.sorted()
242+
val packageNames = SuperUserViewModel.apps.map { it.packageName }.sorted()
252243
val jsonArray = JSONArray()
253244
for (pkgName in packageNames) {
254245
jsonArray.put(pkgName)
@@ -259,23 +250,24 @@ class WebViewInterface(
259250
@RequiresApi(Build.VERSION_CODES.P)
260251
@JavascriptInterface
261252
fun getPackagesInfo(packageNamesJson: String): String {
262-
val pm = context.packageManager
263253
val packageNames = JSONArray(packageNamesJson)
264254
val jsonArray = JSONArray()
255+
val appMap = SuperUserViewModel.apps.associateBy { it.packageName }
265256
for (i in 0 until packageNames.length()) {
266257
val pkgName = packageNames.getString(i)
267-
try {
268-
val pkg = pm.getPackageInfo(pkgName, 0)
269-
val appInfo = pkg.applicationInfo
258+
val appInfo = appMap[pkgName]
259+
if (appInfo != null) {
260+
val pkg = appInfo.packageInfo
261+
val app = pkg.applicationInfo
270262
val obj = JSONObject()
271263
obj.put("packageName", pkg.packageName)
272264
obj.put("versionName", pkg.versionName ?: "")
273265
obj.put("versionCode", pkg.longVersionCode)
274-
obj.put("appLabel", if (appInfo != null) pm.getApplicationLabel(appInfo).toString() else "")
275-
obj.put("isSystem", appInfo != null && (appInfo.flags and ApplicationInfo.FLAG_SYSTEM) != 0)
276-
obj.put("uid", appInfo?.uid ?: JSONObject.NULL)
266+
obj.put("appLabel", appInfo.label)
267+
obj.put("isSystem", app != null && (app.flags and ApplicationInfo.FLAG_SYSTEM) != 0)
268+
obj.put("uid", app?.uid ?: JSONObject.NULL)
277269
jsonArray.put(obj)
278-
} catch (e: Exception) {
270+
} else {
279271
val obj = JSONObject()
280272
obj.put("packageName", pkgName)
281273
obj.put("error", "Package not found or inaccessible")
@@ -289,21 +281,21 @@ class WebViewInterface(
289281

290282
@JavascriptInterface
291283
fun cacheAllPackageIcons(size: Int) {
292-
val pm = context.packageManager
293-
val packages = pm.getInstalledPackages(0)
294284
val outputStream = java.io.ByteArrayOutputStream()
295-
for (pkg in packages) {
296-
val pkgName = pkg.packageName
297-
if (packageIconCache.containsKey(pkgName)) continue
285+
SuperUserViewModel.apps.forEach { appInfo ->
286+
val pkgName = appInfo.packageName
287+
if (packageIconCache.containsKey(pkgName)) return@forEach
298288
try {
299-
val appInfo = pm.getApplicationInfo(pkgName, 0)
300-
val drawable = pm.getApplicationIcon(appInfo)
301-
val bitmap = drawableToBitmap(drawable, size)
302-
outputStream.reset()
303-
bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream)
304-
val byteArray = outputStream.toByteArray()
305-
val iconBase64 = "data:image/png;base64," + Base64.encodeToString(byteArray, Base64.NO_WRAP)
306-
packageIconCache[pkgName] = iconBase64
289+
SuperUserViewModel.getAppIconDrawable(context, pkgName)?.let { drawable ->
290+
val bitmap = drawableToBitmap(drawable, size)
291+
outputStream.reset()
292+
bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream)
293+
val byteArray = outputStream.toByteArray()
294+
val iconBase64 = "data:image/png;base64," + Base64.encodeToString(byteArray, Base64.NO_WRAP)
295+
packageIconCache[pkgName] = iconBase64
296+
} ?: run {
297+
packageIconCache[pkgName] = ""
298+
}
307299
} catch (_: Exception) {
308300
packageIconCache[pkgName] = ""
309301
}
@@ -312,7 +304,6 @@ class WebViewInterface(
312304

313305
@JavascriptInterface
314306
fun getPackagesIcons(packageNamesJson: String, size: Int): String {
315-
val pm = context.packageManager
316307
val packageNames = JSONArray(packageNamesJson)
317308
val jsonArray = JSONArray()
318309
val outputStream = java.io.ByteArrayOutputStream()
@@ -323,17 +314,19 @@ class WebViewInterface(
323314
var iconBase64 = packageIconCache[pkgName]
324315
if (iconBase64 == null) {
325316
try {
326-
val appInfo = pm.getApplicationInfo(pkgName, 0)
327-
val drawable = pm.getApplicationIcon(appInfo)
328-
val bitmap = drawableToBitmap(drawable, size)
329-
outputStream.reset()
330-
bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream)
331-
val byteArray = outputStream.toByteArray()
332-
iconBase64 = "data:image/png;base64," + Base64.encodeToString(byteArray, Base64.NO_WRAP)
317+
SuperUserViewModel.getAppIconDrawable(context, pkgName)?.let { drawable ->
318+
val bitmap = drawableToBitmap(drawable, size)
319+
outputStream.reset()
320+
bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream)
321+
val byteArray = outputStream.toByteArray()
322+
iconBase64 = "data:image/png;base64," + Base64.encodeToString(byteArray, Base64.NO_WRAP)
323+
} ?: run {
324+
iconBase64 = ""
325+
}
333326
} catch (_: Exception) {
334327
iconBase64 = ""
335328
}
336-
packageIconCache[pkgName] = iconBase64
329+
packageIconCache[pkgName] = iconBase64 ?: ""
337330
}
338331
obj.put("icon", iconBase64)
339332
jsonArray.put(obj)

0 commit comments

Comments
 (0)