blob: 06dd6652677dedada073f08bfd242629ff9cdfa4 [file] [log] [blame]
page.title=Scoped Directory Access
page.keywords=preview,sdk,scoped directory access
page.tags=androidn
@jd:body
<div id="qv-wrapper">
<div id="qv">
<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 N provides a new simplified API to access
common external storage directories. </p>
<h2 id="accessing">Accessing an External Storage Directory</h2>
<p>Use the <code>StorageManager</code> class to get the appropriate
<code>StorageVolume</code> instance. Then, create an intent by calling the
<code>StorageVolume.createAccessIntent()</code> 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
<code>StorageManager.getVolumesList()</code>.</p>
<p>If you have information about a specific file, use
<code>StorageManager.getStorageVolume(File)</code> to get the
<code>StorageVolume</code> that contains the file. Call
<code>createAccessIntent()</code> on this <code>StorageVolume</code> to access
the external storage directory for the file.</p>
<p>
On secondary volumes, such as external SD cards, pass in null when calling
<code>StorageVolume.createAccessIntent()</code> to request access to the entire
volume, instead of a specific directory.
<code>StorageVolume.createAccessIntent()</code> 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.getPrimaryVolume();
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}preview/images/scoped-folder-access-framed.png"
srcset="{@docRoot}preview/images/scoped-folder-access-framed.png 1x,
{@docRoot}preview/images/scoped-folder-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
<code>onActivityResult()</code> override with a result code of
<code>Activity.RESULT_OK</code>, 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
<code>onActivityResult()</code> override with a result code of
<code>Activity.RESULT_CANCELED</code>, and null intent data.</p>
<p class="note"><b>Note</b>: 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>
&lt;receiver
android:name=".MediaMountedReceiver"
android:enabled="true"
android:exported="true" &gt;
&lt;intent-filter&gt;
&lt;action android:name="android.intent.action.MEDIA_MOUNTED" /&gt;
&lt;data android:scheme="file" /&gt;
&lt;/intent-filter&gt;
&lt;/receiver&gt;
</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 <code>StorageVolume</code> 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
<code>getContentResolver().takePersistableUriPermssion()</code> with the
directory access URI. The system will persist the URI and subsequent access
requests will return <code>RESULT_OK</code> 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}preview/images/scoped-folder-access-dont-ask.png"
srcset="{@docRoot}preview/images/scoped-folder-access-dont-ask.png 1x,
{@docRoot}preview/images/scoped-folder-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>