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 <queries>) 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 <queries> 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 <agent@bdeshi.space>
This commit is contained in:
3 files changed
+132
-6
No files matched your search
@@ -1,5 +1,15 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<!-- Lets the "Add app" picker list launchable apps, including ones that can't see Veil
|
||||
and so never reach its discovery provider. This only widens what Veil can see; it
|
||||
doesn't make Veil any more visible to the apps it hides things from. -->
|
||||
<queries>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent>
|
||||
</queries>
|
||||
|
||||
<application
|
||||
android:label="@string/app_name"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
|
||||
@@ -1,16 +1,23 @@
|
||||
package space.bdeshi.veil
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.graphics.Typeface
|
||||
import android.os.Bundle
|
||||
import android.text.Editable
|
||||
import android.text.InputType
|
||||
import android.text.TextWatcher
|
||||
import android.view.Gravity
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ArrayAdapter
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.ListView
|
||||
import android.widget.ScrollView
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.button.MaterialButton
|
||||
import com.google.android.material.card.MaterialCardView
|
||||
import com.google.android.material.checkbox.MaterialCheckBox
|
||||
@@ -20,6 +27,8 @@ import com.google.android.material.textfield.TextInputEditText
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
import java.io.File
|
||||
|
||||
private data class LaunchableApp(val label: String, val pkg: String)
|
||||
|
||||
private val HELP_TIPS: List<Pair<String, String>> = 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<Pair<String, String>> = 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 <queries>.
|
||||
private fun appLabel(pkg: String): String? = try {
|
||||
packageManager.getApplicationInfo(pkg, 0).loadLabel(packageManager).toString()
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
}
|
||||
|
||||
private fun launchableApps(): List<LaunchableApp> {
|
||||
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<LaunchableApp>) {
|
||||
val adapter = object : ArrayAdapter<LaunchableApp>(
|
||||
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<TextView>(android.R.id.text1).text = app.label
|
||||
view.findViewById<TextView>(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()) {
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in new issue
Block a user