Recently Written · git

assistkey

Hardware key remapper for the Viwoods AiPaper Reader: AI key, volume keys and power, with multi-tap, hold and chords

git clone https://github.com/equwal/assistkey

Log | Files | Refs


commit ff747da6b3f57ce17162c210ecab0a5e0711335d
truex <truex@equwal.com>
2026-09-21 04:38:34 -0700

List the grants of the extensions on the Permissions screen

Three items come back in a new form: the switch that lets Rebind hand
an app to the installer, usage access for Ink Recents, and the choice
of inkOS or ThinkLauncher as the home screen. Each item shows only
where it can apply. The walk through the missing grants includes them.
The count on the main screen stays the grants of Rebind itself.

 CHANGELOG.md                                       |  6 +++
 .../dev/equwal/assistkey/ui/PermissionsActivity.kt | 59 ++++++++++++++++++++++
 2 files changed, 65 insertions(+)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 80a1a9d..857c4b3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,12 @@
 
 ## Unreleased
 
+- **Permissions covers the extensions again.** New items, each only where it
+  can apply: "Install apps from Rebind" (the build that carries apps), "Ink
+  Recents: app usage data" (when Ink Recents is installed), and "Home screen"
+  to choose inkOS or ThinkLauncher (when one is installed). "Set up what is
+  missing" walks through them too. The main screen still counts only the
+  grants of Rebind itself.
 - **Fix: voice typing said "The microphone is not allowed" although it was
   allowed.** Android gives the microphone to an app only while that app is in
   use. The Viwoods firmware does not count a speech app as in use while it
diff --git a/app/src/main/java/dev/equwal/assistkey/ui/PermissionsActivity.kt b/app/src/main/java/dev/equwal/assistkey/ui/PermissionsActivity.kt
index 5a2e8d8..abd78df 100644
--- a/app/src/main/java/dev/equwal/assistkey/ui/PermissionsActivity.kt
+++ b/app/src/main/java/dev/equwal/assistkey/ui/PermissionsActivity.kt
@@ -4,6 +4,9 @@ import android.Manifest
 import android.app.Activity
 import android.content.Context
 import android.content.Intent
+import dev.equwal.assistkey.bundle.Bundled
+import android.net.Uri
+import android.content.pm.PackageManager
 import android.provider.Settings
 import android.widget.LinearLayout
 import dev.equwal.assistkey.channel.Channel
@@ -70,9 +73,64 @@ class PermissionsActivity : Activity() {
             "For Voice typing",
             state[2]
         ) { requestPermissions(arrayOf(Manifest.permission.RECORD_AUDIO), 1) }
+        return list + extensions()
+    }
+
+    /**
+     * What the apps that go with Rebind need. Each one is a switch in the
+     * settings of Android that only the user can set. An item shows only when
+     * it can apply: the install switch in the build that carries apps, the
+     * others when their app is installed.
+     */
+    private fun extensions(): List<Item> {
+        val list = ArrayList<Item>()
+        if (Bundled.SUPPORTED) {
+            list += Item(
+                "Install apps from Rebind",
+                "For the apps that Rebind carries",
+                packageManager.canRequestPackageInstalls()
+            ) {
+                start(Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES, Uri.parse("package:" + packageName)))
+            }
+        }
+        if (installed(INK_RECENTS)) {
+            list += Item(
+                "Ink Recents: app usage data",
+                "Puts the recent apps in order",
+                usageAccess(INK_RECENTS)
+            ) {
+                val page = Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS)
+                // Straight to the switch of that app, where the settings app can. Else the list.
+                val direct = Intent(page).setData(Uri.parse("package:" + INK_RECENTS))
+                if (!Channels.safeStart(this, direct)) start(page)
+            }
+        }
+        val homes = Bundled.items.filter { it.group == Bundled.HOME && it.pkg != INK_RECENTS }.map { it.pkg }
+        if (homes.any(::installed)) {
+            val home = packageManager.resolveActivity(
+                Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME),
+                PackageManager.MATCH_DEFAULT_ONLY
+            )?.activityInfo?.packageName
+            list += Item(
+                "Home screen",
+                "Choose inkOS or ThinkLauncher",
+                home in homes
+            ) { start(Intent(Settings.ACTION_HOME_SETTINGS)) }
+        }
         return list
     }
 
+    private fun installed(pkg: String): Boolean =
+        runCatching { packageManager.getPackageInfo(pkg, 0) }.isSuccess
+
+    /** Whether another app has usage access. False where Android does not tell. */
+    private fun usageAccess(pkg: String): Boolean = runCatching {
+        val uid = packageManager.getApplicationInfo(pkg, 0).uid
+        getSystemService(android.app.AppOpsManager::class.java)
+            .unsafeCheckOpNoThrow(android.app.AppOpsManager.OPSTR_GET_USAGE_STATS, uid, pkg) ==
+            android.app.AppOpsManager.MODE_ALLOWED
+    }.getOrDefault(false)
+
     override fun onResume() {
         super.onResume()
         Shell.connect(this)
@@ -146,6 +204,7 @@ class PermissionsActivity : Activity() {
     companion object {
         private const val PREFS = "assistkey_setup"
         private const val K_SHOWN = "permissions_shown"
+        private const val INK_RECENTS = "dev.equwal.inkrecents"
 
         /**
          * Whether each grant is in place, in the order this screen lists them.