Skip to content

Commit 489489d

Browse files
committed
Make ResolverCompat an object for quick method access.
1 parent 7377303 commit 489489d

2 files changed

Lines changed: 68 additions & 130 deletions

File tree

dfc/src/main/java/com/lazygeniouz/dfc/controller/DocumentController.kt

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import android.content.pm.PackageManager
77
import android.net.Uri
88
import android.provider.DocumentsContract
99
import android.provider.DocumentsContract.Document.MIME_TYPE_DIR
10-
import android.provider.DocumentsContract.isDocumentUri
1110
import com.lazygeniouz.dfc.file.DocumentFileCompat
1211
import com.lazygeniouz.dfc.resolver.ResolverCompat
1312

@@ -26,32 +25,33 @@ internal class DocumentController(
2625
// DocumentsContract API level 24.
2726
private val flagVirtualDocument = 1 shl 9
2827

29-
// File Handler to call delegate functions.
30-
private val resolverCompat by lazy { ResolverCompat(context, fileCompat.uri) }
28+
// Uri of the given [DocumentFileCompat]
29+
private val fileUri get() = fileCompat.uri
3130

3231
/**
3332
* This will return a list of [DocumentFileCompat] with all the defined fields.
3433
*/
3534
internal fun listFiles(): List<DocumentFileCompat> {
3635
return if (!isDirectory())
3736
throw UnsupportedOperationException("Selected document is not a Directory.")
38-
else resolverCompat.queryAndMakeDocumentList()
37+
else ResolverCompat.listFiles(context, fileUri)
3938
}
4039

4140
/**
4241
* This will return the children count in the directory.
42+
*
4343
* More optimised than using [List.size] via [listFiles].
4444
*/
4545
internal fun count(): Int {
4646
return if (!isDirectory()) 0
47-
else resolverCompat.count()
47+
else ResolverCompat.count(context, fileUri)
4848
}
4949

5050
/**
5151
* Returns True if the Document Folder / File exists, False otherwise.
5252
*/
5353
internal fun exists(): Boolean {
54-
return resolverCompat.exists()
54+
return ResolverCompat.exists(context, fileUri)
5555
}
5656

5757
/**
@@ -61,7 +61,7 @@ internal class DocumentController(
6161
* and doesn't have byte representation in the MIME type specified as COLUMN_MIME_TYPE.
6262
*/
6363
internal fun isVirtual(): Boolean {
64-
if (!isDocumentUri(context, fileCompat.uri)) return false
64+
if (!DocumentsContract.isDocumentUri(context, fileUri)) return false
6565

6666
return fileCompat.documentFlags and flagVirtualDocument != 0
6767
}
@@ -85,7 +85,7 @@ internal class DocumentController(
8585
*/
8686
internal fun canRead(): Boolean {
8787
if (context.checkCallingOrSelfUriPermission(
88-
fileCompat.uri, Intent.FLAG_GRANT_READ_URI_PERMISSION
88+
fileUri, Intent.FLAG_GRANT_READ_URI_PERMISSION
8989
) != PackageManager.PERMISSION_GRANTED
9090
) return false
9191

@@ -99,7 +99,7 @@ internal class DocumentController(
9999
*/
100100
internal fun canWrite(): Boolean {
101101
if (context.checkCallingOrSelfUriPermission(
102-
fileCompat.uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION
102+
fileUri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION
103103
) != PackageManager.PERMISSION_GRANTED
104104
) return false
105105

@@ -111,6 +111,7 @@ internal class DocumentController(
111111
if (MIME_TYPE_DIR == fileCompat.documentMimeType &&
112112
fileCompat.documentFlags and DocumentsContract.Document.FLAG_DIR_SUPPORTS_CREATE != 0
113113
) return true
114+
114115
else if (fileCompat.documentMimeType.isNotEmpty() &&
115116
fileCompat.documentFlags and DocumentsContract.Document.FLAG_SUPPORTS_WRITE != 0
116117
) return true
@@ -127,22 +128,22 @@ internal class DocumentController(
127128
* @return A Uri if file was created successfully, **null** otherwise.
128129
*/
129130
internal fun createFile(mimeType: String, name: String): Uri? {
130-
return resolverCompat.createFile(mimeType, name)
131+
return ResolverCompat.createFile(context, fileUri, mimeType, name)
131132
}
132133

133134
/**
134135
* Rename a Document File / Folder.
135136
*
136137
* Returns True if the rename was successful, False otherwise.
137138
*/
138-
internal fun renameTo(name: String): Boolean {
139-
return resolverCompat.renameTo(name)
139+
internal fun renameTo(name: String): Uri? {
140+
return ResolverCompat.renameTo(context, fileUri, name)
140141
}
141142

142143
/**
143144
* Delete a document.
144145
*/
145146
internal fun delete(): Boolean {
146-
return resolverCompat.deleteDocument()
147+
return ResolverCompat.deleteDocument(context, fileUri)
147148
}
148149
}

dfc/src/main/java/com/lazygeniouz/dfc/resolver/ResolverCompat.kt

Lines changed: 54 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -6,45 +6,35 @@ import android.database.Cursor
66
import android.net.Uri
77
import android.provider.DocumentsContract
88
import com.lazygeniouz.dfc.file.DocumentFileCompat
9-
import com.lazygeniouz.dfc.file.internals.SingleDocumentFileCompat
109
import com.lazygeniouz.dfc.file.internals.TreeDocumentFileCompat
11-
import com.lazygeniouz.dfc.logger.ErrorLogger.logError
10+
import com.lazygeniouz.dfc.logger.ErrorLogger
1211

1312
/**
14-
* This class calls relevant queries on the [ContentResolver]
15-
*
16-
* @param context Required to access the underlying **ContentResolver**
13+
* Helper class for calling relevant methods on [DocumentsContract] & queries via [ContentResolver].
1714
*/
18-
internal class ResolverCompat(
19-
private val context: Context,
20-
private val uri: Uri,
21-
) {
15+
internal object ResolverCompat {
2216

23-
// Projections
24-
private val _idProjection = DocumentsContract.Document.COLUMN_DOCUMENT_ID
25-
private val documentIdProjection = arrayOf(_idProjection)
26-
private val fullProjection = arrayOf(
27-
_idProjection,
17+
private val iconProjection = arrayOf(DocumentsContract.Document.COLUMN_ICON)
18+
private val idProjection = arrayOf(DocumentsContract.Document.COLUMN_DOCUMENT_ID)
19+
val fullProjection = arrayOf(
20+
DocumentsContract.Document.COLUMN_DOCUMENT_ID,
2821
DocumentsContract.Document.COLUMN_DISPLAY_NAME,
2922
DocumentsContract.Document.COLUMN_SIZE,
3023
DocumentsContract.Document.COLUMN_LAST_MODIFIED,
3124
DocumentsContract.Document.COLUMN_MIME_TYPE,
3225
DocumentsContract.Document.COLUMN_FLAGS
3326
)
3427

35-
// The star of the show!
36-
private val contentResolver by lazy { context.contentResolver }
37-
3828
/**
3929
* Delete the file.
4030
*
4131
* @return True if deletion succeeded, False otherwise
4232
*/
43-
internal fun deleteDocument(): Boolean {
33+
internal fun deleteDocument(context: Context, uri: Uri): Boolean {
4434
return try {
45-
DocumentsContract.deleteDocument(contentResolver, uri)
35+
DocumentsContract.deleteDocument(context.contentResolver, uri)
4636
} catch (exception: Exception) {
47-
logError("Exception while deleting document", exception)
37+
ErrorLogger.logError("Exception while deleting document", exception)
4838
false
4939
}
5040
}
@@ -54,12 +44,12 @@ internal class ResolverCompat(
5444
*
5545
* Returns True if the rename was successful, False otherwise.
5646
*/
57-
internal fun renameTo(name: String): Boolean {
47+
internal fun renameTo(context: Context, uri: Uri, name: String): Uri? {
5848
return try {
59-
(DocumentsContract.renameDocument(contentResolver, uri, name) != null)
49+
return DocumentsContract.renameDocument(context.contentResolver, uri, name)
6050
} catch (exception: Exception) {
61-
logError("Exception while renaming document", exception)
62-
false
51+
ErrorLogger.logError("Exception while renaming document", exception)
52+
null
6353
}
6454
}
6555

@@ -71,22 +61,15 @@ internal class ResolverCompat(
7161
*
7262
* @return A Uri if file was created successfully, **null** if any exception was caught.
7363
*/
74-
internal fun createFile(mimeType: String, name: String): Uri? {
64+
internal fun createFile(context: Context, uri: Uri, mimeType: String, name: String): Uri? {
7565
return try {
76-
DocumentsContract.createDocument(contentResolver, uri, mimeType, name)
66+
DocumentsContract.createDocument(context.contentResolver, uri, mimeType, name)
7767
} catch (exception: Exception) {
78-
logError("Exception while creating a document", exception)
68+
ErrorLogger.logError("Exception while creating a document", exception)
7969
null
8070
}
8171
}
8272

83-
/**
84-
* Queries the ContentResolver & builds a list of [DocumentFileCompat] with all the required fields.
85-
*/
86-
internal fun queryAndMakeDocumentList(): List<DocumentFileCompat> {
87-
return runTreeQuery()
88-
}
89-
9073
/**
9174
* Returns the children count without creating [DocumentFileCompat] objects.
9275
*
@@ -96,109 +79,63 @@ internal class ResolverCompat(
9679
* - Min: 0.275, Max: 0.613 (listFiles().size)
9780
* - Avg: 0.444, Diff: 0.338, % Change: 55.14
9881
*/
99-
internal fun count(): Int {
100-
val childrenUri = buildChildDocumentsUriUsingTree()
101-
val projection = arrayOf(DocumentsContract.Document.COLUMN_ICON)
102-
getCursor(childrenUri, projection)?.use { cursor -> return cursor.count }
82+
internal fun count(context: Context, uri: Uri): Int {
83+
val childrenUri = createChildrenUri(uri)
84+
getCursor(context, childrenUri, iconProjection)?.use { cursor -> return cursor.count }
10385
return 0
10486
}
10587

106-
// Returns True if the Uri is a Tree Uri, False otherwise.
107-
private fun isTreeUri(): Boolean {
108-
val paths = uri.pathSegments
109-
return paths.size >= 2 && "tree" == paths[0]
110-
}
111-
112-
// Create a child document uri from the tree uri.
113-
private fun buildChildDocumentsUriUsingTree(): Uri {
114-
return DocumentsContract.buildChildDocumentsUriUsingTree(
115-
getTreeUri(), DocumentsContract.getDocumentId(getTreeUri())
116-
)
117-
}
118-
119-
// Build relevant Tree Uri.
120-
private fun getTreeUri(): Uri {
121-
val isDocument = DocumentsContract.isDocumentUri(context, uri)
122-
return DocumentsContract.buildDocumentUriUsingTree(
123-
uri, if (isDocument) DocumentsContract.getDocumentId(uri)
124-
else DocumentsContract.getTreeDocumentId(uri)
125-
)
126-
}
127-
128-
/**
129-
* Builds a complete [DocumentFileCompat] object
130-
* when a first call to building a [TreeDocumentFileCompat] or a [SingleDocumentFileCompat] is made.
131-
*
132-
* @param isTree Returns a [TreeDocumentFileCompat] if True, [SingleDocumentFileCompat] otherwise.
133-
*
134-
* @throws UnsupportedOperationException If `isTree` is True but the Uri is not Tree based.
135-
*/
136-
internal fun getInitialFileCompat(isTree: Boolean): DocumentFileCompat? {
137-
if (isTree && !isTreeUri())
138-
throw UnsupportedOperationException("Document Uri is not a Tree uri.")
139-
return runInitialQuery(isTree)
140-
}
141-
14288
/**
14389
* Returns True if the Document Folder / File exists, False otherwise.
14490
*/
145-
internal fun exists(): Boolean {
146-
getCursor(uri, documentIdProjection)?.use { cursor -> return (cursor.count > 0) }
91+
internal fun exists(context: Context, uri: Uri): Boolean {
92+
getCursor(context, uri, idProjection)?.use { cursor -> return (cursor.count > 0) }
14793
return false
14894
}
14995

150-
// Get a Cursor to query the given Uri against provided projection
151-
private fun getCursor(uri: Uri, projection: Array<String>): Cursor? {
152-
return contentResolver.query(uri, projection, null, null, null)
153-
}
154-
15596
/**
156-
* Runs query to build initial [TreeDocumentFileCompat] or a [SingleDocumentFileCompat].
157-
*
158-
* @param isTree Returns a [TreeDocumentFileCompat] if True, [SingleDocumentFileCompat] otherwise.
97+
* Queries the ContentResolver & builds a list of [DocumentFileCompat] with all the required fields.
15998
*/
160-
private fun runInitialQuery(isTree: Boolean): DocumentFileCompat? {
161-
val uriToQuery = if (!isTree) uri
162-
else DocumentsContract.buildDocumentUriUsingTree(
163-
getTreeUri(), DocumentsContract.getDocumentId(getTreeUri())
164-
)
165-
166-
var document: DocumentFileCompat? = null
99+
internal fun listFiles(context: Context, uri: Uri): List<DocumentFileCompat> {
100+
val childrenUri = createChildrenUri(uri)
101+
// empty list
102+
val listOfDocuments = arrayListOf<DocumentFileCompat>()
167103

168-
getCursor(uriToQuery, fullProjection)?.use { cursor ->
169-
if (cursor.moveToFirst()) {
104+
getCursor(context, childrenUri, fullProjection)?.use { cursor ->
105+
while (cursor.moveToNext()) {
170106
val documentId: String = cursor.getString(0)
171-
val documentUri: Uri = if (!isTree) uri
172-
else DocumentsContract.buildDocumentUriUsingTree(getTreeUri(), documentId)
173-
174-
// Same logic but moved to separate classes for easy readability & understanding.
175-
document = if (!isTree) SingleDocumentFileCompat.make(context, cursor, documentUri)
176-
else TreeDocumentFileCompat.make(context, cursor, documentUri)
107+
val documentUri = DocumentsContract.buildDocumentUriUsingTree(uri, documentId)
108+
109+
val documentName: String = cursor.getString(1)
110+
val documentSize: Long = cursor.getLong(2)
111+
val documentLastModified: Long = cursor.getLong(3)
112+
val documentMimeType: String = cursor.getString(4)
113+
val documentFlags: Int = cursor.getLong(5).toInt()
114+
115+
TreeDocumentFileCompat(
116+
context, documentUri, documentName,
117+
documentSize, documentLastModified,
118+
documentMimeType, documentFlags
119+
).also { childFile -> listOfDocuments.add(childFile) }
177120
}
178121
}
179122

180-
return document
123+
return listOfDocuments
181124
}
182125

183126
/**
184-
* Run query on the passed uri with full projections.
185-
*
186-
* @return A list of [DocumentFileCompat] with all fields.
127+
* Get [Cursor] from [ContentResolver.query] with given [projection] on a given [uri].
187128
*/
188-
private fun runTreeQuery(): List<DocumentFileCompat> {
189-
val childrenUri = buildChildDocumentsUriUsingTree()
190-
191-
// empty list
192-
val listOfDocuments = arrayListOf<DocumentFileCompat>()
193-
194-
getCursor(childrenUri, fullProjection)?.use { cursor ->
195-
while (cursor.moveToNext()) {
196-
val docId: String = cursor.getString(0)
197-
val docUri = DocumentsContract.buildDocumentUriUsingTree(getTreeUri(), docId)
198-
listOfDocuments.add(TreeDocumentFileCompat.make(context, cursor, docUri))
199-
}
200-
}
129+
fun getCursor(context: Context, uri: Uri, projection: Array<String>): Cursor? {
130+
return context.contentResolver.query(
131+
uri, projection, null, null, null
132+
)
133+
}
201134

202-
return listOfDocuments
135+
// Make children uri for query.
136+
private fun createChildrenUri(uri: Uri): Uri {
137+
return DocumentsContract.buildChildDocumentsUriUsingTree(
138+
uri, DocumentsContract.getDocumentId(uri)
139+
)
203140
}
204141
}

0 commit comments

Comments
 (0)