Recently Written · git

scan-subread

PDF reader that follows an audiobook with the SubRead suite

git clone https://github.com/equwal/scan-subread

Log | Files | Refs


commit 15a90360991a88dd0bc4665f928a2cf2204f66d6
truex <truex@equwal.com>
2026-09-26 13:53:50 -0700

Open SubRead Overlay from the reader

The reader needs SubRead Overlay with notification access. The new
plugin method openOverlay starts the launcher activity of SubRead
Overlay: the release build first, then the debug build. Without SubRead
Overlay, it opens the notification access settings of Android. It
resolves which one opened. On the web, the call rejects.

 .../java/com/equwal/scansubread/SubReadPlugin.java | 31 +++++++++++++++++++++-
 src/subread.ts                                     | 10 +++++++
 test/subread.test.ts                               |  4 +++
 3 files changed, 44 insertions(+), 1 deletion(-)
diff --git a/android/app/src/main/java/com/equwal/scansubread/SubReadPlugin.java b/android/app/src/main/java/com/equwal/scansubread/SubReadPlugin.java
index 709aa35..84b1c79 100644
--- a/android/app/src/main/java/com/equwal/scansubread/SubReadPlugin.java
+++ b/android/app/src/main/java/com/equwal/scansubread/SubReadPlugin.java
@@ -15,6 +15,7 @@ import android.database.Cursor;
 import android.net.Uri;
 import android.os.Bundle;
 import android.provider.OpenableColumns;
+import android.provider.Settings;
 import android.view.WindowManager;
 import androidx.activity.result.ActivityResult;
 import androidx.core.content.FileProvider;
@@ -48,7 +49,9 @@ import java.util.List;
  *   (`space.subread.app.action.ALIGN`).
  * - SubRead Anki, through `space.subread.anki.action.ADD`: a card for a
  *   word and its sentence.
- * - The package manager: which apps of the suite are installed.
+ * - The package manager: which apps of the suite are installed. SubRead
+ *   Overlay opens from the reader, so that the user can give it
+ *   notification access.
  * - The screen, which stays on during read-along.
  */
 @CapacitorPlugin(name = "SubRead")
@@ -375,6 +378,32 @@ public class SubReadPlugin extends Plugin {
         call.resolve(ret);
     }
 
+    /**
+     * Opens SubRead Overlay, so that the user can give it notification
+     * access. Without SubRead Overlay, opens the notification access
+     * settings of Android.
+     */
+    @PluginMethod
+    public void openOverlay(PluginCall call) {
+        PackageManager pm = getContext().getPackageManager();
+        Intent open = pm.getLaunchIntentForPackage(OVERLAY_PACKAGE);
+        if (open == null) open = pm.getLaunchIntentForPackage(OVERLAY_DEBUG_PACKAGE);
+        String opened = "overlay";
+        if (open == null) {
+            open = new Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS);
+            opened = "settings";
+        }
+        try {
+            getActivity().startActivity(open);
+        } catch (ActivityNotFoundException e) {
+            call.reject("Cannot open the " + opened + ".", e);
+            return;
+        }
+        JSObject ret = new JSObject();
+        ret.put("opened", opened);
+        call.resolve(ret);
+    }
+
     // --- The subtitle maker, through the SubRead app ---
 
     /** Writes `text` to cacheDir/share/<name> and returns its FileProvider Uri. */
diff --git a/src/subread.ts b/src/subread.ts
index d4cc101..0cb0519 100644
--- a/src/subread.ts
+++ b/src/subread.ts
@@ -103,6 +103,13 @@ export interface SubReadPlugin {
    * On the web: all false, null and 0.
    */
   suite(): Promise<SuiteApps>;
+  /**
+   * Opens SubRead Overlay (the release build, else the debug build), so that
+   * the user can give it notification access. Without SubRead Overlay, opens
+   * the notification access settings of Android. Resolves which one opened.
+   * On the web: rejects with Error('Android only.').
+   */
+  openOverlay(): Promise<{ opened: 'overlay' | 'settings' }>;
   makeSubtitles(options: {
     audio: string;
     bookText: string;
@@ -170,6 +177,9 @@ class SubReadWeb implements SubReadPlugin {
       dictionaries: 0,
     });
   }
+  openOverlay(): Promise<{ opened: 'overlay' | 'settings' }> {
+    return Promise.reject(new Error('Android only.'));
+  }
   makeSubtitles(): Promise<SubtitlesResult> {
     return Promise.resolve({ error: 'not_installed' });
   }
diff --git a/test/subread.test.ts b/test/subread.test.ts
index 7a8224e..babfe04 100644
--- a/test/subread.test.ts
+++ b/test/subread.test.ts
@@ -63,6 +63,10 @@ describe('the web fallback of the plugin', () => {
     });
   });
 
+  it('cannot open SubRead Overlay', async () => {
+    await expect(SubRead.openOverlay()).rejects.toThrow('Android only.');
+  });
+
   it('ignores a browser with no wake lock, and a refused request', async () => {
     vi.stubGlobal('navigator', {});
     await expect(SubRead.keepAwake({ on: true })).resolves.toBeUndefined();