• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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