feat(design): Micro-Widgets Studio with island content sources (#273, Phase 5) - #328
Conversation
…and translator Phase 4 (D4vidDf#272) data layer: ComposerTemplate/IslandTemplateDefinition model covering the 10 Xiaomi templates and their slots (left graphic, text, progress, buttons, rule), a Room-backed repository with a hand-written 1->2 migration (avoids destructive fallback wiping settings), a pure ComposerTemplateMatcher cloned from RulesEngine, and a ComposerTemplateTranslator hooked into NotificationReaderService just before the built-in translator dispatch. A matched template now wins over native Live Updates and the built-in per-type translators for STANDARD/MESSAGE/PROGRESS/DOWNLOAD/MEDIA notifications. Co-Authored-By: Claude Fable 5.1 <[email protected]>
Covers package/title/text regex matching, priority ordering, disabled templates, invalid-regex safety, and JSON round-tripping every field of IslandTemplateDefinition through the Room repository (fake in-memory DAO, matching this repo's existing testing conventions). ComposerTemplateMatcher is kept free of android.util.Log so it stays plain-JUnit testable. Co-Authored-By: Claude Fable 5.1 <[email protected]>
…y point Phase 4 (D4vidDf#272) Compose layer: a shared IslandTemplatePreview live pill plus 10 default-definition catalog wrappers for the Xiaomi templates, slot editors (left graphic, text/badge, progress, action buttons, rule binder with an app picker + advanced regex toggle), IslandComposerScreen (type switcher + live preview + gated editors + save), and ComposerTemplateListScreen (list/enable/delete), wired into DesignScreen and HomeScreen's hand-rolled route enum alongside the existing Widgets/Themes flows. Co-Authored-By: Claude Fable 5.1 <[email protected]>
NotificationReaderService dispatches each notification onto Dispatchers.Default (a real thread pool), so this singleton's regex cache can be hit concurrently by multiple notifications. Switch the plain mutableMapOf to a ConcurrentHashMap to avoid data races on the 24/7 service path. Co-Authored-By: Claude Fable 5.1 <[email protected]>
…tor, and pipeline hooks (D4vidDf#273) Lays the data/service foundation for the KWGT-style micro-widget studio: recursive CustomWidgetNode AST with kotlinx-serialization, a pure WidgetVariableEngine and WidgetDimensionValidator (JUnit-tested), a .hwidget file-based repository cloned from ThemeRepository, a RemoteViews CustomWidgetRenderer, and a CustomWidgetTranslator consulted from NotificationReaderService before the built-in translators (the minimal translator-registry hook, since Phase 3's real TranslatorRegistry does not exist yet). Also adds the "island content sources" add-on: a permission-protected UpdateSourceReceiver + Room-backed SourceRepository/allow-list feeding {source.<id>.text}/{source.<id>.icon}, wired into PermanentIslandManager so an allow-listed app can drive a micro-widget on the permanent island, plus a Settings screen to manage the allow-list. Bumps the settings Room DB to version 2 with an explicit migration for the two new tables. Co-Authored-By: Claude Fable 5.1 <[email protected]>
…oint (D4vidDf#273) Adds WidgetStudioScreen (a click-to-select spatial canvas with a numeric property inspector, a separate pure-Compose preview renderer kept in sync with the RemoteViews one via WidgetDimensionValidator) and SavedCustomWidgetsScreen (list + .hwidget import/export), then wires both into the Design tab's local router in HomeScreen.kt and a new "Widget Studio (Beta)" entry in DesignScreen's FAB sheet. Co-Authored-By: Claude Fable 5.1 <[email protected]>
Co-Authored-By: Claude Fable 5.1 <[email protected]>
… code (D4vidDf#273) CustomWidgetRepository.exportWidget() zipped into cacheDir/widget_exports, but res/xml/file_paths.xml only declares a cache-path root named "exports" - the Export button in WidgetStudioScreen would have thrown "Failed to find configured root" from FileProvider.getUriForFile at runtime. Reuse the same exports/ dir ThemeRepository already shares. Also removes the unused, inaccurately-documented getWidgetSync() (its doc comment claimed PermanentIslandManager used it; that class actually calls the suspend getWidget()), and adds a soft (warning-only, matching the existing overlap-check pattern) button-count check to WidgetDimensionValidator since HyperOS islands realistically fit only 2-3 tappable buttons. Co-Authored-By: Claude Fable 5.1 <[email protected]>
…ndings like templates (D4vidDf#273) - SourceRepository.update refuses a write for a source id currently owned by a different package (first allowed writer claims the id until its value expires), and lookup resolves to null once the owner has been revoked in Settings. - UpdateSourceReceiver drops updates whose self-reported owner package is not installed, so the allow-list never shows phantom entries. - NotificationReaderService: custom widget bindings now share the composer template eligibility (no CALL/NAVIGATION/SCREEN_RECORDING/TIMER) and also force the custom island path over native live updates; a rule-matched composer template keeps precedence over a per-package widget binding. - DesignRoute enum: restore the comma lost while merging the two entry points. Co-Authored-By: Claude Fable 5.1 <[email protected]>
…ries, never throw from hasBinding (D4vidDf#273) Caching "no binding" as a null value in a ConcurrentHashMap threw an NPE from resolveWidgetId() inside processStandardNotification, so every package without a custom widget lost its island (reproduced on device with an ntfy notification: no island, stack trace in HyperBridgeDebug). The cache now stores a small holder with a nullable id and a timestamp, entries expire after 15 s so a widget bound in the Studio is picked up without restarting the service, and hasBinding() catches and logs any failure, treating it as unbound. Co-Authored-By: Claude Fable 5.1 <[email protected]>
…d slot and give the pill an icon (D4vidDf#273) On a real HyperOS device the custom widget island showed an anonymous pill that never expanded: setCustomRemoteView only fills the notification's custom view, while the expanded island reads setCustomIslandExpandRemoteView, and without a big-island info block the collapsed pill has no content. The translator now sets both RemoteViews slots and uses the source app's icon for the collapsed pill (same recipe as WidgetTranslator); the permanent island path sets both slots too. Co-Authored-By: Claude Fable 5.1 <[email protected]>
…tification path too (D4vidDf#273) Only the permanent island filled the device values into VariableContext, so a micro-widget bound to an app rendered "Bateria % ·" with an empty bar. The battery/time helpers move to data/widget/DeviceVariables and both render paths use them. Co-Authored-By: Claude Fable 5.1 <[email protected]>
|
2026-09-16 03:43:58.782 4305-4667 JavaExceptionHandler com.miui.daemon E Process: com.d4viddf.hyperbridge, PID: 16295 (Fix with AI) |
…e upgrades instead of crashing (D4vidDf#273) The composer branch (D4vidDf#327) and this branch both declared version 2 with different schemas, so installing this build over a composer build failed with "Room cannot verify the data integrity" and the app killed itself. MIGRATION_1_2 is now identical to D4vidDf#327 (composer_templates only) and the source_apps / source_values tables move to a new MIGRATION_2_3. Co-Authored-By: Claude Fable 5.1 <[email protected]>
|
Thanks for the log, that one's on me. Both #327 and this branch declared Room Fixed in a7727bc:
Both upgrade paths work now: dev/0_6_0 (v1) → this build, and a #327 build (v2) → this build. Verified on my Note 15 Pro with the exact sequence you hit (composer APK, then this APK on top): DB goes 1 → 2 → 3, no Room error, settings intact, permanent island comes back. 378 unit tests + assembleDebug pass. No rush on merging, agreed that dev3 should go out first. |
|
We are going to work against the fetaure branch #273. This will let us create sub issues and keep working on the feature before merging the complete feature in the dev branch. |
…Screen Adds `DiagnosticsScreenReconnectServicePreview` to allow previewing `DiagnosticsContent` when notification access is granted but the service is disconnected (`serviceConnected = false`).
…time, not a flat 30 s (D4vidDf#335) (D4vidDf#337) * fix: permanent island yields to a native island for its declared lifetime, not a flat 30 s (D4vidDf#335) HyperOS 3 draws two islands side by side, the second one as an app-icon bubble. Re-posting the permanent island 30 s after a native island appears (D4vidDf#304) therefore shows a HyperBridge bubble next to any long-running native island such as the Clock stopwatch. Read the native island's own lifetime instead: Xiaomi's protocol declares param_v2.param_island.islandTimeout in seconds with a documented default of one hour. NativeIslandYieldPolicy turns a focus param into a yield window (declared timeout, 1 h default, 0 without param_island, 30 s for MediaStyle players and unreadable payloads), NativeIslandTracker keeps that window per sighting, and the re-assert timer now follows the remaining windows when a native island is forgotten. Co-Authored-By: Claude Fable 5.1 <[email protected]> * fix: permanent island also yields to bridged islands on HyperOS 3 (D4vidDf#335) D4vidDf#243 kept the permanent island posted underneath bridged islands so it was revealed the instant they collapsed. HyperOS 3 draws two islands at once, the older one as a mini island with only the app icon, so the posted pill becomes a HyperBridge bubble next to every WhatsApp island. Route PermanentIslandManager.desiredActive() through the existing PermanentIslandVisibilityPolicy, which already requires no active bridged island, widget island or VPN island. Co-Authored-By: Claude Fable 5.1 <[email protected]> * fix: time-limit the yield only for media players, other islands hide the pill while posted (D4vidDf#335) Review feedback on D4vidDf#337: HyperOS turns every notification that carries a MediaSession into an island player, so those are the only islands whose notification outlives the island (a paused player, D4vidDf#255) and the only ones that keep the short 30 s window. Any other native island now hides the permanent island for as long as its notification is posted; the only bound is the island lifetime it declares to HyperOS (param_island.islandTimeout, default 1 h), after which HyperOS has already dropped the island. - NativeIslandYieldPolicy: media wins over a focus param; an unreadable focus param means "hide until removed" instead of the old flat window. - NotificationReaderService: a media player is a MediaStyle template, a notification with a MediaSession token, or one carrying miui.focus.param.media. Co-Authored-By: Claude Fable 5.1 <[email protected]> --------- Co-authored-by: noelpatata2 <[email protected]>
Update `versionCode` to 38 and `versionName` to "0.6.0-dev5" in `app/build.gradle.kts`.
…fact (D4vidDf#349) (D4vidDf#352) * ci: add GitHub Actions workflow for PR test gating and debug APK artifact (D4vidDf#349) * ci: separate unit tests and debug apk build workflows with direct PR download links
Resolves the strings.xml conflict: the listener watchdog strings (D4vidDf#330) and the Island Template Composer strings (D4vidDf#272) were both appended at the end of the file. Keeps both blocks, watchdog first to match dev order. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Resolves two additive conflicts introduced by the listener watchdog work (D4vidDf#330) landing in the feature branch: - AndroidManifest.xml: keeps both ListenerReconnectReceiver and the Widget Studio receivers (WidgetActionReceiver, UpdateSourceReceiver). - strings.xml: keeps both the watchdog and Island Template Composer string blocks, watchdog first to match the base branch order. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
…D4vidDf#365) * feat: add system update island integration and customization settings Add support for the system updater package with a dedicated translator, timeout policy, and customization screen for configuring layout design, icon sources, and auto-hide timeouts. * feat: support system updates without progress and refine timeout policy * Add `hasProgress` check to SystemUpdateTimeoutPolicy to apply system update timeouts when progress is absent. * Handle non-progress system updates in SystemUpdateTranslator with customized layout configurations. * Refactor system update routing in NotificationReaderService and add unit tests.
…exists (D4vidDf#358) (D4vidDf#368) The app group from D4vidDf#334 needs a real summary notification, and HyperOS shows that summary as an ordinary "Hyper Bridge / Active Islands" row in the shade while it hides the Focus children. So on any HyperOS 3 phone below Android 16 the row sits in the shade for as long as one island (the permanent one included) exists, and users read it as a second island. Force grouping at 2 (config_autoGroupAtCount) only exists from Android 16, so the group and its summary are now a no-op below SDK 36. Android 16+ keeps the D4vidDf#334 behaviour unchanged. Co-authored-by: noelpatata2 <[email protected]>
…tor, Dynamic Engine, Theme Bundling & SAF Sharing (D4vidDf#271) (D4vidDf#353) * feat(translator): add custom translator model, database persistence, and room migration (D4vidDf#344) * feat(translator): define serializable CustomTranslator model and configuration schemas * feat(db): create TranslatorEntity and TranslatorDao with database migration from v1 to v2 * test(translator): add unit tests for serialization roundtrip and entity conversion * feat(translators): introduce dynamic custom translator framework (D4vidDf#345) * feat(translators): add DynamicTranslator and TranslatorRegistry for declarative notification parsing * feat(service): integrate custom translator evaluation and overrides into NotificationReaderService * feat(theme): add synchronous theme lookup by ID in ThemeRepository * fix(service): refine notification listener status check and watchdog binding on permission grant * test(translators): add tests for registry priority, scope/condition matching, and regex extraction * feat(ui): Design Hub Translators Section, App Config Screen Integration, Multi-Filtering & Visual Editor (D4vidDf#346) * feat(translators): custom translators manager, visual editor, and app integration * feat(translators): add visual translator editor with live island preview * feat(translators): add translator manager screen with search and filtering * feat(design): add translators carousel to design dashboard * feat(settings): integrate app-specific custom translator controls in app config * feat(service): expose NotificationReaderService instance for channel querying * feat(translator): presentation modes, theme binding, and progress editor enhancements * feat(translator): theme binding and installed themes support in editor and live preview * feat(translator): presentation mode, template, widget, and icon slot selection sheets * feat(translator): text template fields with dismissable variable chips and quick insertion * feat(translator): progress slot type selector, regex presets, and progress guide sheet * feat(i18n): string resources for presentation modes, themes, and progress configurations * feat(translator): inline reply action source and dynamic button configuration UI * feat(translator): add inline reply action source and broadcast handling * feat(ui): redesign translator action slot editor into action buttons with reordering and visibility toggling * feat(ui): add bottomsheet option pickers for action sources, smart types, display modes, and action guide * feat(i18n): update translator string resources and spanish translations for action buttons * feat(translator): overhaul behavior settings UI with engine mode selection and guide sheet * feat(translator): add engine selection and behavior guide bottom sheets * feat(translator): redesign behavior screen with sectioned cards and timeout sliders * feat(i18n): add English and Spanish strings for behavior guide and engine settings * feat(translator): compact pill customization, symmetrical island preview, and dynamic presentation * feat(translator): add compact pill config models and dynamic presentation logic * feat(ui): add pill customization editor, symmetrical preview layout, and selection sheets * feat(i18n): add English and Spanish string resources for compact pill settings * feat(translator): add HyperOS 3 island preview components and enhanced action handling * feat(ui): add HyperOS 3 expanded island and compact pill Compose components with previews * feat(ui): integrate interactive island preview switcher (expanded/compact pill) into translator editor * feat(translator): add progress bar, timer, theme highlight override, and themed action icon handling in `DynamicTranslator` * feat(translator): expand presentation template presets and update string resources * feat(translator): custom icon selection and execution priority editor sheets * feat(translator): add `iconName` property to translator metadata model * feat(translator): add icon selection and execution priority bottomsheets with presets and guide * feat(translator): render custom translator icons in editor and manager lists * refactor(translator): remove preview shell from conditions content * feat(translators): add advanced filter sheet, dynamic scope icon badges, and state sync * feat(translators): introduce multi-criteria filter bottom sheet for filtering by scope, notification types, apps, authors, and icons * feat(ui): update translator items and previews across screens with scope-colored icon badges and metadata chips * fix(translators): sync enabled status and priority updates back into translator JSON content * feat(translator): implement specialized dynamic templates, diagnostics integration, and allowlist bypass * feat(translator): add dynamic layout templates for media, calls, timers, progress, and standard notifications in `DynamicTranslator` * feat(translator): support avatar, sender, and large icon extraction for custom left slot graphics * feat(service): evaluate custom translators before type enablement checks and bypass app allowlist for targeted packages * feat(ui): integrate custom translator configurations and latest applied status into bug reports and diagnostics screens * feat(translator): update translator registry to evaluate notification types alongside categories for accurate scope matching * feat(translator): introduce system apps target scope and library allowance evaluation * feat(translator): add `SYSTEM_APPS` target scope and `isLibraryAllowed` matching logic in translator registry * feat(translator): split user launcher apps and system apps loading with dedicated selection bottom sheets in editor * feat(service): update notification reader to process custom translators for explicitly targeted system apps * feat(ui): add system apps scope color mappings, filter options, and metadata chips across manager and design screens * feat(i18n): add English and Spanish string resources for system apps scope and filters * Merge branch 'dev/0_6_0' into feature/271-custom-translators-framework (D4vidDf#347) * `test(ui)`: add preview for disconnected service state in DiagnosticsScreen Adds `DiagnosticsScreenReconnectServicePreview` to allow previewing `DiagnosticsContent` when notification access is granted but the service is disconnected (`serviceConnected = false`). * fix: permanent island yields to a native island for its declared lifetime, not a flat 30 s (D4vidDf#335) (D4vidDf#337) * fix: permanent island yields to a native island for its declared lifetime, not a flat 30 s (D4vidDf#335) HyperOS 3 draws two islands side by side, the second one as an app-icon bubble. Re-posting the permanent island 30 s after a native island appears (D4vidDf#304) therefore shows a HyperBridge bubble next to any long-running native island such as the Clock stopwatch. Read the native island's own lifetime instead: Xiaomi's protocol declares param_v2.param_island.islandTimeout in seconds with a documented default of one hour. NativeIslandYieldPolicy turns a focus param into a yield window (declared timeout, 1 h default, 0 without param_island, 30 s for MediaStyle players and unreadable payloads), NativeIslandTracker keeps that window per sighting, and the re-assert timer now follows the remaining windows when a native island is forgotten. * fix: permanent island also yields to bridged islands on HyperOS 3 (D4vidDf#335) D4vidDf#243 kept the permanent island posted underneath bridged islands so it was revealed the instant they collapsed. HyperOS 3 draws two islands at once, the older one as a mini island with only the app icon, so the posted pill becomes a HyperBridge bubble next to every WhatsApp island. Route PermanentIslandManager.desiredActive() through the existing PermanentIslandVisibilityPolicy, which already requires no active bridged island, widget island or VPN island. * fix: time-limit the yield only for media players, other islands hide the pill while posted (D4vidDf#335) Review feedback on D4vidDf#337: HyperOS turns every notification that carries a MediaSession into an island player, so those are the only islands whose notification outlives the island (a paused player, D4vidDf#255) and the only ones that keep the short 30 s window. Any other native island now hides the permanent island for as long as its notification is posted; the only bound is the island lifetime it declares to HyperOS (param_island.islandTimeout, default 1 h), after which HyperOS has already dropped the island. - NativeIslandYieldPolicy: media wins over a focus param; an unreadable focus param means "hide until removed" instead of the old flat window. - NotificationReaderService: a media player is a MediaStyle template, a notification with a MediaSession token, or one carrying miui.focus.param.media. --------- Co-authored-by: noelpatata2 <[email protected]> --------- Co-authored-by: noelpatata2 <[email protected]> Co-authored-by: noelpatata2 <[email protected]> * feat(translator): import, export, and share functionality for custom translators (D4vidDf#350) * feat(translator): introduce `TranslatorRepository` for packaging, extracting, and sharing `.htrans` archives (JSON and icons) and raw JSON files * feat(ui): add SAF-based import/export launchers and intent-based sharing actions to the translator manager screen * feat(theme): automatically discover and import bundled custom translators when processing themes * feat(i18n): add string resources for import, export, and sharing feedback messages --------- Co-authored-by: noelpatata2 <[email protected]> Co-authored-by: noelpatata2 <[email protected]>
…tions (D4vidDf#369) Introduce `RAW_PARAM_V2` presentation mode to allow Custom Translators to provide direct JSON payload templates for HyperOS. - `CustomTranslator`: add configurations for custom variables (regex extraction, image sources, step mapping), custom actions (smart extraction, inline replies), and `RawParamV2Config`. - `DynamicTranslator`: process custom variable and action extraction logic, including fallback chain evaluation for templates (e.g., `{notif.title | notif.text ?: 'Default'}`). Add validation and normalization for raw JSON templates and bundle assets dynamically. - `HyperOsIslandPreview`: support parsing and previewing raw JSON templates to reflect dynamic values in the UI. - `TranslatorEditorScreen`: adapt UI for `RAW_PARAM_V2` by showing a banner and hiding standard presentation configuration tabs (Pill, Progress, Actions) since they are handled within the JSON template. - Add serialization, fallback chaining, and JSON validation tests.
…) (D4vidDf#366) For MESSAGE islands the tap is routed through our own broadcast so we can cancel the source notification afterwards. That makes HyperBridge, not the system, the *sender* of the app's content PendingIntent. Since API 34 a sender no longer lends its background-activity-launch privilege unless it opts in via ActivityOptions, and since API 35 the creator denies it by default. Google Messages targets 35+, so its launch was silently dropped while the cancel still ran: notification gone, app not opened, message still unread. WhatsApp only worked because it still targets an older SDK. Opt in with MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS (ALLOWED below API 36) when sending the original intent from the click receiver. Co-authored-by: noelpatata2 <[email protected]>
… (D4vidDf#373) The "Hyper Bridge / Active Islands" summary is only released from onNotificationRemoved, and that callback only arrives while the listener is bound. Anything that takes the last island down outside that window -- a reboot, HyperOS killing the service, an app update, stop() cancelling the VPN island as the service dies -- leaves the summary behind with nothing under it, and the orphan sweep in syncNotifications skips summaries on purpose. On HyperOS the Focus children are hidden from the shade, so that orphaned summary is the only row left: it reads as an island that never goes away and cannot be dismissed without turning the whole group off. Make the summary a function of what is actually posted instead of a function of the events we happened to see: - BridgeIslandGroup.reconcile() releases the summary when no child is left and re-posts it when children outlive it (swiped-away summary, process death between the two posts). syncNotifications calls it, so it runs on listener connect and on every sync tick. - The VPN controller and the permanent island schedule the release right where they cancel, since both cancel while the service is going down. The release stays debounced, so a Shizuku cancel+repost of the only child is still not mistaken for an empty group. Co-authored-by: noelpatata2 <[email protected]>
Update `versionName` to "0.6.0-beta1" in `app/build.gradle.kts`.
Phase 3 landed in dev/0_6_0 (D4vidDf#353, D4vidDf#369), so the Composer no longer needs a store of its own. Resolved by taking dev/0_6_0 for AppDatabase, NotificationReaderService, DesignScreen and HomeScreen, and dropping the composer's parallel Room table, matcher and translator: templates ride the Phase 3 translator pipeline instead, which is what D4vidDf#272 Phase 4 is for.
…, Phase 4) Rework of the Composer along the lines David asked for in D4vidDf#327: templates are not a feature of their own any more, they are presets for the Phase 3 translator pipeline, and every preview goes through the component added for TranslatorScreen. - IslandTemplateCatalog: the ten official Xiaomi templates as named PresentationConfig presets. Reuses the ids the editor's picker already offered (so tpl_call_kit / tpl_media_compact keep the meaning DynamicTranslator gives them) and adds the two that were missing, boarding pass and courier tracking. Picking one produces an ordinary CustomTranslator with presentation.mode = TEMPLATE, so templates inherit matching, priority, theming and .htrans import/export instead of a parallel store. - HyperOsIslandPreview gains an IslandPreviewSample (so each template previews with content that suits it), a showChrome flag for thumbnail use, and resolves TEMPLATE presets before rendering. The three copies of the token replacement chain collapse into one helper, which also fixes the compact pill showing a raw {notif.title}. - Design tab: a Designs section listing the active designs, and Add design -> from a template (gallery of the ten, each previewed) or a custom design, which stays disabled until the Widget Studio lands. After picking a template the only question asked is the notification type. - Translator editor: the template sheet is the same gallery, and picking a template now also opens the slot editors, so each element can be bound to notification data alongside the match conditions already there. - DynamicTranslator fills the slots from the preset for a translator that only carries a templateId, so an imported .htrans renders as its template. Tests: 12 new IslandTemplateCatalog cases; :app:testDebugUnitTest is 416 green and :app:assembleDebug builds.
…as David asked (D4vidDf#328) The merge brings in Phase 3 (D4vidDf#353, D4vidDf#369) and the D4vidDf#272 rework, where a template stopped being a feature of its own and became a PresentationConfig preset on a normal translator. A custom design is the other half of that idea, so this resolves the merge by taking the same route and then works through the review notes from 2026-09-16. Merge resolution - AppDatabase: entities are now AppSetting + TranslatorEntity + the two source tables, at version 4. MIGRATION_1_2 is dev/0_6_0's custom_translators one byte for byte; 2->3 adds the source tables; 3->4 drops composer_templates and backfills custom_translators, which is what stops Room refusing to open the databases created by this branch's earlier test builds (the crash reported in this PR). - NotificationReaderService: the per-package hasBinding() hook is gone. A design is selected by TranslatorRegistry like anything else, and a matched translator whose presentation mode is WIDGET renders through CustomWidgetTranslator instead of DynamicTranslator's slot builder. CustomWidgetTranslator now takes a widgetId, so its binding cache disappears with the hook. - DesignScreen's "custom design" option is live and opens the Studio. Studio Design (was Widget Studio) - Renamed throughout: "Widget Studio" read as Android's app widgets. - KWGT-style layout: the canvas and the controls are on screen together, a tab row switches between the elements of the design, the top bar saves or restores the last saved state, and Add is a FAB opening a screen of element types. - Drag to move and a corner grip to resize, inside free-positioned groups; new designs start as an ABSOLUTE root so both work immediately. - Layers: child order is the stacking order, with Bring forward / Send back. - Conditions: an element can be shown only when the notification has a given button, an inline reply, a smart action, progress, or a value that is not blank / matches a regex. - Tap actions on any element, not just buttons: dismiss, the app's own button, reply, a smart action, open an app or link, or a custom broadcast. - The canvas backdrop is a fixed dark, like a real island, instead of following colorScheme and going light-on-light in dark mode. - Saving writes the .hwidget document and upserts the translator that makes it a design, so "shown for" lives on the design tab. Tests: 456 green, including 19 new cases for the condition evaluator, layer moves and the .hwidget round trip. That round trip caught a real bug: both HasSmartAction and SmartAction had a `type` property, which collides with the polymorphic discriminator and threw on encode; they serialize as `smart_action` now.
|
Went through your list. Rebased on beta1 with the reworked #327 underneath, and rebuilt the editor.
Since #272 made a template a preset on a normal translator, a design is the same thing with DB is v4: 1→2 is your One thing on your side: 456 unit tests green and CI passes. Same caveat as #327 though: I'm away from my phone today, so the new editor screens are compiled and unit tested only, not run on a device. The round-trip test did catch one real bug — |
…s saved (D4vidDf#272) effectivePresentation() merged the preset slot by slot, comparing each whole slot to the class defaults, and refilled an empty action list from the preset. Two silent losses: editing one text field of a translator that still carried a bare templateId dropped the preset's other fields (the payment template's highlight / OTP text), and deleting a preset's only action brought it back on every render. A template is now either bare (only a templateId: rendered as the preset) or concrete (rendered as saved). The editor materializes it when a translator is loaded, when the mode is switched to TEMPLATE, and when a template is picked (applyTemplate), so every edit and deletion sticks. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
…s as untrusted (D4vidDf#273) - WidgetDimensionValidator only reported MAX_DEPTH / MAX_NODE_COUNT and kept every node, and no caller rejected on its errors. The clamp now cuts the tree at those limits, so the renderer, the condition pruner and the clamp itself never recurse past them. - Import checks widget.json's bracket nesting before decoding: kotlinx decodes the node tree recursively and a StackOverflowError is an Error, which no catch in the path stopped. - The widget id from widget.json is a folder name that gets deleteRecursively()'d: an id like "../../databases" wiped app data. Unsafe ids are replaced with a generated one, and get/delete/export ignore them. - Unpacked size and widget.json size are capped (zip bomb), and the zip-slip prefix check now includes the separator. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
…e the source id (D4vidDf#273) - UPDATE_SOURCE trusted a self-reported owner_package, and the permission is normal, so any app could write an allowed app's source. The owner is now the platform-reported sender (getSentFromPackage, sender shares its identity via BroadcastOptions); owner_package is optional and must match. - The source id was used unsanitized as sources/<id>.png: '../' escaped the icons dir. Ids are now [A-Za-z0-9._-], max 64, no leading dot. Text is capped at 512 chars. - Expired rows are pruned together with their icon files instead of leaving them behind. - Widget PendingIntents used the node id as request code, so two islands from one design (or two designs sharing a node id) overwrote each other's intent: Dismiss on one closed the other. The request code now includes the design id and bridge id. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
|
Pushed some hardening: the depth/node limits are actually enforced now, .hwidget imports are treated as untrusted (id, size, nesting), and content sources use the real sender via |
…lly extracts (D4vidDf#272) The preset used {regex.1}, which nothing in DynamicTranslator's variable map ever fills, so the payment island's highlight / pill text rendered blank. It now uses {smart_action.OTP.code}, filled by the existing OTP extraction. A test pins every preset to variables the translator provides. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
|
Tested on the Note 15 Pro: the widget island renders, and Open app / Deep link buttons both launch fine (SystemUI sends them, so no BAL opt-in needed there). |
…rt timer (D4vidDf#379) Since D4vidDf#337 a focus island can hold the permanent island for its declared lifetime (up to an hour), so NativeIslandTracker's wall clock matters: a timezone change, manual edit or NTP step inside the window stretched or cut it short, and the window never self-heals because note() keeps the first-seen time. It now defaults to elapsedRealtime. nativeYieldJob is rescheduled from listener callbacks and from the IO sync loop; the cancel/replace is now done under a lock so a timer can't be orphaned between them. Co-authored-by: noelpatata2 <[email protected]>
…dDf#368, D4vidDf#373) (D4vidDf#380) * Revert "fix: drop the island group summary once no island is left (D4vidDf#372) (D4vidDf#373)" This reverts commit 435b0bf. * Revert "fix: only group bridged islands on Android 16+, where force grouping exists (D4vidDf#358) (D4vidDf#368)" This reverts commit 7fe8c03. * Revert "fix: keep bridged islands in one app group so Android 16+ cannot force-group and silence them (D4vidDf#331) (D4vidDf#334)" This reverts commit 2c0d183. --------- Co-authored-by: noelpatata2 <[email protected]>
…idDf#359 follow-up) (D4vidDf#377) VpnIslandController.disconnect() sent the provider's disconnect PendingIntent from our receiver without the BAL opt-in D4vidDf#366 added for the message island tap. OpenVPN for Android's disconnect is an activity (DisconnectVPN), so on Android 15+ the launch was dropped silently, send() did not throw, and the island sat on "disconnecting" until the verify timeout. Both call sites now share PendingIntent.sendAllowingBackgroundLaunch(). Co-authored-by: noelpatata2 <[email protected]>
Update `versionCode` to 40 and `versionName` to "0.6.0-beta3" in `app/build.gradle.kts`.
…dcast trampoline (D4vidDf#371, D4vidDf#382) (D4vidDf#386) Co-authored-by: noelpatata2 <[email protected]>
|
Updated branch with all the changes made on #390 Once the conflict are resolved I'll start working on ui/ux improvements to make the experience as easy as possible. Thank you for the work |
Takes David's D4vidDf#390/D4vidDf#391 versions of the composer, Design hub and 0.6 service code, and re-applies only the Studio (D4vidDf#273) changes on top: - Design Manager opens WIDGET-mode designs in the Studio instead of the translator editor - dropped the leftover BridgeIslandGroup.scheduleRelease calls (the group was reverted in D4vidDf#380) - Room stays v2 upstream + our v3 (source tables) / v4 (composer test builds) migrations Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
|
the branch needs to be updated with dev/0_6_0 changes before i can continue with the development |
…udio # Conflicts: # app/src/main/java/com/d4viddf/hyperbridge/service/NotificationReaderService.kt
|
Done, merged dev/0_6_0 (beta3) in 👍 |
1966848
into
D4vidDf:feature/273-feature-kwgt-style-micro-widgets-studio-spatial-island-builder-phase-5






Summary
Studio Design: the element-by-element island builder (#273), reworked against the review notes in this comment and rebased on 0.6.0-beta1 with the reworked #327 underneath.
Fixes #273
Built with AI assistance (Claude Code), as I always disclose when AI tooling is involved.
Your review notes
ABSOLUTEroot so both work from the first element. Numeric fields stay, in sync with the canvas.colorScheme.inverseSurface, which went light-on-light in dark mode.What the rebase changed
Phase 3 landed in beta1, and #272 turned a template into a
PresentationConfigpreset on an ordinary translator. A custom design is the same idea withpresentation.mode = WIDGET+widgetId, which your model already reserved, so:CustomWidgetTranslator.hasBinding()used to be consulted for every posted notification; nowTranslatorRegistryselects a design like any other translator andNotificationReaderServiceroutes a WIDGET-mode match to the renderer. The binding cache (and the NPE class of bug it caused) goes with it..hwidgetdocument and its translator, so matching, priority, scope and conditions come from the same place as everything else. "Shown for" is on the Design tab.CustomWidgetDocument.boundPackagestays in the format for imported files but no longer selects anything.Database
Version 4, three migrations:
custom_translators, byte-identical todev/0_6_0source_apps,source_valuescomposer_templates, backfillscustom_translators3 → 4 exists only for the test builds of this branch: those shipped a schema whose v2 was the composer's table. Reaching v4 through a migration instead of matching an existing version is what stops Room refusing to open them with "Room cannot verify the data integrity" — the crash you hit on 09-16.
Island content sources
Unchanged from the previous revision: a permission-gated
UPDATE_SOURCEbroadcast, an allow-list per app,source_idowned by the first allowed writer, and{source.<id>.text}/{source.<id>.icon}bindings. Source identity is still self-reported — aBroadcastReceivercannot prove its sender — so the custom permission, the installed-package check and the allow-list are what stand in for it.Testing
:app:testDebugUnitTest— 456 green, including 19 new cases for the condition evaluator, the layer moves and the.hwidgetround trip.:app:assembleDebugbuilds.HasSmartActionandSmartActionboth had atypeproperty, which collides with kotlinx-serialization's polymorphic discriminator and threw on encode. They serialize assmart_actionnow.Checklist for a device run
Still descoped
ContactAvatarOf,TextNode.marqueeandProgressStyle.RINGare modeled but no-ops in theRemoteViewsrenderer.🤖 Generated with Claude Code