| page.title=Using Scoped Directory Access |
| page.keywords=scoped directory access |
| |
| @jd:body |
| |
| <div id="tb-wrapper"> |
| <div id="tb"> |
| <h2>In this document</h2> |
| <ol> |
| <li><a href="#accessing">Accessing an External Storage Directory</a></li> |
| <li><a href="#removable">Accessing a Directory on Removable Media</a></li> |
| <li><a href="#best">Best Practices</a></li> |
| </ol> |
| </div> |
| </div> |
| |
| <p>Apps such as photo apps usually just need access to specific directories in |
| external storage, such as the <code>Pictures</code> directory. Existing |
| approaches to accessing external storage aren't designed to easily provide |
| targeted directory access for these types of apps. For example:</p> |
| |
| <ul> |
| <li>Requesting {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} |
| or {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} in your manifest |
| allows access to all public directories on external storage, which might be |
| more access than what your app needs.</li> |
| <li>Using the |
| <a href="{@docRoot}guide/topics/providers/document-provider.html">Storage |
| Access Framework</a> usually makes your user pick directories |
| via a system UI, which is unnecessary if your app always accesses the same |
| external directory.</li> |
| </ul> |
| |
| <p>Android 7.0 provides a simplified API to access common external storage |
| directories.</p> |
| |
| <h2 id="accessing">Accessing an External Storage Directory</h2> |
| |
| <p>Use the {@link android.os.storage.StorageManager} class to get the |
| appropriate {@link android.os.storage.StorageVolume} instance. Then, create |
| an intent by calling the |
| {@link android.os.storage.StorageVolume#createAccessIntent |
| StorageVolume.createAccessIntent()} method of that instance. |
| Use this intent to access external storage directories. To get a list of |
| all available volumes, including removable media |
| volumes, use {@link android.os.storage.StorageManager#getStorageVolumes |
| StorageManager.getStorageVolumes()}.</p> |
| |
| <p>If you have information about a specific file, use |
| {@link android.os.storage.StorageManager#getStorageVolume |
| StorageManager.getStorageVolume(File)} to get the |
| {@link android.os.storage.StorageVolume} that contains the file. Call |
| {@link android.os.storage.StorageVolume#createAccessIntent |
| createAccessIntent()} on this |
| {@link android.os.storage.StorageVolume} to access |
| the external storage directory for the file.</p> |
| |
| <p> |
| On secondary volumes, such as external SD cards, pass in null when calling |
| {@link android.os.storage.StorageVolume#createAccessIntent |
| createAccessIntent()} to request access to the entire |
| volume, instead of a specific directory. |
| {@link android.os.storage.StorageVolume#createAccessIntent |
| createAccessIntent()} returns null if you pass in |
| null to the primary volume, or if you pass in an invalid directory name. |
| </p> |
| |
| <p>The following code snippet is an example of how to open the |
| <code>Pictures</code> directory in the primary shared storage:</p> |
| |
| <pre> |
| StorageManager sm = (StorageManager)getSystemService(Context.STORAGE_SERVICE); |
| StorageVolume volume = sm.getPrimaryStorageVolume(); |
| Intent intent = volume.createAccessIntent(Environment.DIRECTORY_PICTURES); |
| startActivityForResult(intent, request_code); |
| </pre> |
| |
| <p>The system attempts to grant access to the external directory, and if |
| necessary confirms access with the user using a simplified UI:</p> |
| |
| <img src="{@docRoot}images/android-7.0/scoped-directory-access-framed.png" |
| srcset="{@docRoot}images/android-7.0/scoped-directory-access-framed.png 1x, |
| {@docRoot}images/android-7.0/scoped-directory-access-framed_2x.png 2x" /> |
| <p class="img-caption"><strong>Figure 1.</strong> An application requesting |
| access to the Pictures directory.</p> |
| |
| <p>If the user grants access, the system calls your |
| {@link android.app.Activity#onActivityResult onActivityResult()} override |
| with a result code of {@link android.app.Activity#RESULT_OK |
| RESULT_OK}, and intent data that contains the URI. Use |
| the provided URI to access directory information, similar to using URIs |
| returned by the |
| <a href="{@docRoot}guide/topics/providers/document-provider.html">Storage |
| Access Framework</a>.</p> |
| |
| <p>If the user doesn't grant access, the system calls your |
| {@link android.app.Activity#onActivityResult onActivityResult()} override |
| with a result code of {@link android.app.Activity#RESULT_CANCELED |
| RESULT_CANCELED}, and null intent data.</p> |
| |
| <p>Getting access to a specific external directory |
| also gains access to subdirectories within that directory.</p> |
| |
| <h2 id="removable">Accessing a Directory on Removable Media</h2> |
| |
| <p>To use Scoped Directory Access to access directories on removable media, |
| first add a {@link android.content.BroadcastReceiver} that listens for the |
| {@link android.os.Environment#MEDIA_MOUNTED} notification, for example:</p> |
| |
| <pre> |
| <receiver |
| android:name=".MediaMountedReceiver" |
| android:enabled="true" |
| android:exported="true" > |
| <intent-filter> |
| <action android:name="android.intent.action.MEDIA_MOUNTED" /> |
| <data android:scheme="file" /> |
| </intent-filter> |
| </receiver> |
| </pre> |
| |
| <p>When the user mounts removable media, like an SD card, the system sends a |
| {@link android.os.Environment#MEDIA_MOUNTED} notification. This notification |
| provides a {@link android.os.storage.StorageVolume} object in the intent data |
| that you can use to access directories on the removable media. The following |
| example accesses the <code>Pictures</code> directory on removable media:</p> |
| |
| <pre> |
| // BroadcastReceiver has already cached the MEDIA_MOUNTED |
| // notification Intent in mediaMountedIntent |
| StorageVolume volume = (StorageVolume) |
| mediaMountedIntent.getParcelableExtra(StorageVolume.EXTRA_STORAGE_VOLUME); |
| volume.createAccessIntent(Environment.DIRECTORY_PICTURES); |
| startActivityForResult(intent, request_code); |
| </pre> |
| |
| <h2 id="best">Best Practices</h2> |
| |
| <p>Where possible, persist the external directory access URI so you don't have |
| to repeatedly ask the user for access. Once the user has granted access, call |
| {@link android.content.Context#getContentResolver getContentResolver()} and |
| with the returned {@link android.content.ContentResolver} call |
| {@link android.content.ContentResolver#takePersistableUriPermission |
| takePersistableUriPermission()} with the directory access URI. The system will |
| persist the URI and subsequent access requests will return |
| {@link android.app.Activity#RESULT_OK RESULT_OK} and not show confirmation |
| UI to the user.</p> |
| |
| <p>If the user denies access to an external directory, do not immediately |
| request access again. Repeatedly insisting on access results in a poor user |
| experience. If a request is denied by the user, and the app requests access |
| again, the UI displays a <b>Don't ask again</b> checkbox:</p> |
| |
| <img src="{@docRoot}images/android-7.0/scoped-directory-access-dont-ask.png" |
| srcset="{@docRoot}images/android-7.0/scoped-directory-access-dont-ask.png 1x, |
| {@docRoot}images/android-7.0/scoped-directory-access-dont-ask_2x.png 2x" /> |
| <p class="img-caption"><strong>Figure 1.</strong> An application making a |
| second request for access to removable media.</p> |
| |
| <p>If the user selects <b>Don't ask again</b> and denies the request, all |
| future requests for the given directory from your app will be automatically |
| denied, and no request UI will be presented to the user.</p> |