1page.title=Sharing a File 2 3trainingnavtop=true 4@jd:body 5 6 7<div id="tb-wrapper"> 8<div id="tb"> 9 10<h2>This lesson teaches you to</h2> 11<ol> 12 <li><a href="#ReceiveRequests">Receive File Requests</a></li> 13 <li><a href="#CreateFileSelection">Create a File Selection Activity</a></li> 14 <li><a href="#RespondToRequest">Respond to a File Selection</a></li> 15 <li><a href="#GrantPermissions">Grant Permissions for the File</a></li> 16 <li><a href="#ShareFile">Share the File with the Requesting App</a> 17</ol> 18 19<h2>You should also read</h2> 20<ul> 21 <li> 22 <a href="{@docRoot}guide/topics/providers/content-provider-creating.html#ContentURI" 23 >Designing Content URIs</a> 24 </li> 25 <li> 26 <a href="{@docRoot}guide/topics/providers/content-provider-creating.html#Permissions" 27 >Implementing Content Provider Permissions</a> 28 </li> 29 <li> 30 <a href="{@docRoot}guide/topics/security/permissions.html">Permissions</a> 31 </li> 32 <li> 33 <a href="{@docRoot}guide/components/intents-filters.html">Intents and Intent Filters</a> 34 </li> 35</ul> 36 37</div> 38</div> 39<p> 40 Once you have set up your app to share files using content URIs, you can respond to other apps' 41 requests for those files. One way to respond to these requests is to provide a file selection 42 interface from the server app that other applications can invoke. This approach allows a client 43 application to let users select a file from the server app and then receive the selected file's 44 content URI. 45</p> 46<p> 47 This lesson shows you how to create a file selection {@link android.app.Activity} in your app 48 that responds to requests for files. 49</p> 50<h2 id="ReceiveRequests">Receive File Requests</h2> 51<p> 52 To receive requests for files from client apps and respond with a content URI, your app should 53 provide a file selection {@link android.app.Activity}. Client apps start this 54 {@link android.app.Activity} by calling {@link android.app.Activity#startActivityForResult 55 startActivityForResult()} with an {@link android.content.Intent} containing the action 56 {@link android.content.Intent#ACTION_PICK ACTION_PICK}. When the client app calls 57 {@link android.app.Activity#startActivityForResult startActivityForResult()}, your app can 58 return a result to the client app, in the form of a content URI for the file the user selected. 59</p> 60<p> 61 To learn how to implement a request for a file in a client app, see the lesson 62 <a href="request-file.html">Requesting a Shared File</a>. 63</p> 64<h2 id="CreateFileSelection">Create a File Selection Activity</h2> 65<p> 66 To set up the file selection {@link android.app.Activity}, start by specifying the 67 {@link android.app.Activity} in your manifest, along with an intent filter 68 that matches the action {@link android.content.Intent#ACTION_PICK ACTION_PICK} and the 69 categories {@link android.content.Intent#CATEGORY_DEFAULT CATEGORY_DEFAULT} and 70 {@link android.content.Intent#CATEGORY_OPENABLE CATEGORY_OPENABLE}. Also add MIME type filters 71 for the files your app serves to other apps. The following snippet shows you how to specify the 72 new {@link android.app.Activity} and intent filter: 73</p> 74<pre> 75<manifest xmlns:android="http://schemas.android.com/apk/res/android"> 76 ... 77 <application> 78 ... 79 <activity 80 android:name=".FileSelectActivity" 81 android:label="@File Selector" > 82 <intent-filter> 83 <action 84 android:name="android.intent.action.PICK"/> 85 <category 86 android:name="android.intent.category.DEFAULT"/> 87 <category 88 android:name="android.intent.category.OPENABLE"/> 89 <data android:mimeType="text/plain"/> 90 <data android:mimeType="image/*"/> 91 </intent-filter> 92 </activity></pre> 93<h3>Define the file selection Activity in code</h3> 94<p> 95 Next, define an {@link android.app.Activity} subclass that displays the files available from 96 your app's <code>files/images/</code> directory in internal storage and allows the user to pick 97 the desired file. The following snippet demonstrates how to define this 98 {@link android.app.Activity} and respond to the user's selection: 99</p> 100<pre> 101public class MainActivity extends Activity { 102 // The path to the root of this app's internal storage 103 private File mPrivateRootDir; 104 // The path to the "images" subdirectory 105 private File mImagesDir; 106 // Array of files in the images subdirectory 107 File[] mImageFiles; 108 // Array of filenames corresponding to mImageFiles 109 String[] mImageFilenames; 110 // Initialize the Activity 111 @Override 112 protected void onCreate(Bundle savedInstanceState) { 113 ... 114 // Set up an Intent to send back to apps that request a file 115 mResultIntent = 116 new Intent("com.example.myapp.ACTION_RETURN_FILE"); 117 // Get the files/ subdirectory of internal storage 118 mPrivateRootDir = getFilesDir(); 119 // Get the files/images subdirectory; 120 mImagesDir = new File(mPrivateRootDir, "images"); 121 // Get the files in the images subdirectory 122 mImageFiles = mImagesDir.listFiles(); 123 // Set the Activity's result to null to begin with 124 setResult(Activity.RESULT_CANCELED, null); 125 /* 126 * Display the file names in the ListView mFileListView. 127 * Back the ListView with the array mImageFilenames, which 128 * you can create by iterating through mImageFiles and 129 * calling File.getAbsolutePath() for each File 130 */ 131 ... 132 } 133 ... 134}</pre> 135<h2 id="RespondToRequest">Respond to a File Selection</h2> 136<p> 137 Once a user selects a shared file, your application must determine what file was selected and 138 then generate a content URI for the file. Since the {@link android.app.Activity} displays the 139 list of available files in a {@link android.widget.ListView}, when the user clicks a file name 140 the system calls the method {@link android.widget.AdapterView.OnItemClickListener#onItemClick 141 onItemClick()}, in which you can get the selected file. 142</p> 143<p> 144 In {@link android.widget.AdapterView.OnItemClickListener#onItemClick onItemClick()}, get a 145 {@link java.io.File} object for the file name of the selected file and pass it as an argument to 146 {@link android.support.v4.content.FileProvider#getUriForFile getUriForFile()}, along with the 147 authority that you specified in the 148 <code><a href="{@docRoot}guide/topics/manifest/provider-element.html" 149 ><provider></a></code> element for the {@link android.support.v4.content.FileProvider}. 150 The resulting content URI contains the authority, a path segment corresponding to the file's 151 directory (as specified in the XML meta-data), and the name of the file including its 152 extension. How {@link android.support.v4.content.FileProvider} maps directories to path 153 segments based on XML meta-data is described in the section 154 <a href="setup-sharing.html#DefineMetaData">Specify Sharable Directories</a>. 155</p> 156<p> 157 The following snippet shows you how to detect the selected file and get a content URI for it: 158</p> 159<pre> 160 protected void onCreate(Bundle savedInstanceState) { 161 ... 162 // Define a listener that responds to clicks on a file in the ListView 163 mFileListView.setOnItemClickListener( 164 new AdapterView.OnItemClickListener() { 165 @Override 166 /* 167 * When a filename in the ListView is clicked, get its 168 * content URI and send it to the requesting app 169 */ 170 public void onItemClick(AdapterView<?> adapterView, 171 View view, 172 int position, 173 long rowId) { 174 /* 175 * Get a File for the selected file name. 176 * Assume that the file names are in the 177 * mImageFilename array. 178 */ 179 File requestFile = new File(mImageFilename[position]); 180 /* 181 * Most file-related method calls need to be in 182 * try-catch blocks. 183 */ 184 // Use the FileProvider to get a content URI 185 try { 186 fileUri = FileProvider.getUriForFile( 187 MainActivity.this, 188 "com.example.myapp.fileprovider", 189 requestFile); 190 } catch (IllegalArgumentException e) { 191 Log.e("File Selector", 192 "The selected file can't be shared: " + 193 clickedFilename); 194 } 195 ... 196 } 197 }); 198 ... 199 }</pre> 200<p> 201 Remember that you can only generate content URIs for files that reside in a directory 202 you've specified in the meta-data file that contains the <code><paths></code> element, as 203 described in the section <a href="setup-sharing.html#DefineMetaData" 204 >Specify Sharable Directories</a>. If you call 205 {@link android.support.v4.content.FileProvider#getUriForFile getUriForFile()} for a 206 {@link java.io.File} in a path that you haven't specified, you receive an 207 {@link java.lang.IllegalArgumentException}. 208</p> 209<h2 id="GrantPermissions">Grant Permissions for the File</h2> 210<p> 211 Now that you have a content URI for the file you want to share with another app, you need to 212 allow the client app to access the file. To allow access, grant permissions to the client app by 213 adding the content URI to an {@link android.content.Intent} and then setting permission flags on 214 the {@link android.content.Intent}. The permissions you grant are temporary and expire 215 automatically when the receiving app's task stack is finished. 216</p> 217<p> 218 The following code snippet shows you how to set read permission for the file: 219</p> 220<pre> 221 protected void onCreate(Bundle savedInstanceState) { 222 ... 223 // Define a listener that responds to clicks in the ListView 224 mFileListView.setOnItemClickListener( 225 new AdapterView.OnItemClickListener() { 226 @Override 227 public void onItemClick(AdapterView<?> adapterView, 228 View view, 229 int position, 230 long rowId) { 231 ... 232 if (fileUri != null) { 233 // Grant temporary read permission to the content URI 234 mResultIntent.addFlags( 235 Intent.FLAG_GRANT_READ_URI_PERMISSION); 236 } 237 ... 238 } 239 ... 240 }); 241 ... 242 }</pre> 243<p class="caution"> 244 <strong>Caution:</strong> Calling {@link android.content.Intent#setFlags setFlags()} is the only 245 way to securely grant access to your files using temporary access permissions. Avoid calling 246 {@link android.content.Context#grantUriPermission Context.grantUriPermission()} method for a 247 file's content URI, since this method grants access that you can only revoke by 248 calling {@link android.content.Context#revokeUriPermission Context.revokeUriPermission()}. 249</p> 250<h2 id="ShareFile">Share the File with the Requesting App</h2> 251<p> 252 To share the file with the app that requested it, pass the {@link android.content.Intent} 253 containing the content URI and permissions to {@link android.app.Activity#setResult 254 setResult()}. When the {@link android.app.Activity} you have just defined is finished, the 255 system sends the {@link android.content.Intent} containing the content URI to the client app. 256 The following code snippet shows you how to do this: 257</p> 258<pre> 259 protected void onCreate(Bundle savedInstanceState) { 260 ... 261 // Define a listener that responds to clicks on a file in the ListView 262 mFileListView.setOnItemClickListener( 263 new AdapterView.OnItemClickListener() { 264 @Override 265 public void onItemClick(AdapterView<?> adapterView, 266 View view, 267 int position, 268 long rowId) { 269 ... 270 if (fileUri != null) { 271 ... 272 // Put the Uri and MIME type in the result Intent 273 mResultIntent.setDataAndType( 274 fileUri, 275 getContentResolver().getType(fileUri)); 276 // Set the result 277 MainActivity.this.setResult(Activity.RESULT_OK, 278 mResultIntent); 279 } else { 280 mResultIntent.setDataAndType(null, ""); 281 MainActivity.this.setResult(RESULT_CANCELED, 282 mResultIntent); 283 } 284 } 285 });</pre> 286<p> 287 Provide users with an way to return immediately to the client app once they have chosen a file. 288 One way to do this is to provide a checkmark or <b>Done</b> button. Associate a method with 289 the button using the button's 290 <code><a href="{@docRoot}reference/android/view/View.html#attr_android:onClick" 291 >android:onClick</a></code> attribute. In the method, call 292 {@link android.app.Activity#finish finish()}. For example: 293</p> 294<pre> 295 public void onDoneClick(View v) { 296 // Associate a method with the Done button 297 finish(); 298 }</pre> 299