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> — 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> — 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> — 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 — 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 "file chooser" 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 "opened", 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 "audio/ogg". 268 // To search for all documents available via installed storage providers, 269 // it would be "*/*". 270 intent.setType("image/*"); 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>@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 // "if there's anything to look at, look at it" conditionals. 335 if (cursor != null && cursor.moveToFirst()) { 336 337 // Note it's called "Display Name". 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, "Display Name: " + 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 "unpredictable". 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 = "Unknown"; 357 } 358 Log.i(TAG, "Size: " + 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 "opened", 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 "opened", 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("text/plain"); 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 & (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 — 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><provider></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>"true"</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>"true"</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><bool name="atLeastKitKat">false</bool></pre></li> 586 587<li>No arquivo de recursos {@code bool.xml} em {@code res/values-v19/}, adicione 588esta linha: <pre><bool name="atLeastKitKat">true</bool></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><manifest... > 599 ... 600 <uses-sdk 601 android:minSdkVersion="19" 602 android:targetSdkVersion="19" /> 603 .... 604 <provider 605 android:name="com.example.android.storageprovider.MyCloudProvider" 606 android:authorities="com.example.android.storageprovider.documents" 607 android:grantUriPermissions="true" 608 android:exported="true" 609 android:permission="android.permission.MANAGE_DOCUMENTS" 610 android:enabled="@bool/atLeastKitKat"> 611 <intent-filter> 612 <action android:name="android.content.action.DOCUMENTS_PROVIDER" /> 613 </intent-filter> 614 </provider> 615 </application> 616 617</manifest></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><bool name="atMostJellyBeanMR2">true</bool></pre></li> 640 641<li>No arquivo de recursos {@code bool.xml} em {@code res/values-v19/}, adicione 642esta linha: <pre><bool name="atMostJellyBeanMR2">false</bool></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<!-- This activity alias is added so that GET_CONTENT intent-filter 651 can be disabled for builds on API level 19 and higher. --> 652<activity-alias android:name="com.android.example.app.MyPicker" 653 android:targetActivity="com.android.example.app.MyActivity" 654 ... 655 android:enabled="@bool/atMostJellyBeanMR2"> 656 <intent-filter> 657 <action android:name="android.intent.action.GET_CONTENT" /> 658 <category android:name="android.intent.category.OPENABLE" /> 659 <category android:name="android.intent.category.DEFAULT" /> 660 <data android:mimeType="image/*" /> 661 <data android:mimeType="video/*" /> 662 </intent-filter> 663</activity-alias> 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 — 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@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 "MyCloud". 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 "Recents" 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 — qualquer arquivo:</p> 791 792<pre>@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>@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>@Override 844public ParcelFileDescriptor openDocument(final String documentId, 845 final String mode, 846 CancellationSignal signal) throws 847 FileNotFoundException { 848 Log.v(TAG, "openDocument, mode: " + 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 @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, "A file with id " + 869 documentId + " has been closed! 870 Time to " + 871 "update the server."); 872 } 873 874 }); 875 } catch (IOException e) { 876 throw new FileNotFoundException("Failed to open document with id " 877 + documentId + " and mode " + 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>