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();