Filtro de intenção do Android: associar o aplicativo à extensão do arquivo

92

Tenho um tipo / extensão de arquivo personalizado ao qual desejo associar meu aplicativo.

Pelo que eu sei, o elemento de dados é feito para essa finalidade, mas não consigo fazê-lo funcionar. http://developer.android.com/guide/topics/manifest/data-element.html De acordo com os documentos e várias postagens do fórum, deve funcionar assim:

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

Bem, isso não funciona. O que eu fiz errado? Eu simplesmente quero declarar meu próprio tipo de arquivo.

Tamas
fonte
Adicione android: label = "@ string / app_name" no filtro de intent
Prashant

Respostas:

116

Você precisa de vários filtros de intent para lidar com as diferentes situações que deseja controlar.

Exemplo 1, lidar com solicitações http sem tipos MIME:

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

Lidar com tipos MIME, onde o sufixo é irrelevante:

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

Manipule a intenção de um aplicativo de navegador de arquivos:

  <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>
Phyrum Tea
fonte
Achei que host = "*" poderia ser deixado de fora, mas começou a ser muito amplo.
Phyrum Tea
@Phyrum Tea: Existe alguma maneira de associar uma extensão de arquivo a OUTRO aplicativo usando meu aplicativo? Meu aplicativo gera arquivos .list que eu quero dizer ao Android que eles são arquivos de texto e abri-los com um editor de texto (meu aplicativo NÃO é um editor de texto).
Luis A. Florit
@ LuisA.Florit você tentou chamar seus arquivos de * .txt? Pode funcionar.
Gavriel
extensão personalizada não funciona com o MimeType. Se eu fornecer o tipo MIME como / meu aplicativo é exibido quando eu clico em notificação do Gmail também.
madan V
1
Não está funcionando .. Estou farto agora de pesquisar nos últimos 2 dias não funcionou. estou usando o Android 9
Ahmad Arslan
48

As outras soluções não funcionaram de maneira confiável para mim até que eu adicionei:

android:mimeType="*/*" 

Antes funcionava em algumas aplicações, em outras não ...

solução completa para mim:

<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>
Emmanuel Touzery
fonte
1
Uau! isso me ajudou. Parece que a documentação do Android está incorreta. Tudo (esquema, host, caminho [padrão], mimeType) deve ser declarado para funcionar
Gavriel
Você deve ter tanto uma regra com o mimeTypee outra sem se você quiser ser completa. Consulte developer.android.com/guide/components/…
Andrew Sun
1
E se você quiser abrir seu aplicativo de arquivos do Gmail?
IgorGanapolsky
30

As respostas dadas por Phyrum Tea e yuku já são muito informativas.

Quero acrescentar que, a partir do Android 7.0 Nougat, há uma mudança na maneira como o compartilhamento de arquivos entre aplicativos é tratado:

Das alterações oficiais do Android 7.0 :

Para aplicativos direcionados ao Android 7.0, a estrutura do Android aplica a política da API StrictMode que proíbe a exposição de file: // URIs fora do seu aplicativo. Se um intent contendo um URI de arquivo deixar seu aplicativo, o aplicativo falhará com uma exceção FileUriExposedException.

Para compartilhar arquivos entre aplicativos, você deve enviar um content: // URI e conceder uma permissão de acesso temporário no URI. A maneira mais fácil de conceder essa permissão é usando a classe FileProvider. Para obter mais informações sobre permissões e compartilhamento de arquivos, consulte Compartilhando arquivos.

Se você tiver seu próprio arquivo personalizado terminando sem um específico mime-type(ou eu acho que até mesmo com um), pode ser necessário adicionar um segundo schemevalor ao seu intent-filterpara fazê-lo funcionar FileProviderstambém.

Exemplo:

<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's ugly primitive PatternMatcher
        implementation that can't cope with finding a . early in
        the path unless it's 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>

O importante aqui é a adição de

<data android:scheme="content" />

para o filtro.

Tive dificuldade em descobrir sobre essa pequena mudança que impedia minha atividade de abrir em dispositivos Android 7.0, enquanto tudo estava bem nas versões mais antigas. Eu espero que isso ajude alguém.

Markus Ressel
fonte
Sem essa dica e informações extras, não consegui fazer meu aplicativo se registrar no explorador de arquivos. Isso é crucial e deve ser votado positivamente
final de
Você salvou um dia! Big tnx. Esta deve ser a resposta aceita!
Andris de
⚠️ Se você tentar obter o arquivo fornecido por meio do esquema de conteúdo, por exemplo File(uri.path), ele travará devido a No such file or directory- terá que lidar com esse cenário de maneira diferente ao atualizar para suportar o Nougat +!
Jordan H
19

Minhas descobertas:

Você precisa de vários filtros para lidar com as diferentes maneiras de recuperar um arquivo. ou seja, por anexo do gmail, por explorador de arquivos, por HTTP, por FTP ... Todos eles enviam intenções muito diferentes.

E você precisa filtrar a intenção que aciona sua atividade em seu código de atividade.

Para o exemplo abaixo, criei um tipo de arquivo falso new.mrz. E eu recuperei do anexo do gmail e do explorador de arquivos.

Código de atividade adicionado no 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!
            }
        }

Filtro de anexo do 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: intenção de conteúdo detectada: android.intent.action.VIEW: content: //gmail-ls/[email protected]/messages/2950/attachments/0.1/BEST/false: application / octet-stream: new. mrz

Filtro do explorador de arquivos:

        <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: intenção de arquivo detectada: android.intent.action.VIEW: file: ///storage/sdcard0/My%20Documents/new.mrz: null: new.mrz

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

Funções privadas usadas acima:

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());
    }
}
LFOR
fonte
A atividade tem que ser lançador ou tipo principal? Porque tentei o filtro de anexo do Gmail e não está funcionando.
Amit
15

o pathPattern

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

não funciona se o caminho do arquivo contiver um ou mais pontos antes de ".pdf".

Isso vai funcionar:

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

Adicione mais se quiser suportar mais pontos.

Randy Sugianto 'Yuku'
fonte
1
A partir de agora, esta é a única opção para ir para subpastas
LokiDroid
Estou usando o mesmo código, mas o IDE me dá um aviso, veja esta pergunta stackoverflow.com/questions/35833776/…
AndreaF
5

Venho tentando fazer isso funcionar há muito tempo, tentei basicamente todas as soluções sugeridas e ainda não consigo fazer o Android reconhecer extensões de arquivo específicas. Eu tenho um filtro de intenção com um "*/*"tipo MIME que é a única coisa que parece funcionar e os navegadores de arquivos agora listam meu aplicativo como uma opção para abrir arquivos, no entanto, meu aplicativo agora é mostrado como uma opção para abrir QUALQUER TIPO de arquivo, embora Especifiquei extensões de arquivo específicas usando a tag pathPattern. Isso vai tão longe que, mesmo quando tento visualizar / editar um contato na minha lista de contatos, o Android me pergunta se eu quero usar meu aplicativo para visualizar o contato, e essa é apenas uma das muitas situações em que isso ocorre, MUITO MUITO irritante.

Acabei encontrando esta postagem do Google Groups com uma pergunta semelhante à qual um engenheiro de estrutura Android real respondeu. Ela explica que o Android simplesmente não sabe nada sobre extensões de arquivo, apenas tipos MIME ( https://groups.google.com/forum/#!topic/android-developers/a7qsSl3vQq0 ).

Portanto, pelo que vi, experimentei e li, o Android simplesmente não consegue distinguir entre extensões de arquivo e a tag pathPattern é basicamente uma perda gigantesca de tempo e energia. Se você tiver a sorte de precisar apenas de arquivos de um certo tipo MIME (por exemplo, texto, vídeo ou áudio), você pode usar um filtro de intenção com um tipo MIME. Se você precisa de uma extensão de arquivo específica ou um tipo MIME não conhecido pelo Android, então você está sem sorte.

Se eu estiver errado sobre qualquer coisa, por favor me diga, até agora eu li todos os posts e tentei todas as soluções propostas que pude encontrar, mas nenhuma funcionou.

Eu poderia escrever mais uma ou duas páginas sobre como esse tipo de coisa parece comum no Android e como a experiência do desenvolvedor é complicada, mas vou poupar meus discursos furiosos;). Espero ter salvado alguém de alguns problemas.

PeeGee85
fonte
5

Markus Ressel está correto. O Android 7.0 Nougat não permite mais o compartilhamento de arquivos entre aplicativos usando um URI de arquivo. Um URI de conteúdo deve ser usado. No entanto, um URI de conteúdo não permite que um caminho de arquivo seja compartilhado, apenas um tipo MIME. Portanto, você não pode usar um URI de conteúdo para associar seu aplicativo à sua própria extensão de arquivo.

Drobpox tem um comportamento interessante no Android 7.0. Quando encontra uma extensão de arquivo desconhecida, parece formar uma intenção de URI de arquivo, mas em vez de iniciar a intenção, ele chama o sistema operacional para descobrir quais aplicativos podem aceitar a intenção. Se houver apenas um aplicativo que pode aceitar esse URI de arquivo, ele enviará um URI de conteúdo explícito diretamente para esse aplicativo. Portanto, para trabalhar com o Dropbox, você não precisa alterar os filtros de intenção em seu aplicativo. Ele não requer um filtro de intenção de URI de conteúdo. Certifique-se de que o aplicativo pode receber um URI de conteúdo e que seu aplicativo com sua própria extensão de arquivo funcionará com o Dropbox da mesma forma que funcionava antes do Android 7.0.

Aqui está um exemplo do meu código de carregamento de arquivo modificado para aceitar um URI de conteúdo:

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);
        ...
    }
}
Peter Newman
fonte
1

Tente adicionar

<action android:name="android.intent.action.VIEW"/>
magaio
fonte
Agora que funciona com tipos de arquivo definidos de fábrica, como application / pdf, como eu declararia meu próprio tipo de arquivo? E quando digo tipo de arquivo, quero dizer mimeType;)
Tamas
Que tipo de arquivo você deseja que este tipo MIME capture? Além disso, este arquivo é aberto no navegador ou gerenciador de arquivos ou enviado de outro aplicativo criado por você?
magaio
pode ser do navegador, do cliente de e-mail, do gerenciador de arquivos ou de qualquer lugar. Ou a extensão de arquivo do meu próprio app ofc :) é personalizada, especificada pelo cliente.
Tamas,
Bem, ainda estou preso ... alguém poderia ajudar, por favor?
Tamas
@Tamas todos vocês resolveram isso. Eu estou preso nisso!
StuStirling
1

Para anexo do Gmail, você pode usar:

<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++ / ... -->

Adicione quantos tipos de MIME forem necessários. Eu só preciso disso para o meu projeto.

Hrk
fonte
1
         <!--
            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>

Observe que isso fará com que seu aplicativo abra todos os anexos de arquivo do gmail, não há como contornar isso

Ofir Bitton
fonte
Isso irá lidar com cada arquivo enviado por e-mail ao usuário, não apenas .teamz
IgorGanapolsky
1

Aqueles que tiveram problemas com outros aplicativos File Manager \ Explorer, como @yuku e @ phyrum-tea responderam

Isso funciona com o aplicativo gerenciador de arquivos padrão 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>

mas não podia funcionar com ES File Explorer e outros gerenciadores de arquivos, então adicionei

 android:mimeType="*/*"

então ele funciona com o ES Explorer, mas o gerenciador de arquivos LG não conseguiu detectar o tipo de arquivo, então minha solução é

     <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>
Sumit
fonte
1

URI de conteúdo ftw e com o filtro de intenção no manifesto ... se seus arquivos tiverem uma extensão personalizada .xyz, adicione um tipo MIME correspondente:

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

Alguns aplicativos, como e-mail, parecem converter a extensão em um tipo mime. Agora posso clicar no anexo do e-mail e abri-lo no meu aplicativo.

Sofi Software LLC
fonte
1

Se você tentar isso, vai te ajudar. Em vez de pdf, você também pode usar outras extensões. Primeiro, você precisa adicionar permissão de leitura de armazenamento externo no arquivo androidmanifest.xml .

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

Em seguida, no arquivo androidmanifest na tag Activity, você adiciona um filtro de intent conforme mostrado abaixo.

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

Finalmente, em seu código, você obtém o caminho do arquivo PDF conforme mostrado abaixo:

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

        }
Arjun Othayoth
fonte
Para este Intent.ACTION_VIEW, incapaz de obter o caminho do arquivo, ele causa o erro: java.io.FileNotFoundException: Permission denied. É apenas de alguns aplicativos específicos, não sempre. alguma solução?
Hardik Joshi
@HardikJoshi Os aplicativos podem não estar configurando a permissão GRANT_READ_URI_PERMISSION.
Mira_Cole
1

Leia o arquivo de abertura em 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) 
        }
    }
}
Brucemax
fonte
-1

Coloque este filtro de intent dentro da tag de atividade no manifesto que você gostaria de abrir tocando no arquivo:

<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>
Ráfagan
fonte
-1

// Eu tentei este código. E está funcionando bem. Você pode usar este código para aceitar o arquivo 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>
Arslan Tahir
fonte