Android intent filter: associate app with file ext

2018-12-31 14:36发布

I have a custom file type/extension that I want to associate my app with.

As far as I know, the data element is made for this purpose, but I can't get it working. http://developer.android.com/guide/topics/manifest/data-element.html According to the docs, and a lot of forum posts, it should work like this:

<intent-filter>
    <action android:name="android.intent.action.MAIN" />
    <category android:name="android.intent.category.DEFAULT" />
    <category android:name="android.intent.category.BROWSABLE" />
    <data android:mimeType="application/pdf" />
</intent-filter>

Well, it does not work. What did I do wrong? I simply want to declare my own file type.

13条回答
何处买醉
2楼-- · 2018-12-31 14:40
         <!--
            Works for Files, Drive and DropBox
        -->
        <intent-filter>
            <action android:name="android.intent.action.VIEW" />
            <category android:name="android.intent.category.DEFAULT" />
            <data android:scheme="file" />
            <data android:mimeType="*/*" />
            <data android:host="*" />
            <data android:pathPattern=".*\\.teamz" />
        </intent-filter>

        <!--
            Works for Gmail
        -->
        <intent-filter>
            <action android:name="android.intent.action.VIEW"/>
            <category android:name="android.intent.category.BROWSABLE" />
            <category android:name="android.intent.category.DEFAULT"/>
            <data android:host="gmail-ls" android:scheme="content" android:mimeType="application/octet-stream"/>
        </intent-filter>

Notice that this will get your app open all gmail file attachments, there is no way to work around it

查看更多
闭嘴吧你
3楼-- · 2018-12-31 14:45

The pathPattern

<data android:pathPattern=".*\\.pdf" />

does not work if the file path contains one or more dots before ".pdf".

This will work:

<data android:pathPattern=".*\\.pdf" />
<data android:pathPattern=".*\\..*\\.pdf" />
<data android:pathPattern=".*\\..*\\..*\\.pdf" />
<data android:pathPattern=".*\\..*\\..*\\..*\\.pdf" />

Add more if you want to support more dots.

查看更多
泛滥B
4楼-- · 2018-12-31 14:46

Markus Ressel is correct. Android 7.0 Nougat no longer permits file sharing between apps using a file URI. A content URI must be used. However, a content URI does not allow a file path to be shared, only a mime type. So you cannot use a content URI to associate your app with your own file extension.

Drobpox has an interesting behavior on Android 7.0. When it meets an unknown file extension it appears to form a file URI intent but instead of launching the intent it calls the operating system to find out which apps can accept the intent. If there is only one app that can accept that file URI it then sends an explicit content URI directly to that app. So to work with Dropbox you do not need to change the intent filters on your app. It does not require a content URI intent filter. Just make sure the app can receive a content URI and your app with your own file extension will work with Dropbox just like it did before Android 7.0.

Here is an example of my file loading code modified to accept a content URI:

Uri uri = getIntent().getData();
if (uri != null) {
    File myFile = null;
    String scheme = uri.getScheme();
    if (scheme.equals("file")) {
        String fileName = uri.getEncodedPath();
        myFile = new File(filename);
    }
    else if (!scheme.equals("content")) {
        //error
        return;
    }
    try {
        InputStream inStream;
        if (myFile != null) inStream = new FileInputStream(myFile);
        else inStream = getContentResolver().openInputStream(uri);
        InputStreamReader rdr = new InputStreamReader(inStream);
        ...
    }
}
查看更多
若你有天会懂
5楼-- · 2018-12-31 14:48

The other solutions did not work reliably for me until I added:

android:mimeType="*/*" 

Before that it worked in some applications, in some not...

complete solution for me:

<intent-filter>
  <action android:name="android.intent.action.VIEW" />
  <category android:name="android.intent.category.DEFAULT" />
  <data android:scheme="file"  android:host="*" android:pathPattern=".*\\.EXT" android:mimeType="*/*"  />
</intent-filter>
查看更多
只若初见
6楼-- · 2018-12-31 14:50

My findings:

You need several filters to deal with the different ways of retrieving a file. ie, by gmail attachment, by file explorer, by HTTP, by FTP... They all send very different intents.

And you need to filter out the intent that trigger your activity in your activity code.

For the example below, I created a fake file type new.mrz. And I retrieved it from gmail attachment and file explorer.

Activity code added in the onCreate():

        Intent intent = getIntent();
        String action = intent.getAction();

        if (action.compareTo(Intent.ACTION_VIEW) == 0) {
            String scheme = intent.getScheme();
            ContentResolver resolver = getContentResolver();

            if (scheme.compareTo(ContentResolver.SCHEME_CONTENT) == 0) {
                Uri uri = intent.getData();
                String name = getContentName(resolver, uri);

                Log.v("tag" , "Content intent detected: " + action + " : " + intent.getDataString() + " : " + intent.getType() + " : " + name);
                InputStream input = resolver.openInputStream(uri);
                String importfilepath = "/sdcard/My Documents/" + name; 
                InputStreamToFile(input, importfilepath);
            }
            else if (scheme.compareTo(ContentResolver.SCHEME_FILE) == 0) {
                Uri uri = intent.getData();
                String name = uri.getLastPathSegment();

                Log.v("tag" , "File intent detected: " + action + " : " + intent.getDataString() + " : " + intent.getType() + " : " + name);
                InputStream input = resolver.openInputStream(uri);
                String importfilepath = "/sdcard/My Documents/" + name; 
                InputStreamToFile(input, importfilepath);
            }
            else if (scheme.compareTo("http") == 0) {
                // TODO Import from HTTP!
            }
            else if (scheme.compareTo("ftp") == 0) {
                // TODO Import from FTP!
            }
        }

Gmail attachement filter:

        <intent-filter android:label="@string/app_name">
            <action android:name="android.intent.action.VIEW" />
            <category android:name="android.intent.category.DEFAULT" />
            <data android:scheme="content" />
            <data android:mimeType="application/octet-stream" />
        </intent-filter>
  • LOG: Content intent detected: android.intent.action.VIEW : content://gmail-ls/l.foul@gmail.com/messages/2950/attachments/0.1/BEST/false : application/octet-stream : new.mrz

File explorer filter:

        <intent-filter android:label="@string/app_name">
            <action android:name="android.intent.action.VIEW" />
            <category android:name="android.intent.category.DEFAULT" />
            <data android:scheme="file" />
            <data android:pathPattern=".*\\.mrz" />
        </intent-filter>
  • LOG: File intent detected: android.intent.action.VIEW : file:///storage/sdcard0/My%20Documents/new.mrz : null : new.mrz

HTTP filter:

        <intent-filter android:label="@string/rbook_viewer">
            <action android:name="android.intent.action.VIEW" />
            <category android:name="android.intent.category.DEFAULT" />
            <data android:scheme="http" />
            <data android:pathPattern=".*\\.mrz" />
        </intent-filter>

Private functions used above:

private String getContentName(ContentResolver resolver, Uri uri){
    Cursor cursor = resolver.query(uri, null, null, null, null);
    cursor.moveToFirst();
    int nameIndex = cursor.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME);
    if (nameIndex >= 0) {
        return cursor.getString(nameIndex);
    } else {
        return null;
    }
}

private void InputStreamToFile(InputStream in, String file) {
    try {
        OutputStream out = new FileOutputStream(new File(file));

        int size = 0;
        byte[] buffer = new byte[1024];

        while ((size = in.read(buffer)) != -1) {
            out.write(buffer, 0, size);
        }

        out.close();
    }
    catch (Exception e) {
        Log.e("MainActivity", "InputStreamToFile exception: " + e.getMessage());
    }
}
查看更多
刘海飞了
7楼-- · 2018-12-31 14:54

Content URI ftw, and with the intent filter in the manifest... if your files have a custom extension .xyz, add a matching mime type:

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

            <category android:name="android.intent.category.DEFAULT" />

            <data
                android:host="*"
                android:mimeType="application/xyz"
                android:scheme="content" />
        </intent-filter>

Some apps such as email seem to convert the extension into a mime type. Now I can click on the attachment in email and have it open in my app.

查看更多
登录 后发表回答