• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1page.title=Estrutura de acesso ao armazenamento
2@jd:body
3<div id="qv-wrapper">
4<div id="qv">
5
6<h2>Neste documento
7 <a href="#" onclick="hideNestedItems('#toc44',this);return false;" class="header-toggle">
8        <span class="more">mostrar mais</span>
9        <span class="less" style="display:none">mostrar menos</span></a></h2>
10<ol id="toc44" class="hide-nested">
11    <li>
12        <a href="#overview">Visão geral</a>
13    </li>
14    <li>
15        <a href="#flow">Controlar fluxo</a>
16    </li>
17    <li>
18        <a href="#client">Programação de um aplicativo cliente</a>
19        <ol>
20        <li><a href="#search">Busca de documentos</a></li>
21        <li><a href="#process">Processamento de resultados</a></li>
22        <li><a href="#metadata">Examinação de metadados de documentos</a></li>
23        <li><a href="#open">Abertura de um documento</a></li>
24        <li><a href="#create">Criação de um novo documento</a></li>
25        <li><a href="#delete">Exclusão de um documento</a></li>
26        <li><a href="#edit">Edição de um documento</a></li>
27        <li><a href="#permissions">Manutenção de permissões</a></li>
28        </ol>
29    </li>
30    <li><a href="#custom">Criação de um provedor de documentos personalizado</a>
31        <ol>
32        <li><a href="#manifest">Manifesto</a></li>
33        <li><a href="#contract">Contratos</a></li>
34        <li><a href="#subclass">Subclasse DocumentsProvider</a></li>
35        <li><a href="#security">Segurança</a></li>
36        </ol>
37    </li>
38
39</ol>
40<h2>Classes principais</h2>
41<ol>
42    <li>{@link android.provider.DocumentsProvider}</li>
43    <li>{@link android.provider.DocumentsContract}</li>
44</ol>
45
46<h2>Vídeos</h2>
47
48<ol>
49    <li><a href="http://www.youtube.com/watch?v=zxHVeXbK1P4">
50DevBytes: estrutura de acesso ao armazenamento do Android 4.4: Provedor</a></li>
51     <li><a href="http://www.youtube.com/watch?v=UFj9AEz0DHQ">
52DevBytes: estrutura de acesso ao armazenamento do Android 4.4: Cliente</a></li>
53</ol>
54
55
56<h2>Amostras de código</h2>
57
58<ol>
59    <li><a href="{@docRoot}samples/StorageProvider/index.html">
60Provedor de armazenamento</a></li>
61     <li><a href="{@docRoot}samples/StorageClient/index.html">
62StorageClient</a></li>
63</ol>
64
65<h2>Veja também</h2>
66<ol>
67    <li>
68        <a href="{@docRoot}guide/topics/providers/content-provider-basics.html">
69        Preceitos do provedor de conteúdo
70        </a>
71    </li>
72</ol>
73
74</div>
75</div>
76
77
78<p>O Android 4.4 (API de nível 19) introduz a Estrutura de Acesso ao Armazenamento (SAF). A SAF
79 simplifica para os usuários a busca e abertura de documentos, imagens e outros arquivos
80dentre todos os provedores de armazenamento de documentos de preferência. A interface gráfica padrão é fácil de usar
81e permite aos usuários buscar arquivos e acessar arquivos recentes de modo coerente em todos os aplicativos e provedores.</p>
82
83<p>Serviços de armazenamento local ou em nuvem podem participar desse ecossistema por meio da implementação
84de um {@link android.provider.DocumentsProvider} que encapsula os serviços. Aplicativos
85clientes que precisam acessar documentos de um provedor podem integrar-se com a SAF com apenas algumas
86linhas de código.</p>
87
88<p>A SAF contém:</p>
89
90<ul>
91<li><strong>Provedor de documentos</strong> &mdash; Provedor de conteúdo que oferece
92um serviço de armazenamento (como o Google Drive) para exibir os arquivos que gerencia. O provedor de documentos
93é implementado como uma subclasse da classe {@link android.provider.DocumentsProvider}.
94O esquema do provedor de documento se baseia em uma hierarquia de arquivo tradicional,
95embora o modo de armazenamento físico de dados do provedor de documentos seja definido pelo programador.
96A plataforma do Android contém diversos provedores de documento embutidos, como
97Downloads, Imagens e Vídeos.</li>
98
99<li><strong>Aplicativo cliente</strong> &mdash; Aplicativo personalizado que chama a intenção
100{@link android.content.Intent#ACTION_OPEN_DOCUMENT}
101e/ou {@link android.content.Intent#ACTION_CREATE_DOCUMENT} e recebe
102os arquivos retornados pelos provedores de documentos.</li>
103
104<li><strong>Seletor</strong> &mdash; IU de sistema que permite aos usuários acessar documentos de todos
105os provedores de documentos que satisfazem os critérios de busca do aplicativo cliente.</li>
106</ul>
107
108<p>A seguir há alguns recursos oferecidos pela SAF:</p>
109<ul>
110<li>Permitir que usuários busquem conteúdo de todos os provedores de documentos, não somente de um único aplicativo.</li>
111<li>Possibilitar ao aplicativo a obtenção de acesso persistente e de longo prazo
112a documentos de propriedade de um provedor de documentos. Por meio deste acesso, os usuários podem adicionar, editar,
113 salvar e excluir arquivos no provedor.</li>
114<li>É compatível com diversas contas de usuário e raízes transitórias como provedores
115de armazenamento USB, que só aparecem se o dispositivo estiver plugado. </li>
116</ul>
117
118<h2 id ="overview">Visão geral</h2>
119
120<p>A SAF consiste em um provedor de conteúdo que é uma
121subclasse da classe {@link android.provider.DocumentsProvider}. Dentro de um <em>provedor de documentos</em>, os dados
122são estruturados como uma hierarquia de arquivo tradicional:</p>
123<p><img src="{@docRoot}images/providers/storage_datamodel.png" alt="data model" /></p>
124<p class="img-caption"><strong>Figura 1.</strong> Modelo de dados do provedor de documentos Uma Raiz aponta para um único Documento,
125que então inicia o fan-out de toda a árvore.</p>
126
127<p>Observe o seguinte:</p>
128<ul>
129
130<li>Cada provedor de documentos relata uma ou mais
131"raízes", que são pontos de partida na exploração de uma árvore de documentos.
132Cada raiz tem um {@link android.provider.DocumentsContract.Root#COLUMN_ROOT_ID} exclusivo
133e ele aponta para um documento (um diretório)
134representando o conteúdo sob essa raiz.
135As raízes têm um projeto dinâmico para oferecer compatibilidade a casos de uso como diversas contas,
136dispositivos de armazenamento USB transitórios ou login/logout do usuário.</li>
137
138<li>Sob cada raiz há um documento único. Esse documento indica 1 a <em>N</em> documentos,
139cada um deles, por sua vez, podem indicar 1 a <em>N</em> documentos. </li>
140
141<li>Cada back-end de armazenamento apresenta
142arquivos e diretórios individuais referenciando-os com um
143{@link android.provider.DocumentsContract.Document#COLUMN_DOCUMENT_ID} exclusivo.
144IDs de documentos devem ser exclusivos e não podem mudar depois de emitidos, pois são usados para concessões persistentes
145da URI em reinicializações do dispositivo.</li>
146
147
148<li>Documentos podem ser um arquivo ou um diretório que pode ser aberto (com um tipo MIME específico)
149 contendo documentos adicionais (com
150o tipo MIME{@link android.provider.DocumentsContract.Document#MIME_TYPE_DIR}).</li>
151
152<li>Cada documento tem diferentes recursos, como descrito por
153{@link android.provider.DocumentsContract.Document#COLUMN_FLAGS COLUMN_FLAGS}.
154Por exemplo,{@link android.provider.DocumentsContract.Document#FLAG_SUPPORTS_WRITE},
155{@link android.provider.DocumentsContract.Document#FLAG_SUPPORTS_DELETE}
156e {@link android.provider.DocumentsContract.Document#FLAG_SUPPORTS_THUMBNAIL}.
157O mesmo {@link android.provider.DocumentsContract.Document#COLUMN_DOCUMENT_ID} pode ser
158incluído em diversos diretórios.</li>
159</ul>
160
161<h2 id="flow">Controle de fluxo</h2>
162<p>Como indicado anteriormente, o modelo de dados do provedor de documentos se baseia
163em uma hierarquia de arquivo tradicional. Contudo, é possível armazenar os dados fisicamente como quiser desde
164que eles possam ser acessados pela API {@link android.provider.DocumentsProvider}. Por exemplo: seria
165possível usar armazenamento em nuvem com base em tag para os dados.</p>
166
167<p>A figura 2 ilustra um exemplo de um aplicativo de foto que usa a SAF
168para acessar dados armazenados:</p>
169<p><img src="{@docRoot}images/providers/storage_dataflow.png" alt="app" /></p>
170
171<p class="img-caption"><strong>Figura 2.</strong> Fluxo da estrutura de acesso ao armazenamento</p>
172
173<p>Observe o seguinte:</p>
174<ul>
175
176<li>Na SAF, provedores e clientes não interagem
177diretamente. O cliente solicita permissão para interagir
178com arquivos (ou seja, para ler, editar, criar ou excluir arquivos).</li>
179
180<li>A interação começa quando um aplicativo (neste exemplo, um aplicativo de foto) dispara a intenção
181{@link android.content.Intent#ACTION_OPEN_DOCUMENT} ou {@link android.content.Intent#ACTION_CREATE_DOCUMENT}. A intenção pode conter filtros
182para refinar ainda mais os critérios &mdash; por exemplo: "quero todos os arquivos que podem ser abertos
183e que tenham o tipo MIME de tal imagem".</li>
184
185<li>Ao disparar a intenção, o seletor do sistema contata cada provedor registrado
186e exibe as raízes de conteúdo correspondentes ao usuário.</li>
187
188<li>O seletor fornece aos usuários uma interface padrão para acessar documentos,
189embora os provedores de documentos subjacentes possam ser bem diferentes. Por exemplo: a figura 2
190exibe um provedor do Google Drive, um provedor USB e um provedor de nuvem.</li>
191</ul>
192
193<p>A figura 3 exibe um seletor em que um usuário em busca de imagens selecionou
194uma conta do Google Drive:</p>
195
196<p><img src="{@docRoot}images/providers/storage_picker.png" width="340" alt="picker" style="border:2px solid #ddd" /></p>
197
198<p class="img-caption"><strong>Figura 3.</strong> Seletor</p>
199
200<p>Quando o usuário seleciona o Google Drive, as imagens são exibidas como ilustrado
201na figura 4. Desse momento em diante, o usuário poderá interagir com eles de todos os modos
202compatíveis com o provedor e o aplicativo cliente.
203
204<p><img src="{@docRoot}images/providers/storage_photos.png" width="340" alt="picker" style="border:2px solid #ddd" /></p>
205
206<p class="img-caption"><strong>Figura 4.</strong> Imagens</p>
207
208<h2 id="client">Programação de um aplicativo cliente</h2>
209
210<p>No Android 4.3 e em versões anteriores, para o aplicativo recuperar um arquivo de outro
211aplicativo, ele precisa chamar uma intenção como {@link android.content.Intent#ACTION_PICK}
212ou {@link android.content.Intent#ACTION_GET_CONTENT}. Em seguida, o usuário deve selecionar
213um único aplicativo do qual deseja retirar um arquivo e o aplicativo selecionado deve fornecer uma interface
214do usuário para a busca e a seleção dos arquivos disponíveis. </p>
215
216<p>No Android 4.4 e em versões posteriores, existe a opção adicional de usar
217a intenção {@link android.content.Intent#ACTION_OPEN_DOCUMENT},
218que exibe uma IU do seletor controlada pelo sistema que permite ao usuário
219buscar todos os arquivos que outros aplicativos disponibilizaram. Nessa IU exclusiva,
220o usuário pode selecionar um arquivo de qualquer aplicativo compatível.</p>
221
222<p>{@link android.content.Intent#ACTION_OPEN_DOCUMENT}
223não substitui {@link android.content.Intent#ACTION_GET_CONTENT}.
224O uso de cada um deles depende da necessidade do aplicativo:</p>
225
226<ul>
227<li>Use {@link android.content.Intent#ACTION_GET_CONTENT} se deseja que o aplicativo
228simplesmente leia ou importe dados. Nessa abordagem, o aplicativo importa uma cópia dos dados,
229assim como um arquivo de imagem.</li>
230
231<li>Use {@link android.content.Intent#ACTION_OPEN_DOCUMENT} se deseja que o aplicativo
232tenha acesso persistente e de longo prazo a documentos de propriedade de um provedor
233de documentos. Um exemplo seria um aplicativo de edição de fotos que permite aos usuários editar
234imagens armazenadas em um provedor de documentos. </li>
235
236</ul>
237
238
239<p>Esta seção descreve como programar aplicativos clientes com base nas intenções
240{@link android.content.Intent#ACTION_OPEN_DOCUMENT}
241e {@link android.content.Intent#ACTION_CREATE_DOCUMENT}.</p>
242
243
244<h3 id="search">Busca de documentos</h3>
245
246<p>
247O fragmento a seguir usa {@link android.content.Intent#ACTION_OPEN_DOCUMENT}
248para buscar provedores de documentos
249que contenham arquivos de imagem:</p>
250
251<pre>private static final int READ_REQUEST_CODE = 42;
252...
253/**
254 * Fires an intent to spin up the &quot;file chooser&quot; UI and select an image.
255 */
256public void performFileSearch() {
257
258    // ACTION_OPEN_DOCUMENT is the intent to choose a file via the system's file
259    // browser.
260    Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
261
262    // Filter to only show results that can be &quot;opened&quot;, such as a
263    // file (as opposed to a list of contacts or timezones)
264    intent.addCategory(Intent.CATEGORY_OPENABLE);
265
266    // Filter to show only images, using the image MIME data type.
267    // If one wanted to search for ogg vorbis files, the type would be &quot;audio/ogg&quot;.
268    // To search for all documents available via installed storage providers,
269    // it would be &quot;*/*&quot;.
270    intent.setType(&quot;image/*&quot;);
271
272    startActivityForResult(intent, READ_REQUEST_CODE);
273}</pre>
274
275<p>Observe o seguinte:</p>
276<ul>
277<li>Quando o aplicativo dispara a intenção
278{@link android.content.Intent#ACTION_OPEN_DOCUMENT}, ele aciona um seletor que exibe todos os provedores de documentos compatíveis.</li>
279
280<li>A adição da categoria {@link android.content.Intent#CATEGORY_OPENABLE}
281à intenção filtra os resultados, que exibem somente documentos que podem ser abertos, como os arquivos de imagem.</li>
282
283<li>A declaração {@code intent.setType("image/*")} filtra ainda mais
284para exibir somente documentos que têm o tipo de dados MIME da imagem.</li>
285</ul>
286
287<h3 id="results">Processamento de resultados</h3>
288
289<p>Quando o usuário seleciona um documento no seletor,
290{@link android.app.Activity#onActivityResult onActivityResult()} é chamado.
291A URI direcionada ao documento selecionado está presente no parâmetro
292{@code resultData}. Extraia a URI usando {@link android.content.Intent#getData getData()}.
293Depois, será possível usá-la para recuperar o documento que o usuário deseja. Por
294exemplo:</p>
295
296<pre>&#64;Override
297public void onActivityResult(int requestCode, int resultCode,
298        Intent resultData) {
299
300    // The ACTION_OPEN_DOCUMENT intent was sent with the request code
301    // READ_REQUEST_CODE. If the request code seen here doesn't match, it's the
302    // response to some other intent, and the code below shouldn't run at all.
303
304    if (requestCode == READ_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
305        // The document selected by the user won't be returned in the intent.
306        // Instead, a URI to that document will be contained in the return intent
307        // provided to this method as a parameter.
308        // Pull that URI using resultData.getData().
309        Uri uri = null;
310        if (resultData != null) {
311            uri = resultData.getData();
312            Log.i(TAG, "Uri: " + uri.toString());
313            showImage(uri);
314        }
315    }
316}
317</pre>
318
319<h3 id="metadata">Examinação de metadados de documentos</h3>
320
321<p>Quando tiver a URI de um documento, você terá acesso aos seus metadados. Este
322fragmento apanha os metadados de um documento especificado pela URI e registra-os:</p>
323
324<pre>public void dumpImageMetaData(Uri uri) {
325
326    // The query, since it only applies to a single document, will only return
327    // one row. There's no need to filter, sort, or select fields, since we want
328    // all fields for one document.
329    Cursor cursor = getActivity().getContentResolver()
330            .query(uri, null, null, null, null, null);
331
332    try {
333    // moveToFirst() returns false if the cursor has 0 rows.  Very handy for
334    // &quot;if there's anything to look at, look at it&quot; conditionals.
335        if (cursor != null &amp;&amp; cursor.moveToFirst()) {
336
337            // Note it's called &quot;Display Name&quot;.  This is
338            // provider-specific, and might not necessarily be the file name.
339            String displayName = cursor.getString(
340                    cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME));
341            Log.i(TAG, &quot;Display Name: &quot; + displayName);
342
343            int sizeIndex = cursor.getColumnIndex(OpenableColumns.SIZE);
344            // If the size is unknown, the value stored is null.  But since an
345            // int can't be null in Java, the behavior is implementation-specific,
346            // which is just a fancy term for &quot;unpredictable&quot;.  So as
347            // a rule, check if it's null before assigning to an int.  This will
348            // happen often:  The storage API allows for remote files, whose
349            // size might not be locally known.
350            String size = null;
351            if (!cursor.isNull(sizeIndex)) {
352                // Technically the column stores an int, but cursor.getString()
353                // will do the conversion automatically.
354                size = cursor.getString(sizeIndex);
355            } else {
356                size = &quot;Unknown&quot;;
357            }
358            Log.i(TAG, &quot;Size: &quot; + size);
359        }
360    } finally {
361        cursor.close();
362    }
363}
364</pre>
365
366<h3 id="open-client">Abertura de um documento</h3>
367
368<p>Assim que tiver a URI de um documento, você poderá abri-lo ou fazer o que
369quiser com ele.</p>
370
371<h4>Bitmap</h4>
372
373<p>A seguir há um exemplo de como abrir um {@link android.graphics.Bitmap}:</p>
374
375<pre>private Bitmap getBitmapFromUri(Uri uri) throws IOException {
376    ParcelFileDescriptor parcelFileDescriptor =
377            getContentResolver().openFileDescriptor(uri, "r");
378    FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor();
379    Bitmap image = BitmapFactory.decodeFileDescriptor(fileDescriptor);
380    parcelFileDescriptor.close();
381    return image;
382}
383</pre>
384
385<p>Observe que não se deve realizar essa operação no encadeamento da IU. Faça isso
386em segundo plano usando {@link android.os.AsyncTask}. Assim que abrir o bitmap, você
387poderá exibi-lo em uma {@link android.widget.ImageView}.
388</p>
389
390<h4>Obter uma InputStream</h4>
391
392<p>Abaixo há um exemplo de como obter uma {@link java.io.InputStream} dessa URI.
393Neste fragmento, as linhas do arquivo estão sendo lidas em uma string:</p>
394
395<pre>private String readTextFromUri(Uri uri) throws IOException {
396    InputStream inputStream = getContentResolver().openInputStream(uri);
397    BufferedReader reader = new BufferedReader(new InputStreamReader(
398            inputStream));
399    StringBuilder stringBuilder = new StringBuilder();
400    String line;
401    while ((line = reader.readLine()) != null) {
402        stringBuilder.append(line);
403    }
404    fileInputStream.close();
405    parcelFileDescriptor.close();
406    return stringBuilder.toString();
407}
408</pre>
409
410<h3 id="create">Criação de um novo documento</h3>
411
412<p>O aplicativo pode criar um novo documento em um provedor de documentos usando
413a intenção
414{@link android.content.Intent#ACTION_CREATE_DOCUMENT}. Para criar um arquivo, deve-se fornecer um tipo MIME e um nome para o arquivo à intenção
415e ativá-la com um código de solicitação exclusivo. O resto você não precisa fazer:</p>
416
417
418<pre>
419// Here are some examples of how you might call this method.
420// The first parameter is the MIME type, and the second parameter is the name
421// of the file you are creating:
422//
423// createFile("text/plain", "foobar.txt");
424// createFile("image/png", "mypicture.png");
425
426// Unique request code.
427private static final int WRITE_REQUEST_CODE = 43;
428...
429private void createFile(String mimeType, String fileName) {
430    Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
431
432    // Filter to only show results that can be &quot;opened&quot;, such as
433    // a file (as opposed to a list of contacts or timezones).
434    intent.addCategory(Intent.CATEGORY_OPENABLE);
435
436    // Create a file with the requested MIME type.
437    intent.setType(mimeType);
438    intent.putExtra(Intent.EXTRA_TITLE, fileName);
439    startActivityForResult(intent, WRITE_REQUEST_CODE);
440}
441</pre>
442
443<p>Ao criar um novo documento, é possível obter sua URI
444em {@link android.app.Activity#onActivityResult onActivityResult()} para que
445seja possível continuar a gravar nele.</p>
446
447<h3 id="delete">Exclusão de um documento</h3>
448
449<p>Se você tem uma URI de um documento
450e os {@link android.provider.DocumentsContract.Document#COLUMN_FLAGS Document.COLUMN_FLAGS} do documento
451 contêm
452{@link android.provider.DocumentsContract.Document#FLAG_SUPPORTS_DELETE SUPPORTS_DELETE},
453você pode excluir o documento. Por exemplo:</p>
454
455<pre>
456DocumentsContract.deleteDocument(getContentResolver(), uri);
457</pre>
458
459<h3 id="edit">Edição de um documento</h3>
460
461<p>Você pode usar a SAF para editar o documento de texto no local em que está armazenado.
462Esse fragmento dispara
463a intenção {@link android.content.Intent#ACTION_OPEN_DOCUMENT} e usa
464a categoria {@link android.content.Intent#CATEGORY_OPENABLE} para exibir somente
465documentos que possam ser abertos. Ela filtra ainda mais para exibir somente arquivos de texto:</p>
466
467<pre>
468private static final int EDIT_REQUEST_CODE = 44;
469/**
470 * Open a file for writing and append some text to it.
471 */
472 private void editDocument() {
473    // ACTION_OPEN_DOCUMENT is the intent to choose a file via the system's
474    // file browser.
475    Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
476
477    // Filter to only show results that can be &quot;opened&quot;, such as a
478    // file (as opposed to a list of contacts or timezones).
479    intent.addCategory(Intent.CATEGORY_OPENABLE);
480
481    // Filter to show only text files.
482    intent.setType(&quot;text/plain&quot;);
483
484    startActivityForResult(intent, EDIT_REQUEST_CODE);
485}
486</pre>
487
488<p>Em seguida, de {@link android.app.Activity#onActivityResult onActivityResult()}
489(consulte <a href="#results">Processar resultados</a>), você pode chamar o código para realizar a edição.
490O fragmento a seguir obtém um {@link java.io.FileOutputStream}
491do {@link android.content.ContentResolver}. Por padrão, ele usa o modo de "gravação".
492Recomenda-se solicitar a menor quantidade de acesso necessária, portanto não solicite
493acesso de leitura e programação se só for necessária a programação:</p>
494
495<pre>private void alterDocument(Uri uri) {
496    try {
497        ParcelFileDescriptor pfd = getActivity().getContentResolver().
498                openFileDescriptor(uri, "w");
499        FileOutputStream fileOutputStream =
500                new FileOutputStream(pfd.getFileDescriptor());
501        fileOutputStream.write(("Overwritten by MyCloud at " +
502                System.currentTimeMillis() + "\n").getBytes());
503        // Let the document provider know you're done by closing the stream.
504        fileOutputStream.close();
505        pfd.close();
506    } catch (FileNotFoundException e) {
507        e.printStackTrace();
508    } catch (IOException e) {
509        e.printStackTrace();
510    }
511}</pre>
512
513<h3 id="permissions">Manutenção de permissões</h3>
514
515<p>Quando o aplicativo abre um arquivo para leitura ou programação, o sistema lhe fornece
516uma concessão da permissão da URI para tal arquivo. Ela vigora até a reinicialização do dispositivo do usuário.
517Contudo, suponhamos que seu aplicativo seja de edição de imagens e você queira que os usuários
518acessem as últimas 5 imagens que editaram diretamente do aplicativo. Se o dispositivo do usuário
519reiniciou, você teria que enviar o usuário de volta ao seletor do sistema para encontrar
520os arquivos, o que, obviamente, não é o ideal.</p>
521
522<p>Para evitar que isso aconteça, você pode manter as permissões que o sistema
523forneceu ao aplicativo. Efetivamente, o aplicativo "toma" a concessão de permissão da URI persistente
524que o sistema está oferecendo. Isso concede ao usuário um acesso contínuo aos arquivos
525por meio do aplicativo mesmo se o dispositivo for reiniciado:</p>
526
527
528<pre>final int takeFlags = intent.getFlags()
529            &amp; (Intent.FLAG_GRANT_READ_URI_PERMISSION
530            | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
531// Check for the freshest data.
532getContentResolver().takePersistableUriPermission(uri, takeFlags);</pre>
533
534<p>Há uma etapa final. Você pode ter salvo as URIs
535mais recentes acessadas pelo seu aplicativo, mas elas não serão mais válidas &mdash; outro aplicativo
536pode ter excluído ou modificado um documento. Portanto, deve-se sempre chamar
537{@code getContentResolver().takePersistableUriPermission()} para verificar
538se há dados mais recentes.</p>
539
540<h2 id="custom">Criação de um provedor de documentos personalizado</h2>
541
542<p>
543Se você está desenvolvimento um aplicativo que fornece serviços de armazenamento para arquivos (como
544um serviço de armazenamento em nuvem), é possível disponibilizar os arquivos
545pela SAF criando um provedor de documentos personalizado.  Esta seção mostra
546como fazê-lo.</p>
547
548
549<h3 id="manifest">Manifesto</h3>
550
551<p>Para implementar um provedor de documentos personalizado, adicione ao manifesto
552do aplicativo:</p>
553<ul>
554
555<li>Um alvo de API de nível 19 ou posterior.</li>
556
557<li>Um elemento <code>&lt;provider&gt;</code> que declare o provedor de armazenamento
558personalizado. </li>
559
560<li>O nome do provedor, que é o nome da classe, inclusive o nome do pacote.
561Por exemplo: <code>com.example.android.storageprovider.MyCloudProvider</code>.</li>
562
563<li>O nome da autoridade, que é o nome do pacote (neste exemplo,
564<code>com.example.android.storageprovider</code>) e o tipo de provedor de conteúdo
565(<code>documents</code>). Por exemplo: {@code com.example.android.storageprovider.documents}.</li>
566
567<li>O atributo <code>android:exported</code> definido como <code>&quot;true&quot;</code>.
568É necessário exportar o provedor para que outros aplicativos possam vê-lo.</li>
569
570<li>O atributo <code>android:grantUriPermissions</code> definido como
571<code>&quot;true&quot;</code>. Essa configuração permite ao sistema conceder acesso ao conteúdo
572no provedor a outros aplicativos. Para ver como manter uma concessão
573para determinado documento, consulte <a href="#permissions">Manutenção de permissões</a>.</li>
574
575<li>A permissão {@code MANAGE_DOCUMENTS}. Por padrão, um provedor está disponível
576para todos. A adição dessa permissão restringirá o provedor em relação ao sistema.
577Essa restrição é importante em termos de segurança.</li>
578
579<li>O atributo {@code android:enabled} definido como um valor booleano determinado em um arquivo
580de recursos. Esse atributo visa desativar o provedor em dispositivos que executam o Android 4.3 ou versões anteriores.
581Por exemplo: {@code android:enabled="@bool/atLeastKitKat"}. Além
582disso, para incluir esse atributo no manifesto, deve-se fazer o seguinte:
583<ul>
584<li>No arquivo de recursos {@code bool.xml} em {@code res/values/}, adicione
585esta linha: <pre>&lt;bool name=&quot;atLeastKitKat&quot;&gt;false&lt;/bool&gt;</pre></li>
586
587<li>No arquivo de recursos {@code bool.xml} em {@code res/values-v19/}, adicione
588esta linha: <pre>&lt;bool name=&quot;atLeastKitKat&quot;&gt;true&lt;/bool&gt;</pre></li>
589</ul></li>
590
591<li>Um filtro de intenções que contenha
592a ação {@code android.content.action.DOCUMENTS_PROVIDER} para que o provedor
593apareça no seletor quando o sistema procurar provedores.</li>
594
595</ul>
596<p>Abaixo há alguns excertos de amostra de um manifesto que contém um provedor:</p>
597
598<pre>&lt;manifest... &gt;
599    ...
600    &lt;uses-sdk
601        android:minSdkVersion=&quot;19&quot;
602        android:targetSdkVersion=&quot;19&quot; /&gt;
603        ....
604        &lt;provider
605            android:name=&quot;com.example.android.storageprovider.MyCloudProvider&quot;
606            android:authorities=&quot;com.example.android.storageprovider.documents&quot;
607            android:grantUriPermissions=&quot;true&quot;
608            android:exported=&quot;true&quot;
609            android:permission=&quot;android.permission.MANAGE_DOCUMENTS&quot;
610            android:enabled=&quot;&#64;bool/atLeastKitKat&quot;&gt;
611            &lt;intent-filter&gt;
612                &lt;action android:name=&quot;android.content.action.DOCUMENTS_PROVIDER&quot; /&gt;
613            &lt;/intent-filter&gt;
614        &lt;/provider&gt;
615    &lt;/application&gt;
616
617&lt;/manifest&gt;</pre>
618
619<h4 id="43">Compatibilidade com dispositivos que executam Android 4.3 ou anterior</h4>
620
621<p>
622A intenção {@link android.content.Intent#ACTION_OPEN_DOCUMENT} está disponível somente
623em dispositivos que executam o Android 4.4 ou posteriores.
624Se você deseja que o aplicativo seja compatível com {@link android.content.Intent#ACTION_GET_CONTENT}
625para adaptar-se a dispositivos que executam o Android 4.3 ou versões anteriores, é necessário
626desativar o filtro de intenção {@link android.content.Intent#ACTION_GET_CONTENT}
627no manifesto para dispositivos que executam Android 4.4 ou versões posteriores.
628Um provedor de documentos e {@link android.content.Intent#ACTION_GET_CONTENT} devem ser avaliados
629de forma mutuamente exclusiva. Se houver compatibilidade com ambos simultaneamente, o aplicativo
630aparecerá duas vezes na IU do seletor do sistema, oferecendo dois meios de acesso
631diferentes aos dados armazenados. Isso pode confundir os usuários.</p>
632
633<p>A seguir apresenta-se a forma recomendada de desativar
634o filtro de intenções {@link android.content.Intent#ACTION_GET_CONTENT} para dispositivos
635que executam o Android 4.4 ou versões posteriores:</p>
636
637<ol>
638<li>No arquivo de recursos {@code bool.xml} em {@code res/values/}, adicione
639esta linha: <pre>&lt;bool name=&quot;atMostJellyBeanMR2&quot;&gt;true&lt;/bool&gt;</pre></li>
640
641<li>No arquivo de recursos {@code bool.xml} em {@code res/values-v19/}, adicione
642esta linha: <pre>&lt;bool name=&quot;atMostJellyBeanMR2&quot;&gt;false&lt;/bool&gt;</pre></li>
643
644<li>Adicione
645um <a href="{@docRoot}guide/topics/manifest/activity-alias-element.html">alias
646de atividade</a> para desativar o filtro de intenções
647{@link android.content.Intent#ACTION_GET_CONTENT} para versões 4.4 (API de nível 19) e posteriores. Por exemplo:
648
649<pre>
650&lt;!-- This activity alias is added so that GET_CONTENT intent-filter
651     can be disabled for builds on API level 19 and higher. --&gt;
652&lt;activity-alias android:name=&quot;com.android.example.app.MyPicker&quot;
653        android:targetActivity=&quot;com.android.example.app.MyActivity&quot;
654        ...
655        android:enabled=&quot;@bool/atMostJellyBeanMR2&quot;&gt;
656    &lt;intent-filter&gt;
657        &lt;action android:name=&quot;android.intent.action.GET_CONTENT&quot; /&gt;
658        &lt;category android:name=&quot;android.intent.category.OPENABLE&quot; /&gt;
659        &lt;category android:name=&quot;android.intent.category.DEFAULT&quot; /&gt;
660        &lt;data android:mimeType=&quot;image/*&quot; /&gt;
661        &lt;data android:mimeType=&quot;video/*&quot; /&gt;
662    &lt;/intent-filter&gt;
663&lt;/activity-alias&gt;
664</pre>
665</li>
666</ol>
667<h3 id="contract">Contratos</h3>
668
669<p>Normalmente, ao criar um provedor de conteúdo personalizado, uma das tarefas
670é implementar classes de contrato, como descrito
671no guia dos desenvolvedores de<a href="{@docRoot}guide/topics/providers/content-provider-creating.html#ContractClass">
672Provedores de conteúdo</a>. Classe de contrato é uma classe {@code public final}
673que contém definições constantes das URIs, nomes de coluna, tipos MIME
674e outros metadados que pertencem ao provedor. A SAF
675fornece essas classes de contrato, portanto não é necessário
676programá-las:</p>
677
678<ul>
679   <li>{@link android.provider.DocumentsContract.Document}</li>
680   <li>{@link android.provider.DocumentsContract.Root}</li>
681</ul>
682
683<p>Por exemplo, eis as colunas que podem retornar em um cursor
684ao consultar documentos ou a raiz do provedor de documentos:</p>
685
686<pre>private static final String[] DEFAULT_ROOT_PROJECTION =
687        new String[]{Root.COLUMN_ROOT_ID, Root.COLUMN_MIME_TYPES,
688        Root.COLUMN_FLAGS, Root.COLUMN_ICON, Root.COLUMN_TITLE,
689        Root.COLUMN_SUMMARY, Root.COLUMN_DOCUMENT_ID,
690        Root.COLUMN_AVAILABLE_BYTES,};
691private static final String[] DEFAULT_DOCUMENT_PROJECTION = new
692        String[]{Document.COLUMN_DOCUMENT_ID, Document.COLUMN_MIME_TYPE,
693        Document.COLUMN_DISPLAY_NAME, Document.COLUMN_LAST_MODIFIED,
694        Document.COLUMN_FLAGS, Document.COLUMN_SIZE,};
695</pre>
696
697<h3 id="subclass">Subclasse DocumentsProvider</h3>
698
699<p>A próxima etapa na criação de um provedor de documentos personalizado é atribuir uma subclasse à
700classe {@link android.provider.DocumentsProvider} abstrata. No mínimo, é necessário
701implementar os seguintes métodos:</p>
702
703<ul>
704<li>{@link android.provider.DocumentsProvider#queryRoots queryRoots()}</li>
705
706<li>{@link android.provider.DocumentsProvider#queryChildDocuments queryChildDocuments()}</li>
707
708<li>{@link android.provider.DocumentsProvider#queryDocument queryDocument()}</li>
709
710<li>{@link android.provider.DocumentsProvider#openDocument openDocument()}</li>
711</ul>
712
713<p>Esses são os únicos métodos que obrigatoriamente devem ser implementados, mas há
714muitos outros que podem ser usados. Consulte {@link android.provider.DocumentsProvider}
715para ver mais detalhes.</p>
716
717<h4 id="queryRoots">Implementação de queryRoots</h4>
718
719<p>A implementação de {@link android.provider.DocumentsProvider#queryRoots
720queryRoots()} deve retornar um {@link android.database.Cursor} que aponta para todos
721os diretórios raiz dos provedores de documentos, usando colunas definidas
722em {@link android.provider.DocumentsContract.Root}.</p>
723
724<p>No fragmento a seguir, o parâmetro {@code projection} representa
725os campos específicos que o autor da chamada quer receber de volta. O fragmento cria um novo cursor
726e adiciona-lhe uma linha &mdash; uma raiz, um diretório de nível superior, como
727Downloads ou Imagens.  A maioria dos provedores tem somente uma raiz. É possível ter mais de uma,
728por exemplo, no caso de diversas contas de usuário. Nesse caso, adicione somente
729uma segunda linha ao cursor.</p>
730
731<pre>
732&#64;Override
733public Cursor queryRoots(String[] projection) throws FileNotFoundException {
734
735    // Create a cursor with either the requested fields, or the default
736    // projection if "projection" is null.
737    final MatrixCursor result =
738            new MatrixCursor(resolveRootProjection(projection));
739
740    // If user is not logged in, return an empty root cursor.  This removes our
741    // provider from the list entirely.
742    if (!isUserLoggedIn()) {
743        return result;
744    }
745
746    // It's possible to have multiple roots (e.g. for multiple accounts in the
747    // same app) -- just add multiple cursor rows.
748    // Construct one row for a root called &quot;MyCloud&quot;.
749    final MatrixCursor.RowBuilder row = result.newRow();
750    row.add(Root.COLUMN_ROOT_ID, ROOT);
751    row.add(Root.COLUMN_SUMMARY, getContext().getString(R.string.root_summary));
752
753    // FLAG_SUPPORTS_CREATE means at least one directory under the root supports
754    // creating documents. FLAG_SUPPORTS_RECENTS means your application's most
755    // recently used documents will show up in the &quot;Recents&quot; category.
756    // FLAG_SUPPORTS_SEARCH allows users to search all documents the application
757    // shares.
758    row.add(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_CREATE |
759            Root.FLAG_SUPPORTS_RECENTS |
760            Root.FLAG_SUPPORTS_SEARCH);
761
762    // COLUMN_TITLE is the root title (e.g. Gallery, Drive).
763    row.add(Root.COLUMN_TITLE, getContext().getString(R.string.title));
764
765    // This document id cannot change once it's shared.
766    row.add(Root.COLUMN_DOCUMENT_ID, getDocIdForFile(mBaseDir));
767
768    // The child MIME types are used to filter the roots and only present to the
769    //  user roots that contain the desired type somewhere in their file hierarchy.
770    row.add(Root.COLUMN_MIME_TYPES, getChildMimeTypes(mBaseDir));
771    row.add(Root.COLUMN_AVAILABLE_BYTES, mBaseDir.getFreeSpace());
772    row.add(Root.COLUMN_ICON, R.drawable.ic_launcher);
773
774    return result;
775}</pre>
776
777<h4 id="queryChildDocuments">Implementação de queryChildDocuments</h4>
778
779<p>A implementação
780de {@link android.provider.DocumentsProvider#queryChildDocuments queryChildDocuments()}
781deve retornar um {@link android.database.Cursor} que aponta para todos os arquivos
782no diretório especificado com colunas definidas em
783{@link android.provider.DocumentsContract.Document}.</p>
784
785<p>Esse método é chamado quando uma raiz do aplicativo é escolhida na IU do seletor.
786Ele coleta os documentos filhos de um diretório na raiz.  Ele pode ser chamado em qualquer nível
787na hierarquia de arquivos, não somente na raiz. Esse fragmento
788cria um novo cursor com as colunas solicitadas e, em seguida, adiciona informações ao cursor
789sobre cada filho imediato no diretório pai.
790O filho pode ser uma imagem, outro diretório &mdash; qualquer arquivo:</p>
791
792<pre>&#64;Override
793public Cursor queryChildDocuments(String parentDocumentId, String[] projection,
794                              String sortOrder) throws FileNotFoundException {
795
796    final MatrixCursor result = new
797            MatrixCursor(resolveDocumentProjection(projection));
798    final File parent = getFileForDocId(parentDocumentId);
799    for (File file : parent.listFiles()) {
800        // Adds the file's display name, MIME type, size, and so on.
801        includeFile(result, null, file);
802    }
803    return result;
804}
805</pre>
806
807<h4 id="queryDocument">Implementação de queryDocument</h4>
808
809<p>A implementação de
810{@link android.provider.DocumentsProvider#queryDocument queryDocument()}
811deve retornar um {@link android.database.Cursor} que aponta para o arquivo especificado
812com colunas definidas em {@link android.provider.DocumentsContract.Document}.
813</p>
814
815<p>O método {@link android.provider.DocumentsProvider#queryDocument queryDocument()}
816retorna as mesmas informações passadas em
817{@link android.provider.DocumentsProvider#queryChildDocuments queryChildDocuments()},
818mas para um arquivo específico.</p>
819
820
821<pre>&#64;Override
822public Cursor queryDocument(String documentId, String[] projection) throws
823        FileNotFoundException {
824
825    // Create a cursor with the requested projection, or the default projection.
826    final MatrixCursor result = new
827            MatrixCursor(resolveDocumentProjection(projection));
828    includeFile(result, documentId, null);
829    return result;
830}
831</pre>
832
833<h4 id="openDocument">Implementação de openDocument</h4>
834
835<p>Deve-se implementar {@link android.provider.DocumentsProvider#openDocument
836openDocument()} para retornar um {@link android.os.ParcelFileDescriptor} que represente
837o arquivo especificado. Outros aplicativos podem usar o {@link android.os.ParcelFileDescriptor} retornado
838para transmitir dados. O sistema chama esse método quando o usuário seleciona um arquivo
839e o aplicativo cliente solicita acesso a ele chamando
840{@link android.content.ContentResolver#openFileDescriptor openFileDescriptor()}.
841Por exemplo:</p>
842
843<pre>&#64;Override
844public ParcelFileDescriptor openDocument(final String documentId,
845                                         final String mode,
846                                         CancellationSignal signal) throws
847        FileNotFoundException {
848    Log.v(TAG, &quot;openDocument, mode: &quot; + mode);
849    // It's OK to do network operations in this method to download the document,
850    // as long as you periodically check the CancellationSignal. If you have an
851    // extremely large file to transfer from the network, a better solution may
852    // be pipes or sockets (see ParcelFileDescriptor for helper methods).
853
854    final File file = getFileForDocId(documentId);
855
856    final boolean isWrite = (mode.indexOf('w') != -1);
857    if(isWrite) {
858        // Attach a close listener if the document is opened in write mode.
859        try {
860            Handler handler = new Handler(getContext().getMainLooper());
861            return ParcelFileDescriptor.open(file, accessMode, handler,
862                        new ParcelFileDescriptor.OnCloseListener() {
863                &#64;Override
864                public void onClose(IOException e) {
865
866                    // Update the file with the cloud server. The client is done
867                    // writing.
868                    Log.i(TAG, &quot;A file with id &quot; +
869                    documentId + &quot; has been closed!
870                    Time to &quot; +
871                    &quot;update the server.&quot;);
872                }
873
874            });
875        } catch (IOException e) {
876            throw new FileNotFoundException(&quot;Failed to open document with id &quot;
877            + documentId + &quot; and mode &quot; + mode);
878        }
879    } else {
880        return ParcelFileDescriptor.open(file, accessMode);
881    }
882}
883</pre>
884
885<h3 id="security">Segurança</h3>
886
887<p>Suponha que o provedor de documentos seja um serviço de armazenamento em nuvem protegido por senha
888e que você queira certificar-se de que os usuários estejam conectados antes de iniciar o compartilhamento dos arquivos.
889O que o aplicativo deve fazer se o usuário não estiver conectado?  A solução é retornar
890zero raiz na implementação de {@link android.provider.DocumentsProvider#queryRoots
891queryRoots()}, ou seja, um cursor de raiz vazio:</p>
892
893<pre>
894public Cursor queryRoots(String[] projection) throws FileNotFoundException {
895...
896    // If user is not logged in, return an empty root cursor.  This removes our
897    // provider from the list entirely.
898    if (!isUserLoggedIn()) {
899        return result;
900}
901</pre>
902
903<p>A outra etapa é chamar {@code getContentResolver().notifyChange()}.
904Lembra-se do {@link android.provider.DocumentsContract}?  Estamos usando-o para criar
905esta URI. O fragmento a seguir pede ao sistema que consulte as raízes
906do provedor de documentos sempre que o status de login do usuário mudar. Se o usuário não estiver
907conectado, uma chamada de {@link android.provider.DocumentsProvider#queryRoots queryRoots()} retornará um
908cursor vazio, como exibido anteriormente. Isso garante que os documentos do provedor estejam
909disponíveis somente se o usuário tiver acesso ao provedor.</p>
910
911<pre>private void onLoginButtonClick() {
912    loginOrLogout();
913    getContentResolver().notifyChange(DocumentsContract
914            .buildRootsUri(AUTHORITY), null);
915}
916</pre>