Skip to content

Commit 90fa271

Browse files
committed
Working on adding a friendlier post hoc interface for accessing the files based on the paths selected by the user
1 parent 46def50 commit 90fa271

5 files changed

Lines changed: 166 additions & 14 deletions

File tree

AndroidFilePickerLightLibrary/build.gradle

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,13 @@ def getGitCommitDate() {
2626
android {
2727

2828
compileSdkVersion 30
29-
buildToolsVersion "30.0.0"
29+
buildToolsVersion "30.0.1"
3030

3131
defaultConfig {
3232
minSdkVersion 29
3333
targetSdkVersion 30
34-
versionCode 11
35-
versionName "1.1.0"
34+
versionCode 12
35+
versionName "1.1.1"
3636
}
3737

3838
compileOptions {

AndroidFilePickerLightLibrary/src/main/AndroidManifest.xml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,6 @@
7171
android:permission="android.permission.MANAGE_DOCUMENTS"
7272
android:initOrder="100"
7373
>
74-
<!--"com.maxieds.androidfilepickerlightlibrary.FileChooserActivity"-->
7574

7675
<intent-filter>
7776
<action android:name="android.content.action.DOCUMENTS_PROVIDER" />

AndroidFilePickerLightLibrary/src/main/java/com/maxieds/androidfilepickerlightlibrary/BasicFileProvider.java

Lines changed: 128 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ private boolean setLegacyBaseFolderByName(String namedSubFolder) {
145145
return true;
146146
}
147147

148-
public void selectBaseDirectoryByType(FileChooserBuilder.BaseFolderPathType baseFolderType) {
148+
public boolean selectBaseDirectoryByType(FileChooserBuilder.BaseFolderPathType baseFolderType) {
149149
Context appCtx = FileChooserActivity.getInstance();
150150
switch(baseFolderType) {
151151
case BASE_PATH_TYPE_FILES_DIR:
@@ -189,8 +189,9 @@ public void selectBaseDirectoryByType(FileChooserBuilder.BaseFolderPathType base
189189
case BASE_PATH_TYPE_MEDIA_STORE:
190190
case BASE_PATH_EXTERNAL_PROVIDER:
191191
default:
192-
return;
192+
break;
193193
}
194+
return baseDirPath != null;
194195
}
195196

196197
public String getCWD() {
@@ -400,6 +401,7 @@ public AssetFileDescriptor openDocumentThumbnail(String documentId, Point sizeHi
400401
if(extDocsProviderStaticInst != null) {
401402
return extDocsProviderStaticInst.openDocumentThumbnail(documentId, sizeHint, signal);
402403
}
404+
// The document needs to have the flag: FLAG_SUPPORTS_THUMBNAIL ???
403405

404406
final File file = getFileForDocId(documentId);
405407
final ParcelFileDescriptor pfd =
@@ -668,17 +670,21 @@ private File getFileForDocId(String docId) throws FileNotFoundException {
668670
} else {
669671
final String path = docId.substring(splitIndex + 1);
670672
target = new File(target, path);
671-
//Log.i(LOGTAG, "DOCID effective path -> " + target.getAbsolutePath());
672673
if (!target.exists()) {
673674
throw new FileNotFoundException("Missing file for " + docId + " at " + target);
674675
}
675676
return target;
676677
}
677678
}
678679

680+
private long getDocumentSize(String docId) throws FileNotFoundException {
681+
File docFileRef = getFileForDocId(docId);
682+
return docFileRef.length();
683+
}
684+
679685
public static final boolean CURSOR_TYPE_IS_ROOT = true;
680686

681-
public static String getDocumentIdForCursorType(MatrixCursor mcResult, boolean cursorType) {
687+
private static String getDocumentIdForCursorType(MatrixCursor mcResult, boolean cursorType) {
682688
if(mcResult.getCount() == 0) {
683689
return null;
684690
}
@@ -766,18 +772,22 @@ public String[] getPropertiesOfCurrentRow(MatrixCursor mcResult, boolean cursorT
766772

767773
private static final int BYTE_BUFFER_SIZE = 128;
768774

769-
public StringBuilder readFileContentsAsString(final String documentId) {
775+
public StringBuilder readFileContentsAsString(final String documentId, int offset, int maxBytes) {
776+
if(offset < 0 || maxBytes <= 0) {
777+
return null;
778+
}
770779
try {
771780
ParcelFileDescriptor docDesc = openDocument(documentId, "r+", null);
772781
FileDescriptor fd = docDesc.getFileDescriptor();
773782
FileInputStream inputStream = new FileInputStream(fd);
774783
StringBuilder sbuilder = new StringBuilder();
775784
byte[] byteBuf = new byte[BYTE_BUFFER_SIZE];
776-
int bytesRead = inputStream.read(byteBuf, 0, BYTE_BUFFER_SIZE);
785+
int bytesRead = inputStream.read(byteBuf, offset, Math.min(maxBytes, BYTE_BUFFER_SIZE));
777786
while(bytesRead > 0) {
787+
maxBytes -= bytesRead;
778788
byte[] bytesReadBuf = Arrays.copyOf(byteBuf, bytesRead);
779789
sbuilder.append(new String(bytesReadBuf));
780-
bytesRead = inputStream.read(byteBuf, 0, BYTE_BUFFER_SIZE);
790+
bytesRead = inputStream.read(byteBuf, 0, Math.min(maxBytes, BYTE_BUFFER_SIZE));
781791
}
782792
inputStream.close();
783793
docDesc.close();
@@ -791,15 +801,19 @@ public StringBuilder readFileContentsAsString(final String documentId) {
791801
}
792802
}
793803

794-
public byte[] readFileContentsAsBytesArray(final String documentId) {
804+
public byte[] readFileContentsAsByteArray(final String documentId, int offset, int maxBytes) {
805+
if(offset < 0 || maxBytes <= 0) {
806+
return null;
807+
}
795808
try {
796809
ParcelFileDescriptor docDesc = openDocument(documentId, "r+", null);
797810
FileDescriptor fd = docDesc.getFileDescriptor();
798811
FileInputStream inputStream = new FileInputStream(fd);
799812
int actualSize = 0, bufCapacity = BYTE_BUFFER_SIZE;
800813
byte[] returnBuf = new byte[BYTE_BUFFER_SIZE], byteBuf = new byte[BYTE_BUFFER_SIZE];
801-
int bytesRead = inputStream.read(byteBuf, 0, BYTE_BUFFER_SIZE);
814+
int bytesRead = inputStream.read(byteBuf, offset, Math.min(maxBytes, BYTE_BUFFER_SIZE));
802815
while(bytesRead > 0) {
816+
maxBytes -= bytesRead;
803817
byte[] bytesReadBuf = Arrays.copyOf(byteBuf, bytesRead);
804818
System.arraycopy(returnBuf, actualSize, bytesReadBuf, 0, bytesRead);
805819
actualSize += bytesRead;
@@ -809,7 +823,7 @@ public byte[] readFileContentsAsBytesArray(final String documentId) {
809823
System.arraycopy(nextReturnBuf, 0, returnBuf, 0, actualSize);
810824
returnBuf = nextReturnBuf;
811825
}
812-
bytesRead = inputStream.read(byteBuf, 0, BYTE_BUFFER_SIZE);
826+
bytesRead = inputStream.read(byteBuf, 0, Math.min(maxBytes, BYTE_BUFFER_SIZE));
813827
}
814828
inputStream.close();
815829
docDesc.close();
@@ -823,4 +837,108 @@ public byte[] readFileContentsAsBytesArray(final String documentId) {
823837
}
824838
}
825839

840+
/* More robust DocumentsProvider functionality:
841+
* -> Support document creation: https://developer.android.com/guide/topics/providers/create-document-provider#creating
842+
* -> Support document management features: (delete, rename, copy, move, remove)
843+
* https://developer.android.com/guide/topics/providers/create-document-provider#browsing
844+
*/
845+
846+
class DocumentPointer {
847+
848+
private BasicFileProvider fpInst;
849+
private boolean isValid;
850+
private FileChooserBuilder.BaseFolderPathType baseFolderType;
851+
private String relativeFolderPathOffset;
852+
private String absFolderPathOffset;
853+
private String filePath; // Should be trimmed relative to the initial base folders
854+
private String documentId;
855+
private Cursor documentCursorEntry; // Of size one
856+
857+
private void initializeDefaults() {
858+
fpInst = BasicFileProvider.getInstance();
859+
if(fpInst == null) {
860+
throw new FileChooserException.BasicFileProviderException();
861+
}
862+
isValid = false;
863+
baseFolderType = null;
864+
relativeFolderPathOffset = null;
865+
absFolderPathOffset = null;
866+
filePath = null;
867+
documentId = null;
868+
documentCursorEntry = null;
869+
}
870+
871+
DocumentPointer(FileChooserBuilder.BaseFolderPathType baseFolderType) {
872+
initializeDefaults();
873+
fpInst.resetBaseDirectory();
874+
isValid = fpInst.selectBaseDirectoryByType(baseFolderType);
875+
this.baseFolderType = baseFolderType;
876+
}
877+
878+
DocumentPointer(FileChooserBuilder.BaseFolderPathType baseFolderType, String relativeOffsetPath) {
879+
initializeDefaults();
880+
fpInst.resetBaseDirectory();
881+
isValid = fpInst.selectBaseDirectoryByType(baseFolderType);
882+
isValid = isValid && fpInst.enterNextSubfolder(relativeOffsetPath);
883+
this.baseFolderType = baseFolderType;
884+
this.relativeFolderPathOffset = relativeOffsetPath;
885+
}
886+
887+
DocumentPointer(String absInitFolderPath) {
888+
initializeDefaults();
889+
fpInst.resetBaseDirectory();
890+
isValid = fpInst.enterNextSubfolder(absInitFolderPath);
891+
this.absFolderPathOffset = absInitFolderPath;
892+
}
893+
894+
public boolean isValid() { return isValid; }
895+
896+
public boolean locateDocument(String documentPath) throws FileNotFoundException {
897+
if(!isValid()) {
898+
return false;
899+
}
900+
this.filePath = documentPath.replaceFirst(fpInst.getCWD(), "");
901+
File docFile = new File(fpInst.getCWD(), filePath);
902+
documentId = fpInst.getDocIdForFile(docFile);
903+
return true;
904+
}
905+
906+
public boolean deleteDocument() throws FileNotFoundException {
907+
if(!isValid() || documentId == null) {
908+
return false;
909+
}
910+
fpInst.deleteDocument(documentId);
911+
return true;
912+
}
913+
914+
public String getDocumentType() throws FileNotFoundException {
915+
if(!isValid() || documentId == null) {
916+
return null;
917+
}
918+
return fpInst.getDocumentType(documentId);
919+
}
920+
921+
public long getDocumentSize() throws FileNotFoundException {
922+
if(!isValid() || documentId == null) {
923+
return 0;
924+
}
925+
return fpInst.getDocumentSize(documentId);
926+
}
927+
928+
public StringBuilder readFileContentsAsString(int offset, int maxBytes) {
929+
if(!isValid() || documentId == null) {
930+
return null;
931+
}
932+
return fpInst.readFileContentsAsString(documentId, offset, maxBytes);
933+
}
934+
935+
public byte[] readFileContentsAsByteArray(int offset, int maxBytes) {
936+
if(!isValid() || documentId == null) {
937+
return null;
938+
}
939+
return fpInst.readFileContentsAsByteArray(documentId, offset, maxBytes);
940+
}
941+
942+
}
943+
826944
}

AndroidFilePickerLightLibrary/src/main/java/com/maxieds/androidfilepickerlightlibrary/FileChooserException.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,22 @@ public InvalidActivityContextException() {
341341

342342
}
343343

344+
public static class BasicFileProviderException extends AndroidFilePickerLightException {
345+
346+
private static int UNIQUE_ERROR_CODE = claimNextUniqueErrorCode();
347+
348+
protected static BasicFileProviderException getNewInstance() {
349+
return new BasicFileProviderException();
350+
}
351+
352+
public BasicFileProviderException() {
353+
configureExceptionParams(UNIQUE_ERROR_CODE,
354+
"The BasicFileProvider is not initialized or has reached an illegal state",
355+
true, DEFAULT_DATA_ITEMS_TYPE, null);
356+
}
357+
358+
}
359+
344360
public static class UnsupportedTypeException extends AndroidFilePickerLightException {
345361

346362
private static int UNIQUE_ERROR_CODE = claimNextUniqueErrorCode();

WorkingItems.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,25 @@
11
# TODO: A working tabulation of items that still need to get fixed for the next release of the library (TBD)
22

33
* Ability for users to create / specify icon types for custom file types (see ``FileChooserBuilder.DefaultFileTypes`` enum).
4+
* Moving forward (especially with the restrictive upcoming Android 11 permissions settings), are going to need a way to
5+
translate an obtained chosen path, and then read from it without having to tag the returned results with
6+
FileProvider docIds that the user doesn't need to know about. Create a handy utils interface for this ???
7+
8+
* A working question of mine is why we need all of this abstraction just to access files on the local disks?
9+
I get that more abstract file types (e.g., from over the network) or interfaces can be desirable in complicated setups,
10+
but why when the stock Java ``File`` interfaces work already are we having to go through this convoluted way of
11+
confusing the user's access by standard file paths as IDs? The inner implementation suggested by Google just
12+
transforms the ``File`` back and forth from that object type to the document ID, or ``root:pathData`` format.
13+
1. NOTE: It is not always possible to set Posix file permissions on external storage in recent Android APIs.
14+
2. The system also does not like to always honor returning genuine properties back to the user about the document,
15+
even if it just corresponds to a (correct) path on disk obtained legitimately by the working ``DocumentsProvider` instance.
16+
3. In this case (just working off of the default file system on your Droid device), is the difference that the system
17+
gives some magic credence to interfaces derived from ``DocumentsProvider``? E.g., so that it will allow an
18+
instance of that interface to magically grok FS data and properties, but deny the same access to users that are only
19+
working within stock Java ``File`` I/O contexts?
20+
4. If (3) is true, I repeat my confusion and distaste for this scheme and added bits of convolution rather than
21+
useful abstraction to a generalized more powerful interface.
22+
I need more information, but the documentation for this is still very missing in a lot of places.
423

524
## Documentation and approach to handling files internally within the picker library
625

0 commit comments

Comments
 (0)