Skip to content

Shevery v13.7.0.r15 hotfix-2

Choose a tag to compare

@HmnDev-Tech HmnDev-Tech released this 04 Jun 15:37

Release r15 hotfix-2

Bug Fixes and Technical Improvements

  1. Android 17 / API 37 Compatibility Layer

    • Fixed dynamic reflection type mismatch when passing default device IDs as integer parameters (switched to default String).
    • Resolved parameter transposition in package checks.
    • Refactored UID permission auditing to look up the correct signature.
  2. Comput Console Deadlock Resolution

    • Replaced sequential blocking stream reads with concurrent coroutine-based async/await draining, eliminating deadlocks when reading stdout and stderr pipes.
  3. KeyStore API Key Encryption

    • Implemented real AES-GCM-NoPadding encryption backed by Android KeyStore, securing the Gemini API key.
    • Automatically migrates existing plain text keys.
  4. WebView JS Bridge Isolation

    • Constrained WebView client navigations to open HTTP/HTTPS links in system browsers.
    • Restricted Javascript interface usage to same-origin checks comparing caller context to local module root paths.
  5. Dhizuku Integration

    • Fully restored the Dhizuku integration and verified build compatibility.

Commits

  • 2200390 fix(project): resolve Comput screen deadlocks, Gemini KeyStore encryption, WebView JS bridge security, and Android 17 compat

Line-by-Line Changes Diff

--- a/docs/adb-modules-api.md
+++ b/docs/adb-modules-api.md
@@ -252,14 +252,10 @@
-The optional AI checker is hidden by default. It is toggled from Settings by tapping the translation contributors row five times quickly. After unlock:
-
-- the AI settings row appears in module settings;
-- the Gemini star appears in ReCommand dialogs;
-- the API key is stored encrypted with Android Keystore;
-- supported model ids are `gemini-3.1-pro-preview`, `gemini-3-flash-preview`, and `gemini-3.1-flash-lite-preview`.
-
-Repeating the same five-tap gesture hides the AI settings row and Gemini star again.
+The optional AI checker is fully integrated into the manager:
+- Gemini console explanation is available in the Comput screen.
+- The API key is securely encrypted using Android Keystore.
+- Configurable settings are directly accessible under Comput Console settings.

--- a/manager/src/main/java/moe/shizuku/manager/logs/ComputScreen.kt
+++ b/manager/src/main/java/moe/shizuku/manager/logs/ComputScreen.kt
@@ -71,6 +71,8 @@
+import kotlinx.coroutines.async
+import kotlinx.coroutines.coroutineScope
@@ -202,9 +204,15 @@
-                    val stdoutText = ParcelFileDescriptor.AutoCloseInputStream(remote.getInputStream()).bufferedReader().use { it.readText() }
-                    val stderrText = ParcelFileDescriptor.AutoCloseInputStream(remote.getErrorStream()).bufferedReader().use { it.readText() }
+                    val (stdoutText, stderrText) = kotlinx.coroutines.coroutineScope {
+                        val stdoutDeferred = async {
+                            ParcelFileDescriptor.AutoCloseInputStream(remote.getInputStream()).bufferedReader().use { it.readText() }
+                        }
+                        val stderrDeferred = async {
+                            ParcelFileDescriptor.AutoCloseInputStream(remote.getErrorStream()).bufferedReader().use { it.readText() }
+                        }
+                        stdoutDeferred.await() to stderrDeferred.await()
+                    }

--- a/manager/src/main/java/moe/shizuku/manager/module/ModuleJsBridge.kt
+++ b/manager/src/main/java/moe/shizuku/manager/module/ModuleJsBridge.kt
+    private fun isOriginValid(): Boolean {
+        val latch = java.util.concurrent.CountDownLatch(1)
+        var url: String? = null
+        webView.post {
+            url = webView.url
+            latch.countDown()
+        }
+        try {
+            latch.await(500, java.util.concurrent.TimeUnit.MILLISECONDS)
+        } catch (e: Exception) {
+            return false
+        }
+        val currentUrl = url ?: return false
+        if (!currentUrl.startsWith("file://", ignoreCase = true)) {
+            return false
+        }
+        val cleanUrl = currentUrl.substring(7).split("?")[0].split("#")[0]
+        val root = module.webRoot ?: return false
+        return try {
+            val rootPath = root.canonicalPath
+            val filePath = File(cleanUrl).canonicalPath
+            filePath.startsWith(rootPath)
+        } catch (e: Exception) {
+            false
+        }
+    }

--- a/server/src/main/java/rikka/shizuku/server/util/Android17Compat.java
+++ b/server/src/main/java/rikka/shizuku/server/util/Android17Compat.java
@@ -148,7 +148,8 @@
-                    return (int) invokeMethod(pm, sCheckPermissionMethod, permissionName, packageName, userId);
+                    // Pass packageName first, permissionName second to match IPermissionManager signature
+                    return (int) invokeMethod(pm, sCheckPermissionMethod, packageName, permissionName, userId);
@@ -168,17 +169,16 @@
-                            sCheckPermissionUidMethod = findMethod(pm, "checkPermission", String.class, int.class);
+                            sCheckPermissionUidMethod = findMethod(pm, "checkUidPermission", int.class, String.class);
-                    if (paramTypes.length == 3 && paramTypes[1] == int.class && paramTypes[2] == int.class) {
-                        // (String permission, int deviceId, int uid)
-                        return (int) sCheckPermissionUidMethod.invoke(pm, permissionName, DEVICE_ID_DEFAULT, uid);
+                    if (paramTypes.length == 3 && paramTypes[0] == int.class && paramTypes[1] == String.class && paramTypes[2] == int.class) {
+                        // (int uid, String permission, int deviceId)
+                        return (int) sCheckPermissionUidMethod.invoke(pm, uid, permissionName, 0);
-                    return (int) sCheckPermissionUidMethod.invoke(pm, permissionName, uid);
@@ -227,7 +227,7 @@
-                        sRevokeRuntimePermissionMethod.invoke(pm, packageName, permissionName, DEVICE_ID_DEFAULT, userId, "shizuku");
+                        sRevokeRuntimePermissionMethod.invoke(pm, packageName, permissionName, "default", userId, "shizuku");
@@ -272,7 +272,11 @@
-            args[prefixLen] = DEVICE_ID_DEFAULT;
+            if (paramTypes[prefixLen] == String.class) {
+                args[prefixLen] = "default";
+            } else {
+                args[prefixLen] = 0; // DEVICE_ID_DEFAULT
+            }
+```