How to Create Browse Files Option in Android

Android provides storage access framework (SAF) using which an app can provide options such as browse, read, write, and delete files. Storage access framework consists of document providers, client app and file picker.

Document providers are content providers, which provide read and write access to files stored on local disk, cloud storage or any other type of storage. Android system provides built-in document providers related to downloads, images and videos.

Document provider data model is file hierarchy model with one or more roots, each root points to one document which in turn points to 1 or many documents, each of which in turn can point to 1 or more documents.

Document can be openable or it can contain other documents. You can know whether a document contains other documents or not using mime type. If mime type of a document is MIME_TYPE_DIR, it contains other documents. Document capabilities can be known by reading column flags. Document can have capabilities such as FLAG_SUPPORTS_WRITE, FLAG_SUPPORTS_DELETE, FLAG_SUPPORTS_THUMBNAIL, FLAG_SUPPORTS_COPY, FLAG_SUPPORTS_MOVE, and FLAG_SUPPORTS_REMOVE.

Client apps interact with document providers via intents and android system. To receive files, client applications need to fire ACTION_CREATE_DOCUMENT or ACTION_OPEN_DOCUMENT intents. Intents can include additional information to filter files like getting specific type of files by specifying mime type.

In response to the fired intents, android system displays UI picker which shows matching content roots from all the registered document providers. Once user selects a file from the list, system calls onActivityResult() of the activity that fired the intent. System sends uri of the selected file in results intent, granting temporary read and write permissions to the file.

Read File

You can read a file by firing an intent with ACTION_OPEN_DOCUMENT action. System opens file picker listing matching roots of registered document providers. Once you select a file, system sends uri of the selected data file in the result inent. Using content resolver, you can get input stream for the selected file.

Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); intent.addCategory(Intent.CATEGORY_OPENABLE); intent.setType("text/*"); startActivityForResult(intent, READ_REQ);

Create File

To create a file, you need fire intent with ACTION_CREATE_DOCUMENT action. You can set intent type to the type of file and pass file name as intent extras. Once you get uri from the results intent, you can use content resolver and get output stream to write content to the created file.

Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT); intent.addCategory(Intent.CATEGORY_OPENABLE); intent.setType("text/plain"); intent.putExtra(Intent.EXTRA_TITLE, "zoftino.txt"); startActivityForResult(intent, WRITE_REQ);

Edit File

To modify a file, first you need to get uri of the file by firing an intent and then get the output stream of the file using content resolver to write modifications to the file.

ParcelFileDescriptor fileDescriptor = this.getContentResolver().openFileDescriptor(uri, "w"); FileOutputStream fileOutputStream = new FileOutputStream(fileDescriptor.getFileDescriptor()); fileOutputStream.write(("android latest updates

").getBytes()); fileOutputStream.write(("android latest features

").getBytes()); fileOutputStream.close(); fileDescriptor.close();

Delete File

Cursor cursor = this.getContentResolver() .query(uri, null, null, null, null); try { if (cursor != null && cursor.moveToFirst()) { String flags = cursor.getString(cursor.getColumnIndex(DocumentsContract.Document.COLUMN_FLAGS)); if(flags.contains(""+DocumentsContract.Document.FLAG_SUPPORTS_DELETE)){ DocumentsContract.deleteDocument(getContentResolver(), uri); } } } finally { cursor.close(); }

Storage Access Framework (SAF) Example Complete Code

Activity

package com.zoftino.content; import android.app.Activity; import android.content.Intent; import android.database.Cursor; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.ParcelFileDescriptor; import android.provider.DocumentsContract; import android.support.annotation.RequiresApi; import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.view.View; import android.view.ViewGroup; import android.widget.ListView; import java.io.BufferedReader; import java.io.FileOutputStream; import java.io.InputStream; import java.io.InputStreamReader; public class SAFActivity extends AppCompatActivity { static final int READ_REQ = 24; static final int WRITE_REQ = 25; static final int EDIT_REQ = 26; static final int DELETE_REQ = 27; ViewGroup cont; ListView contactLst; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_saf); } public void readFile(View view) { Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); intent.addCategory(Intent.CATEGORY_OPENABLE); intent.setType("text/*"); startActivityForResult(intent, READ_REQ); } public void createFile(View view) { Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT); intent.addCategory(Intent.CATEGORY_OPENABLE); intent.setType("text/plain"); intent.putExtra(Intent.EXTRA_TITLE, "zoftino.txt"); startActivityForResult(intent, WRITE_REQ); } public void deleteFile(View view) { Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); intent.setType("text/plain"); startActivityForResult(intent, DELETE_REQ); } public void editDocument(View view) { Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); intent.addCategory(Intent.CATEGORY_OPENABLE); intent.setType("text/plain"); startActivityForResult(intent, EDIT_REQ); } @RequiresApi(api = Build.VERSION_CODES.KITKAT) @Override public void onActivityResult(int requestCode, int resultCode, Intent resultData) { if (resultCode == Activity.RESULT_OK) { Uri uri = null; if (resultData != null) { uri = resultData.getData(); } if(requestCode == READ_REQ){ readTextFile(uri); }else if(requestCode == EDIT_REQ){ editDocument(uri); }else if(requestCode == WRITE_REQ){ editDocument(uri); }else if(requestCode == DELETE_REQ){ deleteFile(uri); } } } private void editDocument(Uri uri) { try { ParcelFileDescriptor fileDescriptor = this.getContentResolver().openFileDescriptor(uri, "w"); FileOutputStream fileOutputStream = new FileOutputStream(fileDescriptor.getFileDescriptor()); fileOutputStream.write(("android latest updates

").getBytes()); fileOutputStream.write(("android latest features

").getBytes()); fileOutputStream.close(); fileDescriptor.close(); } catch (Exception e) { } } private void readTextFile(Uri uri) { InputStream inputStream = null; try { inputStream = getContentResolver().openInputStream(uri); BufferedReader reader = new BufferedReader(new InputStreamReader( inputStream)); String line; Log.i("","open text file - content"+"

"); while ((line = reader.readLine()) != null) { Log.i("",line+"

"); } reader.close(); inputStream.close(); } catch (Exception e) { e.printStackTrace(); } } @RequiresApi(api = Build.VERSION_CODES.KITKAT) private void deleteFile(Uri uri) { Cursor cursor = this.getContentResolver() .query(uri, null, null, null, null); try { if (cursor != null && cursor.moveToFirst()) { String flags = cursor.getString(cursor.getColumnIndex(DocumentsContract.Document.COLUMN_FLAGS)); String[] columns = cursor.getColumnNames(); for(String col : columns) { Log.i("", "Column Flags " + col); } Log.i("", "Delete Flags " + flags); if(flags.contains(""+DocumentsContract.Document.FLAG_SUPPORTS_DELETE)){ DocumentsContract.deleteDocument(getContentResolver(), uri); } } } finally { cursor.close(); } } }

Layout xml

<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/activity_saf" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context="com.zoftino.content.SAFActivity"> <Button android:id="@+id/button2" android:layout_width="match_parent" android:layout_height="wrap_content" android:onClick="readFile" android:text="Read File"></Button> <Button android:id="@+id/button1" android:layout_width="match_parent" android:layout_height="wrap_content" android:onClick="createFile" android:text="Create File"></Button> <Button android:id="@+id/button3" android:layout_width="match_parent" android:layout_height="wrap_content" android:onClick="editDocument" android:text="Edit File"></Button> <Button android:id="@+id/button4" android:layout_width="match_parent" android:layout_height="wrap_content" android:onClick="deleteFile" android:text="Delete File"></Button> </LinearLayout>

To delete a file first you need to get uri of the file and then using content resolver you need to get meta data of the file to check whether the file can be deleted or not. If file flags contains FLAG_SUPPORTS_DELETE flag, then the file can be deleted. Delete is performed using content resolver.