1 /*
2  * Copyright (C) 2013 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.example.android.apis.content;
18 
19 import android.app.Activity;
20 import android.content.ContentResolver;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.database.Cursor;
24 import android.net.Uri;
25 import android.os.Bundle;
26 import android.os.Environment;
27 import android.os.storage.StorageManager;
28 import android.os.storage.StorageVolume;
29 import android.provider.DocumentsContract;
30 import android.provider.DocumentsContract.Document;
31 import android.util.Log;
32 import android.view.View;
33 import android.view.View.OnClickListener;
34 import android.widget.Button;
35 import android.widget.CheckBox;
36 import android.widget.LinearLayout;
37 import android.widget.ScrollView;
38 import android.widget.TextView;
39 
40 import java.io.ByteArrayOutputStream;
41 import java.io.IOException;
42 import java.io.InputStream;
43 import java.io.OutputStream;
44 
45 /**
46  * Example that exercises client side of {@link DocumentsContract}.
47  */
48 public class DocumentsSample extends Activity {
49     private static final String TAG = "DocumentsSample";
50 
51     private static final int CODE_READ = 42;
52     private static final int CODE_WRITE = 43;
53     private static final int CODE_TREE = 44;
54     private static final int CODE_RENAME = 45;
55 
56     private TextView mResult;
57 
58     @Override
onCreate(Bundle icicle)59     public void onCreate(Bundle icicle) {
60         super.onCreate(icicle);
61 
62         final Context context = this;
63 
64         final LinearLayout view = new LinearLayout(context);
65         view.setOrientation(LinearLayout.VERTICAL);
66 
67         mResult = new TextView(context);
68         view.addView(mResult);
69 
70         final CheckBox multiple = new CheckBox(context);
71         multiple.setText("ALLOW_MULTIPLE");
72         view.addView(multiple);
73         final CheckBox localOnly = new CheckBox(context);
74         localOnly.setText("LOCAL_ONLY");
75         view.addView(localOnly);
76 
77         Button button;
78         button = new Button(context);
79         button.setText("OPEN_DOC */*");
80         button.setOnClickListener(new OnClickListener() {
81             @Override
82             public void onClick(View v) {
83                 Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
84                 intent.addCategory(Intent.CATEGORY_OPENABLE);
85                 intent.setType("*/*");
86                 if (multiple.isChecked()) {
87                     intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
88                 }
89                 if (localOnly.isChecked()) {
90                     intent.putExtra(Intent.EXTRA_LOCAL_ONLY, true);
91                 }
92                 startActivityForResult(intent, CODE_READ);
93             }
94         });
95         view.addView(button);
96 
97         button = new Button(context);
98         button.setText("OPEN_DOC image/*");
99         button.setOnClickListener(new OnClickListener() {
100             @Override
101             public void onClick(View v) {
102                 Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
103                 intent.addCategory(Intent.CATEGORY_OPENABLE);
104                 intent.setType("image/*");
105                 if (multiple.isChecked()) {
106                     intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
107                 }
108                 if (localOnly.isChecked()) {
109                     intent.putExtra(Intent.EXTRA_LOCAL_ONLY, true);
110                 }
111                 startActivityForResult(intent, CODE_READ);
112             }
113         });
114         view.addView(button);
115 
116         button = new Button(context);
117         button.setText("OPEN_DOC audio/ogg");
118         button.setOnClickListener(new OnClickListener() {
119             @Override
120             public void onClick(View v) {
121                 Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
122                 intent.addCategory(Intent.CATEGORY_OPENABLE);
123                 intent.setType("audio/ogg");
124                 if (multiple.isChecked()) {
125                     intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
126                 }
127                 if (localOnly.isChecked()) {
128                     intent.putExtra(Intent.EXTRA_LOCAL_ONLY, true);
129                 }
130                 startActivityForResult(intent, CODE_READ);
131             }
132         });
133         view.addView(button);
134 
135         button = new Button(context);
136         button.setText("OPEN_DOC text/plain, application/msword");
137         button.setOnClickListener(new OnClickListener() {
138             @Override
139             public void onClick(View v) {
140                 Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
141                 intent.addCategory(Intent.CATEGORY_OPENABLE);
142                 intent.setType("*/*");
143                 intent.putExtra(Intent.EXTRA_MIME_TYPES, new String[] {
144                         "text/plain", "application/msword" });
145                 if (multiple.isChecked()) {
146                     intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
147                 }
148                 if (localOnly.isChecked()) {
149                     intent.putExtra(Intent.EXTRA_LOCAL_ONLY, true);
150                 }
151                 startActivityForResult(intent, CODE_READ);
152             }
153         });
154         view.addView(button);
155 
156         button = new Button(context);
157         button.setText("CREATE_DOC text/plain");
158         button.setOnClickListener(new OnClickListener() {
159             @Override
160             public void onClick(View v) {
161                 Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
162                 intent.addCategory(Intent.CATEGORY_OPENABLE);
163                 intent.setType("text/plain");
164                 intent.putExtra(Intent.EXTRA_TITLE, "foobar.txt");
165                 if (localOnly.isChecked()) {
166                     intent.putExtra(Intent.EXTRA_LOCAL_ONLY, true);
167                 }
168                 startActivityForResult(intent, CODE_WRITE);
169             }
170         });
171         view.addView(button);
172 
173         button = new Button(context);
174         button.setText("CREATE_DOC image/png");
175         button.setOnClickListener(new OnClickListener() {
176             @Override
177             public void onClick(View v) {
178                 Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
179                 intent.addCategory(Intent.CATEGORY_OPENABLE);
180                 intent.setType("image/png");
181                 intent.putExtra(Intent.EXTRA_TITLE, "mypicture.png");
182                 if (localOnly.isChecked()) {
183                     intent.putExtra(Intent.EXTRA_LOCAL_ONLY, true);
184                 }
185                 startActivityForResult(intent, CODE_WRITE);
186             }
187         });
188         view.addView(button);
189 
190         button = new Button(context);
191         button.setText("GET_CONTENT */*");
192         button.setOnClickListener(new OnClickListener() {
193             @Override
194             public void onClick(View v) {
195                 Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
196                 intent.addCategory(Intent.CATEGORY_OPENABLE);
197                 intent.setType("*/*");
198                 if (multiple.isChecked()) {
199                     intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
200                 }
201                 if (localOnly.isChecked()) {
202                     intent.putExtra(Intent.EXTRA_LOCAL_ONLY, true);
203                 }
204                 startActivityForResult(Intent.createChooser(intent, "Kittens!"), CODE_READ);
205             }
206         });
207         view.addView(button);
208 
209         button = new Button(context);
210         button.setText("GET_CONTENT */* with content query \"FILE\"");
211         button.setOnClickListener(new OnClickListener() {
212             @Override
213             public void onClick(View v) {
214                 Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
215                 intent.addCategory(Intent.CATEGORY_OPENABLE);
216                 intent.setType("*/*");
217                 intent.putExtra(Intent.EXTRA_CONTENT_QUERY, "FILE");
218                 if (multiple.isChecked()) {
219                     intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
220                 }
221                 if (localOnly.isChecked()) {
222                     intent.putExtra(Intent.EXTRA_LOCAL_ONLY, true);
223                 }
224                 startActivityForResult(Intent.createChooser(intent, "Kittens!"), CODE_READ);
225             }
226         });
227         view.addView(button);
228 
229         button = new Button(context);
230         button.setText("OPEN_DOC_TREE");
231         button.setOnClickListener(new OnClickListener() {
232             @Override
233             public void onClick(View v) {
234                 Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
235                 if (localOnly.isChecked()) {
236                     intent.putExtra(Intent.EXTRA_LOCAL_ONLY, true);
237                 }
238                 startActivityForResult(Intent.createChooser(intent, "Kittens!"), CODE_TREE);
239             }
240         });
241         view.addView(button);
242 
243         button = new Button(context);
244         button.setText("OPEN_DOC_TREE primary volume initial uri");
245         button.setOnClickListener(new OnClickListener() {
246             @Override
247             public void onClick(View v) {
248                 Intent intent = getSystemService(StorageManager.class)
249                         .getPrimaryStorageVolume()
250                         .createOpenDocumentTreeIntent();
251                 startActivityForResult(intent, CODE_TREE);
252             }
253         });
254         view.addView(button);
255 
256         button = new Button(context);
257         button.setText("OPEN_DOC */* for rename");
258         button.setOnClickListener(new OnClickListener() {
259             @Override
260             public void onClick(View v) {
261                 Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
262                 intent.addCategory(Intent.CATEGORY_OPENABLE);
263                 intent.setType("*/*");
264                 startActivityForResult(intent, CODE_RENAME);
265             }
266         });
267         view.addView(button);
268 
269         final ScrollView scroll = new ScrollView(context);
270         scroll.addView(view);
271 
272         setContentView(scroll);
273     }
274 
275     @Override
onActivityResult(int requestCode, int resultCode, Intent data)276     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
277         final ContentResolver cr = getContentResolver();
278 
279         clearLog();
280 
281         log("resultCode=" + resultCode);
282         log("data=" + String.valueOf(data));
283 
284         final Uri uri = data != null ? data.getData() : null;
285         if (uri != null) {
286             log("isDocumentUri=" + DocumentsContract.isDocumentUri(this, uri));
287         } else {
288             log("missing URI?");
289             return;
290         }
291 
292         if (requestCode == CODE_READ) {
293             try {
294                 cr.takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
295             } catch (SecurityException e) {
296                 log("FAILED TO TAKE PERMISSION", e);
297             }
298             InputStream is = null;
299             try {
300                 is = cr.openInputStream(uri);
301                 log("read length=" + readFullyNoClose(is).length);
302             } catch (Exception e) {
303                 log("FAILED TO READ", e);
304             } finally {
305                 closeQuietly(is);
306             }
307         } else if (requestCode == CODE_WRITE) {
308             try {
309                 cr.takePersistableUriPermission(uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
310             } catch (SecurityException e) {
311                 log("FAILED TO TAKE PERMISSION", e);
312             }
313             OutputStream os = null;
314             try {
315                 os = cr.openOutputStream(uri);
316                 os.write("THE COMPLETE WORKS OF SHAKESPEARE".getBytes());
317                 log("wrote data");
318             } catch (Exception e) {
319                 log("FAILED TO WRITE", e);
320             } finally {
321                 closeQuietly(os);
322             }
323         } else if (requestCode == CODE_TREE) {
324             try {
325                 cr.takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
326             } catch (SecurityException e) {
327                 log("FAILED TO TAKE PERMISSION", e);
328             }
329             // Find existing docs
330             Uri doc = DocumentsContract.buildDocumentUriUsingTree(uri,
331                     DocumentsContract.getTreeDocumentId(uri));
332             Uri child = DocumentsContract.buildChildDocumentsUriUsingTree(uri,
333                     DocumentsContract.getTreeDocumentId(uri));
334             Cursor c = cr.query(child, new String[] {
335                     Document.COLUMN_DISPLAY_NAME, Document.COLUMN_MIME_TYPE }, null, null, null);
336             try {
337                 while (c.moveToNext()) {
338                     log("found child=" + c.getString(0) + ", mime=" + c.getString(1));
339                 }
340             } finally {
341                 closeQuietly(c);
342             }
343 
344             // Create some documents
345             Uri pic = createDocument(cr, doc, "image/png", "pic.png");
346             Uri dir = createDocument(cr, doc, Document.MIME_TYPE_DIR, "my dir");
347             Uri dirPic = createDocument(cr, dir, "image/png", "pic2.png");
348 
349             log("created " + pic);
350             log("created " + dir);
351             log("created " + dirPic);
352 
353             // Write to one of them
354             OutputStream os = null;
355             try {
356                 os = cr.openOutputStream(dirPic);
357                 os.write("THE COMPLETE WORKS OF SHAKESPEARE".getBytes());
358                 log("wrote data");
359             } catch (Exception e) {
360                 log("FAILED TO WRITE", e);
361             } finally {
362                 closeQuietly(os);
363             }
364 
365             // And delete the first pic
366             if (deleteDocument(cr, pic)) {
367                 log("deleted untouched pic");
368             } else {
369                 log("FAILED TO DELETE PIC");
370             }
371         } else if (requestCode == CODE_RENAME) {
372             final Uri newUri = renameDocument(cr, uri, "MEOW.TEST");
373             log("rename result=" + newUri);
374 
375             InputStream is = null;
376             try {
377                 is = cr.openInputStream(newUri);
378                 log("read length=" + readFullyNoClose(is).length);
379             } catch (Exception e) {
380                 log("FAILED TO READ", e);
381             } finally {
382                 closeQuietly(is);
383             }
384         }
385     }
386 
createDocument(ContentResolver resolver, Uri documentUri, String mimeType, String displayName)387     private Uri createDocument(ContentResolver resolver, Uri documentUri, String mimeType,
388             String displayName) {
389         Uri uri;
390         try {
391             uri = DocumentsContract.createDocument(resolver, documentUri, mimeType, displayName);
392         } catch (Exception e) {
393             uri = null;
394         }
395         return uri;
396     }
397 
deleteDocument(ContentResolver resolver, Uri documentUri)398     private boolean deleteDocument(ContentResolver resolver, Uri documentUri) {
399         try {
400             return DocumentsContract.deleteDocument(resolver, documentUri);
401         } catch (Exception e) {
402             return false;
403         }
404     }
405 
renameDocument(ContentResolver resolver, Uri uri, String newName)406     private Uri renameDocument(ContentResolver resolver, Uri uri, String newName) {
407         try {
408             return DocumentsContract.renameDocument(resolver, uri, newName);
409         } catch (Exception e) {
410             return null;
411         }
412     }
413 
clearLog()414     private void clearLog() {
415         mResult.setText(null);
416     }
417 
log(String msg)418     private void log(String msg) {
419         log(msg, null);
420     }
421 
log(String msg, Throwable t)422     private void log(String msg, Throwable t) {
423         Log.d(TAG, msg, t);
424         mResult.setText(mResult.getText() + "\n" + msg);
425     }
426 
readFullyNoClose(InputStream in)427     public static byte[] readFullyNoClose(InputStream in) throws IOException {
428         ByteArrayOutputStream bytes = new ByteArrayOutputStream();
429         byte[] buffer = new byte[1024];
430         int count;
431         while ((count = in.read(buffer)) != -1) {
432             bytes.write(buffer, 0, count);
433         }
434         return bytes.toByteArray();
435     }
436 
closeQuietly(AutoCloseable closeable)437     public static void closeQuietly(AutoCloseable closeable) {
438         if (closeable != null) {
439             try {
440                 closeable.close();
441             } catch (RuntimeException rethrown) {
442                 throw rethrown;
443             } catch (Exception ignored) {
444             }
445         }
446     }
447 }
448