Skip to content

fix: classify Android change notifications by MediaProvider flags on API 30+ - #1444

Merged
AlexV525 merged 4 commits into
mainfrom
fix/1443-notify-misclassified-change-type
Aug 26, 2026
Merged

fix: classify Android change notifications by MediaProvider flags on API 30+#1444
AlexV525 merged 4 commits into
mainfrom
fix/1443-notify-misclassified-change-type

Conversation

@AlexV525

Copy link
Copy Markdown
Member

Problem

Closes #1443.

The issue reports that change callbacks never fire when a file is dragged from the desktop into an emulator, and proposes registering additional ContentObservers on the external_primary volume URIs.

Investigation on API 34 and API 36 emulators shows the diagnosis is different:

  • MediaProvider does notify the merged external view for every row change (acceptWithExpansion in AOSP recurses into VOLUME_EXTERNAL), so the existing observers do receive the event — verified via setprop log.tag.MediaProvider VERBOSE and dumpsys content.
  • Registering external_primary as well would produce duplicate callbacks for every regular write, since both volumes are notified.

The actual failure: rows inserted via adb push / desktop drag-and-drop are owned by com.android.shell and stay is_pending=1 indefinitely, so they are not visible to the app. MediaObserver.onChange classifies the event by querying the row; the query comes back empty, and the code falls into the "row not found ⇒ delete" branch. The callback fires, but misreports the insert as a deletion.

Fix

On API 30+, MediaProvider tags row notifications with NOTIFY_INSERT / NOTIFY_UPDATE / NOTIFY_DELETE flags (observed values on an API 36 emulator: insert=5, update=9, delete=17). PhotoManagerNotifyChannel now overrides onChange(selfChange, uri, flags) and uses the flag as the authoritative change type, keeping the existing date-diff inference only for untagged notifications (API < 30 or plain notifyChange(uri, null) senders).

Additionally:

  • delete no longer queries the (already gone) row.
  • When the gallery cannot be resolved, the event is still delivered without galleryId instead of being dropped.

Verification

Example app with addChangeCallback + startChangeNotify on emulators:

Scenario API 36 (flags path) API 28 (inference path)
adb push file (pending row) insert (was delete) ✅ insert (via scan broadcast) ✅
content insert complete row insert + galleryId ✅ n/a
content update favorite update (heuristic would say insert) ✅ n/a
content delete delete n/a

One callback per change in all cases — no duplicates.

Note: flutter test has one pre-existing failure (convertMapToAsset reads Android trash state) that also fails on clean main, unrelated to this change.

…API 30+

MediaProvider tags row notifications with NOTIFY_INSERT/UPDATE/DELETE
flags. Use them as the authoritative change type instead of the
30-second date heuristic. This also fixes inserts of rows that are not
visible to the app yet — such as files pushed via adb push or desktop
drag-and-drop, which stay pending and hidden — being misreported as
deletations because the classification query cannot find the row.

Verified on emulators: API 36 (push/insert/update/delete all classified
correctly, one callback per change) and API 28 (legacy inference path
intact).

#1443
@AlexV525
AlexV525 force-pushed the fix/1443-notify-misclassified-change-type branch from 40e7e64 to ae2c775 Compare August 21, 2026 15:13
Flutter stable (3.44+) auto-appends an analyzer 'exclude' section
(build/android/ios/web/windows/macos/linux) to analysis_options.yaml
during 'flutter pub get'. The Runnable workflow's format check runs
'git diff --exit-code .' right after pub get, so every PR and main
itself have failed since the 3.47 stable rollout. Committing the
generated section keeps the tree clean; also applies to the example
options where the 3.44 tool writes the same section.
Flutter stable now enforces minimum versions at build time:
Gradle 8.14 (was 8.13), AGP 8.11.1 (was 8.9.3), Kotlin 2.2.20
(was 2.1.20). The Runnable workflow's Android jobs build the example
without the bypass flag, so they fail on every PR since the 3.47
rollout. Bump the wrapper, plugin and Kotlin versions accordingly, and
commit the migrator-generated minSdkVersion/android.newDsl changes so
the tree matches what the Flutter tool writes.

Verified locally: flutter build apk --release (no bypass) and
./gradlew photo_manager:test both pass.
@github-actions

Copy link
Copy Markdown

Download apk from here for 9ce99c9

Reorder the migrator-generated analyzer exclude section so the file
keeps the conventional shape: include first, one blank line between
sections. The Flutter tool accepts this order and does not rewrite it.
@github-actions

Copy link
Copy Markdown

Download apk from here for 1b567f0

@AlexV525
AlexV525 merged commit c1ca44f into main Aug 26, 2026
15 checks passed
@AlexV525
AlexV525 deleted the fix/1443-notify-misclassified-change-type branch August 26, 2026 23:27
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.

[Bug report] Android change notifications fail for external_primary MediaStore volume

1 participant