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

Commit a4d1f220 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 42d26792
Loading
Loading
Loading
Loading
+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;
    }

+89 −54

File changed.

Preview size limit exceeded, changes collapsed.

+119 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2013 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.documentsui;

import static com.android.documentsui.DocumentsActivity.TAG;

import android.database.AbstractCursor;
import android.database.Cursor;
import android.os.Bundle;
import android.provider.DocumentsContract.Document;
import android.util.Log;

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

    private final int[] mPosition;
    private int mCount;

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

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

        cursor.moveToPosition(-1);
        while (cursor.moveToNext()) {
            final String mimeType = cursor.getString(
                    cursor.getColumnIndex(Document.COLUMN_MIME_TYPE));
            if (MimePredicate.mimeMatches(acceptMimes, mimeType)) {
                mPosition[mCount++] = cursor.getPosition();
            }
        }

        Log.d(TAG, "Before filtering " + cursor.getCount() + ", after " + mCount);
    }

    @Override
    public Bundle getExtras() {
        return mCursor.getExtras();
    }

    @Override
    public void close() {
        super.close();
        mCursor.close();
    }

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

    @Override
    public String[] getColumnNames() {
        return mCursor.getColumnNames();
    }

    @Override
    public int getCount() {
        return mCount;
    }

    @Override
    public double getDouble(int column) {
        return mCursor.getDouble(column);
    }

    @Override
    public float getFloat(int column) {
        return mCursor.getFloat(column);
    }

    @Override
    public int getInt(int column) {
        return mCursor.getInt(column);
    }

    @Override
    public long getLong(int column) {
        return mCursor.getLong(column);
    }

    @Override
    public short getShort(int column) {
        return mCursor.getShort(column);
    }

    @Override
    public String getString(int column) {
        return mCursor.getString(column);
    }

    @Override
    public int getType(int column) {
        return mCursor.getType(column);
    }

    @Override
    public boolean isNull(int column) {
        return mCursor.isNull(column);
    }
}
Loading