From af97a85c54eeecad3475a47f916aafa3bae93156 Mon Sep 17 00:00:00 2001 From: bdeshi Date: Tue, 3 Nov 2026 18:54:13 +0600 Subject: [PATCH] Add an app picker for targets that can't report themselves Auto-discovery only works for apps that can see Veil: on Android 11+ an app without QUERY_ALL_PACKAGES (or a matching ) can't resolve Veil's provider, so its first-launch check-in silently fails. A real app hit this: scoped and hooked, but never listed. That made 615f000's premise, that auto-detection covers every target, wrong. - "Add app" opens a searchable list of launchable apps; picking one saves its default feature set, which is what gives an app a card. A launcher-intent is enough for this and narrower than QUERY_ALL_PACKAGES; it widens what Veil sees, not who sees Veil. - App cards show the app's label above the package name. - reportScoped checks resolveContentProvider first and skips the call when Veil isn't visible. That also drops the framework's "Failed to find provider info for space.bdeshi.veil.provider" line, which named Veil in the target's own, app-readable logcat. - New help tip explains why a scoped app may not show up. Verified on device (Android 16, Vector): a scoped app no longer logs the provider failure, and adding it through the picker saves its feature set. Co-Authored-By: agent --- app/src/main/AndroidManifest.xml | 10 ++ .../space/bdeshi/veil/SettingsActivity.kt | 120 +++++++++++++++++- .../main/java/space/bdeshi/veil/VeilHook.kt | 8 ++ 3 files changed, 132 insertions(+), 6 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 5b196fa..2f3192f 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,5 +1,15 @@ + + + + + + + + > = listOf( "Target app still won't launch?" to "Check KernelSU Manager → the target app's profile → make sure \"Umount modules\" " + @@ -30,6 +39,10 @@ private val HELP_TIPS: List> = listOf( "Standalone Zygisk modules (e.g. Zygisk Next / ReZygisk) can carry their own independent " + "DenyList on top of KernelSU's. If an app is still being skipped after fixing the above, " + "check there too.", + "Scoped app doesn't show up here?" to + "Android 11+ only lets an app reach packages it can see, and most apps can't see " + + "Veil, so their first-launch check-in never arrives. Veil still hides everything from " + + "them by default; use \"Add app\" to give one its own settings.", "Changed a setting but nothing happened?" to "Force-stop and reopen the target app. Veil reads its config once when that app's " + "process starts, so changes here (or to LSPosed scope) only take effect on the next " + @@ -106,13 +119,18 @@ class SettingsActivity : AppCompatActivity() { root.addView(sectionTitle("Apps")) root.addView(TextView(this).apply { text = "Scope this module to a target app in LSPosed Manager, then open that app " + - "once — it shows up here automatically. Apps not listed here get everything " + - "hidden by default." + "once — it shows up here automatically if it can see Veil. If it doesn't, add " + + "it below. Apps not listed here get everything hidden by default." textSize = 13f alpha = 0.8f setPadding(0, dp(4), 0, dp(8)) }) + root.addView(MaterialButton(this).apply { + text = "Add app" + setOnClickListener { showAppPicker() } + }) + appListContainer = LinearLayout(this).apply { orientation = LinearLayout.VERTICAL setPadding(0, dp(8), 0, 0) @@ -233,7 +251,7 @@ class SettingsActivity : AppCompatActivity() { if (packages.isEmpty()) { appListContainer.addView(TextView(this).apply { text = "No apps yet — scope Veil to a target app in LSPosed Manager and open " + - "it once." + "it once, or add one with \"Add app\"." textSize = 13f alpha = 0.8f }) @@ -253,12 +271,24 @@ class SettingsActivity : AppCompatActivity() { orientation = LinearLayout.HORIZONTAL gravity = Gravity.CENTER_VERTICAL } - titleRow.addView(TextView(this).apply { - text = pkg + val label = appLabel(pkg) + val titles = LinearLayout(this).apply { + orientation = LinearLayout.VERTICAL + layoutParams = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f) + } + titles.addView(TextView(this).apply { + text = label ?: pkg textSize = 16f setTypeface(typeface, Typeface.BOLD) - layoutParams = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f) }) + if (label != null) { + titles.addView(TextView(this).apply { + text = pkg + textSize = 12f + alpha = 0.7f + }) + } + titleRow.addView(titles) if (isDiscoveredOnly) { titleRow.addView(Chip(this).apply { text = "Auto-detected" @@ -307,6 +337,84 @@ class SettingsActivity : AppCompatActivity() { return cardView } + // Null when Veil can't see the package: only launchable apps are in its . + private fun appLabel(pkg: String): String? = try { + packageManager.getApplicationInfo(pkg, 0).loadLabel(packageManager).toString() + } catch (e: Exception) { + null + } + + private fun launchableApps(): List { + val intent = Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_LAUNCHER) + return packageManager.queryIntentActivities(intent, 0) + .distinctBy { it.activityInfo.packageName } + .map { LaunchableApp(it.loadLabel(packageManager).toString(), it.activityInfo.packageName) } + .sortedBy { it.label.lowercase() } + } + + // Loading labels for every launchable app takes a moment, so it runs off the UI thread. + private fun showAppPicker() { + Thread { + val listed = configuredPackages() + discoveredPackages() + val apps = launchableApps().filter { it.pkg !in listed } + runOnUiThread { + if (!isFinishing) showAppPickerDialog(apps) + } + }.start() + } + + private fun showAppPickerDialog(apps: List) { + val adapter = object : ArrayAdapter( + this, android.R.layout.simple_list_item_2, android.R.id.text1, apps.toMutableList(), + ) { + override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { + val view = super.getView(position, convertView, parent) + val app = getItem(position) ?: return view + view.findViewById(android.R.id.text1).text = app.label + view.findViewById(android.R.id.text2).text = app.pkg + return view + } + } + + val search = textInput("Search apps") + search.addTextChangedListener(object : TextWatcher { + override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {} + override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {} + override fun afterTextChanged(s: Editable?) { + val query = s.toString().trim() + adapter.clear() + adapter.addAll(apps.filter { + it.label.contains(query, ignoreCase = true) || it.pkg.contains(query, ignoreCase = true) + }) + } + }) + + val list = ListView(this).apply { this.adapter = adapter } + val body = LinearLayout(this).apply { + orientation = LinearLayout.VERTICAL + setPadding(dp(24), dp(8), dp(24), 0) + addView(TextInputLayout(this@SettingsActivity).apply { addView(search) }) + addView(list, LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, dp(360))) + } + + val dialog = MaterialAlertDialogBuilder(this) + .setTitle("Add app") + .setView(body) + .setNegativeButton("Cancel", null) + .create() + + // Saving the default feature set is what makes an app "configured", so it gets a card + // whether or not it ever checks in. + list.setOnItemClickListener { _, _, position, _ -> + val app = adapter.getItem(position) ?: return@setOnItemClickListener + prefs.edit().putStringSet(Features.keyFor(app.pkg), Features.ALL).apply() + fixPermissions() + refreshAppList() + dialog.dismiss() + } + dialog.show() + } + private fun refreshHiddenPackages() { hiddenPackagesChips.removeAllViews() for (pkg in hiddenPackages().sorted()) { diff --git a/app/src/main/java/space/bdeshi/veil/VeilHook.kt b/app/src/main/java/space/bdeshi/veil/VeilHook.kt index edaa6f8..51dd535 100644 --- a/app/src/main/java/space/bdeshi/veil/VeilHook.kt +++ b/app/src/main/java/space/bdeshi/veil/VeilHook.kt @@ -93,6 +93,14 @@ class VeilHook : IXposedHookLoadPackage { override fun afterHookedMethod(param: MethodHookParam) { try { val context = param.args[0] as Context + // Most apps can't see Veil (Android 11+ package visibility), and + // then can't reach its provider either. Checking first skips the + // doomed call, and with it the "Failed to find provider info" + // line naming Veil that the framework would write to the app's + // own, app-readable logcat. Such apps are added via the picker. + if (context.packageManager.resolveContentProvider(Features.PROVIDER_AUTHORITY, 0) == null) { + return + } context.contentResolver.call( Uri.parse("content://${Features.PROVIDER_AUTHORITY}"), "register",