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

Commit d8e7455c authored by Zemiao Zhu's avatar Zemiao Zhu
Browse files

Refactor FilteringCursorWrapper to make it easier

to extend.

Bug: 162368496
Test: atest DocumentsUIGoogleTests
Change-Id: Ie971d04f4591432df6342bb7dbeef7bfb55376b8
(cherry picked from commit 43199239)
parent ed7ac98a
Loading
Loading
Loading
Loading
+5 −7
Original line number Diff line number Diff line
@@ -56,7 +56,6 @@ import java.util.concurrent.Executor;
public class DirectoryLoader extends AsyncTaskLoader<DirectoryResult> {

    private static final String TAG = "DirectoryLoader";

    private static final String[] SEARCH_REJECT_MIMES = new String[] { Document.MIME_TYPE_DIR };
    private static final String[] PHOTO_PICKING_ACCEPT_MIMES = new String[]
            {Document.MIME_TYPE_DIR, MimeTypes.IMAGE_MIME};
@@ -178,17 +177,16 @@ public class DirectoryLoader extends AsyncTaskLoader<DirectoryResult> {
            }
            cursor.registerContentObserver(mObserver);

            // Filter hidden files.
            cursor = new FilteringCursorWrapper(cursor, mState.showHiddenFiles);

            FilteringCursorWrapper filteringCursor = new FilteringCursorWrapper(cursor);
            filteringCursor.filterHiddenFiles(mState.showHiddenFiles);
            if (mSearchMode && !mFeatures.isFoldersInSearchResultsEnabled()) {
                // There is no findDocumentPath API. Enable filtering on folders in search mode.
                cursor = new FilteringCursorWrapper(cursor, null, SEARCH_REJECT_MIMES);
                filteringCursor.filterMimes(/* acceptMimes= */ null, SEARCH_REJECT_MIMES);
            }

            if (mPhotoPicking) {
                cursor = new FilteringCursorWrapper(cursor, PHOTO_PICKING_ACCEPT_MIMES, null);
                filteringCursor.filterMimes(PHOTO_PICKING_ACCEPT_MIMES, /* rejectMimes= */ null);
            }
            cursor = filteringCursor;

            // TODO: When API tweaks have landed, use ContentResolver.EXTRA_HONORED_ARGS
            // instead of checking directly for ContentResolver.QUERY_ARG_SORT_COLUMNS (won't work)
+7 −6
Original line number Diff line number Diff line
@@ -181,17 +181,18 @@ public abstract class MultiRootDocumentsLoader extends AsyncTaskLoader<Directory
                            continue;
                        }

                        // Filter hidden files.
                        cursor = new FilteringCursorWrapper(cursor, mState.showHiddenFiles);

                        final FilteringCursorWrapper filtered = new FilteringCursorWrapper(
                                cursor, mState.acceptMimes, getRejectMimes(), rejectBefore) {
                        final FilteringCursorWrapper filteredCursor =
                                new FilteringCursorWrapper(cursor) {
                            @Override
                            public void close() {
                                // Ignored, since we manage cursor lifecycle internally
                            }
                        };
                        cursors.add(filtered);
                        filteredCursor.filterHiddenFiles(mState.showHiddenFiles);
                        filteredCursor.filterMimes(mState.acceptMimes, getRejectMimes());
                        filteredCursor.filterLastModified(rejectBefore);

                        cursors.add(filteredCursor);
                    }

                } catch (InterruptedException e) {
+64 −47
Original line number Diff line number Diff line
@@ -29,68 +29,62 @@ import android.provider.DocumentsContract.Document;
import android.util.Log;

/**
 * Cursor wrapper that filters MIME types not matching given list.
 * Cursor wrapper that filters cursor results by given conditions.
 */
public class FilteringCursorWrapper extends AbstractCursor {
    private final Cursor mCursor;

    private final int[] mPosition;
    private int[] mPositions;
    private int mCount;

    public FilteringCursorWrapper(Cursor cursor, String[] acceptMimes) {
        this(cursor, acceptMimes, null, Long.MIN_VALUE);
    public FilteringCursorWrapper(Cursor cursor) {
        mCursor = cursor;
        mCount = cursor.getCount();
        mPositions = new int[mCount];
        for (int i = 0; i < mCount; i++) {
            mPositions[i] = i;
        }

    public FilteringCursorWrapper(Cursor cursor, String[] acceptMimes, String[] rejectMimes) {
        this(cursor, acceptMimes, rejectMimes, Long.MIN_VALUE);
    }

    public FilteringCursorWrapper(
            Cursor cursor, String[] acceptMimes, String[] rejectMimes, long rejectBefore) {
        mCursor = cursor;

        final int count = cursor.getCount();
        mPosition = new int[count];

        cursor.moveToPosition(-1);
        while (cursor.moveToNext() && mCount < count) {
    /**
     * Filters cursor according to mimes. If both lists are empty, all mimes will be rejected.
     *
     * @param acceptMimes allowed list of mimes
     * @param rejectMimes blocked list of mimes
     */
    public void filterMimes(String[] acceptMimes, String[] rejectMimes) {
        filterByCondition((cursor) -> {
            final String mimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE);
            final long lastModified = getCursorLong(cursor, Document.COLUMN_LAST_MODIFIED);
            if (rejectMimes != null && MimeTypes.mimeMatches(rejectMimes, mimeType)) {
                continue;
            }
            if (lastModified < rejectBefore) {
                continue;
            }
            if (MimeTypes.mimeMatches(acceptMimes, mimeType)) {
                mPosition[mCount++] = cursor.getPosition();
                return false;
            }
            return MimeTypes.mimeMatches(acceptMimes, mimeType);
        });
    }

        if (DEBUG && mCount != cursor.getCount()) {
            Log.d(TAG, "Before filtering " + cursor.getCount() + ", after " + mCount);
        }
    /** Filters cursor according to last modified time, and reject earlier than given timestamp. */
    public void filterLastModified(long rejectBeforeTimestamp) {
        filterByCondition((cursor) -> {
            final long lastModified = getCursorLong(cursor, Document.COLUMN_LAST_MODIFIED);
            return lastModified >= rejectBeforeTimestamp;
        });
    }

    public FilteringCursorWrapper(Cursor cursor, boolean showHiddenFiles) {
        mCursor = cursor;

        final int count = cursor.getCount();
        mPosition = new int[count];

        cursor.moveToPosition(-1);
        while (cursor.moveToNext() && mCount < count) {
            final String documentId = getCursorString(cursor, Document.COLUMN_DOCUMENT_ID);
            if (!showHiddenFiles && documentId != null
                    && (documentId.startsWith(".") || documentId.contains("/."))) {
                continue;
            }
            mPosition[mCount++] = cursor.getPosition();
    /** Filter hidden files based on preference. */
    public void filterHiddenFiles(boolean showHiddenFiles) {
        if (showHiddenFiles) {
            return;
        }

        if (DEBUG && mCount != cursor.getCount()) {
            Log.d(TAG, "Before filtering " + cursor.getCount() + ", after " + mCount);
        }
        filterByCondition((cursor) -> {
            // Judge by name and documentId separately because for some providers
            // e.g. DownloadProvider, documentId may not contain file name.
            final String name = getCursorString(cursor, Document.COLUMN_DISPLAY_NAME);
            final String documentId = getCursorString(cursor, Document.COLUMN_DOCUMENT_ID);
            boolean documentIdHidden = documentId != null && documentId.contains("/.");
            boolean fileNameHidden = name != null && name.startsWith(".");
            return !(documentIdHidden || fileNameHidden);
        });
    }

    @Override
@@ -106,7 +100,7 @@ public class FilteringCursorWrapper extends AbstractCursor {

    @Override
    public boolean onMove(int oldPosition, int newPosition) {
        return mCursor.moveToPosition(mPosition[newPosition]);
        return mCursor.moveToPosition(mPositions[newPosition]);
    }

    @Override
@@ -168,4 +162,27 @@ public class FilteringCursorWrapper extends AbstractCursor {
    public void unregisterContentObserver(ContentObserver observer) {
        mCursor.unregisterContentObserver(observer);
    }

    private interface FilteringCondition {
        boolean accept(Cursor cursor);
    }

    private void filterByCondition(FilteringCondition condition) {
        final int oldCount = this.getCount();
        int[] newPositions = new int[oldCount];
        int newCount = 0;

        this.moveToPosition(-1);
        while (this.moveToNext() && newCount < oldCount) {
            if (condition.accept(mCursor)) {
                newPositions[newCount++] = mPositions[this.getPosition()];
            }
        }

        if (DEBUG && newCount != this.getCount()) {
            Log.d(TAG, "Before filtering " + oldCount + ", after " + newCount);
        }
        mCount = newCount;
        mPositions = newPositions;
    }
}