Фильтр намерений Android: связать приложение с расширением файла

У меня есть собственный тип/расширение файла, с которым я хочу связать свое приложение.

Насколько я знаю, элемент данных создан для этой цели, но я не могу заставить его работать. http://developer.android.com/guide/topics/manifest/data-element.html Согласно документам и большому количеству сообщений на форуме, он должен работать следующим образом:

<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>

Ну, это не сработает. Что я сделал не так? Я просто хочу объявить свой собственный тип файла.

Ответ 1

Вам нужно несколько фильтров намерений для решения различных ситуаций, которые вы хотите обработать.

Пример 1, обрабатывать HTTP-запросы без mimetypes:

  <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:scheme="http" />
    <data android:host="*" />
    <data android:pathPattern=".*\\.pdf" />
  </intent-filter>

Обрабатывать с помощью mimetypes, где суффикс не имеет значения:

  <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:scheme="http" />
    <data android:host="*" />
    <data android:mimeType="application/pdf" />
  </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:pathPattern=".*\\.pdf" />
  </intent-filter>

Ответ 2

Другие решения не работали надежно для меня, пока я не добавил:

android:mimeType="*/*" 

До этого он работал в некоторых приложениях, в некоторых не...

полное решение для меня:

<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>

Ответ 3

Ответы, предоставленные Phyrum Tea и yuku, уже очень информативны.

Я хочу добавить, что начиная с Android 7.0 Nougat происходит изменение способа обмена файлами между приложениями:

От официального Android 7.0 Изменения:

Для приложений, ориентированных на Android 7.0, платформа Android обеспечивает Политика StrictMode API, запрещающая публикацию файлов://URI за пределами ваше приложение. Если намерение, содержащее URI файла, покидает ваше приложение, приложение с ошибкой FileUriExposedException.

Чтобы обмениваться файлами между приложениями, вы должны отправить контент://URI и предоставить разрешение на временное доступ к URI. Самый простой способ предоставить это разрешение с помощью класса FileProvider. Для большего информацию о разрешениях и общих файлах, см. раздел "Совместное использование файлов".

Если у вас есть собственный пользовательский файл, заканчивающийся без определенного mime-type (или я думаю, даже с одним), вам может потребоваться добавить второе значение scheme к вашему intent-filter, чтобы он работал с FileProviders тоже.

Пример:

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

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

    <data android:scheme="file" />
    <data android:scheme="content" />
    <data android:mimeType="*/*" />
    <!--
        Work around Android ugly primitive PatternMatcher
        implementation that can't cope with finding a . early in
        the path unless it explicitly matched.
    -->
    <data android:host="*" />
    <data android:pathPattern=".*\\.sfx" />
    <data android:pathPattern=".*\\..*\\.sfx" />
    <data android:pathPattern=".*\\..*\\..*\\.sfx" />
    <data android:pathPattern=".*\\..*\\..*\\..*\\.sfx" />
    <!-- keep going if you need more -->

</intent-filter>

Важным здесь является добавление

<data android:scheme="content" />

к фильтру.

Мне было трудно узнать об этом небольшом изменении, которое не позволяло моей активности открываться на устройствах Android 7.0, в то время как в старых версиях все было в порядке. Надеюсь, это поможет кому-то.

Ответ 4

Мои выводы:

Вам нужно несколько фильтров для обработки различных способов извлечения файла. т.е. через вложение gmail, файловым проводником, HTTP, FTP... Все они посылают совсем другие намерения.

И вам нужно отфильтровать намерение, которое инициирует вашу активность в вашем коде активности.

В приведенном ниже примере я создал поддельный тип файла new.mrz. И я получил его из вложения gmail и проводника файлов.

Код операции, добавленный в 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:

        <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: обнаружено намерение контента: android.intent.action.VIEW: content://gmail-ls/[email protected]/messages/2950/attachments/0.1/BEST/false: application/octet-stream: new.mrz

Фильтр проводника файлов:

        <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: обнаружено намерение файла: android.intent.action.VIEW: файл:///storage/sdcard0/My%20Documents/new.mrz: null: new.mrz

HTTP-фильтр:

        <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 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());
    }
}

Ответ 5

pathPattern

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

не работает, если путь к файлу содержит одну или несколько точек перед ".pdf".

Это будет работать:

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

Добавьте больше, если вы хотите поддерживать больше точек.

Ответ 6

Маркус Ressel правильно. Android 7.0 Nougat больше не разрешает совместное использование файлов между приложениями с использованием URI файла. Необходимо использовать URI содержимого. Тем не менее, URI контента не разрешает общий доступ к файлу, а только тип mime. Таким образом, вы не можете использовать URI контента, чтобы связать приложение с вашим собственным расширением файла.

Drobpox имеет интересное поведение на Android 7.0. Когда он встречается с неизвестным расширением файла, он, как представляется, формирует намерение URI файла, но вместо запуска намерения он вызывает операционную систему, чтобы выяснить, какие приложения могут принять намерение. Если есть только одно приложение, которое может принять этот URI файла, он затем отправляет явный URL-адрес контента непосредственно этому приложению. Поэтому для работы с Dropbox вам не нужно менять фильтры намерений в вашем приложении. Он не требует фильтра URI для контента. Просто убедитесь, что приложение может получить URI контента, а ваше приложение с вашим собственным расширением файла будет работать с Dropbox, как это было до Android 7.0.

Вот пример кода загрузки файла, измененного для принятия 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);
        ...
    }
}

Ответ 7

Я пытаюсь заставить это работать целую вечность и пробовал все предлагаемые решения, и по-прежнему не может заставить Android распознавать определенные расширения файлов. У меня есть фильтр намерений с типом "*/*" mimetype, который, как представляется, работает, и файловые браузеры теперь перечисляют мое приложение в качестве опции для открытия файлов, однако теперь мое приложение показано как опция для открытия ЛЮБОГО ВИДА хотя я указал конкретные расширения файлов, используя тег pathPattern. Это до сих пор, что даже когда я пытаюсь просмотреть/редактировать контакт в моем списке контактов, Android спрашивает меня, хочу ли я использовать свое приложение для просмотра контакта, и это лишь одна из многих ситуаций, когда это происходит, ОЧЕНЬ ОЧЕНЬ раздражает.

В конце концов я нашел этот пост в группах google с похожим вопросом, на который ответил фактический разработчик Android. Она объясняет, что Android просто ничего не знает о расширениях файлов, только MIME-типы (https://groups.google.com/forum/#!topic/android-developers/a7qsSl3vQq0).

Итак, из того, что я видел, пробовал и читал, Android просто не может различать между расширениями файлов и тегом pathPattern - это в основном гигантская трата времени и энергии. Если вам повезло, что вам нужны только файлы определенного типа mime (например, текст, видео или аудио), вы можете использовать фильтр намерений с типом mime. Если вам требуется определенное расширение файла или тип mime, неизвестный Android, однако вам не повезло.

Если я ошибаюсь в отношении любого из этого, скажите, пожалуйста, до сих пор я читал каждое сообщение и пробовал каждое предлагаемое решение, которое мог найти, но никто не работал.

Я мог бы написать еще одну или две страницы о том, насколько распространены такие вещи в Android и как навязчивый опыт разработчиков, но я сэкономлю вам свои разгневанные разглагольствования;). Надеюсь, я спасла кого-то от неприятностей.

Ответ 8

Попробуйте добавить

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

Ответ 9

В приложении Gmail вы можете использовать:

<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/pdf" /> <!-- .pdf -->
  <data android:mimeType="application/msword" /> <!-- .doc / .dot -->
  <data android:mimeType="application/vnd.openxmlformats-officedocument.wordprocessingml.document" /> <!-- .docx -->
  <data android:mimeType="application/vnd.ms-excel" />  <!-- .xls / .xlt / .xla -->
  <data android:mimeType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" />  <!-- .xlsx -->
  <data android:mimeType="application/vnd.ms-powerpoint" />  <!-- .ppt / .pps / .pot / .ppa -->
  <data android:mimeType="application/vnd.openxmlformats-officedocument.presentationml.presentation" /> <!-- .pptx -->
  <data android:mimeType="application/vnd.openxmlformats-officedocument.presentationml.slideshow" /> <!-- .ppsx -->
  <data android:mimeType="application/zip" /> <!-- .zip -->
  <data android:mimeType="image/jpeg" /> <!-- .jpeg -->
  <data android:mimeType="image/png" /> <!-- .png -->
  <data android:mimeType="image/gif" /> <!-- .gif -->
  <data android:mimeType="text/plain" /> <!-- .txt / .text / .log / .c / .c++ / ... -->

Добавьте столько типов mime, сколько необходимо. Мне нужны только те, для моего проекта.

Ответ 10

         <!--
            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>

Обратите внимание, что это заставит ваше приложение открывать все вложения файлов gmail, нет возможности обойти его

Ответ 11

Те, кто имеет проблемы с другими приложениями File Manager\Explorer, как @yuku и @phyrum-tea, ответили

Это работает с приложением менеджера файлов по умолчанию LG

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

но не смог работать с ES File Explorer и другими файловыми менеджерами, поэтому я добавил

 android:mimeType="*/*"

то он работает с ES Explorer, но менеджер файлов LG не смог определить тип файла, поэтому мое решение

     <intent-filter android:label="@string/app_name_decrypt">
            <action android:name="android.intent.action.VIEW" />
            <category android:name="android.intent.category.DEFAULT" />
            <data android:scheme="file" />
            <data android:pathPattern=".*\\.lock" />
            <data android:pathPattern=".*\\..*\\.lock" />
            <data android:pathPattern=".*\\..*\\..*\\.lock" />
        </intent-filter>
        <intent-filter android:label="@string/app_name_decrypt">
            <action android:name="android.intent.action.VIEW" />
            <category android:name="android.intent.category.DEFAULT" />
            <data android:scheme="file"/>
            <data android:scheme="content" />
            <data android:mimeType="*/*" />
            <data android:pathPattern=".*\\.lock" />
            <data android:pathPattern=".*\\..*\\.lock" />
            <data android:pathPattern=".*\\..*\\..*\\.lock" />
        </intent-filter>

Ответ 12

Попробуйте, это поможет вам. Вместо pdf вы можете использовать и другие расширения. Сначала вы должны добавить разрешение на чтение из внешнего хранилища в файл androidmanifest.xml.

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

Затем в файле androidmanifest в теге Activity вы добавляете фильтр намерений, как показано ниже.

            <action android:name="android.intent.action.SEND" />

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

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

            <data android:mimeType= "application/pdf" />

            <data android:host="*" />

        </intent-filter>

Наконец, в вашем коде вы получите путь к файлу PDF, как показано ниже:

Intent intent=getIntent();

if(intent!=null) {          

        String action=intent.getAction();

        String type=intent.getType();

        if(Intent.ACTION_VIEW.equals(action) && type.endsWith("pdf")) {

            // Get the file from the intent object

            Uri file_uri=intent.getData();

            if(file_uri!=null)

                filepath=file_uri.getPath();

            else

                filepath="No file";

        }

        else if(Intent.ACTION_SEND.equals(action) && type.endsWith("pdf")){

            Uri uri = (Uri) intent.getParcelableExtra(Intent.EXTRA_STREAM);

            filepath = uri.getPath();

        }

Ответ 13

URI содержимого ftw и с фильтром намерений в манифесте... если ваши файлы имеют собственное расширение .xyz, добавьте соответствующий тип mime:

        <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>

Некоторые приложения, такие как электронная почта, похоже, преобразуют расширение в тип mime. Теперь я могу нажать на вложение в электронном письме и открыть его в своем приложении.

Ответ 14

//Я пробовал этот код. И это работает хорошо. Вы можете использовать этот код, чтобы принять файл PDF.

<intent-filter>
   <action android:name="android.intent.action.SEND" />
   <category android:name="android.intent.category.DEFAULT" />
   <data android:mimeType="application/pdf" />
   <data android:pathPattern=".*\\.pdf" />
   <data android:pathPattern=".*\\..*\\.pdf" />
   <data android:pathPattern=".*\\..*\\..*\\.pdf" />
   <data android:pathPattern=".*\\..*\\..*\\..*\\.pdf" />
</intent-filter>

Ответ 15

Прочитайте открытие файла в kotlin:

private fun checkFileOpening(intent: Intent) {
    if (intent.action == Intent.ACTION_VIEW && (intent.scheme == ContentResolver.SCHEME_FILE
                    || intent.scheme == ContentResolver.SCHEME_CONTENT)) {

        val text = intent.data?.let {
            contentResolver.openInputStream(it)?.bufferedReader()?.use(BufferedReader::readText) 
        }
    }
}

Ответ 16

Поместите этот фильтр намерений в тег активности в манифесте, который вы хотите открыть, касаясь файла:

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

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

    <data android:host="*" />
    <data android:mimeType="application/octet-stream" />
    <data android:pathPattern=".*\\..*\\..*\\..*\\..*\\.yourextension" />
    <data android:pathPattern=".*\\..*\\..*\\..*\\.yourextension" />
    <data android:pathPattern=".*\\..*\\..*\\.yourextension" />
    <data android:pathPattern=".*\\..*\\.yourextension" />
    <data android:pathPattern=".*\\.yourextension" />
    <data android:scheme="content" />
</intent-filter>