1717
1818package com .example .android .storageprovider ;
1919
20+ import static android .os .Build .VERSION .SDK_INT ;
21+
2022import android .content .Context ;
2123import android .content .SharedPreferences ;
2224import android .content .res .AssetFileDescriptor ;
2325import android .content .res .TypedArray ;
2426import android .database .Cursor ;
2527import android .database .MatrixCursor ;
2628import android .graphics .Point ;
29+ import android .os .Build ;
2730import android .os .CancellationSignal ;
2831import android .os .Handler ;
2932import android .os .ParcelFileDescriptor ;
3639
3740import java .io .ByteArrayOutputStream ;
3841import java .io .File ;
42+ import java .io .FileInputStream ;
3943import java .io .FileNotFoundException ;
4044import java .io .FileOutputStream ;
4145import java .io .IOException ;
4246import java .io .InputStream ;
47+ import java .io .OutputStream ;
4348import java .util .Collections ;
4449import java .util .Comparator ;
4550import java .util .HashSet ;
@@ -95,7 +100,7 @@ public boolean onCreate() {
95100
96101 mBaseDir = getContext ().getFilesDir ();
97102
98- writeDummyFilesToStorage ();
103+ writeTestFilesToStorage ();
99104
100105 return true ;
101106 }
@@ -127,10 +132,18 @@ public Cursor queryRoots(String[] projection) throws FileNotFoundException {
127132 // FLAG_SUPPORTS_CREATE means at least one directory under the root supports creating
128133 // documents. FLAG_SUPPORTS_RECENTS means your application's most recently used
129134 // documents will show up in the "Recents" category. FLAG_SUPPORTS_SEARCH allows users
130- // to search all documents the application shares.
131- row .add (Root .COLUMN_FLAGS , Root .FLAG_SUPPORTS_CREATE |
132- Root .FLAG_SUPPORTS_RECENTS |
133- Root .FLAG_SUPPORTS_SEARCH );
135+ // to search all documents the application shares. FLAG_SUPPORTS_IS_CHILD allows
136+ // testing parent child relationships, available after SDK 21 (Lollipop).
137+ if (SDK_INT < Build .VERSION_CODES .LOLLIPOP ) {
138+ row .add (Root .COLUMN_FLAGS , Root .FLAG_SUPPORTS_CREATE |
139+ Root .FLAG_SUPPORTS_RECENTS |
140+ Root .FLAG_SUPPORTS_SEARCH );
141+ } else {
142+ row .add (Root .COLUMN_FLAGS , Root .FLAG_SUPPORTS_CREATE |
143+ Root .FLAG_SUPPORTS_RECENTS |
144+ Root .FLAG_SUPPORTS_SEARCH |
145+ Root .FLAG_SUPPORTS_IS_CHILD );
146+ }
134147
135148 // COLUMN_TITLE is the root title (e.g. what will be displayed to identify your provider).
136149 row .add (Root .COLUMN_TITLE , getContext ().getString (R .string .app_name ));
@@ -330,6 +343,27 @@ public void onClose(IOException e) {
330343 // END_INCLUDE(open_document)
331344
332345
346+ public boolean isChildFile (File parentFile , File childFile ){
347+ File realFileParent = childFile .getParentFile ();
348+ return realFileParent == null || realFileParent .equals (parentFile );
349+ }
350+
351+ // BEGIN_INCLUDE(is_child_document)
352+ @ Override
353+ public boolean isChildDocument (String parentDocumentId , String documentId ) {
354+ Log .v (TAG , "isChildDocument" );
355+ try {
356+ File parentFile = getFileForDocId (parentDocumentId );
357+ File childFile = getFileForDocId (documentId );
358+ return isChildFile (parentFile , childFile );
359+ } catch (FileNotFoundException e ) {
360+ Log .e (TAG , "FileNotFound in isChildDocument: " + e .getMessage ());
361+ e .printStackTrace ();
362+ }
363+ return false ;
364+ }
365+ // END_INCLUDE(is_child_document)
366+
333367 // BEGIN_INCLUDE(create_document)
334368 @ Override
335369 public String createDocument (String documentId , String mimeType , String displayName )
@@ -339,9 +373,18 @@ public String createDocument(String documentId, String mimeType, String displayN
339373 File parent = getFileForDocId (documentId );
340374 File file = new File (parent .getPath (), displayName );
341375 try {
342- file .createNewFile ();
343- file .setWritable (true );
344- file .setReadable (true );
376+ // Create the new File to copy into
377+ boolean wasNewFileCreated = false ;
378+ if (file .createNewFile ()) {
379+ if (file .setWritable (true ) && file .setReadable (true )) {
380+ wasNewFileCreated = true ;
381+ }
382+ }
383+
384+ if (!wasNewFileCreated ) {
385+ throw new FileNotFoundException ("Failed to create document with name " +
386+ displayName +" and documentId " + documentId );
387+ }
345388 } catch (IOException e ) {
346389 throw new FileNotFoundException ("Failed to create document with name " +
347390 displayName +" and documentId " + documentId );
@@ -350,6 +393,38 @@ public String createDocument(String documentId, String mimeType, String displayN
350393 }
351394 // END_INCLUDE(create_document)
352395
396+ // BEGIN_INCLUDE(rename_document)
397+ @ Override
398+ public String renameDocument (String documentId , String displayName )
399+ throws FileNotFoundException {
400+ Log .v (TAG , "renameDocument" );
401+ if (displayName == null ) {
402+ throw new FileNotFoundException ("Failed to rename document, new name is null" );
403+ }
404+
405+ // Create the destination file in the same directory as the source file
406+ File sourceFile = getFileForDocId (documentId );
407+ File sourceParentFile = sourceFile .getParentFile ();
408+ if (sourceParentFile == null ) {
409+ throw new FileNotFoundException ("Failed to rename document. File has no parent." );
410+ }
411+ File destFile = new File (sourceParentFile .getPath (), displayName );
412+
413+ // Try to do the rename
414+ try {
415+ boolean renameSucceeded = sourceFile .renameTo (destFile );
416+ if (!renameSucceeded ) {
417+ throw new FileNotFoundException ("Failed to rename document. Renamed failed." );
418+ }
419+ } catch (Exception e ) {
420+ Log .w (TAG , "Rename exception : " + e .getLocalizedMessage () + e .getCause ());
421+ throw new FileNotFoundException ("Failed to rename document. Error: " + e .getMessage ());
422+ }
423+
424+ return getDocIdForFile (destFile );
425+ }
426+ // END_INCLUDE(rename_document)
427+
353428 // BEGIN_INCLUDE(delete_document)
354429 @ Override
355430 public void deleteDocument (String documentId ) throws FileNotFoundException {
@@ -363,6 +438,113 @@ public void deleteDocument(String documentId) throws FileNotFoundException {
363438 }
364439 // END_INCLUDE(delete_document)
365440
441+ // BEGIN_INCLUDE(remove_document)
442+ @ Override
443+ public void removeDocument (String documentId , String parentDocumentId )
444+ throws FileNotFoundException {
445+ Log .v (TAG , "removeDocument" );
446+ File parent = getFileForDocId (parentDocumentId );
447+ File file = getFileForDocId (documentId );
448+
449+ if (file == null ) {
450+ throw new FileNotFoundException ("Failed to delete document with id " + documentId );
451+ }
452+
453+ // removeDocument is the same as deleteDocument but allows the parent to be specified
454+ // Check here if the specified parentDocumentId matches the true parent of documentId
455+ boolean doesFileParentMatch = false ;
456+ File fileParent = file .getParentFile ();
457+
458+ if (fileParent == null || fileParent .equals (parent )) {
459+ doesFileParentMatch = true ;
460+ }
461+
462+ // Remove the file if parent matches or file and parent are equal
463+ if (parent .equals (file ) || doesFileParentMatch ) {
464+ if (file .delete ()) {
465+ Log .i (TAG , "Deleted file with id " + documentId );
466+ } else {
467+ throw new FileNotFoundException ("Failed to delete document with id " + documentId );
468+ }
469+ } else {
470+ throw new FileNotFoundException ("Failed to delete document with id " + documentId );
471+ }
472+ }
473+ // END_INCLUDE(remove_document)
474+
475+ /**
476+ * overload copyDocument to insist that the parent matches
477+ */
478+ public String copyDocument (String sourceDocumentId , String sourceParentDocumentId ,
479+ String targetParentDocumentId ) throws FileNotFoundException {
480+ Log .v (TAG , "copyDocument with document parent" );
481+ if (!isChildDocument (sourceParentDocumentId , sourceDocumentId )) {
482+ throw new FileNotFoundException ("Failed to copy document with id " +
483+ sourceDocumentId + ". Parent is not: " + sourceParentDocumentId );
484+ }
485+ return copyDocument (sourceDocumentId , targetParentDocumentId );
486+ }
487+
488+ // BEGIN_INCLUDE(copyDocument)
489+ @ Override
490+ public String copyDocument (String sourceDocumentId , String targetParentDocumentId )
491+ throws FileNotFoundException {
492+ Log .v (TAG , "copyDocument" );
493+
494+ File parent = getFileForDocId (targetParentDocumentId );
495+ File oldFile = getFileForDocId (sourceDocumentId );
496+ File newFile = new File (parent .getPath (), oldFile .getName ());
497+ try {
498+ // Create the new File to copy into
499+ boolean wasNewFileCreated = false ;
500+ if (newFile .createNewFile ()) {
501+ if (newFile .setWritable (true ) && newFile .setReadable (true )) {
502+ wasNewFileCreated = true ;
503+ }
504+ }
505+
506+ if (!wasNewFileCreated ) {
507+ throw new FileNotFoundException ("Failed to copy document " + sourceDocumentId +
508+ ". Could not create new file." );
509+ }
510+
511+ // Copy the bytes into the new file
512+ try (InputStream inStream = new FileInputStream (oldFile )) {
513+ try (OutputStream outStream = new FileOutputStream (newFile )) {
514+ // Transfer bytes from in to out
515+ byte [] buf = new byte [4096 ]; // ideal range for network: 2-8k, disk: 8-64k
516+ int len ;
517+ while ((len = inStream .read (buf )) > 0 ) {
518+ outStream .write (buf , 0 , len );
519+ }
520+ }
521+ }
522+ } catch (IOException e ) {
523+ throw new FileNotFoundException ("Failed to copy document: " + sourceDocumentId +
524+ ". " + e .getMessage ());
525+ }
526+ return getDocIdForFile (newFile );
527+ }
528+ // END_INCLUDE(copyDocument)
529+
530+ // BEGIN_INCLUDE(moveDocument)
531+ @ Override
532+ public String moveDocument (String sourceDocumentId , String sourceParentDocumentId ,
533+ String targetParentDocumentId ) throws FileNotFoundException {
534+ Log .v (TAG , "moveDocument" );
535+ try {
536+ // Copy document, insisting that the parent is correct
537+ String newDocumentId = copyDocument (sourceDocumentId , sourceParentDocumentId ,
538+ targetParentDocumentId );
539+ // Remove old document
540+ removeDocument (sourceDocumentId ,sourceParentDocumentId );
541+ return newDocumentId ;
542+ } catch (FileNotFoundException e ) {
543+ throw new FileNotFoundException ("Failed to move document " + sourceDocumentId );
544+ }
545+ }
546+ // END_INCLUDE(moveDocument)
547+
366548
367549 @ Override
368550 public String getDocumentType (String documentId ) throws FileNotFoundException {
@@ -471,7 +653,7 @@ private String getDocIdForFile(File file) {
471653 * @param result the cursor to modify
472654 * @param docId the document ID representing the desired file (may be null if given file)
473655 * @param file the File object representing the desired file (may be null if given docID)
474- * @throws java.io. FileNotFoundException
656+ * @throws FileNotFoundException
475657 */
476658 private void includeFile (MatrixCursor result , String docId , File file )
477659 throws FileNotFoundException {
@@ -497,6 +679,16 @@ private void includeFile(MatrixCursor result, String docId, File file)
497679 // FLAG_SUPPORTS_DELETE
498680 flags |= Document .FLAG_SUPPORTS_WRITE ;
499681 flags |= Document .FLAG_SUPPORTS_DELETE ;
682+
683+ // Add SDK specific flags if appropriate
684+ if (SDK_INT >= Build .VERSION_CODES .LOLLIPOP ) {
685+ flags |= Document .FLAG_SUPPORTS_RENAME ;
686+ }
687+ if (SDK_INT >= Build .VERSION_CODES .N ) {
688+ flags |= Document .FLAG_SUPPORTS_REMOVE ;
689+ flags |= Document .FLAG_SUPPORTS_MOVE ;
690+ flags |= Document .FLAG_SUPPORTS_COPY ;
691+ }
500692 }
501693
502694 final String displayName = file .getName ();
@@ -544,13 +736,12 @@ private File getFileForDocId(String docId) throws FileNotFoundException {
544736 }
545737 }
546738
547-
548739 /**
549740 * Preload sample files packaged in the apk into the internal storage directory. This is a
550- * dummy function specific to this demo. The MyCloud mock cloud service doesn't actually
741+ * test function specific to this demo. The MyCloud mock cloud service doesn't actually
551742 * have a backend, so it simulates by reading content from the device's internal storage.
552743 */
553- private void writeDummyFilesToStorage () {
744+ private void writeTestFilesToStorage () {
554745 if (mBaseDir .list ().length > 0 ) {
555746 return ;
556747 }
@@ -572,7 +763,7 @@ private void writeDummyFilesToStorage() {
572763 }
573764
574765 /**
575- * Write a file to internal storage. Used to set up our dummy "cloud server".
766+ * Write a file to internal storage. Used to set up our simple "cloud server".
576767 *
577768 * @param resId the resource ID of the file to write to internal storage
578769 * @param extension the file extension (ex. .png, .mp3)
@@ -610,14 +801,12 @@ private int[] getResourceIdArray(int arrayResId) {
610801 }
611802
612803 /**
613- * Dummy function to determine whether the user is logged in.
804+ * Placeholder function to determine whether the user is logged in.
614805 */
615806 private boolean isUserLoggedIn () {
616807 final SharedPreferences sharedPreferences =
617808 getContext ().getSharedPreferences (getContext ().getString (R .string .app_name ),
618809 Context .MODE_PRIVATE );
619810 return sharedPreferences .getBoolean (getContext ().getString (R .string .key_logged_in ), false );
620811 }
621-
622-
623812}
0 commit comments