Skip to content

feat(platform): treat HarmonyOS as a mobile target - #270

Open
TiaraBasori wants to merge 15 commits into
iebb:masterfrom
TiaraBasori:feat/ohos-platform
Open

TiaraBasori wants to merge 15 commits into
iebb:masterfrom
TiaraBasori:feat/ohos-platform

Conversation

@TiaraBasori

@TiaraBasori TiaraBasori commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

Change

Adds HarmonyOS (NEXT) as a mobile platform target.

The ohos Flutter fork (openharmony-sig/flutter_flutter 3.44.9+ohos, same Dart 3.12.2 lineage as this repo's CI pin) reports Platform.operatingSystem == 'ohos'. The stock SDK's TargetPlatform has no such value, so the detection uses the operating-system string instead of a TargetPlatform.ohos literal — stock CI keeps compiling.

  • New lib/platform/ohos_support.dart: isOhos / isMobilePlatform helpers, pure mapping covered by VM tests. A widget test that simulates Android/iOS through debugDefaultTargetPlatformOverride still counts as mobile (the override is null on real devices, so production decides purely by OS).
  • td_bindings.dart: on ohos, DynamicLibrary.open('libtdjson.so') resolves by soname from the hap's libs/arm64-v8a, same as Android jniLibs.
  • Mobile-form-factor branches (Platform.isAndroid || Platform.isIOS) converge on isMobilePlatform (immersive status bar, orientation, app-lock biometrics, backup consent).
  • Mobile form factor is deliberately split from native capability: a capability the host does not implement degrades to its own unsupported result at the production boundary instead of throwing MissingPluginException at call time.
    • AccountBackupService.isSupported / probes catch MissingPluginException/PlatformException and report unsupported; the login restore icon follows the answered capability (_canRestoreAccountBackup).
    • MediaLibrarySaver: a missing photo_manager port returns the same unsupported result desktop gets (a real ohos port lights it up without a code change); the distinct permissionDenied path is preserved.
    • local_app_lock_controller.dart: a missing secure-storage port degrades to "no lock configured"; a real PlatformException still fails closed; a missing local_auth port reports biometrics unavailable.
    • Call screen: native video views follow manager.supportsMediaCalls (engine capability), not the form factor.
    • Now-playing, sticker export, wallpaper tilt, orientation, system UI: audited, already fail-safe via existing catch arms.
  • api_credentials_view.dart / app_icon_controller.dart: exhaustive switches get a _ catch-all for the fork's extra enum value.

Related issues: none. Companion PRs: #271 (host project), #272 (dependencies), #273 (release CI). TDLib cross-build: TiaraBasori/mithka-tdjson#4.

Telegram API Terms classification

  • Compliant/unchanged: this change complies with applicable Telegram API Terms, preserves Telegram's basic functionality, and does not introduce behavior covered by section 1.4.
  • Section 1.4 exception: the entire feature is guarded by allowTelegramTosViolations (I_DONT_FUCKING_CARE_ABOUT_TOS), disabled in official builds, and available only in an explicitly enabled self-build.
  • Disallowed or unresolved: this PR must not be approved or merged until its classification and implementation are resolved.

Reason and affected Telegram behavior: platform plumbing only — OS detection, dynamic-library loading, status-bar/orientation handling, and capability degradation for unimplemented native channels. No Telegram API behavior, no new TDLib calls, no content policy or service destination changes. Form-factor and capability logic are Android/iOS/desktop-identical (pinned by the keeps-saving / still-fails-closed tests).

Basic checks (every PR)

  • I reviewed the diff for correctness, unintended behavior, secrets, and unrelated changes.
  • Formatting passed, or I recorded why it is inapplicable: dart format --set-exit-if-changed over the 11 touched lib files — 0 changed.
  • Analysis passed, or I recorded why it is inapplicable: flutter analyze at head 9453c285 — no new issues (only the 12 pre-existing unawaited_return_in_try_block warnings already on master).
  • Relevant tests passed, or I recorded their inapplicability; name the commands and test cases below. Quality CI runs flutter test --concurrency=4.
  • I checked affected build/platform paths and applicable localization/generated files; list the evidence below.
  • I completed the Terms classification above and verified that official builds cannot enable section 1.4 exceptions.

Commands, results, platform/build/UI evidence, and reasons for inapplicable checks:

At head 9453c285, run serially on the PR worktree (4G box, one compile at a time):

  • flutter test test/mobile_fullscreen_video_player_widget_test.dart — 33/33 passed (the suite review found failing on the first head, including video-more-orientation at :425; the explicit-override fix restores it).
  • flutter test test/account_backup_consent_test.dart test/call_screen_test.dart test/local_app_lock_controller_test.dart test/media_library_saver_test.dart — 41/41 passed (15 new boundary cases across the four suites; missing-channel, denied-permission, missing-biometrics and answering-implementation paths).
  • flutter test test/ohos_support_test.dart test/local_app_lock_controller_test.dart — 14/14 passed.
  • Full flutter test runs in Quality CI on this head (link in checks), including the three l10n gates.
  • No l10n string changes: translations/tools/check.py untouched, no visible literals introduced.
  • Build/platform paths: stock-SDK compilation is CI-verified (Analyze and test green); the assembled ohos target builds in feat(ohos): add the HarmonyOS host project #271/feat(deps): federated HarmonyOS plugin implementations #272/feat(ci): build the HarmonyOS hap in the release pipeline #273 (credential-free, no local ohos toolchain on the dev box).

Section 1.4 exception checks

Not applicable.

@github-actions

github-actions Bot commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

Automated PR review

Head: 7a15a8f7c7c7d73724fa9805ad5e6898f7d2bc6f · master: 73c1d46bb5a726e68c9bef160ccaf72a27725ef1
LLM recommendation: needs_human. This is not a GitHub approval.
Auto-merge: blocked. Only iebb can authorize the current head.

Review unavailable: Malformed model finding; manual review required.

Remaining gates

  • Waiting for iebb to approve this exact current head.
  • Workflow, controller, policy, or credential-file changes require a manual merge.
  • A reviewer still requests changes; obtain a superseding review or dismiss it explicitly.
  • All current head checks must complete successfully (no skipped/neutral substitutes).
  • master source incomplete: .github/workflows/release.yml
  • head source incomplete: .github/workflows/release.yml
  • master source incomplete: lib/app/desktop_image_preview_window.dart
  • head source incomplete: lib/app/desktop_image_preview_window.dart
  • master source incomplete: lib/auth/login_view.dart
  • head source incomplete: lib/auth/login_view.dart
  • Context budget reached; remaining source omitted.

No PR code runs with the model key or write token. Free-only routing; provider/rate-limit failures never authorize a merge. Existing review findings must be resolved, not hidden by a new recommendation.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review 🔄 Running since 2026-10-23T17:11:28.335730Z 7784f8c PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@iebb iebb left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed current head 7784f8c and its actual failing Quality evidence. This is a useful direction, but is not ready to merge.

  1. [P1] Preserve UI target-platform behavior. isMobilePlatform reads Platform.operatingSystem, but VideoPlayerView previously gated its orientation control on defaultTargetPlatform. On the Linux-hosted iPhone widget test this now removes video-more-orientation: mobile_fullscreen_video_player_widget_test.dart:425 fails with zero matches. Evidence: https://github.com/iebb/mithka/actions/runs/39993012046 . Keep the UI platform boundary mockable/preserve its existing semantics while adding OHOS, and rerun that focused production-widget test plus the full suite. This is a hosted iPhone-layout test finding, not a claim about a physical iPhone.

  2. Split mobile form factor from implemented native capabilities. AccountBackupService now becomes eligible on OHOS, but its isSupported probe directly calls mithka/account_backup; the submitted OHOS host registers generated plugins and contains no implementation of that custom channel. Gallery saving is also enabled while the dependency PR explicitly lacks photo_manager. Unsupported native operations must return a safe unsupported result rather than being advertised just because the OS is mobile. Cover missing channels, unavailable biometrics and denied permissions at the production boundary.

Please complete the repository PR template's Terms classification/basic checks and attach a successful credential-free build of the assembled OHOS target. The new pure OS-string mapping tests do not cover these affected UI/native entry points. Keeping this legitimate PR open.

TiaraBasori added a commit to TiaraBasori/mithka that referenced this pull request Oct 24, 2026
Review finding iebb#2 on PR iebb#270: AccountBackupService became eligible on OHOS
because isMobilePlatform now includes it, but its isSupported probe calls
the mithka/account_backup channel the OHOS host never implements — and
gallery saving was likewise advertised while photo_manager's channel is
absent there. A mobile form factor says nothing about which native
channels a host implements, so each capability now degrades to its own
unsupported result at the production boundary instead of throwing
MissingPluginException at call time:

- AccountBackupService: the isSupported/isLocalStorageSupported probes
  catch MissingPluginException/PlatformException and report false; the
  login restore icon follows the answered capability
  (_canRestoreAccountBackup) instead of the form factor alone.
- MediaLibrarySaver: save/savePreparedFile translate a missing
  photo_manager channel into MediaLibrarySaveResult.unsupported, the
  same answer a desktop host gets.
- LocalAppLockController: a missing flutter_secure_storage port reads as
  no lock configured (nothing was ever persisted) instead of failing
  closed behind a gate no credential could open; a real storage error
  still fails closed.
- CallScreen: native video views and the flip-camera affordance follow
  the engine capability (supportsMediaCalls, Android/iOS tgcalls) rather
  than the form factor, so a mobile host without the engine keeps the
  placeholder glyph.

Behavior on Android/iOS/Windows/macOS/Linux is unchanged: the form
factor and the capability still agree there, and the new branches only
fire when the channel is absent.
@TiaraBasori

Copy link
Copy Markdown
Contributor Author

Finding 2 addressed at head 22f7862 — the mobile form factor is now split from implemented native capability at every isMobilePlatform/isOhos call site:

  • AccountBackupService (the named offender): isSupported/listBackups/backupActiveAccountIfEnabled probes catch MissingPluginException/PlatformException and report unsupported; login_view.dart hides the restore icon until the channel actually answers (_canRestoreAccountBackup), mirroring the consent row.
  • Gallery saving (media_library_saver.dart): a missing photo_manager port returns the same unsupported result desktop gets — host-agnostic, so the upstream ohos port lights it up without a code change. Distinct permissionDenied path preserved.
  • App lock (local_app_lock_controller.dart): a missing secure-storage port degrades to "no lock configured" (nothing was ever persisted — the previous behavior bricked the app behind an unclearable fail-closed gate); a real PlatformException still fails closed; missing local_auth port → biometrics unavailable.
  • Call screen: native video views follow manager.supportsMediaCalls (the real engine capability), not the form factor; a mobile host without the call engine keeps the placeholder view (zero AndroidView/UiKitView).
  • Now-playing, sticker export, wallpaper tilt, orientation, system UI: audited, already fail-safe via existing catch arms — documented, no change.

Android/iOS/desktop behavior is byte-identical (form factor and capability agree there; the new branches never fire — pinned by the "keeps saving"/"still fails closed" tests). 15 new boundary cases across 4 suites, all green: +10 / +11 / +7 / +13.

The image ref note from mithka-tdjson#4 (the original container ref was …/docker, the package actually lives at …/docker/image) is fixed there at 228e9d3b.

TiaraBasori added a commit to TiaraBasori/mithka that referenced this pull request Oct 24, 2026
Review finding iebb#2 on PR iebb#270: AccountBackupService became eligible on OHOS
because isMobilePlatform now includes it, but its isSupported probe calls
the mithka/account_backup channel the OHOS host never implements — and
gallery saving was likewise advertised while photo_manager's channel is
absent there. A mobile form factor says nothing about which native
channels a host implements, so each capability now degrades to its own
unsupported result at the production boundary instead of throwing
MissingPluginException at call time:

- AccountBackupService: the isSupported/isLocalStorageSupported probes
  catch MissingPluginException/PlatformException and report false; the
  login restore icon follows the answered capability
  (_canRestoreAccountBackup) instead of the form factor alone.
- MediaLibrarySaver: save/savePreparedFile translate a missing
  photo_manager channel into MediaLibrarySaveResult.unsupported, the
  same answer a desktop host gets.
- LocalAppLockController: a missing flutter_secure_storage port reads as
  no lock configured (nothing was ever persisted) instead of failing
  closed behind a gate no credential could open; a real storage error
  still fails closed.
- CallScreen: native video views and the flip-camera affordance follow
  the engine capability (supportsMediaCalls, Android/iOS tgcalls) rather
  than the form factor, so a mobile host without the engine keeps the
  placeholder glyph.

Behavior on Android/iOS/Windows/macOS/Linux is unchanged: the form
factor and the capability still agree there, and the new branches only
fire when the channel is absent.
TiaraBasori added a commit to TiaraBasori/mithka that referenced this pull request Oct 24, 2026
Review finding iebb#2 on PR iebb#270: AccountBackupService became eligible on OHOS
because isMobilePlatform now includes it, but its isSupported probe calls
the mithka/account_backup channel the OHOS host never implements — and
gallery saving was likewise advertised while photo_manager's channel is
absent there. A mobile form factor says nothing about which native
channels a host implements, so each capability now degrades to its own
unsupported result at the production boundary instead of throwing
MissingPluginException at call time:

- AccountBackupService: the isSupported/isLocalStorageSupported probes
  catch MissingPluginException/PlatformException and report false; the
  login restore icon follows the answered capability
  (_canRestoreAccountBackup) instead of the form factor alone.
- MediaLibrarySaver: save/savePreparedFile translate a missing
  photo_manager channel into MediaLibrarySaveResult.unsupported, the
  same answer a desktop host gets.
- LocalAppLockController: a missing flutter_secure_storage port reads as
  no lock configured (nothing was ever persisted) instead of failing
  closed behind a gate no credential could open; a real storage error
  still fails closed.
- CallScreen: native video views and the flip-camera affordance follow
  the engine capability (supportsMediaCalls, Android/iOS tgcalls) rather
  than the form factor, so a mobile host without the engine keeps the
  placeholder glyph.

Behavior on Android/iOS/Windows/macOS/Linux is unchanged: the form
factor and the capability still agree there, and the new branches only
fire when the channel is absent.

@TiaraBasori TiaraBasori left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Finding 1 (preserving UI target-platform behavior) addressed at head 9453c28 — the same head announced for finding 2, via c345a45 + a7997d1 "let widget tests pin the mobile branch".

What changed. isMobilePlatform previously read only Platform.operatingSystem. On the Linux-hosted iPhone widget test this made mobile_fullscreen_video_player_widget_test.dart:425 match zero (the mobile-gated orientation action video-more-orientation was never offered). The helper now also returns true when debugDefaultTargetPlatformOverride is explicitly TargetPlatform.android/iOS; the override is null on real devices, so production still decides purely by operating system (ohos included).

Evidence (the failing evidence now passes).

  • Focused hosted suite at this head, on the stock SDK (CI's 3.44.2 — /tmp/flutter-3.44.2 on the dev box): flutter test test/mobile_fullscreen_video_player_widget_test.dart → 33/33 passed, including the video-more-orientation case review cited. No change in app behavior on Android/iOS/desktop (form factor and capability agree there; the new branch never fires — pinned by the keeps-saving / still-fails-closed tests).
  • Full suite in Quality CI on this head: green (13m13s). Format Formatted 11 files (0 changed), analyze no new issues (only the 12 pre-existing unawaited_return_in_try_block already on master).

Finding 2's capability split remains as described in the previous comment (9453c28 "split mobile form factor from native capability" + 41/41 boundary tests across four suites); production behavior is Android/iOS/desktop-identical.

@iebb iebb left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed exact head 9453c285b591f54e15f18479defb288eaa983084. Changes requested before publication.

[P1] Preserve a known app lock when secure-storage capability disappears. In lib/security/local_app_lock_controller.dart:156–162,206–228, any MissingPluginException calls _resetForMissingSecureStorage(), clearing stored lock, locked state and storage-unavailable state. That catch includes reload of a known existing lock and successful credential read followed by failure reading the attempts channel—not just a first install on an unsupported host. This creates a source-derived fail-open path into previously protected UI.

Explicitly distinguish “unsupported capability on a never-configured install” from storage failure/known-lock reload. Preserve known protection and fail closed where state is uncertain. Add production-boundary regressions for existing-lock reload with missing storage and credential-read-success/attempts-read-failure; retain the safe unsupported-first-install behavior separately.

I withdraw the old blanket backup/gallery/missing-native-call crash concerns: the current capability probes, unsupported results and media-engine branches address those named paths with tests. The mandatory author template is now complete; do not interpret this review as still demanding missing author checkboxes. api_credentials_view.dart is ordinary reviewed UI with a generic device-label fallback, not an omitted credential file or protected automation path. No new Terms exception was identified.

The platform effort remains useful. Please supply accurate assembled-target build/capability evidence after the lock fix. This is a statically traced security risk, not a claim that a new device exploit or runtime crash was reproduced.

No approval is granted. Current-base merged-result Quality is not yet fresh in the reviewed evidence; before publication require fresh ordinary PR Quality on this exact head merged with live master, applicable platform checks, resolved findings and genuine exact-head owner approval. No incoming PR code was executed for this review.

@TiaraBasori

Copy link
Copy Markdown
Contributor Author

Addressed the [P1] fail-open path at new head 744cb4de.

A known lock now fails closed when secure storage vanishes. The old catch treated every MissingPluginException as the unsupported-first-install case and reset the stored lock, the locked state and the storage-unavailable flag — but that catch also covers the two paths you traced: reloading a known existing lock, and a credential read that succeeds before the attempts read fails. Both silently disabled existing protection. The reset now applies only when no lock is known (nothing ever persisted — the HarmonyOS first-install case stays exactly as before, still pinned by the existing a mobile host without secure storage stays unlocked instead of failing closed test). A known lock — read this pass (value != null) or established by an earlier one (_stored != null) — takes _failClosedForStorageError instead, in both initialize and reloadFromStorage.

One ordering subtlety the regression forced out: the known-lock flag is recorded before the attempts read (that read is exactly where the plugin can vanish next), not after the whole happy path.

Regressions (test/local_app_lock_controller_test.dart, 13/13 with the two new):

  • a known lock that loses secure storage mid-read fails closed — the credential record reads back fine, the attempts read throws MissingPluginException: enabled, locked and storage-unavailable all hold. On the previous head this fails with Expected: true / Actual: <false> (the reset answered "no lock configured").
  • reloading a known lock against a vanished implementation fails closed — initialize on a working store, the plugin gone before the desktop-sync reload: the old reset path unlocked the app.
  • Mutation-verified: restoring the unconditional reset in initialize fails the first case; re-applied, all 13 pass.

Gates at this head with CI's toolchain (/tmp/flutter-3.44.2): dart format clean, flutter analyze only the 12 pre-existing warnings already on master, local_app_lock_controller_test 13/13.

The assembled OHOS build/capability evidence you asked for after the lock fix is the remaining platform item; this head carries no OHOS host change, so the existing host stack (PR #271/#272/#273) remains the place where that build lands.

@iebb iebb left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-reviewed exact head 744cb4de00e77aabbb056c4db9817776bb2d1896. The two new same-process regressions address the previously identified known-lock reload and credential-read/attempt-read failure paths; those improvements are acknowledged.

[P1] Fresh startup still assumes missing storage means no existing lock. In lib/security/local_app_lock_controller.dart, a newly created controller starts with _missingStorageKnownLock == false. If its first credential read throws MissingPluginException, initialization still calls _resetForMissingSecureStorage() and exposes the unlocked UI. An existing protected installation whose plugin disappears on a new launch takes the same branch as a never-configured unsupported host; neither new test covers this restart boundary. The secure read failing is not evidence that nothing was previously persisted.

Constrain the unsupported-host exception to a positively identified never-supported capability/installation, and keep ordinary supported hosts or uncertain previously protected state fail-closed. Add a fresh-controller/first-read-missing regression, alongside the two existing known-lock tests and a separately justified unsupported-first-install case. This is a static control-flow finding, not a claim of a reproduced device exploit. Do not add plaintext credential fallback.

Current-base Quality and assembled OHOS capability/build/startup evidence are still required before merging this platform change. Keep this legitimate improvement open for revision.

TiaraBasori added a commit to TiaraBasori/mithka that referenced this pull request Oct 27, 2026
Review finding iebb#2 on PR iebb#270: AccountBackupService became eligible on OHOS
because isMobilePlatform now includes it, but its isSupported probe calls
the mithka/account_backup channel the OHOS host never implements — and
gallery saving was likewise advertised while photo_manager's channel is
absent there. A mobile form factor says nothing about which native
channels a host implements, so each capability now degrades to its own
unsupported result at the production boundary instead of throwing
MissingPluginException at call time:

- AccountBackupService: the isSupported/isLocalStorageSupported probes
  catch MissingPluginException/PlatformException and report false; the
  login restore icon follows the answered capability
  (_canRestoreAccountBackup) instead of the form factor alone.
- MediaLibrarySaver: save/savePreparedFile translate a missing
  photo_manager channel into MediaLibrarySaveResult.unsupported, the
  same answer a desktop host gets.
- LocalAppLockController: a missing flutter_secure_storage port reads as
  no lock configured (nothing was ever persisted) instead of failing
  closed behind a gate no credential could open; a real storage error
  still fails closed.
- CallScreen: native video views and the flip-camera affordance follow
  the engine capability (supportsMediaCalls, Android/iOS tgcalls) rather
  than the form factor, so a mobile host without the engine keeps the
  placeholder glyph.

Behavior on Android/iOS/Windows/macOS/Linux is unchanged: the form
factor and the capability still agree there, and the new branches only
fire when the channel is absent.
@TiaraBasori

Copy link
Copy Markdown
Contributor Author

Rebased onto current master 8f221c0b — same changes, new head 13028418; the earlier comment's head hash is superseded by this one. Pushed to settle the quality.yml base race and to fold in the l10n regeneration from the merged canonical XML.

iebb commented Oct 29, 2026

Copy link
Copy Markdown
Owner

Maintainer policy update: one feature/fix must have one canonical open PR per repository. Please consolidate the HarmonyOS platform, host, dependency and build work from #270, #271, #272 and #273 into this existing PR (#270), preserving the intended changes, review findings and validation evidence. Update this PR for subsequent fixes/tests/docs/CI; do not open another replacement or stacked PR for the same feature. The native producer iebb/mithka-tdjson#4 remains its own repository's PR. Close the superseded Mithka PRs only after their work and outstanding findings are preserved in the canonical diff.

Current read-only review against master 6aef1d22: these heads are unchanged. A new integration conflict is that master deleted test/call_screen_test.dart during the test reduction, while #270 modifies it. Preserve the useful missing-capability regression in a retained focused suite when reconciling this; do not restore the entire removed suite or drop the boundary check just to clear the conflict.

The previously requested assembled-target evidence is still necessary. I rechecked the exact pinned TDJSON OHOS release tag used by #271/#273; GitHub still returns 404. Consolidation does not approve the outstanding loader, build-status, capability and immutable-source/publication findings. Require the real pinned artifact and a credential-free, read-only Actions build/startup of the combined source before merge. No incoming code ran locally and no release workflow was dispatched.

isMobilePlatform read only Platform.operatingSystem, so widget tests that
simulate Android or iOS through debugDefaultTargetPlatformOverride kept
seeing the desktop host and lost every mobile-gated surface: the fullscreen
orientation action and the backgrounded-playback controller lifecycle.

Count an explicit Android/iOS override as mobile as well. The override is
null on real devices and profiles, so production still decides purely by
operating system (ohos included).
Review finding iebb#2 on PR iebb#270: AccountBackupService became eligible on OHOS
because isMobilePlatform now includes it, but its isSupported probe calls
the mithka/account_backup channel the OHOS host never implements — and
gallery saving was likewise advertised while photo_manager's channel is
absent there. A mobile form factor says nothing about which native
channels a host implements, so each capability now degrades to its own
unsupported result at the production boundary instead of throwing
MissingPluginException at call time:

- AccountBackupService: the isSupported/isLocalStorageSupported probes
  catch MissingPluginException/PlatformException and report false; the
  login restore icon follows the answered capability
  (_canRestoreAccountBackup) instead of the form factor alone.
- MediaLibrarySaver: save/savePreparedFile translate a missing
  photo_manager channel into MediaLibrarySaveResult.unsupported, the
  same answer a desktop host gets.
- LocalAppLockController: a missing flutter_secure_storage port reads as
  no lock configured (nothing was ever persisted) instead of failing
  closed behind a gate no credential could open; a real storage error
  still fails closed.
- CallScreen: native video views and the flip-camera affordance follow
  the engine capability (supportsMediaCalls, Android/iOS tgcalls) rather
  than the form factor, so a mobile host without the engine keeps the
  placeholder glyph.

Behavior on Android/iOS/Windows/macOS/Linux is unchanged: the form
factor and the capability still agree there, and the new branches only
fire when the channel is absent.
The app-lock controller treated every MissingPluginException as the
unsupported-first-install case, resetting the stored lock, the locked
state and the storage-unavailable flag. That catch also covers reloading
a known existing lock and a credential read that succeeds before the
attempts read fails, so a mobile host losing its secure-storage plugin
mid-session silently disabled existing protection — a fail-open into
previously protected UI.

The reset now happens only when no lock is known (nothing ever
persisted, the HarmonyOS first-install case). A known lock — read this
pass or established by an earlier one — fails closed instead, in both
initialize and reloadFromStorage.

Regressions: a known lock that loses secure storage mid-read fails
closed (credential record reads, attempts read throws) and reloading a
known lock against a vanished implementation fails closed (initialize
on a working store, plugin gone before the desktop-sync reload). Both
fail on the previous head; the safe first-install case stays green.
Make the HarmonyOS host a verifiable runnable target: the manifest now
carries tdjson-ohos-arm64.zip with archive+member SHA-256/size and a
per-asset release_tag pointing at the immutable tdjson#4 secondary
release tdjson-ohos-1.8.68-c15d3f5a5de6-mithka-2e7f8ff14657-build-f6b13ce5aa13-v2
so scripts/build-hap.sh can install arm64-v8a/libtdjson.so into
ohos/entry/libs/arm64-v8a/ via the same pinned schema-v2 manifest contract
the release CI uses. The installer validates sha256/size, checks that the
zipped member matches the pinned digest, and downloads from the per-asset
release when the manifest carries one; HAP verification can then byte-compare
libs/arm64-v8a/libtdjson.so against the same pinned entry.

Align ohos/README.md with the release job's validated SDK combination
(API 26 raw SDK or pinned API 22+patched embedding HAR) and document the
controlled embedding patch boundary.

Addresses PR iebb#271 review findings: pinned OHOS entry, consumer/producer
manifest contract, environment-contract coherence, and build-hap verifiability.
The entry module only declared the home entity/action, so no link could
ever target the ability and app_links had nothing to deliver. The entry
ability now also declares browsable entity + viewData action with the tg
scheme and the https t.me host — the same pair the Android manifest
declares — for both cold launch and warm-delivery routing through the
account-aware link path.
Add OpenHarmony ports of the federated plugins mithka consumes, so the
OHOS Flutter fork can resolve a native implementation when building for
HarmonyOS while stock flutter_tools ignores the ohos platform entries
(verified: pub get, analyze, and `flutter build apk --config-only` stay
green and no plugin registrant changes on android/linux/ios).

Regular dependencies (8, Dart-3 compatible on pub.dev):
- shared_preferences_ohos 2.2.0   (implements shared_preferences)
- path_provider_ohos 2.2.1         (implements path_provider)
- url_launcher_ohos 6.3.0          (implements url_launcher)
- open_filex_ohos 4.7.0            (implements open_filex)
- local_auth_ohos 1.0.0            (implements local_auth)
- webview_flutter_ohos 3.15.0      (implements webview_flutter)
- sensors_plus_ohos 0.1.2          (companion; serves sensors_plus channels)
- app_links_ohos 0.2.0             (companion for app_links)

Overlay-only (4; every published version pins sdk <3.0.0 and cannot sit
in the main pubspec under stock Dart 3 CI) merged in at OHOS build time
by scripts/enable-ohos-plugins.sh from pubspec_overrides.ohos.yaml:
- package_info_plus_ohos 0.0.7, permission_handler_ohos 0.0.8,
  file_picker_ohos 1.0.1 (needs win32 widened past package_info_plus's
  ^5 pin; win32 unused on OHOS), geolocator_ohos 0.0.3.
Verified: overlay pub get + analyze green, host lock pins unchanged.

Still missing on HarmonyOS (no compatible pub.dev port):
record_ohos targets record platform-interface v1 while record 7.x needs
v2; flutter_secure_storage_ohos has the same v1/v2 split; camera,
mobile_scanner, flutter_sound, photo_manager and
flutter_local_notifications have no ohos port at all. Voice notes,
secure storage, camera/QR, photo picking and local notifications are
unavailable on OHOS until ports catch up.

pubspec.lock gains exactly the 8 new entries (repo convention: lock is
committed with dependency changes).
PR iebb#272 review finding [P1]: apply could discard unrelated work. A second apply
overwrote the only backup of the pre-overlay original with the already-overlaid
file, so apply/apply/revert could no longer restore the starting content.
pubspec.lock was not backed up even though pub get rewrites it, and apply
edited tracked .gitignore at runtime. revert without a backup ran git checkout
on pubspec.yaml, losing edits the user had made but not yet committed.

The operation now refuses unsafe states instead of repairing them by resetting
files. apply backs up both pubspec.yaml and pubspec.lock and refuses when an
overlay is already active or when the backup would be overwritten; revert
refuses when no apply is active and restores both locked files byte-for-byte.
No tracked file besides the committed .gitignore entry is edited.

Eight offline behavioral tests (apply, repeated apply, revert without a backup,
dirty-file preservation, apply/resolve/revert lockfile rewriting, stale-backup
refusal, no runtime edit of .gitignore) each include the exact mutation that
fails it.
…evert

Two restoration defects in the overlay helper: a tree that had no
pubspec.lock before apply kept the lock the overlay's pub get created
(revert now removes it — the backup's absence records the original
absence), and the YAML backup round-tripped through text mode, folding
CRLF to LF (the backup now copies the file byte-for-byte, so a dirty
CRLF pubspec restores byte-identically).

Regressions: apply/resolve/revert from a lockless tree restores lock
absence and a dirty CRLF pubspec restores byte-identically, both red on
the previous head.
Add a build-ohos job to the release workflow. It runs in the
niuhuan harmony-next-flutter-pipeline container (HarmonyOS SDK API
22, hvigor/ohpm/node18), installs the 3.44.9+ohos-0.0.1-canary1
Flutter fork at /opt/flutter-ohos (cached), and patches the engine
embedding HAR so its ArkTS compiles against API 22: autoFillManager
API-26 types are replaced by local aliases, duplicate unfocus
XComponent ids are made unique, and CompetitionStrategy.DEFAULT is
spelled as its raw value. The patched har is repacked with the
package/ member prefix ohpm requires.

The job tolerates the not-yet-merged sibling ohos PRs: it runs
scripts/enable-ohos-plugins.sh and installs the pinned
tdjson-ohos-arm64.zip only when present (warning otherwise), and
generates the ohos host via flutter create --platforms ohos when the
repository has no ohos/ directory yet.

flutter build hap exits non-zero without signing configs after
hvigor produced the unsigned hap, so the build step warns and the
verify step gates on the artifact: the hap must exist, unzip, and
contain pack.info and ets/modules.abc. The release job now depends
on build-ohos, requires exactly one mithka-*-ohos-arm64.hap asset,
and cleans .hap assets when re-publishing.
Address PR iebb#273 review findings:

1. Never publish a hap without TDLib. The pinned manifest now carries
   tdjson-ohos-arm64.zip (archive+member SHA-256/size plus a per-asset
   release_tag pointing at the immutable tdjson#4 secondary release), the
   installer treats it as required, and the install step re-hashes the
   installed ohos/entry/libs/arm64-v8a/libtdjson.so against that entry.
   The hap verification now requires libs/arm64-v8a/libtdjson.so inside
   the hap, compares its packaged bytes to the installed pinned bytes,
   checks the arm64 ELF identity, and asserts the td_json_client_* entry
   points the Dart loader resolves are present. pack.info/ets/modules.abc
   alone no longer pass the gate.

2. Only a specifically verified signing failure is tolerated. The build
   step captures the flutter log, and a nonzero exit only continues when
   the unsigned hap exists and the log names a signing cause; compiler,
   resource, dependency, or packaging failures fail the job.

3. scripts/enable-ohos-plugins.sh is invoked with its mandatory apply
   argument so the combined stack with iebb#272 no longer exits 2.

4. The engine embedding HAR patch is an explicitly reviewed capability
   fallback: it refuses to apply unless the engine still guards autofill
   below sdkApiVersion 26, so requestAutoFill/requestAutoSave stubs can
   never change behaviour on API 26+ runtimes without failing the build.

Installer tests cover the required ohos asset, a manifest missing it, and
the per-asset release tag override.
Four release-workflow hardenings for the OHOS pipeline:

- Credential boundary: the build-ohos container job drops to
  contents:read, checks out with persist-credentials:false, and the
  third-party pipeline image is pinned by digest
  (sha256:2ec15faf…e2a9) instead of the mutable latest tag; publication
  credentials remain exclusive to the release job.
- Status capture: the hap build step runs under bash and captures the
  pipeline status explicitly (set -uo pipefail with || status=0), so
  the signing-only tolerance can actually see a non-zero exit instead of
  errexit killing the script first; the release job no longer hard-depends
  on build-ohos while the assembled path is being validated.
- Loader gate: the hap verifier parses the packaged ELF's .dynsym and
  requires the five td_json_client entry points as DEFINED STT_FUNC
  globals — a byte substring could match a string-table mention the
  loader never resolves. Extracted to scripts/verify_ohos_hap_payload.py
  with offline tests against a committed aarch64 stub ELF built by the
  NDK clang (defines exactly the five entry points; digest mismatch also
  proven to fail).
- Autofill shim: the API 22 stub now fails loudly (hilog error before
  the failure callback) so a >= 26 runtime surfaces the lost capability
  instead of silently degrading, with the hilog import ensured.
The Dart client (lib/tdlib/td_bindings.dart) binds td_create_client_id,
td_send, td_receive and td_execute; the gate previously required only the
five legacy td_json_client_* aliases, so a stub library the client cannot
actually load would pass. Require the four modern exports instead and add
a legacy-only stub fixture that fails the gate; both fixtures are
regenerable via scripts/gen-libtdjson-stub-fixtures.py.
Master removed test/call_screen_test.dart and duplicated a manifest key
in tdjson_artifact_installer_test; keep the missing-capability call
regression as a focused suite (call_screen_capability_test.dart) and drop
the duplicate key. All consolidated suites pass on the merged tree.
@TiaraBasori

Copy link
Copy Markdown
Contributor Author

Consolidated per the maintainer's one-canonical-PR policy (2026-10-29): #270 now carries the platform, host (#271), dependency (#272) and release-CI (#273) work as one stack of 15 clean commits rebased on current master 73c1d46b. #271/#272/#273 are closed as superseded; nothing was dropped — every commit, review fix and test from those PRs is in this diff.

Also addressed from the 2026-10-29 review notes:

  • Master deleted test/call_screen_test.dart in the test reduction. The useful missing-capability regression (mobile host without the call engine keeps the placeholder instead of mounting a platform view) is retained as a focused suite in test/call_screen_capability_test.dart; the rest of the removed suite was not restored.
  • [P2] The hap dynsym gate validated the wrong API contract. scripts/verify_ohos_hap_payload.py required only the five legacy td_json_client_* exports, but the client (lib/tdlib/td_bindings.dart) binds td_create_client_id/td_send/td_receive/td_execute. The gate now requires exactly those four modern entry points; a legacy-only stub fails it (new fixture test/fixtures/libtdjson_stub_legacy_only.so + regression in verify_ohos_hap_payload_test.dart). Fixtures are regenerable via scripts/gen-libtdjson-stub-fixtures.py.
  • A duplicated tdjson-ohos-arm64.zip key introduced by the consolidation merge in tdjson_artifact_installer_test.dart was removed (constant-eval error on the merged tree).

Verification on the consolidated head 7a15a8f7 (serial, CI-aligned toolchain for format):

  • dart format --set-exit-if-changed over changed test files — clean.
  • flutter analyze --no-pub — only the pre-existing unawaited_return_in_try_block warnings already on master.
  • flutter test --no-pub test/verify_ohos_hap_payload_test.dart test/call_screen_capability_test.dart test/tdjson_artifact_installer_test.dart test/tdjson_artifact_configuration_test.dart test/ohos_support_test.dart test/enable_ohos_plugins_script_test.dart test/account_backup_consent_test.dart test/local_app_lock_controller_test.dart test/media_library_saver_test.dart — all pass (59 tests).

Still open and unchanged in status: the pinned tdjson-ohos-…-v2 release tag 404s because the mithka-tdjson Sync TDLib Artifacts workflow has been failing since 2026-10-21 (mithka-session-backup.patch and mithka-installed-cloud-themes.patch no longer apply to current upstream td_api.tl/ThemeManager.h — see iebb/mithka-tdjson run 40667195591). The pinned-artifact and hosted assembled-HAP evidence requested in review therefore still cannot be produced from this repository alone; the tdjson producer side is tracked in iebb/mithka-tdjson#4.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants