HarmonyOSArkTSLynsi

256 Characters, No Appeals — After a Restricted HarmonyOS Permission Got Rejected

Lynsi applied for two restricted HarmonyOS permissions. READ_PASTEBOARD passed; READ_IMAGEVIDEO came back rejected: 'scenario does not meet the permission's usage requirements.' On reflection, the rejection was correct — the feature is backup, but every line of copy said 'browse.' This is the full story of the reapplication: spending 256 characters in the order the reviewer judges, answering the PhotoViewPicker suggestion head-on, and the rule above all others — every promise in the application must actually be true in the code.

Lynsi’s HarmonyOS app needs two restricted permissions — the kind you can’t just declare in module.json5. You submit a usage-scenario application to Huawei first, and only an approved permission can enter the release profile’s ACL list. On 2026-08-19 the results came back: READ_PASTEBOARD (clipboard) approved, READ_IMAGEVIDEO (photo library) rejected, with one sentence: “the scenario does not meet the permission’s usage requirements.”

This is the post-rejection retrospective. Conclusion first: the rejection was correct, and the fault was in my own copy.

Even the approved one had a trap: profiles are snapshots

First, a trap worth recording. READ_PASTEBOARD was approved — but the release profile (.p7b) on disk was generated before the approval, and its allowed-acls field is empty. Profiles do not update themselves when a permission is later granted. Skip regenerating it in AGC and the release build’s clipboard sync simply doesn’t get the permission — the kind of failure that only surfaces after shipping.

Verifying what ACLs a profile actually carries needs no tooling; a .p7b is just PKCS#7:

openssl smime -verify -inform DER -in lynsi-release.p7b -noverify 2>/dev/null \
  | python3 -c "import sys,json; raw=sys.stdin.read(); \
      d=json.loads(raw[raw.find('{'):]); print(d.get('acls'))"

Lesson: the permission approval and the profile are two independent pieces of state. Approved ≠ in the package. Check allowed-acls before building — it’s one command.

Why the rejection was correct

Huawei’s stated scope for READ_IMAGEVIDEO is explicit: it is for apps that need to clone, back up, or sync photos and videos from the user’s public directories.

Lynsi’s gallery feature is backup in substance: browse the phone’s albums from the Mac, pull the selected originals over the encrypted LAN link, save them to local disk. But look at the copy that shipped with the first application — the permission-purpose text said “browse and export your photos on the paired Mac,” the gallery switch said “you can browse photos,” the failure toast said “the Mac cannot browse photos.”

What does “browse photos” read as? A photo viewer. And photo viewers are exactly the category Huawei refuses this permission to — the system ships PhotoViewPicker precisely so viewers don’t need library-wide read access. The reviewer classifies by the words you wrote, not by the feature you had in mind. The rejection was sound.

The fix starts with the copy: every string, in both languages, rewritten to backup terms. And this has to come first — after reapplying, the review team installs and checks the actual build. If the build they install still says “browse,” a corrected application form changes nothing. Copy, code, and application materials have to align in the same release.

How to spend 256 characters

The reapplication form allows 256 characters, and this round established there is no appeals channel — after a rejection, the only move is to submit again. Those 256 characters are the entire communication bandwidth.

So the text is organized strictly around what the reviewer judges first:

  1. The first sentence lands inside Huawei’s accepted scenario vocabulary: “This application is a backup scenario: backing up photos and videos from the HarmonyOS phone’s gallery to the user’s own Mac.” No preamble, no product vision — get the classification right before anything else.
  2. The second paragraph answers the previous rejection head-on. Last round’s review suggested PhotoViewPicker; this paragraph must say precisely why it cannot carry a backup feature. It returns only the URIs the user hand-picked that one time, so the app can never know the full set — and without the full set there is no “what’s new,” hence no incremental backup and no whole-album archiving. It also puts the interaction on the wrong end: the user must pick up the phone and tap through a small screen, the exact opposite of “sitting at the Mac, archiving the phone.”
  3. Close with user control: the phone has a master gallery-sharing switch; when it’s off, all gallery requests go unanswered.
  4. Zero feature detail — the review verifies on a real device; the characters go to classification and rebuttal.

Final draft: 249 characters, 7 in reserve — plus a 236-character variant with the spaces between CJK and Latin removed, in case the platform counts them against the limit.

Every promise in the application must be true in the code

This is the heart of the post. Two sentences in those 256 characters had to be made true before they could be written.

Sentence one: “All reads are initiated by the user from the Mac; there is no background scanning.” But Lynsi has a screenshot-relay feature — ScreenshotWatcher subscribes to the entire media library’s changes via registerChange. That is background scanning; both sentences cannot hold at once. The decision: keep the feature in the first release, but disclose it proactively in the AGC review notes — listed as a test step, explained as an optional sub-feature under the same permission, off by default, subscribing only once enabled. The facts backing that call: the default really is false, the switch sits behind an additional Pro gate, and the callback reacts only to new items whose filename starts with Screenshot. Disclosing it yourself and having it discovered are two different events in a reviewer’s eyes. And if it’s still challenged, the fallback is clean: remove the one settings row, keep the code, re-add it after a follow-up scenario application.

Sentence two: “With the switch off, all gallery requests go unanswered — the Mac has no way around it.” Before writing that sentence, it was false. The switch check lived in PhotoRequestService, but the original-file path — onPhotoFetchRequestbypassed that service and sent the file directly. Switch off: thumbnails gone, originals still fetchable. That hole was closed before the application went in. The rejection letter states that at listing time the store re-reviews against actual usage; an application promise punctured by the reviewer’s own device test is a far worse position than the first rejection.

One more HarmonyOS API trap, the other face of the same rule: registerChange succeeds silently without the gallery permission — it throws nothing, returns nothing, and the media library then never delivers a single notification. The failure is invisible from both ends. So the code assumes nothing and checks for itself: checkAccessToken before registering, and if the permission is missing, a log line saying outright that the media library will never notify — giving the invisible failure at least a name.

A side note: one shot in the demo video

The reapplication ships with a demo video, and the “turn the gallery switch off” shot has a counterintuitive detail: demonstrate with browsing, not downloading. Flip the switch and the album list and thumbnails empty out instantly — visible on camera in a frame. A refused original-fetch, by contrast, surfaces through a 20-second timeout on the Mac side: the camera would sit on a motionless UI for 20 seconds, and it reads not as “the protection worked” but as “the feature didn’t.”

Same security behavior; pick the wrong observable and the demonstration says the opposite.

What I took away

  • A permission application is scenario writing. The reviewer reads your copy, not your code. “Browse” versus “back up” are two descriptions of one feature to an engineer — and the difference between denied and granted in the reviewer’s taxonomy.
  • But the listing re-review reads your code, not your copy. Every promise in the application — “no background scanning,” “no way around it” — has to hold in the code first. Align both ends: fix the copy, close the code paths, and only then submit.
  • Name the platform’s silent failures yourself. A profile that doesn’t track approvals; a registerChange that succeeds without permission and never fires. Both are “no error, no effect.” The treatment is the same: assume nothing, verify explicitly, write the failure into the log.

Comments

  • Loading…

Comments are reviewed before publishing; email is visible only to me.