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

Commit d182bb64 authored by Jeff Sharkey's avatar Jeff Sharkey
Browse files

Remember mode and sort on per-directory basis.

Persist the last user-selected list/grid mode and sort order for
each directory.  Remembered user choice always overrides provider
hinting.

Filter out recent documents that don't match requested MIME type, and
show recents in grid mode when picking images.  Hide mode and sort
order in recents.

Add hinting flag for backend to indicate a directory would like to be
sorted by last modified.  Include explicit root in DocumentStack and
clearly mark derived fields.

Bug: 10392047, 10608506
Change-Id: I2dd3a0e4112852ebf87e7dbb08b3781c86587dcf
parent 0c58bd97
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -20796,6 +20796,7 @@ package android.provider {
    field public static final java.lang.String COLUMN_SIZE = "_size";
    field public static final java.lang.String COLUMN_SUMMARY = "summary";
    field public static final int FLAG_DIR_PREFERS_GRID = 32; // 0x20
    field public static final int FLAG_DIR_PREFERS_LAST_MODIFIED = 64; // 0x40
    field public static final int FLAG_DIR_SUPPORTS_CREATE = 8; // 0x8
    field public static final int FLAG_DIR_SUPPORTS_SEARCH = 16; // 0x10
    field public static final int FLAG_SUPPORTS_DELETE = 4; // 0x4
+9 −3
Original line number Diff line number Diff line
@@ -251,6 +251,15 @@ public final class DocumentsContract {
         * @see #COLUMN_FLAGS
         */
        public static final int FLAG_DIR_PREFERS_GRID = 1 << 5;

        /**
         * Flag indicating that a directory prefers its contents be sorted by
         * {@link #COLUMN_LAST_MODIFIED}. Only valid when
         * {@link #COLUMN_MIME_TYPE} is {@link #MIME_TYPE_DIR}.
         *
         * @see #COLUMN_FLAGS
         */
        public static final int FLAG_DIR_PREFERS_LAST_MODIFIED = 1 << 6;
    }

    /**
@@ -292,9 +301,6 @@ public final class DocumentsContract {
         * @see #FLAG_LOCAL_ONLY
         * @see #FLAG_SUPPORTS_CREATE
         * @see #FLAG_ADVANCED
         * @see #FLAG_PROVIDES_AUDIO
         * @see #FLAG_PROVIDES_IMAGES
         * @see #FLAG_PROVIDES_VIDEO
         */
        public static final String COLUMN_FLAGS = "flags";

+1 −1
Original line number Diff line number Diff line
@@ -70,7 +70,7 @@ public class CreateDirectoryFragment extends DialogFragment {

                try {
                    final Uri childUri = DocumentsContract.createDocument(
                            resolver, cwd.uri, Document.MIME_TYPE_DIR, displayName);
                            resolver, cwd.derivedUri, Document.MIME_TYPE_DIR, displayName);

                    // Navigate into newly created child
                    final DocumentInfo childDoc = DocumentInfo.fromUri(resolver, childUri);
+64 −41
Original line number Diff line number Diff line
@@ -20,6 +20,8 @@ import static com.android.documentsui.DocumentsActivity.TAG;
import static com.android.documentsui.DocumentsActivity.State.ACTION_MANAGE;
import static com.android.documentsui.DocumentsActivity.State.MODE_GRID;
import static com.android.documentsui.DocumentsActivity.State.MODE_LIST;
import static com.android.documentsui.DocumentsActivity.State.MODE_UNKNOWN;
import static com.android.documentsui.DocumentsActivity.State.SORT_ORDER_UNKNOWN;
import static com.android.documentsui.model.DocumentInfo.getCursorInt;
import static com.android.documentsui.model.DocumentInfo.getCursorLong;
import static com.android.documentsui.model.DocumentInfo.getCursorString;
@@ -91,43 +93,42 @@ public class DirectoryFragment extends Fragment {

    private int mType = TYPE_NORMAL;

    private int mLastMode = MODE_UNKNOWN;
    private int mLastSortOrder = SORT_ORDER_UNKNOWN;

    private Point mThumbSize;

    private DocumentsAdapter mAdapter;
    private LoaderCallbacks<DirectoryResult> mCallbacks;

    private static final String EXTRA_TYPE = "type";
    private static final String EXTRA_AUTHORITY = "authority";
    private static final String EXTRA_ROOT_ID = "rootId";
    private static final String EXTRA_DOC_ID = "docId";
    private static final String EXTRA_ROOT = "root";
    private static final String EXTRA_DOC = "doc";
    private static final String EXTRA_QUERY = "query";

    private static AtomicInteger sLoaderId = new AtomicInteger(4000);

    private int mLastSortOrder = -1;

    private final int mLoaderId = sLoaderId.incrementAndGet();

    public static void showNormal(FragmentManager fm, Uri uri) {
        show(fm, TYPE_NORMAL, uri.getAuthority(), null, DocumentsContract.getDocumentId(uri), null);
    public static void showNormal(FragmentManager fm, RootInfo root, DocumentInfo doc) {
        show(fm, TYPE_NORMAL, root, doc, null);
    }

    public static void showSearch(FragmentManager fm, Uri uri, String query) {
        show(fm, TYPE_SEARCH, uri.getAuthority(), null, DocumentsContract.getDocumentId(uri),
                query);
    public static void showSearch(
            FragmentManager fm, RootInfo root, DocumentInfo doc, String query) {
        show(fm, TYPE_SEARCH, root, doc, query);
    }

    public static void showRecentsOpen(FragmentManager fm) {
        show(fm, TYPE_RECENT_OPEN, null, null, null, null);
        show(fm, TYPE_RECENT_OPEN, null, null, null);
    }

    private static void show(FragmentManager fm, int type, String authority, String rootId,
            String docId, String query) {
    private static void show(
            FragmentManager fm, int type, RootInfo root, DocumentInfo doc, String query) {
        final Bundle args = new Bundle();
        args.putInt(EXTRA_TYPE, type);
        args.putString(EXTRA_AUTHORITY, authority);
        args.putString(EXTRA_ROOT_ID, rootId);
        args.putString(EXTRA_DOC_ID, docId);
        args.putParcelable(EXTRA_ROOT, root);
        args.putParcelable(EXTRA_DOC, doc);
        args.putString(EXTRA_QUERY, query);

        final DirectoryFragment fragment = new DirectoryFragment();
@@ -167,6 +168,7 @@ public class DirectoryFragment extends Fragment {
        super.onActivityCreated(savedInstanceState);

        final Context context = getActivity();
        final State state = getDisplayState(DirectoryFragment.this);

        mAdapter = new DocumentsAdapter();
        mType = getArguments().getInt(EXTRA_TYPE);
@@ -174,35 +176,48 @@ public class DirectoryFragment extends Fragment {
        mCallbacks = new LoaderCallbacks<DirectoryResult>() {
            @Override
            public Loader<DirectoryResult> onCreateLoader(int id, Bundle args) {
                final State state = getDisplayState(DirectoryFragment.this);

                final String authority = getArguments().getString(EXTRA_AUTHORITY);
                final String rootId = getArguments().getString(EXTRA_ROOT_ID);
                final String docId = getArguments().getString(EXTRA_DOC_ID);
                final RootInfo root = getArguments().getParcelable(EXTRA_ROOT);
                final DocumentInfo doc = getArguments().getParcelable(EXTRA_DOC);
                final String query = getArguments().getString(EXTRA_QUERY);

                Uri contentsUri;
                switch (mType) {
                    case TYPE_NORMAL:
                        contentsUri = DocumentsContract.buildChildDocumentsUri(authority, docId);
                        return new DirectoryLoader(context, rootId, contentsUri, state.sortOrder);
                        contentsUri = DocumentsContract.buildChildDocumentsUri(
                                doc.authority, doc.documentId);
                        return new DirectoryLoader(context, root, doc, contentsUri);
                    case TYPE_SEARCH:
                        contentsUri = DocumentsContract.buildSearchDocumentsUri(
                                authority, docId, query);
                        return new DirectoryLoader(context, rootId, contentsUri, state.sortOrder);
                                doc.authority, doc.documentId, query);
                        return new DirectoryLoader(context, root, doc, contentsUri);
                    case TYPE_RECENT_OPEN:
                        final RootsCache roots = DocumentsApplication.getRootsCache(context);
                        final List<RootInfo> matchingRoots = roots.getMatchingRoots(state);
                        return new RecentLoader(context, matchingRoots);
                        return new RecentLoader(context, matchingRoots, state.acceptMimes);
                    default:
                        throw new IllegalStateException("Unknown type " + mType);

                }
            }

            @Override
            public void onLoadFinished(Loader<DirectoryResult> loader, DirectoryResult result) {
                if (!isAdded()) return;

                mAdapter.swapCursor(result.cursor);

                // Push latest state up to UI
                // TODO: if mode change was racing with us, don't overwrite it
                state.mode = result.mode;
                state.sortOrder = result.sortOrder;
                ((DocumentsActivity) context).onStateChanged();

                updateDisplayState();

                if (mLastSortOrder != result.sortOrder) {
                    mLastSortOrder = result.sortOrder;
                    mListView.smoothScrollToPosition(0);
                    mGridView.smoothScrollToPosition(0);
                }
            }

            @Override
@@ -211,6 +226,9 @@ public class DirectoryFragment extends Fragment {
            }
        };

        // Kick off loader at least once
        getLoaderManager().restartLoader(mLoaderId, null, mCallbacks);

        updateDisplayState();
    }

@@ -220,22 +238,27 @@ public class DirectoryFragment extends Fragment {
        updateDisplayState();
    }

    public void updateDisplayState() {
        final State state = getDisplayState(this);

        if (mLastSortOrder != state.sortOrder) {
    public void onUserSortOrderChanged() {
        // User change always triggers reload
        getLoaderManager().restartLoader(mLoaderId, null, mCallbacks);
            mLastSortOrder = state.sortOrder;
    }

        mListView.smoothScrollToPosition(0);
        mGridView.smoothScrollToPosition(0);
    public void onUserModeChanged() {
        // Mode change is just display; no need to reload
        updateDisplayState();
    }

        mListView.setVisibility(state.mode == MODE_LIST ? View.VISIBLE : View.GONE);
        mGridView.setVisibility(state.mode == MODE_GRID ? View.VISIBLE : View.GONE);
    private void updateDisplayState() {
        final State state = getDisplayState(this);

        mFilter = new MimePredicate(state.acceptMimes);

        if (mLastMode == state.mode) return;
        mLastMode = state.mode;

        mListView.setVisibility(state.mode == MODE_LIST ? View.VISIBLE : View.GONE);
        mGridView.setVisibility(state.mode == MODE_GRID ? View.VISIBLE : View.GONE);

        final int choiceMode;
        if (state.allowMultiple) {
            choiceMode = ListView.CHOICE_MODE_MULTIPLE_MODAL;
@@ -254,14 +277,14 @@ public class DirectoryFragment extends Fragment {
            mGridView.setChoiceMode(choiceMode);
            mCurrentView = mGridView;
        } else if (state.mode == MODE_LIST) {
            thumbSize = getResources().getDimensionPixelSize(android.R.dimen.app_icon_size);
            thumbSize = getResources().getDimensionPixelSize(R.dimen.icon_size);
            mGridView.setAdapter(null);
            mGridView.setChoiceMode(ListView.CHOICE_MODE_NONE);
            mListView.setAdapter(mAdapter);
            mListView.setChoiceMode(choiceMode);
            mCurrentView = mListView;
        } else {
            throw new IllegalStateException();
            throw new IllegalStateException("Unknown state " + state.mode);
        }

        mThumbSize = new Point(thumbSize, thumbSize);
@@ -366,7 +389,7 @@ public class DirectoryFragment extends Fragment {
            intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
            intent.addCategory(Intent.CATEGORY_DEFAULT);
            intent.setType(doc.mimeType);
            intent.putExtra(Intent.EXTRA_STREAM, doc.uri);
            intent.putExtra(Intent.EXTRA_STREAM, doc.derivedUri);

        } else if (docs.size() > 1) {
            intent = new Intent(Intent.ACTION_SEND_MULTIPLE);
@@ -377,7 +400,7 @@ public class DirectoryFragment extends Fragment {
            final ArrayList<Uri> uris = Lists.newArrayList();
            for (DocumentInfo doc : docs) {
                mimeTypes.add(doc.mimeType);
                uris.add(doc.uri);
                uris.add(doc.derivedUri);
            }

            intent.setType(findCommonMimeType(mimeTypes));
@@ -403,7 +426,7 @@ public class DirectoryFragment extends Fragment {
                continue;
            }

            if (!DocumentsContract.deleteDocument(resolver, doc.uri)) {
            if (!DocumentsContract.deleteDocument(resolver, doc.derivedUri)) {
                Log.w(TAG, "Failed to delete " + doc);
                hadTrouble = true;
            }
+72 −12
Original line number Diff line number Diff line
@@ -16,18 +16,29 @@

package com.android.documentsui;

import static com.android.documentsui.DocumentsActivity.TAG;
import static com.android.documentsui.DocumentsActivity.State.MODE_UNKNOWN;
import static com.android.documentsui.DocumentsActivity.State.SORT_ORDER_DISPLAY_NAME;
import static com.android.documentsui.DocumentsActivity.State.SORT_ORDER_LAST_MODIFIED;
import static com.android.documentsui.DocumentsActivity.State.SORT_ORDER_SIZE;
import static com.android.documentsui.DocumentsActivity.State.SORT_ORDER_UNKNOWN;
import static com.android.documentsui.model.DocumentInfo.getCursorInt;

import android.content.AsyncTaskLoader;
import android.content.ContentProviderClient;
import android.content.ContentResolver;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.CancellationSignal;
import android.os.OperationCanceledException;
import android.provider.DocumentsContract.Document;
import android.util.Log;

import com.android.documentsui.DocumentsActivity.State;
import com.android.documentsui.RecentsProvider.StateColumns;
import com.android.documentsui.model.DocumentInfo;
import com.android.documentsui.model.RootInfo;

import libcore.io.IoUtils;

@@ -36,6 +47,9 @@ class DirectoryResult implements AutoCloseable {
    Cursor cursor;
    Exception exception;

    int mode = MODE_UNKNOWN;
    int sortOrder = SORT_ORDER_UNKNOWN;

    @Override
    public void close() {
        IoUtils.closeQuietly(cursor);
@@ -48,18 +62,18 @@ class DirectoryResult implements AutoCloseable {
public class DirectoryLoader extends AsyncTaskLoader<DirectoryResult> {
    private final ForceLoadContentObserver mObserver = new ForceLoadContentObserver();

    private final String mRootId;
    private final RootInfo mRoot;
    private final DocumentInfo mDoc;
    private final Uri mUri;
    private final int mSortOrder;

    private CancellationSignal mSignal;
    private DirectoryResult mResult;

    public DirectoryLoader(Context context, String rootId, Uri uri, int sortOrder) {
    public DirectoryLoader(Context context, RootInfo root, DocumentInfo doc, Uri uri) {
        super(context);
        mRootId = rootId;
        mRoot = root;
        mDoc = doc;
        mUri = uri;
        mSortOrder = sortOrder;
    }

    @Override
@@ -70,20 +84,65 @@ public class DirectoryLoader extends AsyncTaskLoader<DirectoryResult> {
            }
            mSignal = new CancellationSignal();
        }
        final DirectoryResult result = new DirectoryResult();

        final ContentResolver resolver = getContext().getContentResolver();
        final String authority = mUri.getAuthority();

        final DirectoryResult result = new DirectoryResult();

        int userMode = State.MODE_UNKNOWN;
        int userSortOrder = State.SORT_ORDER_UNKNOWN;

        // Pick up any custom modes requested by user
        Cursor cursor = null;
        try {
            final Uri stateUri = RecentsProvider.buildState(
                    mRoot.authority, mRoot.rootId, mDoc.documentId);
            cursor = resolver.query(stateUri, null, null, null, null);
            if (cursor.moveToFirst()) {
                userMode = getCursorInt(cursor, StateColumns.MODE);
                userSortOrder = getCursorInt(cursor, StateColumns.SORT_ORDER);
            }
        } finally {
            IoUtils.closeQuietly(cursor);
        }

        if (userMode != State.MODE_UNKNOWN) {
            result.mode = userMode;
        } else {
            if ((mDoc.flags & Document.FLAG_DIR_PREFERS_GRID) != 0) {
                result.mode = State.MODE_GRID;
            } else {
                result.mode = State.MODE_LIST;
            }
        }

        if (userSortOrder != State.SORT_ORDER_UNKNOWN) {
            result.sortOrder = userSortOrder;
        } else {
            if ((mDoc.flags & Document.FLAG_DIR_PREFERS_LAST_MODIFIED) != 0) {
                result.sortOrder = State.SORT_ORDER_LAST_MODIFIED;
            } else {
                result.sortOrder = State.SORT_ORDER_DISPLAY_NAME;
            }
        }

        Log.d(TAG, "userMode=" + userMode + ", userSortOrder=" + userSortOrder + " --> mode="
                + result.mode + ", sortOrder=" + result.sortOrder);

        try {
            result.client = getContext()
                    .getContentResolver().acquireUnstableContentProviderClient(authority);
            final Cursor cursor = result.client.query(
                    mUri, null, null, null, getQuerySortOrder(mSortOrder), mSignal);
            result.client = resolver.acquireUnstableContentProviderClient(authority);
            cursor = result.client.query(
                    mUri, null, null, null, getQuerySortOrder(result.sortOrder), mSignal);
            cursor.registerContentObserver(mObserver);

            final Cursor withRoot = new RootCursorWrapper(mUri.getAuthority(), mRootId, cursor, -1);
            final Cursor sorted = new SortingCursorWrapper(withRoot, mSortOrder);
            final Cursor withRoot = new RootCursorWrapper(
                    mUri.getAuthority(), mRoot.rootId, cursor, -1);
            final Cursor sorted = new SortingCursorWrapper(withRoot, result.sortOrder);

            result.cursor = sorted;
        } catch (Exception e) {
            Log.d(TAG, "Failed to query", e);
            result.exception = e;
            ContentProviderClient.closeQuietly(result.client);
        } finally {
@@ -91,6 +150,7 @@ public class DirectoryLoader extends AsyncTaskLoader<DirectoryResult> {
                mSignal = null;
            }
        }

        return result;
    }

Loading