Direct answer
Cross-app multi-window drag-and-drop is supported from Android API 24 and is a user-directed transfer of a content reference, not a promise that the receiver owns a durable file. The sender must set DRAG_FLAG_GLOBAL to cross app-window boundaries and can grant URI read or write through DRAG_FLAG_GLOBAL_URI_READ or DRAG_FLAG_GLOBAL_URI_WRITE in the startDragAndDrop flags. The receiver calls Activity.requestDragAndDropPermissions(event) at ACTION_DROP; that method accepts the event, not a requested read/write mode. Which access is offered comes from the sender's flags. A successful request still does not prove content validity or consent for later processing. The drag event carries two different channels. ClipData is transferable and can contain one or more items, including content URIs. DragEvent.localState is source-side in-process state: Android documents it as available only to views in the Activity that started the drag and null in other Activities. A cross-app receiver must not depend on an object reference or trust flag hidden there. Use ClipDescription to filter accepted MIME families, but treat labels as hints; provider metadata and bytes need independent validation. Never infer identity or authorization from a URI, MIME type, title, or drag shadow. The ordinary DragAndDropPermissions grant is temporary and bound to the requesting Activity. requestDragAndDropPermissions(event) may return null when no content URI is associated or permissions cannot be granted. Check independently established access separately; null does not tell you whether another grant exists. If a long-lived grant is truly required, the sender must also offer DRAG_FLAG_GLOBAL_PERSISTABLE_URI_PERMISSION together with URI read and/or write flags. The receiver must then call ContentResolver.takePersistableUriPermission for the offered URI and mode, and verify that call succeeds; the flag alone does not make this app's grant durable. Persisted grants need their own release and audit path and are outside the ordinary temporary-grant flow described here. Never widen read access to write access by assumption. For the ordinary temporary flow, a DragAndDropPermissions grant is bound to its requesting Activity. Access is revoked when that Activity is destroyed or release() is called, whichever comes first. Keep the handle in that bounded lifecycle and release it after use, including cancellation and rejection. Verify Activity recreation, navigation away, and delayed reads. If another Activity will process the URI, Android requires forwarding ClipData and the appropriate URI grant flag; otherwise finish a bounded copy before handoff. A grant and open descriptor are separate resources: test descriptor behavior instead of assuming release instantly invalidates every read. For work that may continue after the drop screen closes, copy selected bytes into an app-owned bounded temporary file while the Activity grant is valid. Stream with a byte ceiling, cancellation checks, and short-read handling; do not trust reported size or display name. Validate actual format with the intended decoder or parser, enforce image or document limits, and reject unsupported content. A provider may be virtual, slow, or fail midway. Keep the URI, temporary copy, parsed values, and model input as separate ownership stages. Delete only app-created temporary data, never the source document. A drop transfers an item to this app, not blanket consent to later operations. Running inference, saving history, retaining a copy, and sharing derived text are distinct decisions. Local inference does not make the import path private: provider, copy, cache, logs, backups, and export have separate boundaries. Use synthetic fixtures and redact URI paths. Diagnostics should record an opaque fixture ID, safe MIME/provider hints, byte-count bucket, result category, and lifecycle transition. Build a test matrix around the platform event, not a helper that directly hands the app a file. Use separate source and target Activities to prove localState is unavailable across that boundary. Exercise read-only grants, absent URI, multiple items, MIME mismatch, null permission result, provider failure, delayed or partial reads, oversized and malformed content, Activity destruction, release, and cancellation. Observe permission requests separately from provider opens. Synthetic files test stream bounds and parsing but cannot prove Android delivered a cross-app grant. A two-app test supports only its tested versions and lifecycle; it does not show that Cove implements drag-and-drop.
A user drops a scan from a document app into an AI review screen
A target Activity receives a ClipData item from a different visible app. The URI looks like a PDF, but the source may be a cloud provider and the analysis task can outlive the initial drop callback.
Decision
Use only the modes offered by the sender. Copy bounded bytes while temporary access is valid, or separately take an offered persistable grant before long work; validate content and confirm downstream processing separately.
Expected outcome
The app can report whether transfer, validation, and the requested task each completed without treating a MIME label or successful drop as proof of content validity or durable permission.
Evidence to collect
The claim becomes reviewable only when the following evidence is attached to the same artifact and test run:
- ACTION_DROP and ClipData evidence
- Grant and Activity lifetime state
- Bounded copy and format-validation results
A privacy claim needs a data inventory and traffic evidence. A local-model label by itself is not proof.
Implementation workflow
- Define accepted drop items. List supported ClipData item shapes, MIME hints, maximum byte and dimension limits, and whether one or multiple items are valid. Reject unsupported items visibly; do not treat localState as cross-app metadata.
- Acquire the sender-offered grant at ACTION_DROP. The source must use DRAG_FLAG_GLOBAL and the URI read/write flags that match its intended access. The target calls requestDragAndDropPermissions(event), which cannot choose modes; handle null without broadening storage access.
- Choose temporary access or take a persistable grant. Use the ordinary Activity-bound grant unless durable access is genuinely needed. Persistence requires the sender persistable flag plus a successful receiver call to takePersistableUriPermission; record and release that grant separately.
- Read within the grant and task lifetime. Keep permission ownership explicit. Open the provider stream, copy with byte bounds, cancellation and partial-read handling, and release ordinary access when no longer needed. Test Activity destruction separately from an already-open descriptor.
- Validate and process a private copy. Check bytes with the exact format parser, dimensions or structure limits, and task-specific policy. Keep temporary file, parsed data and model input distinct; retain none unless a separate product decision requires it.
- Exercise real cross-Activity cases. Use two test apps or Activities to cover localState isolation, valid and absent grants, provider errors, multiple items, malformed content, cancellation, recreation, release and cleanup. Keep URI and content out of ordinary logs.
Keep each transition observable. A failure should identify the stage, artifact, runtime, and recovery action without logging private user content.
Acceptance criteria
- The receiver uses ClipData, not cross-Activity localState; sender DRAG_FLAG_GLOBAL and URI modes determine access, and requestDragAndDropPermissions cannot choose or broaden those modes.
- The receiver handles null without escalating permissions. Ordinary grants are Activity-bound; durable access requires the sender persistable flag and a successful takePersistableUriPermission call.
- Grant ownership, Activity destruction, explicit release, provider stream lifetime, app-owned copy cleanup and later processing are tested as distinct events.
- MIME and display metadata do not substitute for bounded byte reads and actual format validation; no inference, persistence or export consent is inferred from the drop alone.
- The guide presents a generic Android method and makes no claim that Cove already accepts drag-and-drop content.
Decision table
| Dimension | Choice or evidence | How to interpret it |
|---|---|---|
| Same-Activity drag | Use localState only for source-process coordination and use the expected local data path. | This object is not a cross-process handoff contract and is null in other Activities. |
| Cross-app URI drop | Sender sets DRAG_FLAG_GLOBAL and URI modes; receiver requests the event grant and consumes ClipData. | The receiver does not choose grant modes; ordinary access is Activity-bound and does not establish valid bytes or downstream authorization. |
| System document picker | Use the picker contract for explicit selection and its documented URI permission behavior. | This is a different entry path; do not reuse drag-event permission acquisition. |
| Long-running task | Copy bounded bytes to app-owned temporary storage before the Activity grant ends. | The copy becomes a separate owned artifact with its own cleanup and retention policy. |
Failure patterns to prevent
- Treating localState as cross-app data
- Assuming requestDragAndDropPermissions selects modes
- Inferring processing consent from a drop
Also prevent silent fallback, unpinned artifacts, missing cancellation, and conclusions that combine unlike configurations. Store unsuccessful runs alongside successful ones.
Minimum reproducibility record
| Layer | Record |
|---|---|
| Device | Manufacturer, model, chipset, RAM class, operating-system build |
| Software | Application version and git commit |
| Model | Family, variant, revision, format, file length, hash, quantization |
| Runtime | Name, revision, requested backend, observed backend evidence |
| Workload | Fixture revision, input hash, prompt hash, output policy |
| Outcome | Completed, 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
- Android privacy and security
- Android Developers — Drag and drop in multi-window mode
- Android API — DragAndDropPermissions lifetime and release
- Android API — DragEvent ClipData and localState
- Android API — Activity permission request and nullable result
- Android API — ContentResolver persistable URI grants
- Android API — View drag flags and URI access
Chinese deployment and troubleshooting content is organized in the .奇连 AI 端侧专题.
Frequently asked questions
Does a dropped content URI stay readable after the screen closes?
Ordinary DragAndDropPermissions access is tied to the requesting Activity and is revoked on its destruction or release. Copy bounded bytes while access is valid. Persistence is a separate opt-in path requiring sender flags and a successful takePersistableUriPermission call.
Can DragEvent.localState carry an object to another app?
No. Android documents localState as available only to views in the Activity that started the drag; it is null in other Activities. Use ClipData for the transferable item and validate all received values.
Can the target choose read or write mode in requestDragAndDropPermissions?
No. The method takes the DragEvent; startDragAndDrop flags supplied by the sender determine which URI modes are offered. A persistable flag only offers that option: the target must separately take the offered grant successfully.
Does the user dropping a file authorize AI processing?
The gesture conveys a transfer to the receiving app, not blanket consent for retention, model processing, history, or later sharing. Make those downstream actions explicit according to the product contract.