1 /*
<lambda>null2 * Copyright (C) 2024 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 package com.android.documentsui.loaders
17
18 import android.content.Context
19 import android.database.Cursor
20 import android.database.MatrixCursor
21 import android.database.MergeCursor
22 import android.net.Uri
23 import android.os.Bundle
24 import android.os.CancellationSignal
25 import android.os.RemoteException
26 import android.provider.DocumentsContract.Document
27 import android.util.Log
28 import androidx.loader.content.AsyncTaskLoader
29 import com.android.documentsui.DirectoryResult
30 import com.android.documentsui.base.Lookup
31 import com.android.documentsui.base.UserId
32 import com.android.documentsui.roots.RootCursorWrapper
33
34 const val TAG = "SearchV2"
35
36 val FILE_ENTRY_COLUMNS = arrayOf(
37 Document.COLUMN_DOCUMENT_ID,
38 Document.COLUMN_MIME_TYPE,
39 Document.COLUMN_DISPLAY_NAME,
40 Document.COLUMN_LAST_MODIFIED,
41 Document.COLUMN_FLAGS,
42 Document.COLUMN_SUMMARY,
43 Document.COLUMN_SIZE,
44 Document.COLUMN_ICON,
45 )
46
47 fun emptyCursor(): Cursor {
48 return MatrixCursor(FILE_ENTRY_COLUMNS)
49 }
50
51 /**
52 * Helper function that returns a single, non-null cursor constructed from the given list of
53 * cursors.
54 */
toSingleCursornull55 fun toSingleCursor(cursorList: List<Cursor>): Cursor {
56 if (cursorList.isEmpty()) {
57 return emptyCursor()
58 }
59 if (cursorList.size == 1) {
60 return cursorList[0]
61 }
62 return MergeCursor(cursorList.toTypedArray())
63 }
64
65 /**
66 * The base class for search and directory loaders. This class implements common functionality
67 * shared by these loaders. The extending classes should implement loadInBackground, which
68 * should call the queryLocation method.
69 */
70 abstract class BaseFileLoader(
71 context: Context,
72 private val mUserIdList: List<UserId>,
73 protected val mMimeTypeLookup: Lookup<String, String>,
74 ) : AsyncTaskLoader<DirectoryResult>(context) {
75
76 private var mSignal: CancellationSignal? = null
77 private var mResult: DirectoryResult? = null
78
cancelLoadInBackgroundnull79 override fun cancelLoadInBackground() {
80 Log.d(TAG, "${this::class.simpleName}.cancelLoadInBackground")
81 super.cancelLoadInBackground()
82
83 synchronized(this) {
84 mSignal?.cancel()
85 }
86 }
87
deliverResultnull88 override fun deliverResult(result: DirectoryResult?) {
89 Log.d(TAG, "${this::class.simpleName}.deliverResult")
90 if (isReset) {
91 closeResult(result)
92 return
93 }
94 val oldResult: DirectoryResult? = mResult
95 mResult = result
96
97 if (isStarted) {
98 super.deliverResult(result)
99 }
100
101 if (oldResult != null && oldResult !== result) {
102 closeResult(oldResult)
103 }
104 }
105
onStartLoadingnull106 override fun onStartLoading() {
107 Log.d(TAG, "${this::class.simpleName}.onStartLoading")
108 val isCursorStale: Boolean = checkIfCursorStale(mResult)
109 if (mResult != null && !isCursorStale) {
110 deliverResult(mResult)
111 }
112 if (takeContentChanged() || mResult == null || isCursorStale) {
113 forceLoad()
114 }
115 }
116
onStopLoadingnull117 override fun onStopLoading() {
118 Log.d(TAG, "${this::class.simpleName}.onStopLoading")
119 cancelLoad()
120 }
121
onCancelednull122 override fun onCanceled(result: DirectoryResult?) {
123 Log.d(TAG, "${this::class.simpleName}.onCanceled")
124 closeResult(result)
125 }
126
onResetnull127 override fun onReset() {
128 Log.d(TAG, "${this::class.simpleName}.onReset")
129 super.onReset()
130
131 // Ensure the loader is stopped
132 onStopLoading()
133
134 closeResult(mResult)
135 mResult = null
136 }
137
138 /**
139 * Quietly closes the result cursor, if results are still available.
140 */
closeResultnull141 fun closeResult(result: DirectoryResult?) {
142 try {
143 result?.close()
144 } catch (e: Exception) {
145 Log.d(TAG, "Failed to close result", e)
146 }
147 }
148
checkIfCursorStalenull149 private fun checkIfCursorStale(result: DirectoryResult?): Boolean {
150 if (result == null) {
151 return true
152 }
153 val cursor = result.cursor ?: return true
154 if (cursor.isClosed) {
155 return true
156 }
157 Log.d(TAG, "Long check of cursor staleness")
158 val count = cursor.count
159 if (!cursor.moveToPosition(-1)) {
160 return true
161 }
162 for (i in 1..count) {
163 if (!cursor.moveToNext()) {
164 return true
165 }
166 }
167 return false
168 }
169
170 /**
171 * A function that, for the specified location rooted in the root with the given rootId
172 * attempts to obtain a non-null cursor from the content provider client obtained for the
173 * given locationUri. It returns the first non-null cursor, if one can be found, or null,
174 * if it fails to query the given location for all known users.
175 */
queryLocationnull176 fun queryLocation(
177 rootId: String,
178 locationUri: Uri,
179 queryArgs: Bundle?,
180 maxResults: Int,
181 ): Cursor? {
182 val authority = locationUri.authority ?: return null
183 for (userId in mUserIdList) {
184 Log.d(TAG, "BaseFileLoader.queryLocation for $userId at $locationUri")
185 val resolver = userId.getContentResolver(context)
186 try {
187 resolver.acquireUnstableContentProviderClient(
188 authority
189 ).use { client ->
190 if (client == null) {
191 return null
192 }
193 try {
194 val cursor =
195 client.query(locationUri, null, queryArgs, mSignal) ?: return null
196 return RootCursorWrapper(userId, authority, rootId, cursor, maxResults)
197 } catch (e: RemoteException) {
198 Log.d(TAG, "Failed to get cursor for $locationUri", e)
199 }
200 }
201 } catch (e: Exception) {
202 Log.d(TAG, "Failed to get a content provider client for $locationUri", e)
203 }
204 }
205
206 return null
207 }
208 }
209