Merge "Sliding drawer for roots, move sorting, search."
diff --git a/packages/DocumentsUI/Android.mk b/packages/DocumentsUI/Android.mk
index 1e45807..853353d 100644
--- a/packages/DocumentsUI/Android.mk
+++ b/packages/DocumentsUI/Android.mk
@@ -5,6 +5,8 @@
 
 LOCAL_SRC_FILES := $(call all-subdir-java-files)
 
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4
+
 LOCAL_PACKAGE_NAME := DocumentsUI
 LOCAL_CERTIFICATE := platform
 
diff --git a/packages/DocumentsUI/res/drawable-hdpi/drawer_shadow.9.png b/packages/DocumentsUI/res/drawable-hdpi/drawer_shadow.9.png
new file mode 100644
index 0000000..224cc4f
--- /dev/null
+++ b/packages/DocumentsUI/res/drawable-hdpi/drawer_shadow.9.png
Binary files differ
diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_drawer.png b/packages/DocumentsUI/res/drawable-hdpi/ic_drawer.png
new file mode 100644
index 0000000..ff7b1de
--- /dev/null
+++ b/packages/DocumentsUI/res/drawable-hdpi/ic_drawer.png
Binary files differ
diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_menu_search.png b/packages/DocumentsUI/res/drawable-hdpi/ic_menu_search.png
new file mode 100644
index 0000000..cc661e3
--- /dev/null
+++ b/packages/DocumentsUI/res/drawable-hdpi/ic_menu_search.png
Binary files differ
diff --git a/packages/DocumentsUI/res/layout/activity.xml b/packages/DocumentsUI/res/layout/activity.xml
index f96d459..eb6d803 100644
--- a/packages/DocumentsUI/res/layout/activity.xml
+++ b/packages/DocumentsUI/res/layout/activity.xml
@@ -14,20 +14,34 @@
      limitations under the License.
 -->
 
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/drawer_layout"
     android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:orientation="vertical">
+    android:layout_height="match_parent">
 
-    <FrameLayout
-        android:id="@+id/directory"
+    <LinearLayout
         android:layout_width="match_parent"
-        android:layout_height="0dip"
-        android:layout_weight="1" />
+        android:layout_height="match_parent"
+        android:orientation="vertical">
 
-    <FrameLayout
-        android:id="@+id/save"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content" />
+        <FrameLayout
+            android:id="@+id/directory"
+            android:layout_width="match_parent"
+            android:layout_height="0dip"
+            android:layout_weight="1" />
 
-</LinearLayout>
+        <FrameLayout
+            android:id="@+id/save"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content" />
+
+    </LinearLayout>
+
+    <ListView
+        android:id="@+id/roots_list"
+        android:layout_width="300dp"
+        android:layout_height="match_parent"
+        android:layout_gravity="start"
+        android:background="#fff" />
+
+</android.support.v4.widget.DrawerLayout>
diff --git a/packages/DocumentsUI/res/layout/item_backend.xml b/packages/DocumentsUI/res/layout/item_backend.xml
deleted file mode 100644
index 6ec7566..0000000
--- a/packages/DocumentsUI/res/layout/item_backend.xml
+++ /dev/null
@@ -1,58 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2013 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:paddingBottom="?android:attr/listPreferredItemPaddingEnd"
-    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
-
-    <FrameLayout
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:background="@color/chip"
-        android:foreground="?android:attr/selectableItemBackground"
-        android:duplicateParentState="true">
-
-        <LinearLayout
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:padding="8dp"
-            android:orientation="horizontal">
-
-            <ImageView
-                android:id="@android:id/icon"
-                android:layout_width="24dip"
-                android:layout_height="24dip"
-                android:layout_marginEnd="8dp"
-                android:scaleType="centerInside"
-                android:contentDescription="@null" />
-
-            <TextView
-                android:id="@android:id/text1"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:layout_gravity="center_vertical"
-                android:singleLine="true"
-                android:ellipsize="marquee"
-                android:textAppearance="?android:attr/textAppearanceSmall"
-                android:textAlignment="viewStart" />
-
-        </LinearLayout>
-
-    </FrameLayout>
-
-</FrameLayout>
diff --git a/packages/DocumentsUI/res/layout/item_root.xml b/packages/DocumentsUI/res/layout/item_root.xml
new file mode 100644
index 0000000..e9cf3aa
--- /dev/null
+++ b/packages/DocumentsUI/res/layout/item_root.xml
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:minHeight="?android:attr/listPreferredItemHeight"
+    android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+    android:gravity="center_vertical"
+    android:orientation="horizontal">
+
+    <ImageView
+        android:id="@android:id/icon"
+        android:layout_width="@android:dimen/app_icon_size"
+        android:layout_height="@android:dimen/app_icon_size"
+        android:layout_rowSpan="2"
+        android:layout_marginEnd="8dip"
+        android:scaleType="centerInside"
+        android:contentDescription="@null" />
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical">
+
+        <TextView
+            android:id="@android:id/title"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:singleLine="true"
+            android:ellipsize="marquee"
+            android:textAppearance="?android:attr/textAppearanceMedium"
+            android:textAlignment="viewStart" />
+
+        <TextView
+            android:id="@android:id/summary"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:singleLine="true"
+            android:ellipsize="marquee"
+            android:textAppearance="?android:attr/textAppearanceSmall"
+            android:textAlignment="viewStart" />
+
+    </LinearLayout>
+
+</LinearLayout>
diff --git a/packages/DocumentsUI/res/layout/item_title.xml b/packages/DocumentsUI/res/layout/item_title.xml
new file mode 100644
index 0000000..fe6c14d
--- /dev/null
+++ b/packages/DocumentsUI/res/layout/item_title.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:gravity="center_vertical"
+    android:orientation="vertical">
+
+    <TextView
+        android:id="@android:id/title"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:singleLine="true"
+        android:ellipsize="marquee"
+        android:textAppearance="?android:attr/textAppearanceMedium"
+        android:textAlignment="viewStart" />
+
+    <TextView
+        android:id="@android:id/summary"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:singleLine="true"
+        android:ellipsize="marquee"
+        android:textAppearance="?android:attr/textAppearanceSmall"
+        android:textAlignment="viewStart" />
+
+</LinearLayout>
diff --git a/packages/DocumentsUI/res/menu/activity.xml b/packages/DocumentsUI/res/menu/activity.xml
index bf7c161..a0d03b2 100644
--- a/packages/DocumentsUI/res/menu/activity.xml
+++ b/packages/DocumentsUI/res/menu/activity.xml
@@ -19,5 +19,12 @@
         android:id="@+id/menu_create_dir"
         android:title="@string/menu_create_dir"
         android:icon="@drawable/ic_menu_create_dir"
-        android:showAsAction="always" />
+        android:showAsAction="ifRoom" />
+    <item
+        android:id="@+id/menu_search"
+        android:title="@string/menu_search"
+        android:icon="@drawable/ic_menu_search"
+        android:showAsAction="always|collapseActionView"
+        android:actionViewClass="android.widget.SearchView"
+        android:imeOptions="actionSearch" />
 </menu>
diff --git a/packages/DocumentsUI/res/menu/directory.xml b/packages/DocumentsUI/res/menu/directory.xml
index c1fa228..12d0324 100644
--- a/packages/DocumentsUI/res/menu/directory.xml
+++ b/packages/DocumentsUI/res/menu/directory.xml
@@ -25,9 +25,4 @@
         android:title="@string/menu_list"
         android:icon="@drawable/ic_menu_list"
         android:showAsAction="ifRoom" />
-    <item
-        android:id="@+id/menu_sort"
-        android:title="@string/menu_sort"
-        android:icon="@drawable/ic_menu_sort"
-        android:showAsAction="ifRoom" />
 </menu>
diff --git a/packages/DocumentsUI/res/values/strings.xml b/packages/DocumentsUI/res/values/strings.xml
index 6ae2d12..18e486d 100644
--- a/packages/DocumentsUI/res/values/strings.xml
+++ b/packages/DocumentsUI/res/values/strings.xml
@@ -17,20 +17,24 @@
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label">Documents</string>
 
-    <string name="title_open">Open</string>
-    <string name="title_save">Save</string>
+    <string name="title_open">Open from</string>
+    <string name="title_save">Save to</string>
 
     <string name="menu_create_dir">Create folder</string>
     <string name="menu_grid">Grid view</string>
     <string name="menu_list">List view</string>
     <string name="menu_sort">Sort by</string>
+    <string name="menu_search">Search</string>
 
     <string name="menu_open">Open</string>
     <string name="menu_save">Save</string>
 
     <string name="mode_selected_count"><xliff:g id="count" example="3">%1$d</xliff:g> selected</string>
 
-    <string name="sort_name">Name</string>
-    <string name="sort_date">Date modified</string>
+    <string name="sort_name">By name</string>
+    <string name="sort_date">By date modified</string>
+
+    <string name="drawer_open">Open navigation drawer</string>
+    <string name="drawer_close">Close navigation drawer</string>
 
 </resources>
diff --git a/packages/DocumentsUI/src/com/android/documentsui/BackendFragment.java b/packages/DocumentsUI/src/com/android/documentsui/BackendFragment.java
deleted file mode 100644
index fc13487..0000000
--- a/packages/DocumentsUI/src/com/android/documentsui/BackendFragment.java
+++ /dev/null
@@ -1,175 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.documentsui;
-
-import android.app.Fragment;
-import android.app.FragmentManager;
-import android.app.FragmentTransaction;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.ProviderInfo;
-import android.content.res.Resources.NotFoundException;
-import android.database.Cursor;
-import android.graphics.drawable.Drawable;
-import android.net.Uri;
-import android.os.Bundle;
-import android.provider.DocumentsContract;
-import android.provider.DocumentsContract.RootColumns;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.AdapterView;
-import android.widget.AdapterView.OnItemClickListener;
-import android.widget.ArrayAdapter;
-import android.widget.GridView;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-import com.google.android.collect.Lists;
-
-import java.util.List;
-
-/**
- * Display all known storage roots.
- */
-public class BackendFragment extends Fragment {
-
-    // TODO: cluster backends by type
-
-    private GridView mGridView;
-    private BackendAdapter mAdapter;
-
-    public static void show(FragmentManager fm) {
-        final BackendFragment fragment = new BackendFragment();
-
-        final FragmentTransaction ft = fm.beginTransaction();
-        ft.replace(R.id.directory, fragment);
-        ft.setBreadCrumbTitle("TOP");
-        ft.commitAllowingStateLoss();
-    }
-
-    public static class Root {
-        public int rootType;
-        public Uri uri;
-        public Drawable icon;
-        public String title;
-        public String summary;
-
-        public static Root fromCursor(Context context, ProviderInfo info, Cursor cursor) {
-            final Root root = new Root();
-
-            root.rootType = cursor.getInt(cursor.getColumnIndex(RootColumns.ROOT_TYPE));
-            root.uri = DocumentsContract.buildDocumentUri(
-                    info.authority, cursor.getString(cursor.getColumnIndex(RootColumns.GUID)));
-
-            final PackageManager pm = context.getPackageManager();
-            final int icon = cursor.getInt(cursor.getColumnIndex(RootColumns.ICON));
-            if (icon != 0) {
-                try {
-                    root.icon = pm.getResourcesForApplication(info.applicationInfo)
-                            .getDrawable(icon);
-                } catch (NotFoundException e) {
-                    throw new RuntimeException(e);
-                } catch (NameNotFoundException e) {
-                    throw new RuntimeException(e);
-                }
-            } else {
-                root.icon = info.loadIcon(pm);
-            }
-
-            root.title = cursor.getString(cursor.getColumnIndex(RootColumns.TITLE));
-            if (root.title == null) {
-                root.title = info.loadLabel(pm).toString();
-            }
-
-            root.summary = cursor.getString(cursor.getColumnIndex(RootColumns.SUMMARY));
-
-            return root;
-        }
-    }
-
-    @Override
-    public View onCreateView(
-            LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
-        final Context context = inflater.getContext();
-
-        // Gather roots from known storage providers
-        final List<ProviderInfo> providers = context.getPackageManager()
-                .queryContentProviders(null, -1, PackageManager.GET_META_DATA);
-        final List<Root> roots = Lists.newArrayList();
-        for (ProviderInfo info : providers) {
-            if (info.metaData != null
-                    && info.metaData.containsKey(DocumentsContract.META_DATA_DOCUMENT_PROVIDER)) {
-                // TODO: populate roots on background thread, and cache results
-                final Uri uri = DocumentsContract.buildRootsUri(info.authority);
-                final Cursor cursor = context.getContentResolver()
-                        .query(uri, null, null, null, null);
-                try {
-                    while (cursor.moveToNext()) {
-                        roots.add(Root.fromCursor(context, info, cursor));
-                    }
-                } finally {
-                    cursor.close();
-                }
-            }
-        }
-
-        final View view = inflater.inflate(R.layout.fragment_backend, container, false);
-
-        mGridView = (GridView) view.findViewById(R.id.grid);
-        mGridView.setOnItemClickListener(mItemListener);
-
-        mAdapter = new BackendAdapter(context, roots);
-        mGridView.setAdapter(mAdapter);
-        mGridView.setNumColumns(GridView.AUTO_FIT);
-
-        return view;
-    }
-
-    private OnItemClickListener mItemListener = new OnItemClickListener() {
-        @Override
-        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
-            final Root root = mAdapter.getItem(position);
-            ((DocumentsActivity) getActivity()).onRootPicked(root);
-        }
-    };
-
-    public static class BackendAdapter extends ArrayAdapter<Root> {
-        public BackendAdapter(Context context, List<Root> list) {
-            super(context, android.R.layout.simple_list_item_1, list);
-        }
-
-        @Override
-        public View getView(int position, View convertView, ViewGroup parent) {
-            if (convertView == null) {
-                convertView = LayoutInflater.from(parent.getContext())
-                        .inflate(R.layout.item_backend, parent, false);
-            }
-
-            final ImageView icon = (ImageView) convertView.findViewById(android.R.id.icon);
-            final TextView text1 = (TextView) convertView.findViewById(android.R.id.text1);
-
-            final PackageManager pm = parent.getContext().getPackageManager();
-            final Root root = getItem(position);
-            icon.setImageDrawable(root.icon);
-            text1.setText(root.title);
-
-            return convertView;
-        }
-    }
-}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
index 531eaf3..8b3dd99 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
@@ -16,17 +16,12 @@
 
 package com.android.documentsui;
 
-import android.app.AlertDialog;
-import android.app.Dialog;
-import android.app.DialogFragment;
 import android.app.Fragment;
 import android.app.FragmentManager;
 import android.app.FragmentTransaction;
 import android.app.LoaderManager.LoaderCallbacks;
 import android.content.Context;
 import android.content.CursorLoader;
-import android.content.DialogInterface;
-import android.content.DialogInterface.OnClickListener;
 import android.content.Loader;
 import android.database.Cursor;
 import android.net.Uri;
@@ -65,8 +60,6 @@
 
     // TODO: show storage backend in item views when requested
 
-    private static final String TAG_SORT = "sort";
-
     private ListView mListView;
     private GridView mGridView;
 
@@ -81,7 +74,8 @@
 
     private static final int LOADER_DOCUMENTS = 2;
 
-    public static void show(FragmentManager fm, Uri uri, String displayName) {
+    public static void show(
+            FragmentManager fm, Uri uri, String displayName, boolean addToBackStack) {
         final Bundle args = new Bundle();
         args.putParcelable(EXTRA_URI, uri);
 
@@ -90,7 +84,9 @@
 
         final FragmentTransaction ft = fm.beginTransaction();
         ft.replace(R.id.directory, fragment);
-        ft.addToBackStack(displayName);
+        if (addToBackStack) {
+            ft.addToBackStack(displayName);
+        }
         ft.setBreadCrumbTitle(displayName);
         ft.commitAllowingStateLoss();
     }
@@ -136,7 +132,13 @@
                     sortOrder = null;
                 }
 
-                final Uri contentsUri = DocumentsContract.buildContentsUri(uri);
+                final Uri contentsUri;
+                if (uri.getQueryParameter(DocumentsContract.PARAM_QUERY) != null) {
+                    contentsUri = uri;
+                } else {
+                    contentsUri = DocumentsContract.buildContentsUri(uri);
+                }
+
                 return new CursorLoader(context, contentsUri, null, null, null, sortOrder);
             }
 
@@ -198,9 +200,6 @@
             updateMode();
             getFragmentManager().invalidateOptionsMenu();
             return true;
-        } else if (id == R.id.menu_sort) {
-            SortFragment.show(this);
-            return true;
         } else {
             return super.onOptionsItemSelected(item);
         }
@@ -238,7 +237,7 @@
         }
     }
 
-    private void updateSortBy() {
+    public void updateSortBy() {
         getLoaderManager().restartLoader(LOADER_DOCUMENTS, getArguments(), mCallbacks);
     }
 
@@ -358,38 +357,6 @@
         }
     }
 
-    public static class SortFragment extends DialogFragment {
-        public static void show(DirectoryFragment parent) {
-            if (!parent.isAdded()) return;
-
-            final SortFragment dialog = new SortFragment();
-            dialog.setTargetFragment(parent, 0);
-            dialog.show(parent.getFragmentManager(), TAG_SORT);
-        }
-
-        @Override
-        public Dialog onCreateDialog(Bundle savedInstanceState) {
-            final Context context = getActivity();
-            final DisplayState state = getDisplayState(this);
-
-            final AlertDialog.Builder builder = new AlertDialog.Builder(context);
-            builder.setTitle(R.string.menu_sort);
-            builder.setSingleChoiceItems(new CharSequence[] {
-                    getText(R.string.sort_name),
-                    getText(R.string.sort_date),
-            }, state.sortBy, new OnClickListener() {
-                @Override
-                public void onClick(DialogInterface dialog, int which) {
-                    state.sortBy = which;
-                    ((DirectoryFragment) getTargetFragment()).updateSortBy();
-                    dismiss();
-                }
-            });
-
-            return builder.create();
-        }
-    }
-
     private static int getDocumentFlags(Context context, Uri uri) {
         final Cursor cursor = context.getContentResolver().query(uri, new String[] {
                 DocumentColumns.FLAGS }, null, null, null);
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
index e61cea6..13def57 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
@@ -25,7 +25,6 @@
 import android.app.Dialog;
 import android.app.DialogFragment;
 import android.app.FragmentManager;
-import android.app.FragmentManager.BackStackEntry;
 import android.app.FragmentManager.OnBackStackChangedListener;
 import android.content.ClipData;
 import android.content.ContentResolver;
@@ -35,25 +34,42 @@
 import android.content.DialogInterface.OnClickListener;
 import android.content.Intent;
 import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ProviderInfo;
 import android.content.pm.ResolveInfo;
+import android.content.res.Resources.NotFoundException;
 import android.database.Cursor;
+import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
 import android.os.Bundle;
 import android.provider.DocumentsContract;
 import android.provider.DocumentsContract.DocumentColumns;
+import android.provider.DocumentsContract.RootColumns;
+import android.support.v4.app.ActionBarDrawerToggle;
+import android.support.v4.view.GravityCompat;
+import android.support.v4.widget.DrawerLayout;
+import android.support.v4.widget.DrawerLayout.DrawerListener;
 import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.Menu;
 import android.view.MenuItem;
 import android.view.View;
 import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.ArrayAdapter;
 import android.widget.BaseAdapter;
 import android.widget.EditText;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.SearchView;
+import android.widget.SearchView.OnQueryTextListener;
 import android.widget.TextView;
 
-import com.android.documentsui.BackendFragment.Root;
+import com.google.android.collect.Lists;
 
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 
@@ -63,18 +79,30 @@
     // TODO: fragment to show recently opened documents
     // TODO: pull actionbar icon from current backend
 
+    private static final String TAG_CREATE_DIRECTORY = "create_directory";
+
     private static final int ACTION_OPEN = 1;
     private static final int ACTION_CREATE = 2;
 
     private int mAction;
     private String[] mAcceptMimes;
 
+    private SearchView mSearchView;
+
+    private DrawerLayout mDrawerLayout;
+    private ActionBarDrawerToggle mDrawerToggle;
+
+    private ArrayList<Root> mRoots = Lists.newArrayList();
+    private RootsAdapter mRootsAdapter;
+    private ListView mRootsList;
+
     private final DisplayState mDisplayState = new DisplayState();
 
-    private boolean mIgnoreNextNavigation;
+    private Root mCurrentRoot;
 
     private Uri mCurrentDir;
     private boolean mCurrentSupportsCreate;
+    private boolean mCurrentSupportsSearch;
 
     @Override
     public void onCreate(Bundle icicle) {
@@ -106,40 +134,91 @@
         setContentView(R.layout.activity);
 
         getFragmentManager().addOnBackStackChangedListener(mStackListener);
-        BackendFragment.show(getFragmentManager());
-
-        updateActionBar();
 
         if (mAction == ACTION_CREATE) {
             final String mimeType = getIntent().getType();
             final String title = getIntent().getStringExtra(Intent.EXTRA_TITLE);
             SaveFragment.show(getFragmentManager(), mimeType, title);
         }
+
+        mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
+        mRootsAdapter = new RootsAdapter(this, mRoots);
+        mRootsList = (ListView) findViewById(R.id.roots_list);
+        mRootsList.setAdapter(mRootsAdapter);
+        mRootsList.setOnItemClickListener(mRootsListener);
+
+        mDrawerToggle = new ActionBarDrawerToggle(this, mDrawerLayout,
+                R.drawable.ic_drawer, R.string.drawer_open, R.string.drawer_close);
+
+        mDrawerLayout.setDrawerListener(mDrawerListener);
+        mDrawerLayout.setDrawerShadow(R.drawable.drawer_shadow, GravityCompat.START);
+
+        mDrawerLayout.openDrawer(mRootsList);
+
+        updateActionBar();
+        updateRoots();
+    }
+
+    private DrawerListener mDrawerListener = new DrawerListener() {
+        @Override
+        public void onDrawerSlide(View drawerView, float slideOffset) {
+            mDrawerToggle.onDrawerSlide(drawerView, slideOffset);
+        }
+
+        @Override
+        public void onDrawerOpened(View drawerView) {
+            mDrawerToggle.onDrawerOpened(drawerView);
+            updateActionBar();
+        }
+
+        @Override
+        public void onDrawerClosed(View drawerView) {
+            mDrawerToggle.onDrawerClosed(drawerView);
+            updateActionBar();
+        }
+
+        @Override
+        public void onDrawerStateChanged(int newState) {
+            mDrawerToggle.onDrawerStateChanged(newState);
+        }
+    };
+
+    @Override
+    protected void onPostCreate(Bundle savedInstanceState) {
+        super.onPostCreate(savedInstanceState);
+        mDrawerToggle.syncState();
     }
 
     public void updateActionBar() {
         final FragmentManager fm = getFragmentManager();
         final ActionBar actionBar = getActionBar();
 
-        if (fm.getBackStackEntryCount() > 0) {
-            actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST);
-            actionBar.setDisplayShowHomeEnabled(true);
-            actionBar.setDisplayHomeAsUpEnabled(true);
-            actionBar.setTitle(null);
-            actionBar.setListNavigationCallbacks(mStackAdapter, mNavigationListener);
-            actionBar.setSelectedNavigationItem(mStackAdapter.getCount() - 1);
-            mIgnoreNextNavigation = true;
+        actionBar.setDisplayShowHomeEnabled(true);
+        actionBar.setDisplayHomeAsUpEnabled(true);
 
-        } else {
+        if (mDrawerLayout.isDrawerOpen(mRootsList)) {
             actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD);
-            actionBar.setDisplayShowHomeEnabled(false);
-            actionBar.setDisplayHomeAsUpEnabled(false);
+            actionBar.setIcon(new ColorDrawable());
 
             if (mAction == ACTION_OPEN) {
                 actionBar.setTitle(R.string.title_open);
             } else if (mAction == ACTION_CREATE) {
                 actionBar.setTitle(R.string.title_save);
             }
+
+        } else {
+            actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST);
+            if (mCurrentRoot != null) {
+                actionBar.setIcon(mCurrentRoot.icon);
+            }
+            actionBar.setTitle(null);
+            actionBar.setListNavigationCallbacks(mSortAdapter, mSortListener);
+
+            if (fm.getBackStackEntryCount() > 0) {
+                mDrawerToggle.setDrawerIndicatorEnabled(false);
+            } else {
+                mDrawerToggle.setDrawerIndicatorEnabled(true);
+            }
         }
     }
 
@@ -147,6 +226,25 @@
     public boolean onCreateOptionsMenu(Menu menu) {
         super.onCreateOptionsMenu(menu);
         getMenuInflater().inflate(R.menu.activity, menu);
+
+        final MenuItem searchMenu = menu.findItem(R.id.menu_search);
+        mSearchView = (SearchView) searchMenu.getActionView();
+        mSearchView.setOnQueryTextListener(new OnQueryTextListener() {
+            @Override
+            public boolean onQueryTextSubmit(String query) {
+                // TODO: clear existing directory stack?
+                final Uri searchUri = DocumentsContract.buildSearchUri(mCurrentDir, query);
+                DirectoryFragment.show(getFragmentManager(), searchUri, query, true);
+                mSearchView.setIconified(true);
+                return true;
+            }
+
+            @Override
+            public boolean onQueryTextChange(String newText) {
+                return false;
+            }
+        });
+
         return true;
     }
 
@@ -158,17 +256,29 @@
         createDir.setVisible(mAction == ACTION_CREATE);
         createDir.setEnabled(mCurrentSupportsCreate);
 
+        // TODO: close any search in-progress when hiding
+        final MenuItem search = menu.findItem(R.id.menu_search);
+        search.setVisible(mCurrentSupportsSearch);
+
         return true;
     }
 
     @Override
     public boolean onOptionsItemSelected(MenuItem item) {
+        if (mDrawerToggle.onOptionsItemSelected(item)) {
+            return true;
+        }
+
         final int id = item.getItemId();
         if (id == android.R.id.home) {
             getFragmentManager().popBackStack();
             updateActionBar();
+            return true;
         } else if (id == R.id.menu_create_dir) {
             CreateDirectoryFragment.show(getFragmentManager());
+            return true;
+        } else if (id == R.id.menu_search) {
+            return false;
         }
         return super.onOptionsItemSelected(item);
     }
@@ -180,46 +290,73 @@
         }
     };
 
-    private BaseAdapter mStackAdapter = new BaseAdapter() {
+    // TODO: support additional sort orders
+    private BaseAdapter mSortAdapter = new BaseAdapter() {
         @Override
         public int getCount() {
-            return getFragmentManager().getBackStackEntryCount();
+            return 2;
         }
 
         @Override
         public Object getItem(int position) {
-            return getFragmentManager().getBackStackEntryAt(position);
+            switch (position) {
+                case 0:
+                    return getText(R.string.sort_name);
+                case 1:
+                    return getText(R.string.sort_date);
+                default:
+                    return null;
+            }
         }
 
         @Override
         public long getItemId(int position) {
-            return getFragmentManager().getBackStackEntryAt(position).getId();
+            return position;
         }
 
         @Override
         public View getView(int position, View convertView, ViewGroup parent) {
             if (convertView == null) {
                 convertView = LayoutInflater.from(parent.getContext())
+                        .inflate(R.layout.item_title, parent, false);
+            }
+
+            final TextView title = (TextView) convertView.findViewById(android.R.id.title);
+            final TextView summary = (TextView) convertView.findViewById(android.R.id.summary);
+
+            final FragmentManager fm = getFragmentManager();
+            final int count = fm.getBackStackEntryCount();
+            if (count > 0) {
+                title.setText(fm.getBackStackEntryAt(count - 1).getBreadCrumbTitle());
+            } else if (mCurrentRoot != null) {
+                title.setText(mCurrentRoot.title);
+            } else {
+                title.setText(null);
+            }
+
+            summary.setText((String) getItem(position));
+
+            return convertView;
+        }
+
+        @Override
+        public View getDropDownView(int position, View convertView, ViewGroup parent) {
+            if (convertView == null) {
+                convertView = LayoutInflater.from(parent.getContext())
                         .inflate(android.R.layout.simple_dropdown_item_1line, parent, false);
             }
 
-            final BackStackEntry entry = getFragmentManager().getBackStackEntryAt(position);
             final TextView text1 = (TextView) convertView.findViewById(android.R.id.text1);
-            text1.setText(entry.getBreadCrumbTitle());
+            text1.setText((String) getItem(position));
 
             return convertView;
         }
     };
 
-    private OnNavigationListener mNavigationListener = new OnNavigationListener() {
+    private OnNavigationListener mSortListener = new OnNavigationListener() {
         @Override
         public boolean onNavigationItemSelected(int itemPosition, long itemId) {
-            if (mIgnoreNextNavigation) {
-                mIgnoreNextNavigation = false;
-                return false;
-            }
-
-            getFragmentManager().popBackStack((int) itemId, 0);
+            // TODO: request updated sort order
             return true;
         }
     };
@@ -231,6 +368,7 @@
     public void onDirectoryChanged(Uri uri, int flags) {
         mCurrentDir = uri;
         mCurrentSupportsCreate = (flags & DocumentsContract.FLAG_SUPPORTS_CREATE) != 0;
+        mCurrentSupportsSearch = (flags & DocumentsContract.FLAG_SUPPORTS_SEARCH) != 0;
 
         if (mAction == ACTION_CREATE) {
             final FragmentManager fm = getFragmentManager();
@@ -240,15 +378,11 @@
         invalidateOptionsMenu();
     }
 
-    public void onRootPicked(Root root) {
-        DirectoryFragment.show(getFragmentManager(), root.uri, root.title);
-    }
-
     public void onDocumentPicked(Document doc) {
         final FragmentManager fm = getFragmentManager();
         if (DocumentsContract.MIME_TYPE_DIRECTORY.equals(doc.mimeType)) {
             // Nested directory picked, recurse using new fragment
-            DirectoryFragment.show(fm, doc.uri, doc.displayName);
+            DirectoryFragment.show(fm, doc.uri, doc.displayName, true);
         } else if (mAction == ACTION_OPEN) {
             // Explicit file picked, return
             onFinished(doc.uri);
@@ -313,6 +447,46 @@
         public static final int SORT_BY_DATE = 1;
     }
 
+    public static class Root {
+        public int rootType;
+        public Uri uri;
+        public Drawable icon;
+        public String title;
+        public String summary;
+
+        public static Root fromCursor(Context context, ProviderInfo info, Cursor cursor) {
+            final Root root = new Root();
+
+            root.rootType = cursor.getInt(cursor.getColumnIndex(RootColumns.ROOT_TYPE));
+            root.uri = DocumentsContract.buildDocumentUri(
+                    info.authority, cursor.getString(cursor.getColumnIndex(RootColumns.GUID)));
+
+            final PackageManager pm = context.getPackageManager();
+            final int icon = cursor.getInt(cursor.getColumnIndex(RootColumns.ICON));
+            if (icon != 0) {
+                try {
+                    root.icon = pm.getResourcesForApplication(info.applicationInfo)
+                            .getDrawable(icon);
+                } catch (NotFoundException e) {
+                    throw new RuntimeException(e);
+                } catch (NameNotFoundException e) {
+                    throw new RuntimeException(e);
+                }
+            } else {
+                root.icon = info.loadIcon(pm);
+            }
+
+            root.title = cursor.getString(cursor.getColumnIndex(RootColumns.TITLE));
+            if (root.title == null) {
+                root.title = info.loadLabel(pm).toString();
+            }
+
+            root.summary = cursor.getString(cursor.getColumnIndex(RootColumns.SUMMARY));
+
+            return root;
+        }
+    }
+
     public static class Document {
         public Uri uri;
         public String mimeType;
@@ -386,7 +560,74 @@
         }
     }
 
-    private static final String TAG_CREATE_DIRECTORY = "create_directory";
+    /**
+     * Gather roots from all known storage providers.
+     */
+    private void updateRoots() {
+        mRoots.clear();
+
+        final List<ProviderInfo> providers = getPackageManager()
+                .queryContentProviders(null, -1, PackageManager.GET_META_DATA);
+        for (ProviderInfo info : providers) {
+            if (info.metaData != null
+                    && info.metaData.containsKey(DocumentsContract.META_DATA_DOCUMENT_PROVIDER)) {
+                // TODO: populate roots on background thread, and cache results
+                final Uri uri = DocumentsContract.buildRootsUri(info.authority);
+                final Cursor cursor = getContentResolver().query(uri, null, null, null, null);
+                try {
+                    while (cursor.moveToNext()) {
+                        mRoots.add(Root.fromCursor(this, info, cursor));
+                    }
+                } finally {
+                    cursor.close();
+                }
+            }
+        }
+    }
+
+    private OnItemClickListener mRootsListener = new OnItemClickListener() {
+        @Override
+        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+            // Clear entire backstack and start in new root
+            final FragmentManager fm = getFragmentManager();
+            while (fm.getBackStackEntryCount() > 0) {
+                fm.popBackStackImmediate();
+            }
+
+            mCurrentRoot = mRootsAdapter.getItem(position);
+            DirectoryFragment.show(
+                    getFragmentManager(), mCurrentRoot.uri, mCurrentRoot.title, false);
+
+            mDrawerLayout.closeDrawers();
+        }
+    };
+
+    public static class RootsAdapter extends ArrayAdapter<Root> {
+        public RootsAdapter(Context context, List<Root> list) {
+            super(context, android.R.layout.simple_list_item_1, list);
+        }
+
+        @Override
+        public View getView(int position, View convertView, ViewGroup parent) {
+            if (convertView == null) {
+                convertView = LayoutInflater.from(parent.getContext())
+                        .inflate(R.layout.item_root, parent, false);
+            }
+
+            final ImageView icon = (ImageView) convertView.findViewById(android.R.id.icon);
+            final TextView title = (TextView) convertView.findViewById(android.R.id.title);
+            final TextView summary = (TextView) convertView.findViewById(android.R.id.summary);
+
+            final Root root = getItem(position);
+            icon.setImageDrawable(root.icon);
+            title.setText(root.title);
+
+            summary.setText(root.summary);
+            summary.setVisibility(root.summary != null ? View.VISIBLE : View.GONE);
+
+            return convertView;
+        }
+    }
 
     public static class CreateDirectoryFragment extends DialogFragment {
         public static void show(FragmentManager fm) {