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

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

Stronger DocumentsProvider contract.

Using a contract class requires that a provider implement it exactly
with little help. This change introduces a DocumentsProvider abstract
class that provides a client-side implementation of the contract that
greatly reduces developer burden, and improves correctness.

This also moves to first-class DocumentRoot objects, and moves calls
with complex side effects to be ContentProvider.call() invocations,
offering more granular permission control over Uri operations that
shouldn't be available through Uri grants.

This new design also relaxes the requirement that root information be
burned into every Uri.  Migrate ExternalDocumentsProvider and
DocumentsUI to adopt new API.

Bug: 10497206
Change-Id: I6f2b3f519bfd62a9d693223ea5628a971ce2e743
parent d44b3028
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -35,9 +35,9 @@
            </intent-filter>
            <!-- data expected to point at existing root to manage -->
            <intent-filter>
                <action android:name="android.intent.action.MANAGE_DOCUMENT" />
                <action android:name="android.provider.action.MANAGE_DOCUMENTS" />
                <category android:name="android.intent.category.DEFAULT" />
                <data android:mimeType="vnd.android.cursor.item/root" />
                <data android:mimeType="vnd.android.doc/dir" />
            </intent-filter>
        </activity>

+14 −15
Original line number Diff line number Diff line
@@ -20,14 +20,14 @@ import android.app.AlertDialog;
import android.app.Dialog;
import android.app.DialogFragment;
import android.app.FragmentManager;
import android.content.ContentProviderClient;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.net.Uri;
import android.os.Bundle;
import android.provider.DocumentsContract.DocumentColumns;
import android.provider.DocumentsContract;
import android.provider.DocumentsContract.Documents;
import android.view.LayoutInflater;
import android.view.View;
@@ -36,8 +36,6 @@ import android.widget.Toast;

import com.android.documentsui.model.Document;

import java.io.FileNotFoundException;

/**
 * Dialog to create a new directory.
 */
@@ -68,24 +66,25 @@ public class CreateDirectoryFragment extends DialogFragment {
            public void onClick(DialogInterface dialog, int which) {
                final String displayName = text1.getText().toString();

                final ContentValues values = new ContentValues();
                values.put(DocumentColumns.MIME_TYPE, Documents.MIME_TYPE_DIR);
                values.put(DocumentColumns.DISPLAY_NAME, displayName);

                final DocumentsActivity activity = (DocumentsActivity) getActivity();
                final Document cwd = activity.getCurrentDirectory();

                Uri childUri = resolver.insert(cwd.uri, values);
                final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
                        cwd.uri.getAuthority());
                try {
                    final String docId = DocumentsContract.createDocument(client,
                            DocumentsContract.getDocId(cwd.uri), Documents.MIME_TYPE_DIR,
                            displayName);

                    // Navigate into newly created child
                    final Uri childUri = DocumentsContract.buildDocumentUri(
                            cwd.uri.getAuthority(), docId);
                    final Document childDoc = Document.fromUri(resolver, childUri);
                    activity.onDocumentPicked(childDoc);
                } catch (FileNotFoundException e) {
                    childUri = null;
                }

                if (childUri == null) {
                } catch (Exception e) {
                    Toast.makeText(context, R.string.save_error, Toast.LENGTH_SHORT).show();
                } finally {
                    ContentProviderClient.closeQuietly(client);
                }
            }
        });
+12 −47
Original line number Diff line number Diff line
@@ -20,8 +20,8 @@ import static com.android.documentsui.DocumentsActivity.TAG;
import static com.android.documentsui.DocumentsActivity.DisplayState.ACTION_MANAGE;
import static com.android.documentsui.DocumentsActivity.DisplayState.MODE_GRID;
import static com.android.documentsui.DocumentsActivity.DisplayState.MODE_LIST;
import static com.android.documentsui.DocumentsActivity.DisplayState.SORT_ORDER_DATE;
import static com.android.documentsui.DocumentsActivity.DisplayState.SORT_ORDER_NAME;
import static com.android.documentsui.DocumentsActivity.DisplayState.SORT_ORDER_DISPLAY_NAME;
import static com.android.documentsui.DocumentsActivity.DisplayState.SORT_ORDER_LAST_MODIFIED;
import static com.android.documentsui.DocumentsActivity.DisplayState.SORT_ORDER_SIZE;

import android.app.Fragment;
@@ -32,7 +32,6 @@ import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.Loader;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.Point;
import android.net.Uri;
@@ -55,7 +54,6 @@ import android.widget.AbsListView.MultiChoiceModeListener;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.GridView;
import android.widget.ImageView;
import android.widget.ListView;
@@ -64,7 +62,6 @@ import android.widget.Toast;

import com.android.documentsui.DocumentsActivity.DisplayState;
import com.android.documentsui.model.Document;
import com.android.documentsui.model.Root;
import com.android.internal.util.Predicate;
import com.google.android.collect.Lists;

@@ -81,7 +78,6 @@ public class DirectoryFragment extends Fragment {
    private View mEmptyView;
    private ListView mListView;
    private GridView mGridView;
    private Button mMoreView;

    private AbsListView mCurrentView;

@@ -110,7 +106,8 @@ public class DirectoryFragment extends Fragment {
    }

    public static void showSearch(FragmentManager fm, Uri uri, String query) {
        final Uri searchUri = DocumentsContract.buildSearchUri(uri, query);
        final Uri searchUri = DocumentsContract.buildSearchUri(
                uri.getAuthority(), DocumentsContract.getDocId(uri), query);
        show(fm, TYPE_SEARCH, searchUri);
    }

@@ -153,8 +150,6 @@ public class DirectoryFragment extends Fragment {
        mGridView.setOnItemClickListener(mItemListener);
        mGridView.setMultiChoiceModeListener(mMultiListener);

        mMoreView = (Button) view.findViewById(R.id.more);

        mAdapter = new DocumentsAdapter();

        final Uri uri = getArguments().getParcelable(EXTRA_URI);
@@ -168,22 +163,19 @@ public class DirectoryFragment extends Fragment {

                Uri contentsUri;
                if (mType == TYPE_NORMAL) {
                    contentsUri = DocumentsContract.buildContentsUri(uri);
                    contentsUri = DocumentsContract.buildChildrenUri(
                            uri.getAuthority(), DocumentsContract.getDocId(uri));
                } else if (mType == TYPE_RECENT_OPEN) {
                    contentsUri = RecentsProvider.buildRecentOpen();
                } else {
                    contentsUri = uri;
                }

                if (state.localOnly) {
                    contentsUri = DocumentsContract.setLocalOnly(contentsUri);
                }

                final Comparator<Document> sortOrder;
                if (state.sortOrder == SORT_ORDER_DATE || mType == TYPE_RECENT_OPEN) {
                    sortOrder = new Document.DateComparator();
                } else if (state.sortOrder == SORT_ORDER_NAME) {
                    sortOrder = new Document.NameComparator();
                if (state.sortOrder == SORT_ORDER_LAST_MODIFIED || mType == TYPE_RECENT_OPEN) {
                    sortOrder = new Document.LastModifiedComparator();
                } else if (state.sortOrder == SORT_ORDER_DISPLAY_NAME) {
                    sortOrder = new Document.DisplayNameComparator();
                } else if (state.sortOrder == SORT_ORDER_SIZE) {
                    sortOrder = new Document.SizeComparator();
                } else {
@@ -196,28 +188,6 @@ public class DirectoryFragment extends Fragment {
            @Override
            public void onLoadFinished(Loader<DirectoryResult> loader, DirectoryResult result) {
                mAdapter.swapDocuments(result.contents);

                final Cursor cursor = result.cursor;
                if (cursor != null && cursor.getExtras()
                        .getBoolean(DocumentsContract.EXTRA_HAS_MORE, false)) {
                    mMoreView.setText(R.string.more);
                    mMoreView.setVisibility(View.VISIBLE);
                    mMoreView.setOnClickListener(new View.OnClickListener() {
                        @Override
                        public void onClick(View v) {
                            mMoreView.setText(R.string.loading);
                            final Bundle bundle = new Bundle();
                            bundle.putBoolean(DocumentsContract.EXTRA_REQUEST_MORE, true);
                            try {
                                cursor.respond(bundle);
                            } catch (Exception e) {
                                Log.w(TAG, "Failed to respond: " + e);
                            }
                        }
                    });
                } else {
                    mMoreView.setVisibility(View.GONE);
                }
            }

            @Override
@@ -489,8 +459,7 @@ public class DirectoryFragment extends Fragment {
                    task.execute(doc.uri);
                }
            } else {
                icon.setImageDrawable(roots.resolveDocumentIcon(
                        context, doc.uri.getAuthority(), doc.mimeType));
                icon.setImageDrawable(RootsCache.resolveDocumentIcon(context, doc.mimeType));
            }

            title.setText(doc.displayName);
@@ -504,11 +473,7 @@ public class DirectoryFragment extends Fragment {
                    summary.setVisibility(View.INVISIBLE);
                }
            } else if (mType == TYPE_RECENT_OPEN) {
                final Root root = roots.findRoot(doc);
                icon1.setVisibility(View.VISIBLE);
                icon1.setImageDrawable(root.icon);
                summary.setText(root.getDirectoryString());
                summary.setVisibility(View.VISIBLE);
                // TODO: resolve storage root
            }

            if (summaryGrid != null) {
+3 −15
Original line number Diff line number Diff line
@@ -26,7 +26,6 @@ import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.CancellationSignal;
import android.provider.DocumentsContract.DocumentColumns;
import android.util.Log;

import com.android.documentsui.model.Document;
@@ -77,9 +76,10 @@ public class DirectoryLoader extends UriDerivativeLoader<Uri, DirectoryResult> {
    }

    private void loadInBackgroundInternal(
            DirectoryResult result, Uri uri, CancellationSignal signal) {
            DirectoryResult result, Uri uri, CancellationSignal signal) throws RuntimeException {
        // TODO: switch to using unstable CPC
        final ContentResolver resolver = getContext().getContentResolver();
        final Cursor cursor = resolver.query(uri, null, null, null, getQuerySortOrder(), signal);
        final Cursor cursor = resolver.query(uri, null, null, null, null, signal);
        result.cursor = cursor;
        result.cursor.registerContentObserver(mObserver);

@@ -110,16 +110,4 @@ public class DirectoryLoader extends UriDerivativeLoader<Uri, DirectoryResult> {
            Collections.sort(result.contents, mSortOrder);
        }
    }

    private String getQuerySortOrder() {
        if (mSortOrder instanceof Document.DateComparator) {
            return DocumentColumns.LAST_MODIFIED + " DESC";
        } else if (mSortOrder instanceof Document.NameComparator) {
            return DocumentColumns.DISPLAY_NAME + " ASC";
        } else if (mSortOrder instanceof Document.SizeComparator) {
            return DocumentColumns.SIZE + " DESC";
        } else {
            return null;
        }
    }
}
+2 −3
Original line number Diff line number Diff line
@@ -21,12 +21,11 @@ import static com.android.documentsui.DocumentsActivity.TAG;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.provider.DocumentsContract.DocumentRoot;
import android.util.Log;

import com.android.documentsui.model.Root;

/**
 * Handles {@link Root} changes which invalidate cached data.
 * Handles {@link DocumentRoot} changes which invalidate cached data.
 */
public class DocumentChangedReceiver extends BroadcastReceiver {
    @Override
Loading