Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 8eba9560 authored by Bo Majewski's avatar Bo Majewski
Browse files

[DocsUI, Search]: Fix work profile crash.

When switching to work profile while using DocumentsUI as a file picker
it is possible for the top of the folder stack to return null. This CL
adds protection against this case. If the top of the stack is null, we
resort to listing the root. A unit test that checks the new code works
as intended is added to FolderLoaderTest.

Bug: 417872920
Test: DocumentsUIUnitTests
Flag: com.android.documentsui.flags.use_search_v2_read_only
Change-Id: I034d2a0c876c232b431d3b5d6217c59b550655d1
parent 42b12780
Loading
Loading
Loading
Loading
+21 −8
Original line number Diff line number Diff line
@@ -40,9 +40,14 @@ import com.android.documentsui.sorting.SortModel
 *  - A content lock for which a locking content observer is built
 *  - A list of user IDs on behalf of which the search is conducted
 *  - The root info of the listed directory
 *  - The document info of the listed directory
 *  - The document info of the listed directory, may be null.
 *  - a lookup from file extension to file type
 *  - The model capable of sorting results
 *
 *  Typically, here we expect mListedDir to be not null, as this is the directory we are listing.
 *  However, when profile is switched while using the app as a file picker, it is possible that
 *  the listing directory is null. If this is the case, we assume that we should be listing the
 *  location specified by the mRoot.
 */
class FolderLoader(
    context: Context,
@@ -50,7 +55,7 @@ class FolderLoader(
    mimeTypeLookup: Lookup<String, String>,
    contentLock: ContentLock,
    private val mRoot: RootInfo,
    private val mListedDir: DocumentInfo,
    private val mListedDir: DocumentInfo?,
    private val mOptions: QueryOptions,
    private val mSortModel: SortModel,
) : BaseFileLoader(context, userIdList, mimeTypeLookup) {
@@ -61,14 +66,22 @@ class FolderLoader(
    // Creates a directory result object corresponding to the current parameters of the loader.
    override fun loadInBackground(): DirectoryResult? {
        val rejectBeforeTimestamp = mOptions.getRejectBeforeTimestamp()
        val folderChildrenUri = DocumentsContract.buildChildDocumentsUri(
        val folderChildrenUri =
            if (mListedDir == null) {
                DocumentsContract.buildChildDocumentsUri(
                    mRoot.authority,
                    mRoot.documentId
                )
            } else {
                DocumentsContract.buildChildDocumentsUri(
                    mListedDir.authority,
                    mListedDir.documentId
                )
            }
        val result = DirectoryResult()
        // If we are listing an archive, in the current approach, we cache the client as part of
        // DirectoryResult. This way, when the loader is closed, we can close the archive client.
        if (mListedDir.isInArchive) {
        if (mListedDir != null && mListedDir.isInArchive) {
            result.setClient(openArchive(folderChildrenUri))
        }
        var cursor =
@@ -88,7 +101,7 @@ class FolderLoader(
        // TODO(b:380945065): Add filtering by category, such as images, audio, video.
        val sortedCursor = mSortModel.sortCursor(filteredCursor, mimeTypeLookup)

        result.doc = mListedDir
        result.doc = mListedDir ?: DocumentInfo()
        result.cursor = sortedCursor
        return result
    }
+42 −11
Original line number Diff line number Diff line
@@ -25,7 +25,8 @@ import com.android.documentsui.rules.CheckAndForceMaterial3Flag
import com.android.documentsui.testing.TestFileTypeLookup
import com.android.documentsui.testing.TestProvidersAccess
import java.time.Duration
import junit.framework.Assert.assertEquals
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -57,23 +58,31 @@ class FolderLoaderTest(private val testParams: LoaderTestParams) : BaseLoaderTes
    @get:Rule
    val checkFlags = CheckAndForceMaterial3Flag()

    @Test
    @RequiresFlagsEnabled(FLAG_USE_SEARCH_V2_READ_ONLY)
    fun testLoadInBackground() {
        val mockProvider = mEnv.mockProviders[TestProvidersAccess.DOWNLOADS.authority]
        val docs = createDocuments(TOTAL_FILE_COUNT)
        mockProvider!!.setNextChildDocumentsReturns(*docs)
        val userIds = listOf(TestProvidersAccess.DOWNLOADS.userId)
        val queryOptions =
    val contentLock = ContentLock()
    lateinit var queryOptions: QueryOptions

    @Before
    override fun setUp() {
        super.setUp()
        queryOptions =
            QueryOptions(
                TOTAL_FILE_COUNT,
                testParams.lastModifiedDelta,
                null,
                true,
                arrayOf<String>("*/*"),
                arrayOf("*/*"),
                testParams.otherArgs,
            )
        val contentLock = ContentLock()
        // Set up sample files using Downloads provider.
        val mockProvider = mEnv.mockProviders[TestProvidersAccess.DOWNLOADS.authority]
        val docs = createDocuments(TOTAL_FILE_COUNT)
        mockProvider!!.setNextChildDocumentsReturns(*docs)
    }

    @Test
    @RequiresFlagsEnabled(FLAG_USE_SEARCH_V2_READ_ONLY)
    fun testLoadInBackground() {
        val userIds = listOf(TestProvidersAccess.DOWNLOADS.userId)
        // TODO(majewski): Is there a better way to create Downloads root folder DocumentInfo?
        val rootFolderInfo = DocumentInfo()
        rootFolderInfo.authority = TestProvidersAccess.DOWNLOADS.authority
@@ -93,4 +102,26 @@ class FolderLoaderTest(private val testParams: LoaderTestParams) : BaseLoaderTes
        val directoryResult = loader.loadInBackground()
        assertEquals(testParams.expectedCount, getFileCount(directoryResult))
    }

    @Test
    @RequiresFlagsEnabled(FLAG_USE_SEARCH_V2_READ_ONLY)
    fun testListRootIfNullFolder() {
        val mockProvider = mEnv.mockProviders[TestProvidersAccess.DOWNLOADS.authority]
        val docs = createDocuments(TOTAL_FILE_COUNT)
        mockProvider!!.setNextChildDocumentsReturns(*docs)

        val loader =
            FolderLoader(
                mActivity,
                listOf(TestProvidersAccess.DOWNLOADS.userId),
                TestFileTypeLookup(),
                contentLock,
                TestProvidersAccess.DOWNLOADS,
                null,
                queryOptions,
                mEnv.state.sortModel
            )
        val directoryResult = loader.loadInBackground()
        assertEquals(testParams.expectedCount, getFileCount(directoryResult))
    }
}