From a5599ef636e37cb0b6474349936999be1afe6987 Mon Sep 17 00:00:00 2001 From: Jeff Sharkey Date: Thu, 15 Aug 2013 16:17:41 -0700 Subject: [PATCH] Documents management mode; API adjustment. Create documents manage mode to support Downloads and transient storage devices. Locks user into requested backend root, and forces file sizes on and sorting by last modified. Separate API constants for Documents versus Roots, and give concrete MIME types for roots. Treat null sizes as unknown. Documents are always enabled in list so that divider is drawn. Mark external storage file as writable. Bug: 10329983, 10332993, 10332952 Change-Id: I05f4fdf5b04041a38e1ba7fb30202a3b0c615bf6 --- api/current.txt | 34 +-- core/java/android/content/Intent.java | 4 + .../android/provider/DocumentsContract.java | 202 ++++++++++-------- packages/DocumentsUI/AndroidManifest.xml | 6 + .../documentsui/CreateDirectoryFragment.java | 4 +- .../documentsui/DirectoryFragment.java | 21 +- .../documentsui/DocumentsActivity.java | 88 ++++++-- .../android/documentsui/MimePredicate.java | 4 +- .../com/android/documentsui/RootsCache.java | 3 +- .../android/documentsui/RootsFragment.java | 15 +- .../android/documentsui/model/Document.java | 23 +- .../com/android/documentsui/model/Root.java | 6 +- .../ExternalStorageProvider.java | 33 +-- 13 files changed, 267 insertions(+), 176 deletions(-) diff --git a/api/current.txt b/api/current.txt index c0cb9f09a9a24..5a3c7db7e1eb5 100644 --- a/api/current.txt +++ b/api/current.txt @@ -20620,19 +20620,6 @@ package android.provider { field public static final java.lang.String EXTRA_HAS_MORE = "has_more"; field public static final java.lang.String EXTRA_REQUEST_MORE = "request_more"; field public static final java.lang.String EXTRA_THUMBNAIL_SIZE = "thumbnail_size"; - field public static final int FLAG_PREFERS_GRID = 64; // 0x40 - field public static final int FLAG_SUPPORTS_CREATE = 1; // 0x1 - field public static final int FLAG_SUPPORTS_DELETE = 4; // 0x4 - field public static final int FLAG_SUPPORTS_RENAME = 2; // 0x2 - field public static final int FLAG_SUPPORTS_SEARCH = 16; // 0x10 - field public static final int FLAG_SUPPORTS_THUMBNAIL = 8; // 0x8 - field public static final int FLAG_SUPPORTS_WRITE = 32; // 0x20 - field public static final java.lang.String MIME_TYPE_DIRECTORY = "vnd.android.cursor.dir/doc"; - field public static final java.lang.String ROOT_DOC_ID = "0"; - field public static final int ROOT_TYPE_DEVICE = 3; // 0x3 - field public static final int ROOT_TYPE_DEVICE_ADVANCED = 4; // 0x4 - field public static final int ROOT_TYPE_SERVICE = 1; // 0x1 - field public static final int ROOT_TYPE_SHORTCUT = 2; // 0x2 } public static abstract interface DocumentsContract.DocumentColumns implements android.provider.OpenableColumns { @@ -20643,6 +20630,18 @@ package android.provider { field public static final java.lang.String SUMMARY = "summary"; } + public static class DocumentsContract.Documents { + field public static final java.lang.String DOC_ID_ROOT = "0"; + field public static final int FLAG_PREFERS_GRID = 64; // 0x40 + field public static final int FLAG_SUPPORTS_CREATE = 1; // 0x1 + field public static final int FLAG_SUPPORTS_DELETE = 4; // 0x4 + field public static final int FLAG_SUPPORTS_RENAME = 2; // 0x2 + field public static final int FLAG_SUPPORTS_SEARCH = 16; // 0x10 + field public static final int FLAG_SUPPORTS_THUMBNAIL = 8; // 0x8 + field public static final int FLAG_SUPPORTS_WRITE = 32; // 0x20 + field public static final java.lang.String MIME_TYPE_DIR = "vnd.android.cursor.dir/doc"; + } + public static abstract interface DocumentsContract.RootColumns { field public static final java.lang.String AVAILABLE_BYTES = "available_bytes"; field public static final java.lang.String ICON = "icon"; @@ -20652,6 +20651,15 @@ package android.provider { field public static final java.lang.String TITLE = "title"; } + public static class DocumentsContract.Roots { + field public static final java.lang.String MIME_TYPE_DIR = "vnd.android.cursor.dir/root"; + field public static final java.lang.String MIME_TYPE_ITEM = "vnd.android.cursor.item/root"; + field public static final int ROOT_TYPE_DEVICE = 3; // 0x3 + field public static final int ROOT_TYPE_DEVICE_ADVANCED = 4; // 0x4 + field public static final int ROOT_TYPE_SERVICE = 1; // 0x1 + field public static final int ROOT_TYPE_SHORTCUT = 2; // 0x2 + } + public final deprecated class LiveFolders implements android.provider.BaseColumns { field public static final java.lang.String ACTION_CREATE_LIVE_FOLDER = "android.intent.action.CREATE_LIVE_FOLDER"; field public static final java.lang.String DESCRIPTION = "description"; diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index ff350b92ab8a8..017ad98bf3bb4 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -2678,6 +2678,10 @@ public class Intent implements Parcelable, Cloneable { @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) public static final String ACTION_CREATE_DOCUMENT = "android.intent.action.CREATE_DOCUMENT"; + /** {@hide} */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_MANAGE_DOCUMENT = "android.intent.action.MANAGE_DOCUMENT"; + // --------------------------------------------------------------------- // --------------------------------------------------------------------- // Standard intent categories (see addCategory()). diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java index 909c4dda0c1ac..e1810ca05f39e 100644 --- a/core/java/android/provider/DocumentsContract.java +++ b/core/java/android/provider/DocumentsContract.java @@ -53,80 +53,85 @@ public final class DocumentsContract { // content://com.example/roots/sdcard/docs/0/contents/ // content://com.example/roots/sdcard/docs/0/search/?query=pony - /** - * MIME type of a document which is a directory that may contain additional - * documents. - * - * @see #buildContentsUri(String, String, String) - */ - public static final String MIME_TYPE_DIRECTORY = "vnd.android.cursor.dir/doc"; - /** {@hide} */ public static final String META_DATA_DOCUMENT_PROVIDER = "android.content.DOCUMENT_PROVIDER"; /** {@hide} */ public static final String ACTION_DOCUMENT_CHANGED = "android.provider.action.DOCUMENT_CHANGED"; - /** - * {@link DocumentColumns#DOC_ID} value representing the root directory of a - * storage root. - */ - public static final String ROOT_DOC_ID = "0"; + public static class Documents { + private Documents() { + } - /** - * Flag indicating that a document is a directory that supports creation of - * new files within it. - * - * @see DocumentColumns#FLAGS - * @see #createDocument(ContentResolver, Uri, String, String) - */ - public static final int FLAG_SUPPORTS_CREATE = 1; + /** + * MIME type of a document which is a directory that may contain additional + * documents. + * + * @see #buildContentsUri(String, String, String) + */ + public static final String MIME_TYPE_DIR = "vnd.android.cursor.dir/doc"; - /** - * Flag indicating that a document is renamable. - * - * @see DocumentColumns#FLAGS - * @see #renameDocument(ContentResolver, Uri, String) - */ - public static final int FLAG_SUPPORTS_RENAME = 1 << 1; + /** + * {@link DocumentColumns#DOC_ID} value representing the root directory of a + * storage root. + */ + public static final String DOC_ID_ROOT = "0"; - /** - * Flag indicating that a document is deletable. - * - * @see DocumentColumns#FLAGS - */ - public static final int FLAG_SUPPORTS_DELETE = 1 << 2; + /** + * Flag indicating that a document is a directory that supports creation of + * new files within it. + * + * @see DocumentColumns#FLAGS + * @see #createDocument(ContentResolver, Uri, String, String) + */ + public static final int FLAG_SUPPORTS_CREATE = 1; - /** - * Flag indicating that a document can be represented as a thumbnail. - * - * @see DocumentColumns#FLAGS - * @see #getThumbnail(ContentResolver, Uri, Point) - */ - public static final int FLAG_SUPPORTS_THUMBNAIL = 1 << 3; + /** + * Flag indicating that a document is renamable. + * + * @see DocumentColumns#FLAGS + * @see #renameDocument(ContentResolver, Uri, String) + */ + public static final int FLAG_SUPPORTS_RENAME = 1 << 1; - /** - * Flag indicating that a document is a directory that supports search. - * - * @see DocumentColumns#FLAGS - */ - public static final int FLAG_SUPPORTS_SEARCH = 1 << 4; + /** + * Flag indicating that a document is deletable. + * + * @see DocumentColumns#FLAGS + */ + public static final int FLAG_SUPPORTS_DELETE = 1 << 2; - /** - * Flag indicating that a document is writable. - * - * @see DocumentColumns#FLAGS - */ - public static final int FLAG_SUPPORTS_WRITE = 1 << 5; + /** + * Flag indicating that a document can be represented as a thumbnail. + * + * @see DocumentColumns#FLAGS + * @see #getThumbnail(ContentResolver, Uri, Point) + */ + public static final int FLAG_SUPPORTS_THUMBNAIL = 1 << 3; - /** - * Flag indicating that a document is a directory that prefers its contents - * be shown in a larger format grid. Usually suitable when a directory - * contains mostly pictures. - * - * @see DocumentColumns#FLAGS - */ - public static final int FLAG_PREFERS_GRID = 1 << 6; + /** + * Flag indicating that a document is a directory that supports search. + * + * @see DocumentColumns#FLAGS + */ + public static final int FLAG_SUPPORTS_SEARCH = 1 << 4; + + /** + * Flag indicating that a document is writable. + * + * @see DocumentColumns#FLAGS + */ + public static final int FLAG_SUPPORTS_WRITE = 1 << 5; + + /** + * Flag indicating that a document is a directory that prefers its contents + * be shown in a larger format grid. Usually suitable when a directory + * contains mostly pictures. + * + * @see DocumentColumns#FLAGS + */ + public static final int FLAG_PREFERS_GRID = 1 << 6; + } /** * Optimal dimensions for a document thumbnail request, stored as a @@ -189,7 +194,7 @@ public final class DocumentsContract { /** * Build URI representing the contents of the given directory in a storage - * backend. The given document must be {@link #MIME_TYPE_DIRECTORY}. + * backend. The given document must be {@link Documents#MIME_TYPE_DIR}. */ public static Uri buildContentsUri(String authority, String rootId, String docId) { return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority) @@ -296,7 +301,7 @@ public final class DocumentsContract { *

* Type: STRING * - * @see DocumentsContract#MIME_TYPE_DIRECTORY + * @see Documents#MIME_TYPE_DIR */ public static final String MIME_TYPE = "mime_type"; @@ -327,35 +332,43 @@ public final class DocumentsContract { public static final String SUMMARY = "summary"; } - /** - * Root that represents a cloud-based storage service. - * - * @see RootColumns#ROOT_TYPE - */ - public static final int ROOT_TYPE_SERVICE = 1; + public static class Roots { + private Roots() { + } - /** - * Root that represents a shortcut to content that may be available - * elsewhere through another storage root. - * - * @see RootColumns#ROOT_TYPE - */ - public static final int ROOT_TYPE_SHORTCUT = 2; + public static final String MIME_TYPE_DIR = "vnd.android.cursor.dir/root"; + public static final String MIME_TYPE_ITEM = "vnd.android.cursor.item/root"; - /** - * Root that represents a physical storage device. - * - * @see RootColumns#ROOT_TYPE - */ - public static final int ROOT_TYPE_DEVICE = 3; + /** + * Root that represents a cloud-based storage service. + * + * @see RootColumns#ROOT_TYPE + */ + public static final int ROOT_TYPE_SERVICE = 1; - /** - * Root that represents a physical storage device that should only be - * displayed to advanced users. - * - * @see RootColumns#ROOT_TYPE - */ - public static final int ROOT_TYPE_DEVICE_ADVANCED = 4; + /** + * Root that represents a shortcut to content that may be available + * elsewhere through another storage root. + * + * @see RootColumns#ROOT_TYPE + */ + public static final int ROOT_TYPE_SHORTCUT = 2; + + /** + * Root that represents a physical storage device. + * + * @see RootColumns#ROOT_TYPE + */ + public static final int ROOT_TYPE_DEVICE = 3; + + /** + * Root that represents a physical storage device that should only be + * displayed to advanced users. + * + * @see RootColumns#ROOT_TYPE + */ + public static final int ROOT_TYPE_DEVICE_ADVANCED = 4; + } /** * These are standard columns for the roots URI. @@ -370,8 +383,8 @@ public final class DocumentsContract { *

* Type: INTEGER (int) * - * @see DocumentsContract#ROOT_TYPE_SERVICE - * @see DocumentsContract#ROOT_TYPE_DEVICE + * @see Roots#ROOT_TYPE_SERVICE + * @see Roots#ROOT_TYPE_DEVICE */ public static final String ROOT_TYPE = "root_type"; @@ -440,7 +453,7 @@ public final class DocumentsContract { /** * Return thumbnail representing the document at the given URI. Callers are * responsible for their own caching. Given document must have - * {@link #FLAG_SUPPORTS_THUMBNAIL} set. + * {@link Documents#FLAG_SUPPORTS_THUMBNAIL} set. * * @return decoded thumbnail, or {@code null} if problem was encountered. */ @@ -465,7 +478,8 @@ public final class DocumentsContract { * Create a new document under a specific parent document with the given * display name and MIME type. * - * @param parentDocumentUri document with {@link #FLAG_SUPPORTS_CREATE} + * @param parentDocumentUri document with + * {@link Documents#FLAG_SUPPORTS_CREATE} * @param displayName name for new document * @param mimeType MIME type for new document, which cannot be changed * @return newly created document Uri, or {@code null} if failed @@ -480,7 +494,7 @@ public final class DocumentsContract { /** * Rename the document at the given URI. Given document must have - * {@link #FLAG_SUPPORTS_RENAME} set. + * {@link Documents#FLAG_SUPPORTS_RENAME} set. * * @return if rename was successful. */ diff --git a/packages/DocumentsUI/AndroidManifest.xml b/packages/DocumentsUI/AndroidManifest.xml index 9a1953fdeec81..518dcdc519bd7 100644 --- a/packages/DocumentsUI/AndroidManifest.xml +++ b/packages/DocumentsUI/AndroidManifest.xml @@ -32,6 +32,12 @@ + + + + + + parent, View view, int position, long id) { final Document doc = mAdapter.getItem(position); - ((DocumentsActivity) getActivity()).onDocumentPicked(doc); + if (mFilter.apply(doc)) { + ((DocumentsActivity) getActivity()).onDocumentPicked(doc); + } } }; @@ -389,7 +387,7 @@ public class DirectoryFragment extends Fragment { if (state.showSize) { size.setVisibility(View.VISIBLE); - if (doc.isDirectory()) { + if (doc.isDirectory() || doc.size == -1) { size.setText(null); } else { size.setText(Formatter.formatFileSize(context, doc.size)); @@ -415,16 +413,5 @@ public class DirectoryFragment extends Fragment { public long getItemId(int position) { return getItem(position).uri.hashCode(); } - - @Override - public boolean areAllItemsEnabled() { - return false; - } - - @Override - public boolean isEnabled(int position) { - final Document doc = getItem(position); - return mFilter.apply(doc); - } } } diff --git a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java index 89ba66e3d007e..11ccc89a50559 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java +++ b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java @@ -31,6 +31,7 @@ import android.database.Cursor; import android.graphics.drawable.ColorDrawable; import android.net.Uri; import android.os.Bundle; +import android.provider.DocumentsContract; import android.provider.DocumentsContract.DocumentColumns; import android.support.v4.app.ActionBarDrawerToggle; import android.support.v4.view.GravityCompat; @@ -63,6 +64,7 @@ public class DocumentsActivity extends Activity { public static final int ACTION_OPEN = 1; public static final int ACTION_CREATE = 2; public static final int ACTION_GET_CONTENT = 3; + public static final int ACTION_MANAGE = 4; private int mAction; @@ -91,6 +93,8 @@ public class DocumentsActivity extends Activity { mAction = ACTION_CREATE; } else if (Intent.ACTION_GET_CONTENT.equals(action)) { mAction = ACTION_GET_CONTENT; + } else if (Intent.ACTION_MANAGE_DOCUMENT.equals(action)) { + mAction = ACTION_MANAGE; } if (mAction == ACTION_OPEN || mAction == ACTION_GET_CONTENT) { @@ -98,7 +102,9 @@ public class DocumentsActivity extends Activity { Intent.EXTRA_ALLOW_MULTIPLE, false); } - if (intent.hasExtra(Intent.EXTRA_MIME_TYPES)) { + if (mAction == ACTION_MANAGE) { + mDisplayState.acceptMimes = new String[] { "*/*" }; + } else if (intent.hasExtra(Intent.EXTRA_MIME_TYPES)) { mDisplayState.acceptMimes = intent.getStringArrayExtra(Intent.EXTRA_MIME_TYPES); } else { mDisplayState.acceptMimes = new String[] { intent.getType() }; @@ -120,10 +126,14 @@ public class DocumentsActivity extends Activity { moreApps.setComponent(null); moreApps.setPackage(null); RootsFragment.show(getFragmentManager(), moreApps); - } else { + } else if (mAction == ACTION_OPEN || mAction == ACTION_CREATE) { RootsFragment.show(getFragmentManager(), null); } + if (mAction == ACTION_MANAGE) { + mDisplayState.sortOrder = DisplayState.SORT_ORDER_DATE; + } + mRootsContainer = findViewById(R.id.container_roots); mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout); @@ -134,26 +144,54 @@ public class DocumentsActivity extends Activity { mDrawerLayout.setDrawerListener(mDrawerListener); mDrawerLayout.setDrawerShadow(R.drawable.drawer_shadow, GravityCompat.START); - mDrawerLayout.openDrawer(mRootsContainer); + if (mAction == ACTION_MANAGE) { + mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED); - // Restore last stack for calling package - // TODO: move into async loader - final String packageName = getCallingPackage(); - final Cursor cursor = getContentResolver() - .query(RecentsProvider.buildResume(packageName), null, null, null, null); - try { - if (cursor.moveToFirst()) { - final String raw = cursor.getString( - cursor.getColumnIndex(RecentsProvider.COL_PATH)); - mStack = DocumentStack.deserialize(getContentResolver(), raw); + final Uri rootUri = intent.getData(); + final String authority = rootUri.getAuthority(); + final String rootId = DocumentsContract.getRootId(rootUri); + + final Root root = RootsCache.findRoot(this, authority, rootId); + if (root != null) { + onRootPicked(root, true); + } else { + Log.w(TAG, "Failed to find root: " + rootUri); + finish(); } - } catch (FileNotFoundException e) { - Log.w(TAG, "Failed to resume", e); - } finally { - cursor.close(); - } - onCurrentDirectoryChanged(); + } else { + mDrawerLayout.openDrawer(mRootsContainer); + + // Restore last stack for calling package + // TODO: move into async loader + final String packageName = getCallingPackage(); + final Cursor cursor = getContentResolver() + .query(RecentsProvider.buildResume(packageName), null, null, null, null); + try { + if (cursor.moveToFirst()) { + final String raw = cursor.getString( + cursor.getColumnIndex(RecentsProvider.COL_PATH)); + mStack = DocumentStack.deserialize(getContentResolver(), raw); + } + } catch (FileNotFoundException e) { + Log.w(TAG, "Failed to resume", e); + } finally { + cursor.close(); + } + + onCurrentDirectoryChanged(); + } + } + + @Override + public void onStart() { + super.onStart(); + + if (mAction == ACTION_MANAGE) { + mDisplayState.showSize = true; + } else { + mDisplayState.showSize = SettingsActivity.getDisplayFileSize(this); + } } private DrawerListener mDrawerListener = new DrawerListener() { @@ -190,7 +228,6 @@ public class DocumentsActivity extends Activity { final ActionBar actionBar = getActionBar(); actionBar.setDisplayShowHomeEnabled(true); - actionBar.setDisplayHomeAsUpEnabled(true); if (mDrawerLayout.isDrawerOpen(mRootsContainer)) { actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD); @@ -202,6 +239,9 @@ public class DocumentsActivity extends Activity { actionBar.setTitle(R.string.title_save); } + actionBar.setDisplayHomeAsUpEnabled(true); + mDrawerToggle.setDrawerIndicatorEnabled(true); + } else { final Root root = getCurrentRoot(); actionBar.setIcon(root != null ? root.icon : null); @@ -217,8 +257,13 @@ public class DocumentsActivity extends Activity { } if (mStack.size() > 1) { + actionBar.setDisplayHomeAsUpEnabled(true); + mDrawerToggle.setDrawerIndicatorEnabled(false); + } else if (mAction == ACTION_MANAGE) { + actionBar.setDisplayHomeAsUpEnabled(false); mDrawerToggle.setDrawerIndicatorEnabled(false); } else { + actionBar.setDisplayHomeAsUpEnabled(true); mDrawerToggle.setDrawerIndicatorEnabled(true); } } @@ -269,6 +314,7 @@ public class DocumentsActivity extends Activity { final MenuItem search = menu.findItem(R.id.menu_search); final MenuItem grid = menu.findItem(R.id.menu_grid); final MenuItem list = menu.findItem(R.id.menu_list); + final MenuItem settings = menu.findItem(R.id.menu_settings); grid.setVisible(mDisplayState.mode != DisplayState.MODE_GRID); list.setVisible(mDisplayState.mode != DisplayState.MODE_LIST); @@ -293,6 +339,8 @@ public class DocumentsActivity extends Activity { // TODO: close any search in-progress when hiding search.setVisible(searchVisible); + settings.setVisible(mAction != ACTION_MANAGE); + return true; } diff --git a/packages/DocumentsUI/src/com/android/documentsui/MimePredicate.java b/packages/DocumentsUI/src/com/android/documentsui/MimePredicate.java index f945c6a0d98c7..a9929deb9adfa 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/MimePredicate.java +++ b/packages/DocumentsUI/src/com/android/documentsui/MimePredicate.java @@ -49,7 +49,9 @@ public class MimePredicate implements Predicate { } public static boolean mimeMatches(String filter, String test) { - if (filter.equals(test)) { + if (test == null) { + return false; + } else if (filter.equals(test)) { return true; } else if ("*/*".equals(filter)) { return true; diff --git a/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java b/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java index ceab8fcb0b11a..acd9396ff3678 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java +++ b/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java @@ -27,6 +27,7 @@ import android.database.Cursor; import android.graphics.drawable.Drawable; import android.net.Uri; import android.provider.DocumentsContract; +import android.provider.DocumentsContract.Documents; import android.util.Log; import android.util.Pair; @@ -162,7 +163,7 @@ public class RootsCache { } } - if (DocumentsContract.MIME_TYPE_DIRECTORY.equals(mimeType)) { + if (Documents.MIME_TYPE_DIR.equals(mimeType)) { return context.getResources().getDrawable(R.drawable.ic_dir); } else { final PackageManager pm = context.getPackageManager(); diff --git a/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java b/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java index e32414b9d55e2..4973e1dbd98ae 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java +++ b/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java @@ -26,7 +26,7 @@ import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.os.Bundle; -import android.provider.DocumentsContract; +import android.provider.DocumentsContract.Roots; import android.text.format.Formatter; import android.util.Log; import android.view.LayoutInflater; @@ -44,7 +44,6 @@ import com.android.documentsui.model.Root; import com.android.documentsui.model.Root.RootComparator; import java.util.Collection; -import java.util.Iterator; import java.util.List; /** @@ -138,8 +137,8 @@ public class RootsFragment extends Fragment { // Device summary is always available space final String summaryText; - if ((root.rootType == DocumentsContract.ROOT_TYPE_DEVICE - || root.rootType == DocumentsContract.ROOT_TYPE_DEVICE_ADVANCED) + if ((root.rootType == Roots.ROOT_TYPE_DEVICE + || root.rootType == Roots.ROOT_TYPE_DEVICE_ADVANCED) && root.availableBytes >= 0) { summaryText = context.getString(R.string.root_available_bytes, Formatter.formatFileSize(context, root.availableBytes)); @@ -226,17 +225,17 @@ public class RootsFragment extends Fragment { for (Root root : roots) { Log.d(TAG, "Found rootType=" + root.rootType); switch (root.rootType) { - case DocumentsContract.ROOT_TYPE_SERVICE: + case Roots.ROOT_TYPE_SERVICE: mServices.add(root); break; - case DocumentsContract.ROOT_TYPE_SHORTCUT: + case Roots.ROOT_TYPE_SHORTCUT: mShortcuts.add(root); break; - case DocumentsContract.ROOT_TYPE_DEVICE: + case Roots.ROOT_TYPE_DEVICE: mDevices.add(root); mDevicesAdvanced.add(root); break; - case DocumentsContract.ROOT_TYPE_DEVICE_ADVANCED: + case Roots.ROOT_TYPE_DEVICE_ADVANCED: mDevicesAdvanced.add(root); break; } diff --git a/packages/DocumentsUI/src/com/android/documentsui/model/Document.java b/packages/DocumentsUI/src/com/android/documentsui/model/Document.java index 95922b40de3a5..cf45394e3fe20 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/model/Document.java +++ b/packages/DocumentsUI/src/com/android/documentsui/model/Document.java @@ -21,6 +21,7 @@ import android.database.Cursor; import android.net.Uri; import android.provider.DocumentsContract; import android.provider.DocumentsContract.DocumentColumns; +import android.provider.DocumentsContract.Documents; import com.android.documentsui.RecentsProvider; @@ -87,7 +88,7 @@ public class Document { final String mimeType = getCursorString(cursor, DocumentColumns.MIME_TYPE); final String displayName = getCursorString(cursor, DocumentColumns.DISPLAY_NAME); final int flags = getCursorInt(cursor, DocumentColumns.FLAGS) - & DocumentsContract.FLAG_SUPPORTS_THUMBNAIL; + & Documents.FLAG_SUPPORTS_THUMBNAIL; final String summary = getCursorString(cursor, DocumentColumns.SUMMARY); final long size = getCursorLong(cursor, DocumentColumns.SIZE); @@ -127,19 +128,19 @@ public class Document { } public boolean isCreateSupported() { - return (flags & DocumentsContract.FLAG_SUPPORTS_CREATE) != 0; + return (flags & Documents.FLAG_SUPPORTS_CREATE) != 0; } public boolean isSearchSupported() { - return (flags & DocumentsContract.FLAG_SUPPORTS_SEARCH) != 0; + return (flags & Documents.FLAG_SUPPORTS_SEARCH) != 0; } public boolean isThumbnailSupported() { - return (flags & DocumentsContract.FLAG_SUPPORTS_THUMBNAIL) != 0; + return (flags & Documents.FLAG_SUPPORTS_THUMBNAIL) != 0; } public boolean isDirectory() { - return DocumentsContract.MIME_TYPE_DIRECTORY.equals(mimeType); + return Documents.MIME_TYPE_DIR.equals(mimeType); } private static String getCursorString(Cursor cursor, String columnName) { @@ -147,9 +148,19 @@ public class Document { return (index != -1) ? cursor.getString(index) : null; } + /** + * Missing or null values are returned as -1. + */ private static long getCursorLong(Cursor cursor, String columnName) { final int index = cursor.getColumnIndex(columnName); - return (index != -1) ? cursor.getLong(index) : 0; + if (index == -1) return -1; + final String value = cursor.getString(index); + if (value == null) return -1; + try { + return Long.parseLong(value); + } catch (NumberFormatException e) { + return -1; + } } private static int getCursorInt(Cursor cursor, String columnName) { diff --git a/packages/DocumentsUI/src/com/android/documentsui/model/Root.java b/packages/DocumentsUI/src/com/android/documentsui/model/Root.java index 08807312e6134..23d16df1bf4e3 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/model/Root.java +++ b/packages/DocumentsUI/src/com/android/documentsui/model/Root.java @@ -24,7 +24,9 @@ import android.database.Cursor; import android.graphics.drawable.Drawable; import android.net.Uri; import android.provider.DocumentsContract; +import android.provider.DocumentsContract.Documents; import android.provider.DocumentsContract.RootColumns; +import android.provider.DocumentsContract.Roots; import com.android.documentsui.R; @@ -47,7 +49,7 @@ public class Root { final PackageManager pm = context.getPackageManager(); final Root root = new Root(); root.rootId = null; - root.rootType = DocumentsContract.ROOT_TYPE_SHORTCUT; + root.rootType = Roots.ROOT_TYPE_SHORTCUT; root.uri = null; root.icon = context.getResources().getDrawable(R.drawable.ic_dir); root.title = context.getString(R.string.root_recent); @@ -65,7 +67,7 @@ public class Root { root.rootId = cursor.getString(cursor.getColumnIndex(RootColumns.ROOT_ID)); root.rootType = cursor.getInt(cursor.getColumnIndex(RootColumns.ROOT_TYPE)); root.uri = DocumentsContract.buildDocumentUri( - info.providerInfo.authority, root.rootId, DocumentsContract.ROOT_DOC_ID); + info.providerInfo.authority, root.rootId, Documents.DOC_ID_ROOT); root.icon = info.providerInfo.loadIcon(pm); root.title = info.providerInfo.loadLabel(pm).toString(); root.availableBytes = cursor.getLong(cursor.getColumnIndex(RootColumns.AVAILABLE_BYTES)); diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java index 5c1248437729f..659139d5fb83e 100644 --- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java +++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java @@ -28,7 +28,9 @@ import android.os.ParcelFileDescriptor; import android.provider.BaseColumns; import android.provider.DocumentsContract; import android.provider.DocumentsContract.DocumentColumns; +import android.provider.DocumentsContract.Documents; import android.provider.DocumentsContract.RootColumns; +import android.provider.DocumentsContract.Roots; import android.util.Log; import android.webkit.MimeTypeMap; @@ -79,7 +81,7 @@ public class ExternalStorageProvider extends ContentProvider { mRoots.clear(); final Root root = new Root(); - root.rootType = DocumentsContract.ROOT_TYPE_DEVICE_ADVANCED; + root.rootType = Roots.ROOT_TYPE_DEVICE_ADVANCED; root.name = "primary"; root.title = getContext().getString(R.string.root_internal_storage); root.path = Environment.getExternalStorageDirectory(); @@ -173,7 +175,7 @@ public class ExternalStorageProvider extends ContentProvider { String rootPath = root.path.getAbsolutePath(); final String path = file.getAbsolutePath(); if (path.equals(rootPath)) { - return DocumentsContract.ROOT_DOC_ID; + return Documents.DOC_ID_ROOT; } if (!rootPath.endsWith("/")) { @@ -187,7 +189,7 @@ public class ExternalStorageProvider extends ContentProvider { } private File docIdToFile(Root root, String docId) { - if (DocumentsContract.ROOT_DOC_ID.equals(docId)) { + if (Documents.DOC_ID_ROOT.equals(docId)) { return root.path; } else { return new File(root.path, docId); @@ -204,26 +206,27 @@ public class ExternalStorageProvider extends ContentProvider { int flags = 0; if (file.isDirectory()) { - flags |= DocumentsContract.FLAG_SUPPORTS_SEARCH; + flags |= Documents.FLAG_SUPPORTS_SEARCH; } if (file.isDirectory() && file.canWrite()) { - flags |= DocumentsContract.FLAG_SUPPORTS_CREATE; + flags |= Documents.FLAG_SUPPORTS_CREATE; } if (file.canWrite()) { - flags |= DocumentsContract.FLAG_SUPPORTS_RENAME; - flags |= DocumentsContract.FLAG_SUPPORTS_DELETE; + flags |= Documents.FLAG_SUPPORTS_WRITE; + flags |= Documents.FLAG_SUPPORTS_RENAME; + flags |= Documents.FLAG_SUPPORTS_DELETE; } final String mimeType = getTypeForFile(file); if (mimeType.startsWith("image/")) { - flags |= DocumentsContract.FLAG_SUPPORTS_THUMBNAIL; + flags |= Documents.FLAG_SUPPORTS_THUMBNAIL; } final String docId = fileToDocId(root, file); final long id = docId.hashCode(); final String displayName; - if (DocumentsContract.ROOT_DOC_ID.equals(docId)) { + if (Documents.DOC_ID_ROOT.equals(docId)) { displayName = root.title; } else { displayName = file.getName(); @@ -236,6 +239,12 @@ public class ExternalStorageProvider extends ContentProvider { @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 Root root = mRoots.get(DocumentsContract.getRootId(uri)); final String docId = DocumentsContract.getDocId(uri); @@ -249,7 +258,7 @@ public class ExternalStorageProvider extends ContentProvider { private String getTypeForFile(File file) { if (file.isDirectory()) { - return DocumentsContract.MIME_TYPE_DIRECTORY; + return Documents.MIME_TYPE_DIR; } else { return getTypeForName(file.getName()); } @@ -299,7 +308,7 @@ public class ExternalStorageProvider extends ContentProvider { values.getAsString(DocumentColumns.DISPLAY_NAME), mimeType); final File file = new File(parent, name); - if (DocumentsContract.MIME_TYPE_DIRECTORY.equals(mimeType)) { + if (Documents.MIME_TYPE_DIR.equals(mimeType)) { if (!file.mkdir()) { return null; } @@ -359,7 +368,7 @@ public class ExternalStorageProvider extends ContentProvider { } private String validateDisplayName(String displayName, String mimeType) { - if (DocumentsContract.MIME_TYPE_DIRECTORY.equals(mimeType)) { + if (Documents.MIME_TYPE_DIR.equals(mimeType)) { return displayName; } else { // Try appending meaningful extension if needed