Follow stronger DocumentsProvider contract.
Jeff Sharkey [Thu, 29 Aug 2013 00:54:21 +0000 (17:54 -0700)]
Provides same functionality, but follows updated DocumentsProvider
contract in framework.

Bug: 10497206
Change-Id: Ie1f6180047ff7bad289679a14f3368238d47b1d6

AndroidManifest.xml
src/com/android/providers/downloads/Constants.java
src/com/android/providers/downloads/DownloadStorageProvider.java
src/com/android/providers/downloads/TrampolineActivity.java
ui/src/com/android/providers/downloads/ui/DownloadList.java

index cd8aec5..398f8f4 100644 (file)
         <!-- PackageInstaller really wants raw files -->
         <activity
             android:name=".TrampolineActivity"
-            android:theme="@android:style/Theme.NoDisplay">
+            android:theme="@android:style/Theme.NoDisplay"
+            android:permission="android.permission.MANAGE_DOCUMENTS">
             <intent-filter>
                 <action android:name="android.intent.action.VIEW" />
                 <category android:name="android.intent.category.DEFAULT" />
index 3fbd400..e33a636 100644 (file)
@@ -176,5 +176,5 @@ public class Constants {
     public static final boolean LOGVV = LOCAL_LOGVV && LOGV;
 
     public static final String STORAGE_AUTHORITY = "com.android.providers.downloads.documents";
-    public static final String STORAGE_ROOT = "downloads";
+    public static final String STORAGE_DOC_ID_ROOT = "downloads";
 }
index b606e65..58df58a 100644 (file)
@@ -18,63 +18,55 @@ package com.android.providers.downloads;
 
 import android.app.DownloadManager;
 import android.app.DownloadManager.Query;
-import android.content.ContentProvider;
-import android.content.ContentValues;
 import android.content.Context;
-import android.content.UriMatcher;
+import android.content.res.AssetFileDescriptor;
 import android.database.Cursor;
 import android.database.MatrixCursor;
 import android.database.MatrixCursor.RowBuilder;
-import android.net.Uri;
+import android.graphics.Point;
 import android.os.Binder;
+import android.os.CancellationSignal;
 import android.os.ParcelFileDescriptor;
 import android.provider.DocumentsContract;
 import android.provider.DocumentsContract.DocumentColumns;
+import android.provider.DocumentsContract.DocumentRoot;
 import android.provider.DocumentsContract.Documents;
-import android.provider.DocumentsContract.RootColumns;
-import android.provider.DocumentsContract.Roots;
+import android.provider.DocumentsProvider;
+
+import com.google.common.collect.Lists;
 
 import libcore.io.IoUtils;
 
 import java.io.FileNotFoundException;
+import java.util.List;
 
 /**
  * Presents a {@link DocumentsContract} view of {@link DownloadManager}
  * contents.
  */
-public class DownloadStorageProvider extends ContentProvider {
-    private static final String AUTHORITY = Constants.STORAGE_AUTHORITY;
-    private static final String ROOT = Constants.STORAGE_ROOT;
-
-    private static final UriMatcher sMatcher = new UriMatcher(UriMatcher.NO_MATCH);
-
-    private static final int URI_ROOTS = 1;
-    private static final int URI_ROOTS_ID = 2;
-    private static final int URI_DOCS_ID = 3;
-    private static final int URI_DOCS_ID_CONTENTS = 4;
-
-    static {
-        sMatcher.addURI(AUTHORITY, "roots", URI_ROOTS);
-        sMatcher.addURI(AUTHORITY, "roots/*", URI_ROOTS_ID);
-        sMatcher.addURI(AUTHORITY, "roots/*/docs/*", URI_DOCS_ID);
-        sMatcher.addURI(AUTHORITY, "roots/*/docs/*/contents", URI_DOCS_ID_CONTENTS);
-    }
+public class DownloadStorageProvider extends DocumentsProvider {
+    private static final String DOC_ID_ROOT = Constants.STORAGE_DOC_ID_ROOT;
 
-    private static final String[] ALL_ROOTS_COLUMNS = new String[] {
-            RootColumns.ROOT_ID, RootColumns.ROOT_TYPE, RootColumns.ICON, RootColumns.TITLE,
-            RootColumns.SUMMARY, RootColumns.AVAILABLE_BYTES
-    };
-
-    private static final String[] ALL_DOCUMENTS_COLUMNS = new String[] {
+    private static final String[] SUPPORTED_COLUMNS = new String[] {
             DocumentColumns.DOC_ID, DocumentColumns.DISPLAY_NAME, DocumentColumns.SIZE,
             DocumentColumns.MIME_TYPE, DocumentColumns.LAST_MODIFIED, DocumentColumns.FLAGS
     };
 
+    private DocumentRoot mRoot;
+
     private DownloadManager mDm;
     private DownloadManager.Query mBaseQuery;
 
     @Override
     public boolean onCreate() {
+
+        mRoot = new DocumentRoot();
+        mRoot.docId = DOC_ID_ROOT;
+        mRoot.rootType = DocumentRoot.ROOT_TYPE_SHORTCUT;
+        mRoot.title = getContext().getString(R.string.root_downloads);
+        mRoot.icon = R.mipmap.ic_launcher_download;
+        mRoot.flags = DocumentRoot.FLAG_LOCAL_ONLY;
+
         mDm = (DownloadManager) getContext().getSystemService(Context.DOWNLOAD_SERVICE);
         mDm.setAccessAllDownloads(true);
         mBaseQuery = new DownloadManager.Query().setOnlyIncludeVisibleInDownloadsUi(true);
@@ -83,91 +75,98 @@ public class DownloadStorageProvider extends ContentProvider {
     }
 
     @Override
-    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
-            String sortOrder) {
-        switch (sMatcher.match(uri)) {
-            case URI_ROOTS: {
-                final MatrixCursor result = new MatrixCursor(
-                        projection != null ? projection : ALL_ROOTS_COLUMNS);
-                includeDefaultRoot(result);
-                return result;
-            }
-            case URI_ROOTS_ID: {
-                final MatrixCursor result = new MatrixCursor(
-                        projection != null ? projection : ALL_ROOTS_COLUMNS);
-                includeDefaultRoot(result);
-                return result;
-            }
-            case URI_DOCS_ID: {
-                final String docId = DocumentsContract.getDocId(uri);
-                final MatrixCursor result = new MatrixCursor(
-                        projection != null ? projection : ALL_DOCUMENTS_COLUMNS);
+    public List<DocumentRoot> getDocumentRoots() {
+        return Lists.newArrayList(mRoot);
+    }
 
-                if (Documents.DOC_ID_ROOT.equals(docId)) {
-                    includeDefaultDocument(result);
-                } else {
-                    // Delegate to real provider
-                    final long token = Binder.clearCallingIdentity();
-                    Cursor cursor = null;
-                    try {
-                        cursor = mDm.query(
-                                new Query().setFilterById(getDownloadFromDocument(docId)));
-                        if (cursor.moveToFirst()) {
-                            includeDownloadFromCursor(result, cursor);
-                        }
-                    } finally {
-                        IoUtils.closeQuietly(cursor);
-                        Binder.restoreCallingIdentity(token);
-                    }
-                }
-                return result;
+    @Override
+    public void deleteDocument(String docId) throws FileNotFoundException {
+        // Delegate to real provider
+        final long token = Binder.clearCallingIdentity();
+        try {
+            if (mDm.remove(Long.parseLong(docId)) != 1) {
+                throw new IllegalStateException("Failed to delete " + docId);
             }
-            case URI_DOCS_ID_CONTENTS: {
-                final String docId = DocumentsContract.getDocId(uri);
-                final MatrixCursor result = new MatrixCursor(
-                        projection != null ? projection : ALL_DOCUMENTS_COLUMNS);
-
-                if (!Documents.DOC_ID_ROOT.equals(docId)) {
-                    throw new UnsupportedOperationException("Unsupported Uri " + uri);
-                }
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
 
-                // Delegate to real provider
-                final long token = Binder.clearCallingIdentity();
-                Cursor cursor = null;
-                try {
-                    cursor = mDm.query(mBaseQuery);
-                    while (cursor.moveToNext()) {
-                        includeDownloadFromCursor(result, cursor);
-                    }
-                } finally {
-                    IoUtils.closeQuietly(cursor);
-                    Binder.restoreCallingIdentity(token);
+    @Override
+    public Cursor queryDocument(String docId) throws FileNotFoundException {
+        final MatrixCursor result = new MatrixCursor(SUPPORTED_COLUMNS);
+
+        if (DOC_ID_ROOT.equals(docId)) {
+            includeDefaultDocument(result);
+        } else {
+            // Delegate to real provider
+            final long token = Binder.clearCallingIdentity();
+            Cursor cursor = null;
+            try {
+                cursor = mDm.query(new Query().setFilterById(Long.parseLong(docId)));
+                if (cursor.moveToFirst()) {
+                    includeDownloadFromCursor(result, cursor);
                 }
-                return result;
+            } finally {
+                IoUtils.closeQuietly(cursor);
+                Binder.restoreCallingIdentity(token);
             }
-            default: {
-                throw new UnsupportedOperationException("Unsupported Uri " + uri);
+        }
+        return result;
+    }
+
+    @Override
+    public Cursor queryDocumentChildren(String docId) throws FileNotFoundException {
+        final MatrixCursor result = new MatrixCursor(SUPPORTED_COLUMNS);
+
+        // Delegate to real provider
+        final long token = Binder.clearCallingIdentity();
+        Cursor cursor = null;
+        try {
+            cursor = mDm.query(mBaseQuery);
+            while (cursor.moveToNext()) {
+                includeDownloadFromCursor(result, cursor);
             }
+        } finally {
+            IoUtils.closeQuietly(cursor);
+            Binder.restoreCallingIdentity(token);
         }
+        return result;
     }
 
-    private void includeDefaultRoot(MatrixCursor result) {
-        final RowBuilder row = result.newRow();
-        row.offer(RootColumns.ROOT_ID, ROOT);
-        row.offer(RootColumns.ROOT_TYPE, Roots.ROOT_TYPE_SHORTCUT);
-        row.offer(RootColumns.TITLE, getContext().getString(R.string.root_downloads));
+    @Override
+    public ParcelFileDescriptor openDocument(String docId, String mode, CancellationSignal signal)
+            throws FileNotFoundException {
+        if (!"r".equals(mode)) {
+            throw new IllegalArgumentException("Downloads are read-only");
+        }
+
+        // Delegate to real provider
+        final long token = Binder.clearCallingIdentity();
+        try {
+            return mDm.openDownloadedFile(Long.parseLong(docId));
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    @Override
+    public AssetFileDescriptor openDocumentThumbnail(
+            String docId, Point sizeHint, CancellationSignal signal) throws FileNotFoundException {
+        // TODO: extend ExifInterface to support fds
+        final ParcelFileDescriptor pfd = openDocument(docId, "r", signal);
+        return new AssetFileDescriptor(pfd, 0, AssetFileDescriptor.UNKNOWN_LENGTH);
     }
 
     private void includeDefaultDocument(MatrixCursor result) {
         final RowBuilder row = result.newRow();
-        row.offer(DocumentColumns.DOC_ID, Documents.DOC_ID_ROOT);
-        row.offer(DocumentColumns.DISPLAY_NAME, getContext().getString(R.string.root_downloads));
+        row.offer(DocumentColumns.DOC_ID, DOC_ID_ROOT);
         row.offer(DocumentColumns.MIME_TYPE, Documents.MIME_TYPE_DIR);
     }
 
     private void includeDownloadFromCursor(MatrixCursor result, Cursor cursor) {
         final long id = cursor.getLong(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_ID));
-        final String docId = getDocumentFromDownload(id);
+        final String docId = String.valueOf(id);
 
         final String displayName = cursor.getString(
                 cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_TITLE));
@@ -225,101 +224,4 @@ public class DownloadStorageProvider extends ContentProvider {
         row.offer(DocumentColumns.LAST_MODIFIED, lastModified);
         row.offer(DocumentColumns.FLAGS, flags);
     }
-
-    private interface TypeQuery {
-        final String[] PROJECTION = {
-                DocumentColumns.MIME_TYPE };
-
-        final int MIME_TYPE = 0;
-    }
-
-    @Override
-    public String getType(Uri uri) {
-        switch (sMatcher.match(uri)) {
-            case URI_ROOTS: {
-                return Roots.MIME_TYPE_DIR;
-            }
-            case URI_ROOTS_ID: {
-                return Roots.MIME_TYPE_ITEM;
-            }
-            case URI_DOCS_ID: {
-                final Cursor cursor = query(uri, TypeQuery.PROJECTION, null, null, null);
-                try {
-                    if (cursor.moveToFirst()) {
-                        return cursor.getString(TypeQuery.MIME_TYPE);
-                    } else {
-                        return null;
-                    }
-                } finally {
-                    IoUtils.closeQuietly(cursor);
-                }
-            }
-            default: {
-                throw new UnsupportedOperationException("Unsupported Uri " + uri);
-            }
-        }
-    }
-
-    public static long getDownloadFromDocument(String docId) {
-        return Long.parseLong(docId.substring(docId.indexOf(':') + 1));
-    }
-
-    private String getDocumentFromDownload(long id) {
-        return "id:" + id;
-    }
-
-    @Override
-    public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
-        switch (sMatcher.match(uri)) {
-            case URI_DOCS_ID: {
-                final String docId = DocumentsContract.getDocId(uri);
-
-                if (!"r".equals(mode)) {
-                    throw new IllegalArgumentException("Downloads are read-only");
-                }
-
-                // Delegate to real provider
-                final long token = Binder.clearCallingIdentity();
-                try {
-                    return mDm.openDownloadedFile(getDownloadFromDocument(docId));
-                } finally {
-                    Binder.restoreCallingIdentity(token);
-                }
-            }
-            default: {
-                throw new UnsupportedOperationException("Unsupported Uri " + uri);
-            }
-        }
-    }
-
-    @Override
-    public Uri insert(Uri uri, ContentValues values) {
-        throw new UnsupportedOperationException("Unsupported Uri " + uri);
-    }
-
-    @Override
-    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
-        throw new UnsupportedOperationException("Unsupported Uri " + uri);
-    }
-
-    @Override
-    public int delete(Uri uri, String selection, String[] selectionArgs) {
-        switch (sMatcher.match(uri)) {
-            case URI_DOCS_ID: {
-                final String docId = DocumentsContract.getDocId(uri);
-
-                // Delegate to real provider
-                // TODO: only storage UI should be allowed to delete?
-                final long token = Binder.clearCallingIdentity();
-                try {
-                    return mDm.remove(getDownloadFromDocument(docId));
-                } finally {
-                    Binder.restoreCallingIdentity(token);
-                }
-            }
-            default: {
-                throw new UnsupportedOperationException("Unsupported Uri " + uri);
-            }
-        }
-    }
 }
index a1e9916..0f494cf 100644 (file)
 package com.android.providers.downloads;
 
 import android.app.Activity;
+import android.content.ContentUris;
 import android.content.Intent;
-import android.net.Uri;
 import android.os.Bundle;
-import android.provider.DocumentsContract;
 
 /**
  * PackageInstaller really wants raw files.
@@ -30,11 +29,8 @@ public class TrampolineActivity extends Activity {
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
 
-        final Uri documentUri = getIntent().getData();
-        final String docId = DocumentsContract.getDocId(documentUri);
-        final long downloadId = DownloadStorageProvider.getDownloadFromDocument(docId);
-
-        final Intent intent = OpenHelper.buildViewIntent(this, downloadId);
+        final long id = ContentUris.parseId(getIntent().getData());
+        final Intent intent = OpenHelper.buildViewIntent(this, id);
         startActivity(intent);
         finish();
     }
index 5753047..443491a 100644 (file)
@@ -151,9 +151,9 @@ public class DownloadList extends Activity {
         super.onCreate(icicle);
 
         // Trampoline over to new management UI
-        final Intent intent = new Intent(Intent.ACTION_MANAGE_DOCUMENT);
-        intent.setData(DocumentsContract.buildRootUri(
-                Constants.STORAGE_AUTHORITY, Constants.STORAGE_ROOT));
+        final Intent intent = new Intent(DocumentsContract.ACTION_MANAGE_DOCUMENTS);
+        intent.setData(DocumentsContract.buildDocumentUri(
+                Constants.STORAGE_AUTHORITY, Constants.STORAGE_DOC_ID_ROOT));
         startActivity(intent);
         finish();
     }