Skip to content

Keep shared media that providers generate on the fly - #23239

Merged
nbradbury merged 5 commits into
trunkfrom
issue/23047-google-photo-sharing
Aug 20, 2026
Merged

Keep shared media that providers generate on the fly#23239
nbradbury merged 5 commits into
trunkfrom
issue/23047-google-photo-sharing

Conversation

@nbradbury

@nbradbury nbradbury commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Description

TL/DR: Fixes #23047, where a photo shared from Google Photos opens a blank post. The blank post itself is already fixed on trunk by #23233. This PR closes two follow-on gaps in the same path that still mislabel or silently drop shared media.


The "Enhanced" framing in the issue is a red herring. What actually matters is whether the shared content:// URI resolves to a real file: "Original" arrives as a MediaStore URI with a _data column, while "Enhanced" is a stream generated on the fly with no _data and no extension anywhere. The photo picker, SAF/Files, Drive and Dropbox all behave the same way.

MediaUtils.downloadExternalMedia() names its cached copy after the provider's DISPLAY_NAME, falling back to a MIME type parsed out of the URI string — which a content:// URI never carries. A provider reporting no usable display name leaves a file called wp-1755600000000., and since every MIME check downstream reads the file name, FluxCUtils.mediaModelFromLocalUri() runs out of fallbacks and defaults to image/jpeg. A shared PNG, HEIC or video was uploaded labelled as a JPEG. ShareIntentReceiverActivity now renames the copy using the type the provider reported, which isAllowedMediaType() already resolves.

Separately, EditorUnitFunctions.isMediaTypeIntent() derived the type from the URI's file extension for ACTION_SEND_MULTIPLE. getFileExtensionFromUrl() returns an empty string both for those extension-less names and for any name containing a space (Screenshot 2026-08-19.jpg), so sharing several photos at once dropped some of them with no toast and no snackbar. It now falls back to intent.type, the branch the single-share path already trusts, which covers both EditPostActivity and GutenbergKitActivity.

Two related problems are not addressed here and deserve their own issues: the copy still runs on the main thread (an ANR risk for cloud-only photos, but deliberate per #5818 — some providers revoke access as soon as the requesting context goes away), and MediaUtils.getDocumentProviderPathKitkatOrHigher() in wordpress-utils dereferences downloadExternalMedia(...).getPath() with no null check.

Testing instructions

Sharing a generated photo to a post:

  1. Open a photo in Google Photos and tap Share.
  2. Choose Enhanced, then Share, then WordPress.
  3. Choose a site and Post.
  • Verify the image appears in the editor rather than a blank post.
  1. Repeat with Original instead of Enhanced.
  • Verify it still works.

Correct file type on upload (the main payoff of the first commit):

  1. Share a video through.
  • Verify the file that lands in the site's media library keeps its own type and is not renamed to .jpg.

Sharing several photos at once:

  1. In Google Photos, select 2 or more photos, then Share, then WordPress.
  2. Choose a site and Post.
  • Verify all of the selected photos appear in the editor. On trunk today the extension-less ones vanish with no error.

Media Library route:

  1. Repeat the single-photo and multi-photo shares, choosing Media Library instead of Post.
  • Verify the media uploads and appears in the grid.

downloadExternalMedia() names its cached copy after the provider's display name,
falling back to a MIME type parsed out of the URI string - which a content:// URI
never carries. Providers that generate media on the fly (Google Photos sharing an
"Enhanced" photo, the photo picker, SAF) report no usable display name, so the copy
is left with no extension at all.

Every MIME check downstream reads the file name, and FluxCUtils.mediaModelFromLocalUri
ends its fallback chain at image/jpeg, so a shared PNG, HEIC or video was uploaded
labelled as a JPEG. Rename the copy using the type the provider reported, which
isAllowedMediaType() already resolves.
isMediaTypeIntent() derives the type from the URI's file extension, and
getFileExtensionFromUrl() returns an empty string both for a name left without an
extension by the provider that shared it and for any name containing a space
("Screenshot 2026-08-19.jpg"). Those items were dropped with no toast and no
snackbar. Single ACTION_SEND escaped this because it reads intent.type instead.

Fall back to intent.type when the URI yields nothing, so ACTION_SEND_MULTIPLE
behaves like the single-share path it already trusts.
@dangermattic

dangermattic commented Aug 19, 2026

Copy link
Copy Markdown
Collaborator
1 Warning
⚠️ PR is not assigned to a milestone.

Generated by 🚫 Danger

@wpmobilebot

wpmobilebot commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

App Icon📲 You can test the changes from this Pull Request in Jetpack Android by scanning the QR code below to install the corresponding build.

App NameJetpack Android
Build TypeDebug
Versionpr23239-c8295d1
Build Number1498
Application IDcom.jetpack.android.prealpha
Commitc8295d1
Installation URL23a5h4ujc0kb8
Automatticians: You can use our internal self-serve MC tool to give yourself access to those builds if needed.

@wpmobilebot

wpmobilebot commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

App Icon📲 You can test the changes from this Pull Request in WordPress Android by scanning the QR code below to install the corresponding build.

App NameWordPress Android
Build TypeDebug
Versionpr23239-c8295d1
Build Number1498
Application IDorg.wordpress.android.prealpha
Commitc8295d1
Installation URL7eitkvndc2sf0
Automatticians: You can use our internal self-serve MC tool to give yourself access to those builds if needed.

@codecov

codecov Bot commented Aug 19, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 37.94%. Comparing base (2f7d263) to head (c8295d1).

Additional details and impacted files
@@           Coverage Diff           @@
##            trunk   #23239   +/-   ##
=======================================
  Coverage   37.93%   37.94%           
=======================================
  Files        2334     2334           
  Lines      127137   127138    +1     
  Branches    17608    17609    +1     
=======================================
+ Hits        48234    48241    +7     
+ Misses      74959    74952    -7     
- Partials     3944     3945    +1     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@nbradbury
nbradbury marked this pull request as ready for review August 19, 2026 19:30
@nbradbury
nbradbury requested a review from adalpari August 19, 2026 19:30
…type

getExtensionForMimeType() never fails: when MimeTypeMap doesn't know the type it
returns the subtype, so a provider reporting application/octet-stream produced
"wp-123.octet-stream". That reads as a real extension downstream, which suppresses
the image/jpeg fallback in FluxCUtils.mediaModelFromLocalUri() that had been making
the upload work - leaving the media worse off than with no extension at all.

Also cover the case the intent.type fallback could have regressed: a URI whose
extension resolves to a non-media type must still be rejected when the intent type
is media, because the URI wins whenever it resolves.
@nbradbury
nbradbury requested a review from dcalhoun August 19, 2026 19:43
…ypeMap

MimeTypeMap is backed by libcore's MimeUtils, whose coverage of heic, heif, ogv and
3g2 varies by API level - exactly the types isAllowedMediaType() accepts and the ones
Google Photos is most likely to hand us. Where the table is short the guard rejected a
good extension, no rename happened, and the media fell back to the image/jpeg default
this change exists to avoid.

MimeTypes.getMimeTypeForExtension() searches the same lists isSupportedImageMimeType()
and isSupportedVideoMimeType() gate on, so the guard is now symmetric with the accept
check by construction instead of depending on platform coverage.
@adalpari

Copy link
Copy Markdown
Contributor

Overall looks good. I have a couple of points I think are worth to note:

  1. When loading multiple images or big image in the editor, it stays empty with nothing indicating an image is being loaded. Is there any way to include a loading spinner or similar?
  2. Selecting both, videos and images skips the videos in the post edition
screen-20260820-105120-1787215851574.mp4

@nbradbury

Copy link
Copy Markdown
Contributor Author

Overall looks good. I have a couple of points I think are worth to note:

I don't think either of those are related to changes in this branch, so it'd be worth filing separate issues for them.

@adalpari adalpari left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM!

@nbradbury
nbradbury merged commit e51c12c into trunk Aug 20, 2026
23 checks passed
@nbradbury
nbradbury deleted the issue/23047-google-photo-sharing branch August 20, 2026 12:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Google Photos share handler not picking up "enhanced"

4 participants