Search code examples
androidstorageandroid-contentprovider

Android Shared Storage across apps


I'm trying to find the best solution for sharing data between multiple apps on a user's device. All these android apps would be using the same signing. Having prompts to share or grant data permission will not be possible due the user experience being important (so Sharing Intents are out of the picture)

I've tried the following solutions with the following results;

  • SharedPreferences with sharedUserId is not an option as these are existing apps that already have a default sharedUserId value which will prevent users from upgrading
  • ContentProvider using SQLite was promising however since any of the apps can share data and it's not know which one will be installed or installed first, there is no way to determine which app should have the <provider>. Unless there is a way to create a content provider that any of the apps can initialise if its the first app then I'm unsure how to proceed with this solution
  • I'm currently trying to use External storage and simply storing a file with the data that needs to be shared. This is obviously the most vulnerable solution but I might get away with only storing temporary sensitive data (tokens that expire after a few minutes). However still looking for the best solution.

Any expertise or insight would be helpful.


Solution

  • Thank you @morrison-chang and @blackapps. Using key points in your comments I found a solution that works. I had a unique authority for the same provider on each app. However they are all disabled by default. Whenever any of the apps need to access their provider it will check if there is any existing ones already available.

    SomeClassUsingMyContextProvider.java

    ...
    private Uri getActiveSharedStorageContentUri(){
        List<PackageInfo> packs = packageManager.getInstalledPackages(PackageManager.GET_PROVIDERS);
        for (PackageInfo pack : packs) {
            ProviderInfo[] providers = pack.providers;
            if (providers != null) {
                for (ProviderInfo provider : providers) {
                    if(provider != null && provider.authority != null && provider.authority.endsWith(SharedStorageProvider.AUTHORITY_SUFFIX)){
                        return SharedStorageProvider.createContentUri(provider.authority);
                    }
                }
            }
        }
    
        // If no valid provider.authority was found for SharedStorageProvider then we can assume
        // that the current app's SharedStorageProvider provider has not been enabled yet.
        // Lets activate it and then use its CONTENT_URI
        ComponentName componentName = new ComponentName(reactContext, SharedStorageProvider.class);
        packageManager.setComponentEnabledSetting(componentName, PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP);
        return SharedStorageProvider.CONTENT_URI;
    }
    ...
    

    SharedStorageProvider.java

    public class SharedStorageProvider extends ContentProvider {
        public static final String AUTHORITY_SUFFIX = String.format(".SharedStorageProvider.%s", BuildConfig.APP_GROUP);
        public static final String AUTHORITY = String.format("%s%s", BuildConfig.APPLICATION_ID, AUTHORITY_SUFFIX);
        ...
        public static final Uri CONTENT_URI = createContentUri(AUTHORITY);
        ...
        public static Uri createContentUri(String authority) {
            return Uri.parse("content://" + authority + "/" + BASE_PATH);
        }
        ...
    

    AndroidManifest.xml

    ...
    <uses-permission android:name="com.myapps.READ_DATABASE" />
    <uses-permission android:name="com.myapps.WRITE_DATABASE" />
    <permission android:name="com.myapps.READ_DATABASE" android:protectionLevel="signature" />
    <permission android:name="com.myapps.WRITE_DATABASE" android:protectionLevel="signature" />
    ...
    <application
        ...
        <provider
            android:name=".SharedStorageProvider"
            android:authorities="@string/shared_storage_provider_authority"
            android:exported="true"
            android:enabled="false"
            android:readPermission="com.myapps.READ_DATABASE"
            android:writePermission="com.myapps.WRITE_DATABASE"
        />
    </application>
    ...
    

    build.gradle

    ...
    resValue "string", "shared_storage_provider_authority", applicationId + ".SharedStorageProvider." + project.env.get("APP_GROUP") // Used to generate dynamic authority name
    ...
    

    Known limitation; Since this only stores data in the content provider from the app that enabled it, if that app is uninstalled then the "shared" data will be lost from all the apps that were using it. For my use this is fine.