For Android N release, migrating and updating Scoped Directory Access docs into regular DAC docs. Updated TOC and added redirect. Removed preview/features doc. Updated and moved images. Bug: 30257320 Change-Id: I3440fe1b74947cbfbd1ada6f5d6bee5e49847927
162 lines
7.1 KiB
Plaintext
162 lines
7.1 KiB
Plaintext
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> |