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
diff --git a/api/current.txt b/api/current.txt
index c0cb9f0..5a3c7db 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -20620,19 +20620,6 @@
     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 @@
     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 @@
     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 ff350b9..017ad98 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -2678,6 +2678,10 @@
     @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 909c4dd..e1810ca 100644
--- a/core/java/android/provider/DocumentsContract.java
+++ b/core/java/android/provider/DocumentsContract.java
@@ -53,80 +53,85 @@
     // 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 @@
 
     /**
      * 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 @@
          * <p>
          * 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 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 @@
          * <p>
          * 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 @@
     /**
      * 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 @@
      * 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 @@
 
     /**
      * 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 9a1953f..518dcdc 100644
--- a/packages/DocumentsUI/AndroidManifest.xml
+++ b/packages/DocumentsUI/AndroidManifest.xml
@@ -32,6 +32,12 @@
                 <category android:name="android.intent.category.OPENABLE" />
                 <data android:mimeType="*/*" />
             </intent-filter>
+            <!-- data expected to point at existing root to manage -->
+            <intent-filter>
+                <action android:name="android.intent.action.MANAGE_DOCUMENT" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:mimeType="vnd.android.cursor.item/root" />
+            </intent-filter>
         </activity>
 
         <activity
diff --git a/packages/DocumentsUI/src/com/android/documentsui/CreateDirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/CreateDirectoryFragment.java
index 313774b..575947f 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/CreateDirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/CreateDirectoryFragment.java
@@ -27,8 +27,8 @@
 import android.content.DialogInterface.OnClickListener;
 import android.net.Uri;
 import android.os.Bundle;
-import android.provider.DocumentsContract;
 import android.provider.DocumentsContract.DocumentColumns;
+import android.provider.DocumentsContract.Documents;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.widget.EditText;
@@ -69,7 +69,7 @@
                 final String displayName = text1.getText().toString();
 
                 final ContentValues values = new ContentValues();
-                values.put(DocumentColumns.MIME_TYPE, DocumentsContract.MIME_TYPE_DIRECTORY);
+                values.put(DocumentColumns.MIME_TYPE, Documents.MIME_TYPE_DIR);
                 values.put(DocumentColumns.DISPLAY_NAME, displayName);
 
                 final DocumentsActivity activity = (DocumentsActivity) getActivity();
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
index e1b6a915..ac5629e 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
@@ -190,10 +190,6 @@
     @Override
     public void onStart() {
         super.onStart();
-
-        final Context context = getActivity();
-        getDisplayState(this).showSize = SettingsActivity.getDisplayFileSize(context);
-
         getLoaderManager().restartLoader(mLoaderId, getArguments(), mCallbacks);
     }
 
@@ -244,7 +240,9 @@
         @Override
         public void onItemClick(AdapterView<?> 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 @@
 
             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 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 89ba66e..11ccc89 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.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 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 @@
             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 @@
                     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 @@
             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 @@
         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 @@
         final ActionBar actionBar = getActionBar();
 
         actionBar.setDisplayShowHomeEnabled(true);
-        actionBar.setDisplayHomeAsUpEnabled(true);
 
         if (mDrawerLayout.isDrawerOpen(mRootsContainer)) {
             actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD);
@@ -202,6 +239,9 @@
                 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 @@
             }
 
             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 @@
         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 @@
         // 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 f945c6a0..a9929de 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/MimePredicate.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/MimePredicate.java
@@ -49,7 +49,9 @@
     }
 
     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 ceab8fc..acd9396 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.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 @@
             }
         }
 
-        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 e32414b..4973e1d 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.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.RootComparator;
 
 import java.util.Collection;
-import java.util.Iterator;
 import java.util.List;
 
 /**
@@ -138,8 +137,8 @@
 
             // 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 @@
             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 95922b4..cf45394 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.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 @@
             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 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 @@
         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 0880731..23d16df 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.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 @@
         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 @@
         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 5c12484..659139d 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.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 @@
         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 @@
         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 @@
     }
 
     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 @@
         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 @@
     @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 @@
 
     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 @@
                         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 @@
     }
 
     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