Mobile Development 10 min read

Cross‑User Image Sharing in Android Dual‑Open Using FileProvider and URI Permission Adaptation

This article details a practical solution for enabling image sharing between separate Android user profiles in a dual‑open environment by configuring FileProvider, granting URI permissions, and adapting chooser intents to convert URIs across user spaces.

Coolpad Technology Team
Coolpad Technology Team
Coolpad Technology Team
Cross‑User Image Sharing in Android Dual‑Open Using FileProvider and URI Permission Adaptation

Problem Background The dual‑open scheme is built on Google’s multi‑user space design, which isolates work and personal profiles for data security, preventing direct access or sharing between them.

Problem Description When attempting to share a photo from the gallery to a cloned WeChat instance, a toast "Failed to get resource" appears because the native logic blocks cross‑profile image sharing.

Initial Solution Native DocumentUI can share across users via a FileProvider, but simply adding the provider and path configuration is insufficient; additional URI permission and app‑signature settings are required.

Configure FileProvider Add a <provider> entry inside the <application> tag of AndroidManifest.xml:

<provider
    android:name="androidx.core.content.FileProvider"
    android:authorities="com.coolos.myapp.fileprovider"
    android:exported="false"
    android:grantUriPermissions="true">
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/provider_paths" />
</provider>

Configure provider_paths.xml placed in res/xml/ :

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <root-path name="root" path="." />
    <files-path name="image_files" path="images/" />
    <cache-path name="cache" path="." />
    <external-path name="external" path="." />
    <external-files-path name="external_files" path="." />
    <external-cache-path name="external_cache" path="." />
    <external-media-path name="name" path="." />
</paths>

Grant URI Permissions Modify UriGrantsManagerService.java to allow the custom authority com.coolos.myapp.fileprovider and handle security checks.

// Bail early if system is trying to hand out permissions directly; it
// must always grant permissions on behalf of someone explicit.
final int callingAppId = UserHandle.getAppId(callingUid);
if ((callingAppId == SYSTEM_UID) || (callingAppId == ROOT_UID)) {
    if ("com.coolos.myapp.fileprovider".equals(grantUri.uri.getAuthority())) {
        // Exempted authority for sharing
    } else {
        Slog.w(TAG, "For security reasons, the system cannot issue a Uri permission" +
                " grant to " + grantUri + "; use startActivityAsCaller() instead");
        return -1;
    }
}

App System Signature Add system‑level attributes to the manifest and sign the APK with the platform key:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.coolos.myapp"
    coreApp="true"
    android:sharedUserId="android.uid.system">
    ...
</manifest>

Demo Verification Using an image at external/DCIM/Camera/IMG_20210709_195700.jpg :

Initialize the URI:

File imagePath = new File(getExternalStoragePublicDirectory(DIRECTORY_DCIM).getPath(), "/Camera");
File newFile = new File(imagePath, "IMG_20210709_195700.jpg");
Uri contentUri = getUriForFile(getApplicationContext(), "com.coolos.myapp.fileprovider", newFile);
if (newFile.exists()) {
    Log.d(TAG, "START newFile2:" + newFile.getPath());
}

Initialize the Intent and start the chooser:

Intent intent = new Intent(Intent.ACTION_SEND);
intent.setDataAndType(contentUri, "image/*");
intent.putExtra(Intent.EXTRA_STREAM, contentUri);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
startActivity(Intent.createChooser(intent, "Select share target"));

Gradle dependency:

implementation 'androidx.core:core:1.3.1'

Solution Validation & Drawbacks The approach works but requires every participating app to adopt the same FileProvider configuration, which is cumbersome for long‑term maintenance.

Solution Optimization Since most apps use MediaProvider for sharing, we opted to adapt the system side instead of modifying each app. By intercepting the chooser launch (via startActivityAsCaller ) we transform the Intent’s URI to a form compatible with the target user space.

During the chooser handling, if the URI scheme is content and the authority belongs to MediaStore, we strip the user ID, query the original path from the primary user’s MediaProvider database, convert the path using a custom pathConvert() method, and re‑attach the appropriate user ID for the target profile.

if(isDoubleOpen()){
    Uri shareUri = (Uri)mResolvedIntent.getParcelableExtra(Intent.EXTRA_STREAM);
    if(shareUri != null){
        String uriScheme = shareUri.getScheme();
        String uriAuthority = shareUri.getAuthority();
        if(ContentResolver.SCHEME_CONTENT.equals(uriScheme) &&
           uriAuthority != null && uriAuthority.contains(MediaStore.AUTHORITY)){
            Uri uriWithoutUserid = ContentProvider.getUriWithoutUserId(shareUri);
            if(userId == UserHandle.USER_SYSTEM){
                mResolvedIntent.putExtra(Intent.EXTRA_STREAM,
                    ContentProvider.maybeAddUserId(uriWithoutUserid, userId));
            } else {
                String oldPath = getDataColumn(getApplicationContext(), uriWithoutUserid);
                String newPath = pathConvert(oldPath);
                Uri uriWithoutid = ContentUris.removeId(uriWithoutUserid);
                Uri mediaCSpaceUri = ContentProvider.maybeAddUserId(uriWithoutid, userId);
                mResolvedIntent.putExtra(Intent.EXTRA_STREAM,
                    getUriFromPath(getApplicationContext(), mediaCSpaceUri, newPath));
            }
        }
    }
}

The pathConvert() function adapts the original media path based on the mount‑point naming conventions of each user’s storage volume, enabling correct database queries for the target profile.

Additionally, the URI must embed the target user’s ID, e.g., content://UserId@media/external/images/media/328 , so that ContentResolver can locate the resource in the appropriate MediaProvider instance.

After verification, this optimized approach resolves cross‑profile image sharing without requiring modifications to individual apps, offering a more maintainable solution than the initial FileProvider‑only method.

Mobile DevelopmentAndroidFileProviderCross-User SharingMulti-UserURI Permission
Coolpad Technology Team
Written by

Coolpad Technology Team

Committed to advancing technology and supporting innovators. The Coolpad Technology Team regularly shares forward‑looking insights, product updates, and tech news. Tech experts are welcome to join; everyone is invited to follow us.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.