Daniel Yu | 29e6ae6 | 2016-02-22 16:29:34 -0800 | [diff] [blame] | 1 | page.title=Scoped Directory Access |
| 2 | page.keywords=preview,sdk,scoped directory access |
| 3 | page.tags=androidn |
| 4 | |
| 5 | @jd:body |
| 6 | |
Joe Fernandez | 98b0ec6 | 2016-03-08 22:32:51 -0800 | [diff] [blame] | 7 | <div id="qv-wrapper"> |
| 8 | <div id="qv"> |
Daniel Yu | 29e6ae6 | 2016-02-22 16:29:34 -0800 | [diff] [blame] | 9 | <h2>In this document</h2> |
| 10 | <ol> |
| 11 | <li><a href="#accessing">Accessing an External Storage Directory</a></li> |
| 12 | <li><a href="#removable">Accessing a Directory on Removable Media</a></li> |
| 13 | <li><a href="#best">Best Practices</a></li> |
| 14 | </ol> |
| 15 | </div> |
| 16 | </div> |
| 17 | |
| 18 | <p>Apps such as photo apps usually just need access to specific directories in |
| 19 | external storage, such as the <code>Pictures</code> directory. Existing |
| 20 | approaches to accessing external storage aren't designed to easily provide |
| 21 | targeted directory access for these types of apps. For example:</p> |
| 22 | |
| 23 | <ul> |
| 24 | <li>Requesting {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} |
| 25 | or {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} in your manifest |
| 26 | allows access to all public directories on external storage, which might be |
| 27 | more access than what your app needs.</li> |
| 28 | <li>Using the |
| 29 | <a href="{@docRoot}guide/topics/providers/document-provider.html">Storage |
| 30 | Access Framework</a> usually makes your user pick directories |
| 31 | via a system UI, which is unnecessary if your app always accesses the same |
| 32 | external directory.</li> |
| 33 | </ul> |
| 34 | |
David Friedman | fffa8ac | 2016-03-07 22:13:29 -0800 | [diff] [blame] | 35 | <p>Android N provides a new simplified API to access |
Daniel Yu | 29e6ae6 | 2016-02-22 16:29:34 -0800 | [diff] [blame] | 36 | common external storage directories. </p> |
| 37 | |
| 38 | <h2 id="accessing">Accessing an External Storage Directory</h2> |
| 39 | |
| 40 | <p>Use the <code>StorageManager</code> class to get the appropriate |
| 41 | <code>StorageVolume</code> instance. Then, create an intent by calling the |
| 42 | <code>StorageVolume.createAccessIntent()</code> method of that instance. |
| 43 | Use this intent to access external storage directories. To get a list of |
| 44 | all available volumes, including removable media volumes, use |
| 45 | <code>StorageManager.getVolumesList()</code>.</p> |
| 46 | |
Daniel Yu | 9ec08f8 | 2016-04-01 14:29:40 -0700 | [diff] [blame] | 47 | <p> |
| 48 | On secondary volumes, such as external SD cards, pass in null when calling |
| 49 | <code>StorageVolume.createAccessIntent()</code> to request access to the entire |
| 50 | volume, instead of a specific directory. |
| 51 | <code>StorageVolume.createAccessIntent()</code> returns null if you pass in |
| 52 | null to the primary volume, or if you pass in an invalid directory name. |
| 53 | </p> |
| 54 | |
Daniel Yu | 29e6ae6 | 2016-02-22 16:29:34 -0800 | [diff] [blame] | 55 | <p>The following code snippet is an example of how to open the |
| 56 | <code>Pictures</code> directory in the primary shared storage:</p> |
| 57 | |
| 58 | <pre> |
| 59 | StorageManager sm = (StorageManager)getSystemService(Context.STORAGE_SERVICE); |
| 60 | StorageVolume volume = sm.getPrimaryVolume(); |
| 61 | Intent intent = volume.createAccessIntent(Environment.DIRECTORY_PICTURES); |
| 62 | startActivityForResult(intent, request_code); |
| 63 | </pre> |
| 64 | |
| 65 | <p>The system attempts to grant access to the external directory, and if |
| 66 | necessary confirms access with the user using a simplified UI:</p> |
| 67 | |
| 68 | <img src="{@docRoot}preview/images/scoped-folder-access-framed.png" |
| 69 | srcset="{@docRoot}preview/images/scoped-folder-access-framed.png 1x, |
| 70 | {@docRoot}preview/images/scoped-folder-access-framed_2x.png 2x" /> |
| 71 | <p class="img-caption"><strong>Figure 1.</strong> An application requesting |
| 72 | access to the Pictures directory.</p> |
| 73 | |
| 74 | <p>If the user grants access, the system calls your |
| 75 | <code>onActivityResult()</code> override with a result code of |
| 76 | <code>Activity.RESULT_OK</code>, and intent data that contains the URI. Use |
| 77 | the provided URI to access directory information, similar to using URIs |
| 78 | returned by the |
| 79 | <a href="{@docRoot}guide/topics/providers/document-provider.html">Storage |
| 80 | Access Framework</a>.</p> |
| 81 | |
| 82 | <p>If the user doesn't grant access, the system calls your |
| 83 | <code>onActivityResult()</code> override with a result code of |
| 84 | <code>Activity.RESULT_CANCELED</code>, and null intent data.</p> |
| 85 | |
| 86 | <p class="note"><b>Note</b>: Getting access to a specific external directory |
| 87 | also gains access to subdirectories within that directory.</p> |
| 88 | |
| 89 | <h2 id="removable">Accessing a Directory on Removable Media</h2> |
| 90 | |
| 91 | <p>To use Scoped Directory Access to access directories on removable media, |
| 92 | first add a {@link android.content.BroadcastReceiver} that listens for the |
| 93 | {@link android.os.Environment#MEDIA_MOUNTED} notification, for example:</p> |
| 94 | |
| 95 | <pre> |
| 96 | <receiver |
| 97 | android:name=".MediaMountedReceiver" |
| 98 | android:enabled="true" |
| 99 | android:exported="true" > |
| 100 | <intent-filter> |
| 101 | <action android:name="android.intent.action.MEDIA_MOUNTED" /> |
| 102 | <data android:scheme="file" /> |
| 103 | </intent-filter> |
| 104 | </receiver> |
| 105 | </pre> |
| 106 | |
| 107 | <p>When the user mounts removable media, like an SD card, the system sends a |
| 108 | {@link android.os.Environment#MEDIA_MOUNTED} notification. This notification |
| 109 | provides a <code>StorageVolume</code> object in the intent data that you can |
| 110 | use to access directories on the removable media. The following example |
| 111 | accesses the <code>Pictures</code> directory on removable media:</p> |
| 112 | |
| 113 | <pre> |
| 114 | // BroadcastReceiver has already cached the MEDIA_MOUNTED |
| 115 | // notification Intent in mediaMountedIntent |
| 116 | StorageVolume volume = (StorageVolume) |
| 117 | mediaMountedIntent.getParcelableExtra(StorageVolume.EXTRA_STORAGE_VOLUME); |
| 118 | volume.createAccessIntent(Environment.DIRECTORY_PICTURES); |
| 119 | startActivityForResult(intent, request_code); |
| 120 | </pre> |
| 121 | |
| 122 | <h2 id="best">Best Practices</h2> |
| 123 | |
| 124 | <p>Where possible, persist the external directory access URI so you don't have |
| 125 | to repeatedly ask the user for access. Once the user has granted access, call |
| 126 | <code>getContentResolver().takePersistableUriPermssion()</code> with the |
| 127 | directory access URI. The system will persist the URI and subsequent access |
| 128 | requests will return <code>RESULT_OK</code> and not show confirmation UI to the |
| 129 | user.</p> |
| 130 | |
| 131 | <p>If the user denies access to an external directory, do not immediately |
| 132 | request access again. Repeatedly insisting on access results in a poor user |
Daniel Yu | 9ec08f8 | 2016-04-01 14:29:40 -0700 | [diff] [blame] | 133 | experience. If a request is denied by the user, and the app requests access |
| 134 | again, the UI displays a <b>Don't ask again</b> checkbox:</p> |
| 135 | |
| 136 | <img src="{@docRoot}preview/images/scoped-folder-access-dont-ask.png" |
| 137 | srcset="{@docRoot}preview/images/scoped-folder-access-dont-ask.png 1x, |
| 138 | {@docRoot}preview/images/scoped-folder-access-dont-ask_2x.png 2x" /> |
| 139 | <p class="img-caption"><strong>Figure 1.</strong> An application making a |
| 140 | second request for access to removable media.</p> |
| 141 | |
| 142 | <p>If the user selects <b>Don't ask again</b> and denies the request, all |
| 143 | future requests for the given directory from your app will be automatically |
| 144 | denied, and no request UI will be presented to the user.</p> |