Model delivery

Bind Android Download Notification Actions to Attempts

Separate system token matching, attempt payload and transfer-owner cancellation authorization across notification replacement and retries.

Review status: Fact and editorial QA complete; owner review pending · 2026-10-08 · Fact IDs: ANDROID-BG-01, COVE-DOWNLOAD-01, ANDROID-PENDINGINTENT-IDENTITY-2026-10

Direct answer

Treat a notification action as a capability to ask an app-owned component to perform an operation, not as a record of which download was current when the notification appeared. The token is a PendingIntent. Within the same creator and operation type, Android matches the request code, Intent.filterEquals() identity, and relevant descriptor flags. Do not reduce that identity to just an Intent and request code: flags such as one-shot or mutability can participate too. filterEquals() deliberately excludes extras, so changing only an attempt ID extra does not create a distinct token when the other matching inputs stay the same. This matters when attempts overlap, a retry replaces work, or notification updates are delayed. Suppose an older Cancel button and a newer one use the same operation, requestCode, explicit receiver, filter-equal Intent, and matching descriptor flags; only an extra changes. The creator can retrieve the existing token. With FLAG_UPDATE_CURRENT, it updates that token’s extras, so a delayed tap on the old card can deliver the new attempt ID. If the receiver trusts the extra alone, the stale action can cancel work the user did not choose. Without an update flag, the token may retain old payload instead. Neither case distinguishes attempts. Extra data alone never proves which task should be cancelled. Separate token identity, payload, and current authorization. A stable artifact ID identifies a model revision, but retries of that artifact need separate attempt identities. Change a value that participates in PendingIntent matching, such as a collision-checked attempt requestCode or a distinct non-sensitive data URI; preserve the matching descriptor flags and define operation type, explicit component, package, and action deliberately. Do not rely on Java hashCode as collision-free or put secrets in identity fields. A derived numeric key needs collision handling within its lifecycle. Immutable and unique solve different problems. FLAG_IMMUTABLE limits changes by a PendingIntent recipient when no fill-in fields are needed. It does not create an attempt-specific token, make extras part of filterEquals(), or prove a download is active. Android allows the creator to update its immutable PendingIntent with FLAG_UPDATE_CURRENT. Immutability is a recipient-mutation boundary, not task identity or authorization. Select mutability for the action contract, not to carry an attempt ID that belongs in token identity. Bind execution to the current task record as well as the token. On receipt, the transfer owner must compare attempt ID, artifact revision, work ID, and cancellable phase at a serialized or conditional boundary that also performs cancellation. It may cancel the captured matching work handle, but must not validate one attempt and then re-resolve an artifact-wide “current” pointer, which could now name a retry. Condition any status write on the same attempt. Missing, superseded, or terminal attempts are expired no-ops. Idempotency helps with duplicate taps but does not close this race. The Intent ID is a selector, not authorization; keep secrets out of identity fields and notification payloads. Test token matching and work authorization separately. Create tokens for the same creator and operation type with the same requestCode, filter-equal Intent, and relevant descriptor flags, changing only extras; verify they are not independent identities. Then change an identity input and verify distinct actions after notification replacement. Exercise FLAG_UPDATE_CURRENT with an immutable descriptor to distinguish creator updates from recipient fill-in. Finally race an old-card tap against retry creation: the owner must atomically or conditionally cancel only the captured attempt and condition its status write on that ID, never re-resolve a global current pointer. Also cover duplicate taps and completion. Keep the product claim narrow. Android guidance gives persistent work its own lifecycle rules, and the repository download fact describes transfer behavior; neither establishes that Cove currently posts model-download notifications or PendingIntent cancel actions. This method is for apps that wire notification controls to download attempts. It is not a claim about Cove’s UI, a guarantee of background completion, or a substitute for testing the transfer owner’s cancellation behavior.

A stale download notification cancels the retry

An app retries a failed model transfer while the previous notification is still visible. Both Cancel actions use the same operation, requestCode, explicit component, and filter-equal Intent; only an attempt ID extra changes. A delayed tap on the older card arrives after the retry starts.

Decision

Give each attempt a distinct PendingIntent identity, then validate its attempt ID and cancellable state against the authoritative work record before cancelling.

Expected outcome

The stale card becomes a no-op, while the current attempt remains active and its own Cancel action still works.

Evidence to collect

The claim becomes reviewable only when the following evidence is attached to the same artifact and test run:

  • Creator, operation and descriptor identity
  • Extras-update and stale-token fixtures
  • Owner-serialized compare-and-cancel outcomes

A completed byte stream is not enough. Final placement, integrity, runtime load, and recovery must also pass.

Implementation workflow

  1. Define operation and attempt identity. Separate artifact revision from each transfer attempt. Choose an app-owned attempt ID and document creator, operation type, receiver, request-code policy, explicit component, package, action, data, and relevant flags.
  2. Check token matching inputs. Check the PendingIntent creator and operation API, request code, filterEquals identity, and relevant descriptor flags. Confirm that changing only an attempt extra does not create another token.
  3. Choose a distinct identity. Use a collision-checked attempt request code or unique non-sensitive data URI. Do not rely on Java hashCode alone or put signed URLs or private content in identity fields.
  4. Select mutability separately. Use immutable when no fill-in fields are needed. FLAG_IMMUTABLE limits recipient changes, not token reuse; the creator may still update extras with FLAG_UPDATE_CURRENT.
  5. Validate and cancel at the owner boundary. Have the transfer owner compare attempt, revision, work ID, and cancellable state at the same serialized or conditional boundary used to cancel the captured work handle. Never re-resolve an artifact-wide current pointer after validation; condition status writes on the same attempt. Idempotency alone does not prevent supersession races.
  6. Exercise stale and duplicate actions. Test concurrent models, retries, notification replacement, old-card and duplicate taps, completion, and cancellation races. Only the active matching attempt may change; verify cancellation reaches its actual owner.

Keep each transition observable. A failure should identify the stage, artifact, runtime, and recovery action without logging private user content.

Acceptance criteria

  • Within the same creator and operation type, actions differing only in extras are not treated as distinct when requestCode, filterEquals identity, and relevant descriptor flags match.
  • Each actionable attempt has a collision-safe identity in its creator and operation scope, separate from its artifact ID.
  • The transfer owner validates and cancels the captured attempt at a serialized or conditional boundary; status writes are conditioned on that attempt, not a re-read global current pointer.
  • Mutability is justified separately from uniqueness and work authorization.

Decision table

DimensionChoice or evidenceHow to interpret it
Fixed requestCode; attempt ID only in extrasReuse only when one token intentionally represents the current operation.Extras do not distinguish filter-equal Intents; FLAG_UPDATE_CURRENT can replace the existing token payload.
Attempt-scoped requestCodeAllocate a distinct code when attempts must remain separate.Handle collisions and lifecycle reuse; a hash is not inherently unique.
Distinct Intent data identityUse a non-sensitive data URI or another documented matching field.Matching can distinguish tokens, but the receiver must still validate current work state.
FLAG_IMMUTABLEUse when recipients need no fill-in fields.It neither adds extras to identity nor blocks creator updates with FLAG_UPDATE_CURRENT.

Failure patterns to prevent

  • Using extras alone to distinguish tokens
  • Treating immutable flags as current-task authorization

Also prevent silent fallback, unpinned artifacts, missing cancellation, and conclusions that combine unlike configurations. Store unsuccessful runs alongside successful ones.

Minimum reproducibility record

LayerRecord
DeviceManufacturer, model, chipset, RAM class, operating-system build
SoftwareApplication version and git commit
ModelFamily, variant, revision, format, file length, hash, quantization
RuntimeName, revision, requested backend, observed backend evidence
WorkloadFixture revision, input hash, prompt hash, output policy
OutcomeCompleted, failed, cancelled, fallback, and privacy-safe diagnostics

Release checklist

  • ☐ The primary query is answered without an unsupported number.
  • ☐ Every artifact and runtime is pinned.
  • ☐ The representative task and failure policy are explicit.
  • ☐ Lifecycle, cancellation, cleanup, and fallback are tested.
  • ☐ User-content and network boundaries are documented.
  • ☐ Result wording applies only to the recorded configuration.
  • ☐ The page links to raw method or evidence when results are added.

Sources and related evidence

Chinese deployment and troubleshooting content is organized in the .奇连 AI 端侧专题.

Frequently asked questions

Does an attempt ID extra create a new PendingIntent?

No. Intent.filterEquals() excludes extras. Change a documented matching input when active attempts need distinct tokens; preserve descriptor flags that participate in matching too.

Can FLAG_IMMUTABLE block FLAG_UPDATE_CURRENT?

No. The creator can update its immutable token; immutability limits recipient fill-in, not identity or authorization, so the receiver must still check the current attempt.

Is a unique request code enough?

Only if allocation handles collisions within the operation scope. The owner must also validate and cancel the captured attempt at one serialized or conditional boundary, then condition status writes on that attempt.

Should a stale action cancel the newest retry?

No. Treat it as expired; cancellation requires the matching active and cancellable work record, checked by its owner rather than a notification extra alone.