1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
|
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>
|