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

Commit d78ec662 authored by Bo Majewski's avatar Bo Majewski
Browse files

[DocsUI, Search]: Enable listing of archives.

Adds a code that detects if the listed document is an archive. If so, it
calls local archive provider to create a loader that allows us to access
the archive content. Saves the ContentProviderClient in document result,
so that it can be closed when document result is closed.

Bug: 385806471
Test: m DocumentsUIGoogle
Flag: com.android.documentsui.flags.use_search_v2_read_only
Change-Id: Ie35ab4bf9a5a04a4223caf3b28a5acf8151932e6
parent 8a87c516
Loading
Loading
Loading
Loading
+10 −0
Original line number Diff line number Diff line
@@ -55,6 +55,16 @@ public class DirectoryResult implements AutoCloseable {
        setCursor(null);
    }

    /**
     * Sets the client on this result. Generally, this should be considered an ownership
     * transfer. The caller should set its own reference to null, to prevent further interactions
     * with the client.
     * @param client The client whose ownership is transferred to this result.
     */
    public void setClient(ContentProviderClient client) {
        this.client = client;
    }

    public Cursor getCursor() {
        return mCursor;
    }
+37 −3
Original line number Diff line number Diff line
@@ -15,11 +15,16 @@
 */
package com.android.documentsui.loaders

import android.content.ContentProviderClient
import android.content.Context
import android.net.Uri
import android.os.RemoteException
import android.provider.DocumentsContract
import android.util.Log
import com.android.documentsui.ContentLock
import com.android.documentsui.DirectoryResult
import com.android.documentsui.LockingContentObserver
import com.android.documentsui.archives.ArchivesProvider
import com.android.documentsui.base.DocumentInfo
import com.android.documentsui.base.FilteringCursorWrapper
import com.android.documentsui.base.Lookup
@@ -60,9 +65,18 @@ class FolderLoader(
            mListedDir.authority,
            mListedDir.documentId
        )
        val cursor =
        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) {
            result.setClient(openArchive(folderChildrenUri))
        }
        var cursor =
            queryLocation(mRoot.rootId, folderChildrenUri, mOptions.otherQueryArgs, ALL_RESULTS)
                ?: emptyCursor()
        if (cursor == null) {
            cursor = emptyCursor()
            result.setClient(null)
        }
        cursor.registerContentObserver(mObserver)

        val filteredCursor = FilteringCursorWrapper(cursor)
@@ -74,9 +88,29 @@ class FolderLoader(
        // TODO(b:380945065): Add filtering by category, such as images, audio, video.
        val sortedCursor = mSortModel.sortCursor(filteredCursor, mMimeTypeLookup)

        val result = DirectoryResult()
        result.doc = mListedDir
        result.cursor = sortedCursor
        return result
    }

    /**
     * Helper function that attempts to open an archive and return a long lasting content provider
     * client to the soon to be scanned archive. This must be done before attempting to acquire the
     * cursor, as we depend on archive content to be read (see acquireArchive method).
     */
    private fun openArchive(folderChildrenUri: Uri): ContentProviderClient? {
        // If we are opening an archive, we need, in the current approach, to have a long lived
        // ContentProviderClient for it. This is so that the archive can be closed, once the
        // loader results are closed.
        var client: ContentProviderClient? = null
        try {
            val resolver = mRoot.userId.getContentResolver(context)
            client = resolver.acquireUnstableContentProviderClient(folderChildrenUri.authority!!)
            ArchivesProvider.acquireArchive(client, folderChildrenUri)
        } catch (e: RemoteException) {
            Log.e(TAG, "Failed to acquire archive client", e)
            client?.close()
        }
        return client
    }
}
+47 −5
Original line number Diff line number Diff line
@@ -16,17 +16,35 @@

package com.android.documentsui;

import static com.android.documentsui.flags.Flags.FLAG_USE_SEARCH_V2_READ_ONLY;

import android.platform.test.annotations.RequiresFlagsDisabled;
import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;

import androidx.test.filters.LargeTest;

import com.android.documentsui.files.FilesActivity;

import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;

@LargeTest
public class ArchiveUiTest extends ActivityTest<FilesActivity> {
    public ArchiveUiTest() {
        super(FilesActivity.class);
public class ArchiveUiTest extends ActivityTestJunit4<FilesActivity> {

    @Rule
    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();


    @Before
    public void setUp() throws Exception {
        super.setUp();
        initTestFiles();
    }

    public void testArchive_valid() throws Exception {
    private void archiveValid() throws Exception {
        bots.roots.openRoot("ResourcesProvider");
        bots.directory.openDocument("archive.zip");
        bots.directory.waitForDocument("file1.txt");
@@ -35,11 +53,35 @@ public class ArchiveUiTest extends ActivityTest<FilesActivity> {
        bots.directory.waitForDocument("cherries.txt");
    }

    public void testArchive_invalid() throws Exception {
    @Test
    @RequiresFlagsDisabled({FLAG_USE_SEARCH_V2_READ_ONLY})
    public void testArchive_valid() throws Exception {
        archiveValid();
    }

    @Test
    @RequiresFlagsEnabled({FLAG_USE_SEARCH_V2_READ_ONLY})
    public void testArchive_valid_searchV2() throws Exception {
        archiveValid();
    }

    private void archiveInvalid() throws Exception {
        bots.roots.openRoot("ResourcesProvider");
        bots.directory.openDocument("broken.zip");

        final String msg = String.valueOf(context.getString(R.string.empty));
        bots.directory.assertPlaceholderMessageText(msg);
    }

    @Test
    @RequiresFlagsDisabled({FLAG_USE_SEARCH_V2_READ_ONLY})
    public void testArchive_invalid() throws Exception {
        archiveInvalid();
    }

    @Test
    @RequiresFlagsEnabled({FLAG_USE_SEARCH_V2_READ_ONLY})
    public void testArchive_invalid_searchV2() throws Exception {
        archiveInvalid();
    }
}