page.title=Storage Access Framework @jd:body
Android 4.4 (API level 19) memperkenalkan Storage Access Framework (SAF, Kerangka Kerja Akses Penyimpanan). SAF memudahkan pengguna menyusuri dan membuka dokumen, gambar, dan file lainnya di semua penyedia penyimpanan dokumen pilihannya. UI standar yang mudah digunakan memungkinkan pengguna menyusuri file dan mengakses yang terbaru dengan cara konsisten di antara berbagai aplikasi dan penyedia.
Layanan penyimpanan cloud atau lokal bisa dilibatkan dalam ekosistem ini dengan mengimplementasikan sebuah {@link android.provider.DocumentsProvider} yang membungkus layanannya. Aplikasi klien yang memerlukan akses ke dokumen sebuah penyedia bisa berintegrasi dengan SAF cukup dengan beberapa baris kode.
SAF terdiri dari berikut ini:
Beberapa fitur yang disediakan oleh SAF adalah sebagai berikut:
SAF berpusat di seputar penyedia konten yang merupakan subkelas dari kelas {@link android.provider.DocumentsProvider}. Dalam penyedia dokumen, data distrukturkan sebagai hierarki file biasa:
Gambar 1. Model data penyedia dokumen. Root menunjuk ke satu Document, yang nanti memulai pemekaran seluruh pohon.
Perhatikan yang berikut ini:
Seperti dinyatakan di atas, model data penyedia dokumen dibuat berdasarkan hierarki file biasa. Akan tetapi, Anda bisa menyimpan secara fisik data dengan cara apa pun yang disukai, selama data bisa diakses melalui API {@link android.provider.DocumentsProvider}. Misalnya, Anda bisa menggunakan penyimpanan cloud berbasis tag untuk data Anda.
Gambar 2 menampilkan contoh cara aplikasi foto bisa menggunakan SAF untuk mengakses data tersimpan:
Gambar 2. Arus Storage Access Framework
Perhatikan yang berikut ini:
Gambar 3 menunjukkan picker yang di digunakan pengguna mencari gambar telah memilih akun Google Drive:
Gambar 3. Picker
Bila pengguna memilih Google Drive, gambar-gambar akan ditampilkan, seperti yang ditampilkan dalam gambar 4. Dari titik itu, pengguna bisa berinteraksi dengan gambar dengan cara apa pun yang didukung oleh penyedia dan aplikasi klien.
Gambar 4. Gambar
Pada Android 4.3 dan yang lebih rendah, jika Anda ingin aplikasi mengambil file dari aplikasi lain, aplikasi Anda harus memanggil intent seperti {@link android.content.Intent#ACTION_PICK} atau {@link android.content.Intent#ACTION_GET_CONTENT}. Pengguna nanti harus memilih satu aplikasi yang akan digunakan untuk mengambil file dan aplikasi yang dipilih harus menyediakan antarmuka pengguna bagi untuk menyusuri dan mengambil dari file yang tersedia.
Pada Android 4.4 dan yang lebih tinggi, Anda mempunyai opsi tambahan dalam menggunakan intent {@link android.content.Intent#ACTION_OPEN_DOCUMENT}, yang menampilkan UI picker yang dikontrol oleh sistem yang memungkinkan pengguna menyusuri semua file yang disediakan aplikasi lain. Dari satu UI ini, pengguna bisa mengambil file dari aplikasi apa saja yang didukung.
{@link android.content.Intent#ACTION_OPEN_DOCUMENT} tidak dimaksudkan untuk menjadi pengganti {@link android.content.Intent#ACTION_GET_CONTENT}. Yang harus Anda gunakan bergantung pada kebutuhan aplikasi:
Bagian ini menjelaskan cara menulis aplikasi klien berdasarkan {@link android.content.Intent#ACTION_OPEN_DOCUMENT} dan intent {@link android.content.Intent#ACTION_CREATE_DOCUMENT}.
Cuplikan berikut menggunakan {@link android.content.Intent#ACTION_OPEN_DOCUMENT} untuk mencari penyedia dokumen yang berisi file gambar:
private static final int READ_REQUEST_CODE = 42; ... /** * Fires an intent to spin up the "file chooser" UI and select an image. */ public void performFileSearch() { // ACTION_OPEN_DOCUMENT is the intent to choose a file via the system's file // browser. Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); // Filter to only show results that can be "opened", such as a // file (as opposed to a list of contacts or timezones) intent.addCategory(Intent.CATEGORY_OPENABLE); // Filter to show only images, using the image MIME data type. // If one wanted to search for ogg vorbis files, the type would be "audio/ogg". // To search for all documents available via installed storage providers, // it would be "*/*". intent.setType("image/*"); startActivityForResult(intent, READ_REQUEST_CODE); }
Perhatikan yang berikut ini:
Setelah pengguna memilih dokumen di picker, {@link android.app.Activity#onActivityResult onActivityResult()} akan dipanggil. URI yang menunjuk ke dokumen yang dipilih dimasukkan dalam parameter {@code resultData} . Ekstrak URI dengan {@link android.content.Intent#getData getData()}. Setelah mendapatkannya, Anda bisa menggunakannya untuk mengambil dokumen yang diinginkan pengguna. Misalnya :
@Override public void onActivityResult(int requestCode, int resultCode, Intent resultData) { // The ACTION_OPEN_DOCUMENT intent was sent with the request code // READ_REQUEST_CODE. If the request code seen here doesn't match, it's the // response to some other intent, and the code below shouldn't run at all. if (requestCode == READ_REQUEST_CODE && resultCode == Activity.RESULT_OK) { // The document selected by the user won't be returned in the intent. // Instead, a URI to that document will be contained in the return intent // provided to this method as a parameter. // Pull that URI using resultData.getData(). Uri uri = null; if (resultData != null) { uri = resultData.getData(); Log.i(TAG, "Uri: " + uri.toString()); showImage(uri); } } }
Setelah Anda memiliki URI untuk dokumen, Anda akan mendapatkan akses ke metadatanya. Cuplikan ini memegang metadata sebuah dokumen yang disebutkan oleh URI, dan mencatatnya:
public void dumpImageMetaData(Uri uri) { // The query, since it only applies to a single document, will only return // one row. There's no need to filter, sort, or select fields, since we want // all fields for one document. Cursor cursor = getActivity().getContentResolver() .query(uri, null, null, null, null, null); try { // moveToFirst() returns false if the cursor has 0 rows. Very handy for // "if there's anything to look at, look at it" conditionals. if (cursor != null && cursor.moveToFirst()) { // Note it's called "Display Name". This is // provider-specific, and might not necessarily be the file name. String displayName = cursor.getString( cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)); Log.i(TAG, "Display Name: " + displayName); int sizeIndex = cursor.getColumnIndex(OpenableColumns.SIZE); // If the size is unknown, the value stored is null. But since an // int can't be null in Java, the behavior is implementation-specific, // which is just a fancy term for "unpredictable". So as // a rule, check if it's null before assigning to an int. This will // happen often: The storage API allows for remote files, whose // size might not be locally known. String size = null; if (!cursor.isNull(sizeIndex)) { // Technically the column stores an int, but cursor.getString() // will do the conversion automatically. size = cursor.getString(sizeIndex); } else { size = "Unknown"; } Log.i(TAG, "Size: " + size); } } finally { cursor.close(); } }
Setelah mendapatkan URI dokumen, Anda bisa membuka dokumen atau melakukan apa saja yang diinginkan padanya.
Berikut ini adalah contoh cara membuka {@link android.graphics.Bitmap}:
private Bitmap getBitmapFromUri(Uri uri) throws IOException { ParcelFileDescriptor parcelFileDescriptor = getContentResolver().openFileDescriptor(uri, "r"); FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor(); Bitmap image = BitmapFactory.decodeFileDescriptor(fileDescriptor); parcelFileDescriptor.close(); return image; }
Perhatikan bahwa Anda tidak boleh melakukan operasi ini pada thread UI. Lakukan hal ini di latar belakang , dengan menggunakan {@link android.os.AsyncTask}. Setelah membuka bitmap, Anda bisa menampilkannya dalam {@link android.widget.ImageView}.
Berikut ini adalah contoh cara mendapatkan {@link java.io.InputStream} dari URI. Dalam cuplikan ini , baris-baris file dibaca ke dalam sebuah string:
private String readTextFromUri(Uri uri) throws IOException { InputStream inputStream = getContentResolver().openInputStream(uri); BufferedReader reader = new BufferedReader(new InputStreamReader( inputStream)); StringBuilder stringBuilder = new StringBuilder(); String line; while ((line = reader.readLine()) != null) { stringBuilder.append(line); } fileInputStream.close(); parcelFileDescriptor.close(); return stringBuilder.toString(); }
Aplikasi Anda bisa membuat dokumen baru dalam penyedia dokumen dengan menggunakan intent {@link android.content.Intent#ACTION_CREATE_DOCUMENT} . Untuk membuat file, Anda memberikan satu tipe MIME dan satu nama file pada intent, dan menjalankannya dengan kode permintaan yang unik. Selebihnya akan diurus untuk Anda:
// Here are some examples of how you might call this method. // The first parameter is the MIME type, and the second parameter is the name // of the file you are creating: // // createFile("text/plain", "foobar.txt"); // createFile("image/png", "mypicture.png"); // Unique request code. private static final int WRITE_REQUEST_CODE = 43; ... private void createFile(String mimeType, String fileName) { Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT); // Filter to only show results that can be "opened", such as // a file (as opposed to a list of contacts or timezones). intent.addCategory(Intent.CATEGORY_OPENABLE); // Create a file with the requested MIME type. intent.setType(mimeType); intent.putExtra(Intent.EXTRA_TITLE, fileName); startActivityForResult(intent, WRITE_REQUEST_CODE); }
Setelah membuat dokumen baru, Anda bisa mendapatkan URI-nya dalam {@link android.app.Activity#onActivityResult onActivityResult()}, sehingga Anda bisa terus menulis ke dokumen itu.
Jika Anda memiliki URI dokumen dan {@link android.provider.DocumentsContract.Document#COLUMN_FLAGS Document.COLUMN_FLAGS} dokumen berisi {@link android.provider.DocumentsContract.Document#FLAG_SUPPORTS_DELETE SUPPORTS_DELETE}, Anda bisa menghapus dokumen tersebut. Misalnya:
DocumentsContract.deleteDocument(getContentResolver(), uri);
Anda bisa menggunakan SAF untuk mengedit dokumen teks langsung di tempatnya. Cuplikan ini memicu intent {@link android.content.Intent#ACTION_OPEN_DOCUMENT} dan menggunakan kategori {@link android.content.Intent#CATEGORY_OPENABLE} untuk menampilkan dokumen yang bisa dibuka saja. Ini akan menyaring lebih jauh untuk menampilkan file teks saja:
private static final int EDIT_REQUEST_CODE = 44; /** * Open a file for writing and append some text to it. */ private void editDocument() { // ACTION_OPEN_DOCUMENT is the intent to choose a file via the system's // file browser. Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); // Filter to only show results that can be "opened", such as a // file (as opposed to a list of contacts or timezones). intent.addCategory(Intent.CATEGORY_OPENABLE); // Filter to show only text files. intent.setType("text/plain"); startActivityForResult(intent, EDIT_REQUEST_CODE); }
Berikutnya, dari {@link android.app.Activity#onActivityResult onActivityResult()} (lihat Memproses hasil) Anda bisa memanggil kode untuk mengedit. Cuplikan berikut mendapatkan {@link java.io.FileOutputStream} dari {@link android.content.ContentResolver}. Secara default, snipet menggunakan mode “tulis”. Inilah praktik terbaik untuk meminta jumlah akses minimum yang Anda perlukan, jadi jangan meminta baca/tulis jika yang Anda perlukan hanyalah tulis:
private void alterDocument(Uri uri) { try { ParcelFileDescriptor pfd = getActivity().getContentResolver(). openFileDescriptor(uri, "w"); FileOutputStream fileOutputStream = new FileOutputStream(pfd.getFileDescriptor()); fileOutputStream.write(("Overwritten by MyCloud at " + System.currentTimeMillis() + "\n").getBytes()); // Let the document provider know you're done by closing the stream. fileOutputStream.close(); pfd.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } }
Bila aplikasi Anda membuka file untuk membaca atau menulis, sistem akan memberi aplikasi Anda izin URI untuk file itu. Pemberian ini berlaku hingga perangkat pengguna di-restart. Namun anggaplah aplikasi Anda adalah aplikasi pengeditan gambar, dan Anda ingin pengguna bisa mengakses 5 gambar terakhir yang dieditnya, langsung dari aplikasi Anda. Jika perangkat pengguna telah di-restart, maka Anda harus mengirim pengguna kembali ke picker sistem untuk menemukan file, hal ini jelas tidak ideal.
Untuk mencegah terjadinya hal ini, Anda bisa mempertahankan izin yang diberikan sistem ke aplikasi Anda. Secara efektif, aplikasi Anda akan "mengambil" pemberian izin URI yang bisa dipertahankan yang ditawarkan oleh sistem. Hal ini memberi pengguna akses kontinu ke file melalui aplikasi Anda, sekalipun perangkat telah di-restart:
final int takeFlags = intent.getFlags() & (Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); // Check for the freshest data. getContentResolver().takePersistableUriPermission(uri, takeFlags);
Ada satu langkah akhir. Anda mungkin telah menyimpan URI terbaru yang diakses aplikasi, namun URI itu mungkin tidak lagi valid,—aplikasi lain mungkin telah menghapus atau memodifikasi dokumen. Karena itu, Anda harus selalu memanggil {@code getContentResolver().takePersistableUriPermission()} untuk memeriksa data terbaru.
Jika Anda sedang mengembangkan aplikasi yang menyediakan layanan penyimpanan untuk file (misalnya layanan penyimpanan cloud), Anda bisa menyediakan file melalui SAF dengan menulis penyedia dokumen custom. Bagian ini menjelaskan caranya.
Untuk mengimplementasikan penyedia dokumen custom, tambahkan yang berikut ini ke manifes aplikasi Anda:
<provider>
yang mendeklarasikan penyedia penyimpanan custom
Anda. com.example.android.storageprovider.MyCloudProvider
.com.example.android.storageprovider
) plus tipe penyedia konten
(documents
). Misalnya, {@code com.example.android.storageprovider.documents}.android:exported
yang diatur ke "true"
.
Anda harus mengekspor penyedia sehingga aplikasi lain bisa membacanya.android:grantUriPermissions
yang diatur ke
"true"
. Pengaturan ini memungkinkan sistem memberi aplikasi lain akses
ke konten dalam penyedia Anda. Untuk pembahasan cara mempertahankan pemberian bagi
dokumen tertentu, lihat Mempertahankan izin.<bool name="atLeastKitKat">false</bool>
<bool name="atLeastKitKat">true</bool>
Berikut ini adalah kutipan contoh manifes berisi penyedia yang:
<manifest... > ... <uses-sdk android:minSdkVersion="19" android:targetSdkVersion="19" /> .... <provider android:name="com.example.android.storageprovider.MyCloudProvider" android:authorities="com.example.android.storageprovider.documents" android:grantUriPermissions="true" android:exported="true" android:permission="android.permission.MANAGE_DOCUMENTS" android:enabled="@bool/atLeastKitKat"> <intent-filter> <action android:name="android.content.action.DOCUMENTS_PROVIDER" /> </intent-filter> </provider> </application> </manifest>
Intent {@link android.content.Intent#ACTION_OPEN_DOCUMENT} hanya tersedia pada perangkat yang menjalankan Android 4.4 dan yang lebih tinggi. Jika ingin aplikasi Anda mendukung {@link android.content.Intent#ACTION_GET_CONTENT} untuk mengakomodasi perangkat yang menjalankan Android 4.3 dan yang lebih rendah, Anda harus menonaktifkan filter inten {@link android.content.Intent#ACTION_GET_CONTENT} dalam manifes untuk perangkat yang menjalankan Android 4.4 atau yang lebih tinggi. Penyedia dokumen dan {@link android.content.Intent#ACTION_GET_CONTENT} harus dianggap saling eksklusif. Jika Anda mendukung keduanya sekaligus, aplikasi Anda akan muncul dua kali dalam UI picker sistem, yang menawarkan dua cara mengakses data tersimpan Anda. Hal ini akan membingungkan pengguna.
Berikut ini adalah cara yang disarankan untuk menonaktifkan filter intent {@link android.content.Intent#ACTION_GET_CONTENT} untuk perangkat yang menjalankan Android versi 4.4 atau yang lebih tinggi:
<bool name="atMostJellyBeanMR2">true</bool>
<bool name="atMostJellyBeanMR2">false</bool>
<!-- This activity alias is added so that GET_CONTENT intent-filter can be disabled for builds on API level 19 and higher. --> <activity-alias android:name="com.android.example.app.MyPicker" android:targetActivity="com.android.example.app.MyActivity" ... android:enabled="@bool/atMostJellyBeanMR2"> <intent-filter> <action android:name="android.intent.action.GET_CONTENT" /> <category android:name="android.intent.category.OPENABLE" /> <category android:name="android.intent.category.DEFAULT" /> <data android:mimeType="image/*" /> <data android:mimeType="video/*" /> </intent-filter> </activity-alias>
Biasanya bila Anda menulis penyedia konten custom, salah satu tugas adalah mengimplementasikan kelas kontrak, seperti dijelaskan dalam panduan pengembang Penyedia Konten. Kelas kontrak adalah kelas {@code public final} yang berisi definisi konstanta untuk URI, nama kolom, tipe MIME, dan metadata lain yang berkenaan dengan penyedia. SAF menyediakan kelas-kelas kontrak ini untuk Anda, jadi Anda tidak perlu menulisnya sendiri:
Misalnya, berikut ini adalah kolom-kolom yang bisa Anda hasilkan di kursor bila penyedia dokumen Anda membuat query dokumen atau akar:
private static final String[] DEFAULT_ROOT_PROJECTION = new String[]{Root.COLUMN_ROOT_ID, Root.COLUMN_MIME_TYPES, Root.COLUMN_FLAGS, Root.COLUMN_ICON, Root.COLUMN_TITLE, Root.COLUMN_SUMMARY, Root.COLUMN_DOCUMENT_ID, Root.COLUMN_AVAILABLE_BYTES,}; private static final String[] DEFAULT_DOCUMENT_PROJECTION = new String[]{Document.COLUMN_DOCUMENT_ID, Document.COLUMN_MIME_TYPE, Document.COLUMN_DISPLAY_NAME, Document.COLUMN_LAST_MODIFIED, Document.COLUMN_FLAGS, Document.COLUMN_SIZE,};
Langkah berikutnya dalam menulis penyedia dokumen custom adalah menjadikan kelas abstrak sebagai subkelas {@link android.provider.DocumentsProvider}. Setidaknya, Anda perlu mengimplementasikan metode berikut:
Hanya inilah metode yang diwajibkan kepada Anda secara ketat untuk diimplementasikan, namun ada banyak lagi yang mungkin Anda inginkan. Lihat {@link android.provider.DocumentsProvider} untuk detailnya.
Implementasi {@link android.provider.DocumentsProvider#queryRoots queryRoots()} oleh Anda harus menghasilkan {@link android.database.Cursor} yang menunjuk ke semua direktori akar penyedia dokumen, dengan menggunakan kolom-kolom yang didefinisikan dalam {@link android.provider.DocumentsContract.Root}.
Dalam cuplikan berikut, parameter {@code projection} mewakili bidang-bidang tertentu yang ingin didapatkan kembali oleh pemanggil. Cuplikan ini membuat kursor baru dan menambahkan satu baris ke satu akar— kursor, satu direktori level atas, seperti Downloads atau Images. Kebanyakan penyedia hanya mempunyai satu akar. Anda bisa mempunyai lebih dari satu, misalnya, jika ada banyak akun pengguna. Dalam hal itu, cukup tambahkan sebuah baris kedua ke kursor.
@Override public Cursor queryRoots(String[] projection) throws FileNotFoundException { // Create a cursor with either the requested fields, or the default // projection if "projection" is null. final MatrixCursor result = new MatrixCursor(resolveRootProjection(projection)); // If user is not logged in, return an empty root cursor. This removes our // provider from the list entirely. if (!isUserLoggedIn()) { return result; } // It's possible to have multiple roots (e.g. for multiple accounts in the // same app) -- just add multiple cursor rows. // Construct one row for a root called "MyCloud". final MatrixCursor.RowBuilder row = result.newRow(); row.add(Root.COLUMN_ROOT_ID, ROOT); row.add(Root.COLUMN_SUMMARY, getContext().getString(R.string.root_summary)); // FLAG_SUPPORTS_CREATE means at least one directory under the root supports // creating documents. FLAG_SUPPORTS_RECENTS means your application's most // recently used documents will show up in the "Recents" category. // FLAG_SUPPORTS_SEARCH allows users to search all documents the application // shares. row.add(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_CREATE | Root.FLAG_SUPPORTS_RECENTS | Root.FLAG_SUPPORTS_SEARCH); // COLUMN_TITLE is the root title (e.g. Gallery, Drive). row.add(Root.COLUMN_TITLE, getContext().getString(R.string.title)); // This document id cannot change once it's shared. row.add(Root.COLUMN_DOCUMENT_ID, getDocIdForFile(mBaseDir)); // The child MIME types are used to filter the roots and only present to the // user roots that contain the desired type somewhere in their file hierarchy. row.add(Root.COLUMN_MIME_TYPES, getChildMimeTypes(mBaseDir)); row.add(Root.COLUMN_AVAILABLE_BYTES, mBaseDir.getFreeSpace()); row.add(Root.COLUMN_ICON, R.drawable.ic_launcher); return result; }
Implementasi {@link android.provider.DocumentsProvider#queryChildDocuments queryChildDocuments()} oleh Anda harus menghasilkan {@link android.database.Cursor} yang menunjuk ke semua file dalam direktori yang ditentukan, dengan menggunakan kolom-kolom yang didefinisikan dalam {@link android.provider.DocumentsContract.Document}.
Metode ini akan dipanggil bila Anda memilih akar aplikasi dalam picker UI. Metode mengambil dokumen anak dari direktori di bawah akar. Metode ini bisa dipanggil pada level apa saja dalam hierarki file, bukan hanya akar. Cuplikan ini membuat kursor baru dengan kolom-kolom yang diminta, lalu menambahkan informasi tentang setiap anak langsung dalam direktori induk ke kursor. Satu anak bisa berupa gambar, direktori lain—file apa saja:
@Override public Cursor queryChildDocuments(String parentDocumentId, String[] projection, String sortOrder) throws FileNotFoundException { final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection)); final File parent = getFileForDocId(parentDocumentId); for (File file : parent.listFiles()) { // Adds the file's display name, MIME type, size, and so on. includeFile(result, null, file); } return result; }
Implementasi {@link android.provider.DocumentsProvider#queryDocument queryDocument()} oleh Anda harus menghasilkan {@link android.database.Cursor} yang menunjuk ke file yang disebutkan, dengan menggunakan kolom-kolom yang didefinisikan dalam {@link android.provider.DocumentsContract.Document}.
Metode {@link android.provider.DocumentsProvider#queryDocument queryDocument()} menghasilkan informasi yang sama yang diteruskan dalam {@link android.provider.DocumentsProvider#queryChildDocuments queryChildDocuments()}, namun untuk file tertentu:
@Override public Cursor queryDocument(String documentId, String[] projection) throws FileNotFoundException { // Create a cursor with the requested projection, or the default projection. final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection)); includeFile(result, documentId, null); return result; }
Anda harus mengimplementasikan {@link android.provider.DocumentsProvider#openDocument openDocument()} untuk menghasilkan {@link android.os.ParcelFileDescriptor} yang mewakili file yang disebutkan. Aplikasi lain bisa menggunakan {@link android.os.ParcelFileDescriptor} yang dihasilkan untuk mengalirkan data. Sistem memanggil metode ini setelah pengguna memilih file dan aplikasi klien meminta akses ke file itu dengan memanggil {@link android.content.ContentResolver#openFileDescriptor openFileDescriptor()}. Misalnya:
@Override public ParcelFileDescriptor openDocument(final String documentId, final String mode, CancellationSignal signal) throws FileNotFoundException { Log.v(TAG, "openDocument, mode: " + mode); // It's OK to do network operations in this method to download the document, // as long as you periodically check the CancellationSignal. If you have an // extremely large file to transfer from the network, a better solution may // be pipes or sockets (see ParcelFileDescriptor for helper methods). final File file = getFileForDocId(documentId); final boolean isWrite = (mode.indexOf('w') != -1); if(isWrite) { // Attach a close listener if the document is opened in write mode. try { Handler handler = new Handler(getContext().getMainLooper()); return ParcelFileDescriptor.open(file, accessMode, handler, new ParcelFileDescriptor.OnCloseListener() { @Override public void onClose(IOException e) { // Update the file with the cloud server. The client is done // writing. Log.i(TAG, "A file with id " + documentId + " has been closed! Time to " + "update the server."); } }); } catch (IOException e) { throw new FileNotFoundException("Failed to open document with id " + documentId + " and mode " + mode); } } else { return ParcelFileDescriptor.open(file, accessMode); } }
Anggaplah penyedia dokumen Anda sebuah layanan penyimpanan cloud yang dilindungi kata sandi dan Anda ingin memastikan bahwa pengguna sudah login sebelum Anda mulai berbagi file mereka. Apakah yang harus dilakukan aplikasi Anda jika pengguna tidak login? Solusinya adalah menghasilkan akar nol dalam implementasi {@link android.provider.DocumentsProvider#queryRoots queryRoots()} Anda. Yakni, sebuah kursor akar kosong:
public Cursor queryRoots(String[] projection) throws FileNotFoundException { ... // If user is not logged in, return an empty root cursor. This removes our // provider from the list entirely. if (!isUserLoggedIn()) { return result; }
Langkah lainnya adalah memanggil {@code getContentResolver().notifyChange()}. Ingat {@link android.provider.DocumentsContract}? Kita menggunakannya untuk membuat URI ini. Cuplikan berikut memberi tahu sistem untuk membuat query akar penyedia dokumen Anda kapan saja status login pengguna berubah. Jika pengguna tidak login, panggilan ke {@link android.provider.DocumentsProvider#queryRoots queryRoots()} akan menghasilkan kursor kosong, seperti yang ditampilkan di atas. Cara ini akan memastikan bahwa dokumen penyedia hanya tersedia jika pengguna login ke penyedia itu.
private void onLoginButtonClick() { loginOrLogout(); getContentResolver().notifyChange(DocumentsContract .buildRootsUri(AUTHORITY), null); }