androidandroid-intentandroid-storage

open a file in our tablet's Downloads folder, such as by using its mimeType to send an Intent to our app


Our general problem is we need our app in a tablet to send a mail to another tablet where our app reads one attachment file, in HTML.

But because of "security" we cannot manipulate the mail app as an Intent object, and we cannot simply scan the Downloads folder looking for the attachment.

So let's try to register a file mimeType, so at least the user can tap on the file in the Downloads folder, and pick our app in the Intent chooser.

Start by telling AndroidManifest.xml that our app is allowed to read files, just like every other program since the dawn of computing:

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_INTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_INTERNAL_STORAGE" />

Note those are mostly deprecated (for Android 14), and note that the modern replacements, such as READ_MEDIA_IMAGES, seem to refer to civilians playing with their camera rolls, and there's no READ_MEDIA_TEXT or READ_MEDIA_HTML.

Now tell the Manifest that our app is always unique and top-level:

    <activity
        android:name=".MainActivity"
        android:configChanges="orientation|screenSize|keyboard|keyboardHidden|navigation"
        android:theme="@style/AppTheme.NoActionBar"
        android:screenOrientation="sensorLandscape"
        tools:ignore="LockedOrientationActivity"
        android:foregroundServiceType="location"
        android:exported="true"
        android:launchMode="singleTop"
        >

Now after the first <intent-filter> (that makes us an app) add another filter that makes us into a mimeType handler:

        <intent-filter>
            <action android:name="android.intent.action.MAIN" />

            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>

        <intent-filter>
            <action android:name="android.intent.action.VIEW" />
            <category android:name="android.intent.category.DEFAULT" />
            <data android:scheme="file" />
            <data android:host="*" />
            <data android:mimeType="text/html" />
            <data android:pathPattern=".*\\.html" />
        </intent-filter>

Now add to MainActivity.java a handler to receive the Intent:

@Override
protected void onNewIntent(Intent intent) {
    super.onNewIntent(intent);

    if (intent != null && Intent.ACTION_VIEW.equals(intent.getAction())) {
        Uri fileUri = intent.getData();
        
        if (fileUri != null) {
            Log.e("TODO", "yo: " + fileUri);
            setGreenStatusBar(fileUri.getPath());
        }
    }
}

Okay, now we mail a File.html attachment to the tablet, download it to the Downloads folder, and tap on it. We get only the default HTML mimeType handlers - Chrome, HTML Viewer, and Internet.

So how can we get our app to open a file found in the Downloads folder?


Solution

  • Replace:

                <data android:scheme="file" />
                <data android:host="*" />
                <data android:mimeType="text/html" />
                <data android:pathPattern=".*\\.html" />
    

    with:

                <data android:scheme="content" />
                <data android:mimeType="text/html" />
    

    The file scheme largely was abandoned several years ago.

    Start by telling AndroidManifest.xml that our app is allowed to read files

    You will not need any of those. Two of them (the INTERNAL ones) do not even exist. Use ContentResolver and openInputStream() to get an InputStream on the content identified by the Uri that you will receive either in onCreate() (if the activity is being created new) or onNewIntent() (if the activity already existed and is getting a new Intent delivered to it).

    android:foregroundServiceType="location"

    This is for services. It is pointless for an <activity>.