Home | History | Annotate | Download | only in secure-file-sharing
      1 page.title=Sharing a File
      2 
      3 trainingnavtop=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 &lt;manifest xmlns:android="http://schemas.android.com/apk/res/android"&gt;
     76     ...
     77         &lt;application&gt;
     78         ...
     79             &lt;activity
     80                 android:name=".FileSelectActivity"
     81                 android:label="&#64;File Selector" &gt;
     82                 &lt;intent-filter&gt;
     83                     &lt;action
     84                         android:name="android.intent.action.PICK"/&gt;
     85                     &lt;category
     86                         android:name="android.intent.category.DEFAULT"/&gt;
     87                     &lt;category
     88                         android:name="android.intent.category.OPENABLE"/&gt;
     89                     &lt;data android:mimeType="text/plain"/&gt;
     90                     &lt;data android:mimeType="image/*"/&gt;
     91                 &lt;/intent-filter&gt;
     92             &lt;/activity&gt;</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>
    101 public 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     &#64;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     >&lt;provider&gt;</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             &#64;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&lt;?&gt; 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>&lt;paths&gt;</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             &#64;Override
    227             public void onItemClick(AdapterView&lt;?&gt; 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             &#64;Override
    265             public void onItemClick(AdapterView&lt;?&gt; 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