Iterate on storage UI.

Support both grid and list view of documents.  Show breadcrumb
navigation trail in action bar.  Start supporting file and directory
creation.

Change-Id: I93a973da7b0d4387a57fe719e7bb20944adb0290
diff --git a/packages/DocumentsUI/AndroidManifest.xml b/packages/DocumentsUI/AndroidManifest.xml
index 84c5474..69ee466 100644
--- a/packages/DocumentsUI/AndroidManifest.xml
+++ b/packages/DocumentsUI/AndroidManifest.xml
@@ -7,13 +7,15 @@
         <activity
             android:name=".DocumentsActivity"
             android:finishOnCloseSystemDialogs="true"
-            android:excludeFromRecents="true">
+            android:excludeFromRecents="true"
+            android:theme="@android:style/Theme.Holo.Light">
             <intent-filter android:priority="100">
                 <action android:name="android.intent.action.OPEN_DOCUMENT" />
                 <category android:name="android.intent.category.DEFAULT" />
             </intent-filter>
             <intent-filter android:priority="100">
                 <action android:name="android.intent.action.CREATE_DOCUMENT" />
+                <data android:mimeType="*/*"/>
                 <category android:name="android.intent.category.DEFAULT" />
             </intent-filter>
         </activity>
diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_dir.png b/packages/DocumentsUI/res/drawable-hdpi/ic_dir.png
new file mode 100644
index 0000000..d02534f
--- /dev/null
+++ b/packages/DocumentsUI/res/drawable-hdpi/ic_dir.png
Binary files differ
diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_menu_create_dir.png b/packages/DocumentsUI/res/drawable-hdpi/ic_menu_create_dir.png
new file mode 100644
index 0000000..6eb31f1
--- /dev/null
+++ b/packages/DocumentsUI/res/drawable-hdpi/ic_menu_create_dir.png
Binary files differ
diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_menu_grid.png b/packages/DocumentsUI/res/drawable-hdpi/ic_menu_grid.png
new file mode 100644
index 0000000..d1326e5
--- /dev/null
+++ b/packages/DocumentsUI/res/drawable-hdpi/ic_menu_grid.png
Binary files differ
diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_menu_list.png b/packages/DocumentsUI/res/drawable-hdpi/ic_menu_list.png
new file mode 100644
index 0000000..e03e345
--- /dev/null
+++ b/packages/DocumentsUI/res/drawable-hdpi/ic_menu_list.png
Binary files differ
diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_menu_sort.png b/packages/DocumentsUI/res/drawable-hdpi/ic_menu_sort.png
new file mode 100644
index 0000000..680d482
--- /dev/null
+++ b/packages/DocumentsUI/res/drawable-hdpi/ic_menu_sort.png
Binary files differ
diff --git a/packages/DocumentsUI/res/layout/activity.xml b/packages/DocumentsUI/res/layout/activity.xml
new file mode 100644
index 0000000..f96d459
--- /dev/null
+++ b/packages/DocumentsUI/res/layout/activity.xml
@@ -0,0 +1,33 @@
+<?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="match_parent"
+    android:orientation="vertical">
+
+    <FrameLayout
+        android:id="@+id/directory"
+        android:layout_width="match_parent"
+        android:layout_height="0dip"
+        android:layout_weight="1" />
+
+    <FrameLayout
+        android:id="@+id/save"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content" />
+
+</LinearLayout>
diff --git a/packages/DocumentsUI/res/layout/fragment_backend.xml b/packages/DocumentsUI/res/layout/fragment_backend.xml
new file mode 100644
index 0000000..2648de2
--- /dev/null
+++ b/packages/DocumentsUI/res/layout/fragment_backend.xml
@@ -0,0 +1,29 @@
+<?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="match_parent">
+
+    <GridView
+        android:id="@+id/grid"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:listSelector="@android:color/transparent"
+        android:paddingTop="?android:attr/listPreferredItemPaddingStart"
+        android:paddingStart="?android:attr/listPreferredItemPaddingStart" />
+
+</FrameLayout>
diff --git a/packages/DocumentsUI/res/layout/fragment_directory.xml b/packages/DocumentsUI/res/layout/fragment_directory.xml
new file mode 100644
index 0000000..8085fa8
--- /dev/null
+++ b/packages/DocumentsUI/res/layout/fragment_directory.xml
@@ -0,0 +1,34 @@
+<?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="match_parent">
+
+    <ListView
+        android:id="@+id/list"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+
+    <GridView
+        android:id="@+id/grid"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:listSelector="@android:color/transparent"
+        android:paddingTop="?android:attr/listPreferredItemPaddingStart"
+        android:paddingStart="?android:attr/listPreferredItemPaddingStart" />
+
+</FrameLayout>
diff --git a/packages/DocumentsUI/res/layout/fragment_save.xml b/packages/DocumentsUI/res/layout/fragment_save.xml
new file mode 100644
index 0000000..5a4ce94
--- /dev/null
+++ b/packages/DocumentsUI/res/layout/fragment_save.xml
@@ -0,0 +1,51 @@
+<?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:orientation="horizontal"
+    android:gravity="center_vertical"
+    android:background="@color/chip"
+    android:minHeight="?android:attr/listPreferredItemHeightSmall">
+
+    <ImageView
+        android:id="@android:id/icon"
+        android:layout_width="24dp"
+        android:layout_height="24dp"
+        android:layout_marginStart="8dp"
+        android:layout_marginEnd="8dp"
+        android:scaleType="centerInside"
+        android:contentDescription="@null" />
+
+    <EditText
+        android:id="@android:id/title"
+        android:layout_width="0dip"
+        android:layout_height="wrap_content"
+        android:layout_weight="1"
+        android:singleLine="true" />
+
+    <Button
+        android:id="@android:id/button1"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:background="?android:attr/selectableItemBackground"
+        android:text="@string/btn_save"
+        android:textAllCaps="true"
+        android:textAppearance="?android:attr/textAppearanceSmall"
+        android:padding="8dp" />
+
+</LinearLayout>
diff --git a/packages/DocumentsUI/res/layout/item_backend.xml b/packages/DocumentsUI/res/layout/item_backend.xml
new file mode 100644
index 0000000..6ec7566
--- /dev/null
+++ b/packages/DocumentsUI/res/layout/item_backend.xml
@@ -0,0 +1,58 @@
+<?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_doc_grid.xml b/packages/DocumentsUI/res/layout/item_doc_grid.xml
new file mode 100644
index 0000000..93666ab
--- /dev/null
+++ b/packages/DocumentsUI/res/layout/item_doc_grid.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.
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="140dip"
+    android:paddingBottom="?android:attr/listPreferredItemPaddingEnd"
+    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
+
+    <FrameLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:background="@color/chip"
+        android:foreground="?android:attr/selectableItemBackground"
+        android:duplicateParentState="true">
+
+        <GridLayout
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:columnCount="1">
+
+            <ImageView
+                android:id="@android:id/icon"
+                android:layout_width="match_parent"
+                android:layout_height="0dip"
+                android:layout_gravity="fill_vertical"
+                android:background="#bbb"
+                android:scaleType="centerInside"
+                android:contentDescription="@null" />
+
+            <TextView
+                android:id="@android:id/title"
+                android:layout_width="match_parent"
+                android:layout_marginStart="8dip"
+                android:layout_marginEnd="8dip"
+                android:layout_marginTop="8dip"
+                android:layout_marginBottom="8dip"
+                android:singleLine="true"
+                android:ellipsize="marquee"
+                android:textAppearance="?android:attr/textAppearanceSmall"
+                android:textAlignment="viewStart" />
+
+        </GridLayout>
+
+    </FrameLayout>
+
+</FrameLayout>
diff --git a/packages/DocumentsUI/res/layout/item_doc_list.xml b/packages/DocumentsUI/res/layout/item_doc_list.xml
new file mode 100644
index 0000000..85fca79
--- /dev/null
+++ b/packages/DocumentsUI/res/layout/item_doc_list.xml
@@ -0,0 +1,61 @@
+<?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.
+-->
+
+<GridLayout 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:paddingTop="8dip"
+    android:paddingBottom="8dip"
+    android:columnCount="3">
+
+    <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" />
+
+    <TextView
+        android:id="@android:id/title"
+        android:layout_width="0dip"
+        android:layout_gravity="fill_horizontal"
+        android:layout_marginTop="2dip"
+        android:layout_columnSpan="2"
+        android:singleLine="true"
+        android:ellipsize="marquee"
+        android:textAppearance="?android:attr/textAppearanceMedium"
+        android:textAlignment="viewStart" />
+
+    <ImageView
+        android:id="@android:id/icon1"
+        android:layout_width="24dip"
+        android:layout_height="24dip"
+        android:layout_marginEnd="8dip"
+        android:visibility="gone"
+        android:scaleType="centerInside"
+        android:contentDescription="@null" />
+
+    <TextView
+        android:id="@android:id/summary"
+        android:layout_marginTop="2dip"
+        android:textAppearance="?android:attr/textAppearanceSmall" />
+
+</GridLayout>
diff --git a/packages/DocumentsUI/res/menu/activity.xml b/packages/DocumentsUI/res/menu/activity.xml
new file mode 100644
index 0000000..c675fb8
--- /dev/null
+++ b/packages/DocumentsUI/res/menu/activity.xml
@@ -0,0 +1,22 @@
+<?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.
+-->
+
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+    <item
+        android:id="@+id/menu_create_dir"
+        android:title="@string/menu_create_dir"
+        android:icon="@drawable/ic_menu_create_dir" />
+</menu>
diff --git a/packages/DocumentsUI/res/menu/directory.xml b/packages/DocumentsUI/res/menu/directory.xml
new file mode 100644
index 0000000..52c8f5c
--- /dev/null
+++ b/packages/DocumentsUI/res/menu/directory.xml
@@ -0,0 +1,30 @@
+<?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.
+-->
+
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+    <item
+        android:id="@+id/menu_grid"
+        android:title="@string/menu_grid"
+        android:icon="@drawable/ic_menu_grid" />
+    <item
+        android:id="@+id/menu_list"
+        android:title="@string/menu_list"
+        android:icon="@drawable/ic_menu_list" />
+    <item
+        android:id="@+id/menu_sort"
+        android:title="@string/menu_sort"
+        android:icon="@drawable/ic_menu_sort" />
+</menu>
diff --git a/packages/DocumentsUI/res/values/colors.xml b/packages/DocumentsUI/res/values/colors.xml
new file mode 100644
index 0000000..ff3e999
--- /dev/null
+++ b/packages/DocumentsUI/res/values/colors.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+
+<resources>
+    <color name="chip">#ddd</color>
+</resources>
diff --git a/packages/DocumentsUI/res/values/strings.xml b/packages/DocumentsUI/res/values/strings.xml
index 89f6496..65f8da8 100644
--- a/packages/DocumentsUI/res/values/strings.xml
+++ b/packages/DocumentsUI/res/values/strings.xml
@@ -16,4 +16,15 @@
 
 <resources>
     <string name="app_label">Documents</string>
+
+    <string name="title_open">Open</string>
+    <string name="title_save">Save</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="btn_save">Save</string>
+
 </resources>
diff --git a/packages/DocumentsUI/src/com/android/documentsui/BackendFragment.java b/packages/DocumentsUI/src/com/android/documentsui/BackendFragment.java
new file mode 100644
index 0000000..2980e23
--- /dev/null
+++ b/packages/DocumentsUI/src/com/android/documentsui/BackendFragment.java
@@ -0,0 +1,119 @@
+/*
+ * 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.ProviderInfo;
+import android.os.Bundle;
+import android.provider.DocumentsContract;
+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 backends.
+ */
+public class BackendFragment extends Fragment {
+
+    // TODO: handle multiple accounts from single backend
+
+    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();
+    }
+
+    @Override
+    public View onCreateView(
+            LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+        final Context context = inflater.getContext();
+
+        // Gather known storage providers
+        final List<ProviderInfo> providers = context.getPackageManager()
+                .queryContentProviders(null, -1, PackageManager.GET_META_DATA);
+        final List<ProviderInfo> backends = Lists.newArrayList();
+        for (ProviderInfo info : providers) {
+            if (info.metaData != null
+                    && info.metaData.containsKey(DocumentsContract.META_DATA_DOCUMENT_PROVIDER)) {
+                backends.add(info);
+            }
+        }
+
+        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, backends);
+        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 ProviderInfo info = mAdapter.getItem(position);
+            ((DocumentsActivity) getActivity()).onBackendPicked(info);
+        }
+    };
+
+    public static class BackendAdapter extends ArrayAdapter<ProviderInfo> {
+        public BackendAdapter(Context context, List<ProviderInfo> 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 ProviderInfo info = getItem(position);
+            icon.setImageDrawable(info.loadIcon(pm));
+            text1.setText(info.loadLabel(pm));
+
+            return convertView;
+        }
+    }
+}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/ColumnAdapter.java b/packages/DocumentsUI/src/com/android/documentsui/ColumnAdapter.java
new file mode 100644
index 0000000..092b5db
--- /dev/null
+++ b/packages/DocumentsUI/src/com/android/documentsui/ColumnAdapter.java
@@ -0,0 +1,139 @@
+/*
+ * 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.database.DataSetObserver;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.LinearLayout;
+import android.widget.ListAdapter;
+import android.widget.ListView;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * Adapter that wraps an existing adapter, presenting its contents in multiple
+ * equally-sized horizontal columns.
+ */
+public class ColumnAdapter extends BaseAdapter {
+    private final ListAdapter mWrapped;
+    private final OnItemClickListener mListener;
+
+    private int mColumns = 1;
+
+    public interface OnItemClickListener {
+        public void onItemClick(ListAdapter adapter, int position);
+    }
+
+    public ColumnAdapter(ListAdapter wrapped, OnItemClickListener listener) {
+        mWrapped = Preconditions.checkNotNull(wrapped);
+        mListener = Preconditions.checkNotNull(listener);
+
+        if (!wrapped.areAllItemsEnabled()) {
+            throw new IllegalStateException("All items must be enabled");
+        }
+        if (wrapped.getViewTypeCount() > 1) {
+            throw new IllegalStateException("All items must be identical");
+        }
+    }
+
+    public static void prepare(ListView list) {
+        list.setItemsCanFocus(true);
+    }
+
+    public void setColumns(int columns) {
+        mColumns = columns;
+        notifyDataSetChanged();
+    }
+
+    private View.OnClickListener mItemListener = new View.OnClickListener() {
+        @Override
+        public void onClick(View v) {
+            final int position = (Integer) v.getTag();
+            mListener.onItemClick(mWrapped, position);
+        }
+    };
+
+    @Override
+    public int getCount() {
+        return (mWrapped.getCount() + mColumns - 1) / mColumns;
+    }
+
+    @Override
+    public Object getItem(int position) {
+        return position;
+    }
+
+    @Override
+    public long getItemId(int position) {
+        return position;
+    }
+
+    @Override
+    public View getView(int position, View convertView, ViewGroup parent) {
+        if (convertView == null) {
+            convertView = new LinearLayout(parent.getContext());
+        }
+
+        final LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
+                0, LinearLayout.LayoutParams.WRAP_CONTENT);
+        params.weight = 1f / mColumns;
+
+        final LinearLayout row = (LinearLayout) convertView;
+        final int first = position * mColumns;
+        final int last = mWrapped.getCount() - 1;
+
+        for (int i = 0; i < mColumns; i++) {
+            View convertItem = null;
+            if (i < row.getChildCount()) {
+                convertItem = row.getChildAt(i);
+            }
+
+            final int pos = first + i;
+            final int validPos = Math.min(pos, last);
+            final View item = mWrapped.getView(validPos, convertItem, row);
+            item.setTag(validPos);
+            item.setOnClickListener(mItemListener);
+            item.setFocusable(true);
+
+            if (pos == validPos) {
+                item.setVisibility(View.VISIBLE);
+            } else {
+                item.setVisibility(View.INVISIBLE);
+            }
+
+            if (convertItem == null) {
+                row.addView(item, params);
+            }
+        }
+
+        return convertView;
+    }
+
+    @Override
+    public void registerDataSetObserver(DataSetObserver observer) {
+        super.registerDataSetObserver(observer);
+        mWrapped.registerDataSetObserver(observer);
+    }
+
+    @Override
+    public void unregisterDataSetObserver(DataSetObserver observer) {
+        super.unregisterDataSetObserver(observer);
+        mWrapped.unregisterDataSetObserver(observer);
+    }
+}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
index d43abde..5ba1930 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
@@ -16,9 +16,9 @@
 
 package com.android.documentsui;
 
+import android.app.Fragment;
 import android.app.FragmentManager;
 import android.app.FragmentTransaction;
-import android.app.ListFragment;
 import android.app.LoaderManager.LoaderCallbacks;
 import android.content.Context;
 import android.content.CursorLoader;
@@ -28,49 +28,94 @@
 import android.os.Bundle;
 import android.provider.DocumentsContract;
 import android.provider.DocumentsContract.DocumentColumns;
+import android.text.format.DateUtils;
 import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
 import android.view.View;
 import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
 import android.widget.CursorAdapter;
+import android.widget.GridView;
 import android.widget.ImageView;
 import android.widget.ListView;
 import android.widget.TextView;
 
-public class DirectoryFragment extends ListFragment {
+import com.android.documentsui.DocumentsActivity.Document;
+
+/**
+ * Display the documents inside a single directory.
+ */
+public class DirectoryFragment extends Fragment {
+
+    // TODO: show storage backend in item views when requested
+    // TODO: implement sorting dialog
+    // TODO: support multiple selection with actionmode
+
+    private ListView mListView;
+    private GridView mGridView;
+
     private DocumentsAdapter mAdapter;
     private LoaderCallbacks<Cursor> mCallbacks;
 
+    private int mFlags;
+
     private static final String EXTRA_URI = "uri";
+    private static final String EXTRA_MODE = "display_mode";
+
+    private static final int MODE_LIST = 1;
+    private static final int MODE_GRID = 2;
 
     private static final int LOADER_DOCUMENTS = 2;
 
-    public static void show(FragmentManager fm, Uri uri, CharSequence title) {
+    public static void show(FragmentManager fm, Uri uri, String displayName) {
         final Bundle args = new Bundle();
         args.putParcelable(EXTRA_URI, uri);
+        args.putInt(EXTRA_MODE, MODE_LIST);
 
         final DirectoryFragment fragment = new DirectoryFragment();
         fragment.setArguments(args);
 
         final FragmentTransaction ft = fm.beginTransaction();
-        ft.replace(android.R.id.content, fragment);
-        ft.addToBackStack(title.toString());
-        ft.setBreadCrumbTitle(title);
+        ft.replace(R.id.directory, fragment);
+        ft.addToBackStack(displayName);
+        ft.setBreadCrumbTitle(displayName);
         ft.commitAllowingStateLoss();
     }
 
     @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setHasOptionsMenu(true);
+    }
+
+    @Override
     public View onCreateView(
             LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
         final Context context = inflater.getContext();
 
+        final View view = inflater.inflate(R.layout.fragment_directory, container, false);
+
+        mListView = (ListView) view.findViewById(R.id.list);
+        mListView.setOnItemClickListener(mItemListener);
+
+        mGridView = (GridView) view.findViewById(R.id.grid);
+        mGridView.setOnItemClickListener(mItemListener);
+
         mAdapter = new DocumentsAdapter(context);
-        setListAdapter(mAdapter);
+        updateMode();
+
+        // TODO: migrate flags query to loader
+        final Uri uri = getArguments().getParcelable(EXTRA_URI);
+        mFlags = getDocumentFlags(context, uri);
 
         mCallbacks = new LoaderCallbacks<Cursor>() {
             @Override
             public Loader<Cursor> onCreateLoader(int id, Bundle args) {
-                final Uri uri = args.getParcelable(EXTRA_URI);
-                return new CursorLoader(context, uri, null, null, null, null);
+                final Uri contentsUri = DocumentsContract.buildContentsUri(uri);
+                return new CursorLoader(context, contentsUri, null, null, null, null);
             }
 
             @Override
@@ -84,13 +129,17 @@
             }
         };
 
-        return super.onCreateView(inflater, container, savedInstanceState);
+        return view;
     }
 
     @Override
     public void onStart() {
         super.onStart();
         getLoaderManager().restartLoader(LOADER_DOCUMENTS, getArguments(), mCallbacks);
+
+        // TODO: clean up tracking of current directory
+        final Uri uri = getArguments().getParcelable(EXTRA_URI);
+        ((DocumentsActivity) getActivity()).onDirectoryChanged(uri, mFlags);
     }
 
     @Override
@@ -100,26 +149,70 @@
     }
 
     @Override
-    public void onListItemClick(ListView l, View v, int position, long id) {
-        final Cursor cursor = (Cursor) mAdapter.getItem(position);
-        final String guid = getCursorString(cursor, DocumentColumns.GUID);
-        final String mimeType = getCursorString(cursor, DocumentColumns.MIME_TYPE);
+    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+        super.onCreateOptionsMenu(menu, inflater);
+        inflater.inflate(R.menu.directory, menu);
+    }
 
-        final Uri uri = getArguments().getParcelable(EXTRA_URI);
-        final Uri childUri = DocumentsContract.buildDocumentUri(uri.getAuthority(), guid);
+    @Override
+    public void onPrepareOptionsMenu(Menu menu) {
+        super.onPrepareOptionsMenu(menu);
+        final int mode = getMode();
+        menu.findItem(R.id.menu_grid).setVisible(mode != MODE_GRID);
+        menu.findItem(R.id.menu_list).setVisible(mode != MODE_LIST);
+    }
 
-        if (DocumentsContract.MIME_TYPE_DIRECTORY.equals(mimeType)) {
-            // Nested directory picked, recurse using new fragment
-            final Uri childContentsUri = DocumentsContract.buildContentsUri(childUri);
-            final String displayName = cursor.getString(
-                    cursor.getColumnIndex(DocumentColumns.DISPLAY_NAME));
-            DirectoryFragment.show(getFragmentManager(), childContentsUri, displayName);
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        final int id = item.getItemId();
+        if (id == R.id.menu_grid) {
+            getArguments().putInt(EXTRA_MODE, MODE_GRID);
+            updateMode();
+            return true;
+        } else if (id == R.id.menu_list) {
+            getArguments().putInt(EXTRA_MODE, MODE_LIST);
+            updateMode();
+            return true;
         } else {
-            // Explicit file picked, return
-            ((DocumentsActivity) getActivity()).onDocumentPicked(childUri);
+            return super.onOptionsItemSelected(item);
         }
     }
 
+    private void updateMode() {
+        final int mode = getMode();
+
+        mListView.setVisibility(mode == MODE_LIST ? View.VISIBLE : View.GONE);
+        mGridView.setVisibility(mode == MODE_GRID ? View.VISIBLE : View.GONE);
+
+        if (mode == MODE_GRID) {
+            mListView.setAdapter(null);
+            mGridView.setAdapter(mAdapter);
+            mGridView.setNumColumns(GridView.AUTO_FIT);
+        } else {
+            mGridView.setAdapter(null);
+            mListView.setAdapter(mAdapter);
+        }
+    }
+
+    private OnItemClickListener mItemListener = new OnItemClickListener() {
+        @Override
+        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+            final Cursor cursor = (Cursor) mAdapter.getItem(position);
+
+            final Uri uri = getArguments().getParcelable(EXTRA_URI);
+            final Document doc = Document.fromCursor(uri.getAuthority(), cursor);
+            ((DocumentsActivity) getActivity()).onDocumentPicked(doc);
+        }
+    };
+
+    private boolean getSupportsCreate() {
+        return (mFlags & DocumentsContract.FLAG_SUPPORTS_CREATE) != 0;
+    }
+
+    private int getMode() {
+        return getArguments().getInt(EXTRA_MODE, MODE_LIST);
+    }
+
     private class DocumentsAdapter extends CursorAdapter {
         public DocumentsAdapter(Context context) {
             super(context, null, false);
@@ -127,8 +220,15 @@
 
         @Override
         public View newView(Context context, Cursor cursor, ViewGroup parent) {
-            return LayoutInflater.from(context)
-                    .inflate(com.android.internal.R.layout.preference, parent, false);
+            final LayoutInflater inflater = LayoutInflater.from(context);
+            final int mode = getMode();
+            if (mode == MODE_LIST) {
+                return inflater.inflate(R.layout.item_doc_list, parent, false);
+            } else if (mode == MODE_GRID) {
+                return inflater.inflate(R.layout.item_doc_grid, parent, false);
+            } else {
+                throw new IllegalStateException();
+            }
         }
 
         @Override
@@ -137,12 +237,10 @@
             final TextView summary = (TextView) view.findViewById(android.R.id.summary);
             final ImageView icon = (ImageView) view.findViewById(android.R.id.icon);
 
-            icon.setMaxWidth(128);
-            icon.setMaxHeight(128);
-
             final String guid = getCursorString(cursor, DocumentColumns.GUID);
             final String displayName = getCursorString(cursor, DocumentColumns.DISPLAY_NAME);
             final String mimeType = getCursorString(cursor, DocumentColumns.MIME_TYPE);
+            final long lastModified = getCursorLong(cursor, DocumentColumns.LAST_MODIFIED);
             final int flags = getCursorInt(cursor, DocumentColumns.FLAGS);
 
             if ((flags & DocumentsContract.FLAG_SUPPORTS_THUMBNAIL) != 0) {
@@ -150,19 +248,39 @@
                 final Uri childUri = DocumentsContract.buildDocumentUri(uri.getAuthority(), guid);
                 icon.setImageURI(childUri);
             } else {
-                icon.setImageURI(null);
+                icon.setImageDrawable(DocumentsActivity.resolveDocumentIcon(context, mimeType));
             }
 
             title.setText(displayName);
-            summary.setText(mimeType);
+            if (summary != null) {
+                summary.setText(DateUtils.getRelativeTimeSpanString(lastModified));
+            }
         }
     }
-    
-    private static String getCursorString(Cursor cursor, String columnName) {
+
+    private static int getDocumentFlags(Context context, Uri uri) {
+        final Cursor cursor = context.getContentResolver().query(uri, new String[] {
+                DocumentColumns.FLAGS }, null, null, null);
+        try {
+            if (cursor.moveToFirst()) {
+                return getCursorInt(cursor, DocumentColumns.FLAGS);
+            } else {
+                return 0;
+            }
+        } finally {
+            cursor.close();
+        }
+    }
+
+    public static String getCursorString(Cursor cursor, String columnName) {
         return cursor.getString(cursor.getColumnIndex(columnName));
     }
 
-    private static int getCursorInt(Cursor cursor, String columnName) {
+    public static long getCursorLong(Cursor cursor, String columnName) {
+        return cursor.getLong(cursor.getColumnIndex(columnName));
+    }
+
+    public static int getCursorInt(Cursor cursor, String columnName) {
         return cursor.getInt(cursor.getColumnIndex(columnName));
     }
 }
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
index 196776b..c83edc4 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
@@ -16,49 +16,229 @@
 
 package com.android.documentsui;
 
+import static com.android.documentsui.DirectoryFragment.getCursorString;
+
+import android.app.ActionBar;
+import android.app.ActionBar.OnNavigationListener;
 import android.app.Activity;
 import android.app.FragmentManager;
-import android.app.FragmentTransaction;
-import android.app.ListFragment;
+import android.app.FragmentManager.BackStackEntry;
+import android.app.FragmentManager.OnBackStackChangedListener;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.pm.ProviderInfo;
+import android.content.pm.ResolveInfo;
+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.DocumentColumns;
 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.ArrayAdapter;
-import android.widget.ListView;
-
-import com.google.android.collect.Lists;
-
-import java.util.ArrayList;
-import java.util.List;
+import android.widget.BaseAdapter;
+import android.widget.TextView;
 
 public class DocumentsActivity extends Activity {
     private static final String TAG = "Documents";
 
+    // TODO: fragment to show recently opened documents
+    // TODO: pull actionbar icon from current backend
+
+    private static final int MODE_OPEN = 1;
+    private static final int MODE_CREATE = 2;
+
+    private int mMode;
+    private boolean mIgnoreNextNavigation;
+
+    private Uri mCurrentDir;
+    private boolean mCurrentSupportsCreate;
+
     @Override
     public void onCreate(Bundle icicle) {
         super.onCreate(icicle);
 
-        SourceFragment.show(getFragmentManager());
+        final String action = getIntent().getAction();
+        if (Intent.ACTION_OPEN_DOCUMENT.equals(action)) {
+            mMode = MODE_OPEN;
+        } else if (Intent.ACTION_CREATE_DOCUMENT.equals(action)) {
+            mMode = MODE_CREATE;
+        }
+
         setResult(Activity.RESULT_CANCELED);
+        setContentView(R.layout.activity);
+
+        getFragmentManager().addOnBackStackChangedListener(mStackListener);
+        BackendFragment.show(getFragmentManager());
+
+        updateActionBar();
+
+        if (mMode == MODE_CREATE) {
+            final String mimeType = getIntent().getType();
+            final String title = getIntent().getStringExtra(Intent.EXTRA_TITLE);
+            SaveFragment.show(getFragmentManager(), mimeType, title);
+        }
     }
 
-    public void onDocumentPicked(Uri uri) {
-        Log.d(TAG, "onDocumentPicked() " + uri);
+    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;
+
+        } else {
+            actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD);
+            actionBar.setDisplayShowHomeEnabled(false);
+            actionBar.setDisplayHomeAsUpEnabled(false);
+
+            if (mMode == MODE_OPEN) {
+                actionBar.setTitle(R.string.title_open);
+            } else if (mMode == MODE_CREATE) {
+                actionBar.setTitle(R.string.title_save);
+            }
+        }
+    }
+
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        super.onCreateOptionsMenu(menu);
+        getMenuInflater().inflate(R.menu.activity, menu);
+        return true;
+    }
+
+    @Override
+    public boolean onPrepareOptionsMenu(Menu menu) {
+        super.onPrepareOptionsMenu(menu);
+
+        final MenuItem createDir = menu.findItem(R.id.menu_create_dir);
+        createDir.setVisible(mMode == MODE_CREATE);
+        createDir.setEnabled(mCurrentSupportsCreate);
+
+        return true;
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        final int id = item.getItemId();
+        if (id == android.R.id.home) {
+            getFragmentManager().popBackStack();
+            updateActionBar();
+        } else if (id == R.id.menu_create_dir) {
+            // TODO: show dialog to create directory
+        }
+        return super.onOptionsItemSelected(item);
+    }
+
+    private OnBackStackChangedListener mStackListener = new OnBackStackChangedListener() {
+        @Override
+        public void onBackStackChanged() {
+            updateActionBar();
+        }
+    };
+
+    private BaseAdapter mStackAdapter = new BaseAdapter() {
+        @Override
+        public int getCount() {
+            return getFragmentManager().getBackStackEntryCount();
+        }
+
+        @Override
+        public Object getItem(int position) {
+            return getFragmentManager().getBackStackEntryAt(position);
+        }
+
+        @Override
+        public long getItemId(int position) {
+            return getFragmentManager().getBackStackEntryAt(position).getId();
+        }
+
+        @Override
+        public View getView(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());
+
+            return convertView;
+        }
+    };
+
+    private OnNavigationListener mNavigationListener = new OnNavigationListener() {
+        @Override
+        public boolean onNavigationItemSelected(int itemPosition, long itemId) {
+            if (mIgnoreNextNavigation) {
+                mIgnoreNextNavigation = false;
+                return false;
+            }
+
+            getFragmentManager().popBackStack((int) itemId, 0);
+            return true;
+        }
+    };
+
+    public void onDirectoryChanged(Uri uri, int flags) {
+        mCurrentDir = uri;
+        mCurrentSupportsCreate = (flags & DocumentsContract.FLAG_SUPPORTS_CREATE) != 0;
+
+        if (mMode == MODE_CREATE) {
+            final FragmentManager fm = getFragmentManager();
+            SaveFragment.get(fm).setSaveEnabled(mCurrentSupportsCreate);
+        }
+
+        invalidateOptionsMenu();
+    }
+
+    public void onBackendPicked(ProviderInfo info) {
+        final Uri uri = DocumentsContract.buildDocumentUri(
+                info.authority, DocumentsContract.ROOT_GUID);
+        final CharSequence displayName = info.loadLabel(getPackageManager());
+        DirectoryFragment.show(getFragmentManager(), uri, displayName.toString());
+    }
+
+    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);
+        } else if (mMode == MODE_OPEN) {
+            // Explicit file picked, return
+            onFinished(doc.uri);
+        } else if (mMode == MODE_CREATE) {
+            // Overwrite current filename
+            SaveFragment.get(fm).setDisplayName(doc.displayName);
+        }
+    }
+
+    public void onSaveRequested(String mimeType, String displayName) {
+        // TODO: create file, confirming before overwriting
+        onFinished(null);
+    }
+
+    private void onFinished(Uri uri) {
+        Log.d(TAG, "onFinished() " + uri);
 
         final Intent intent = new Intent();
         intent.setData(uri);
 
         intent.addFlags(
                 Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_PERSIST_GRANT_URI_PERMISSION);
-        if (Intent.ACTION_CREATE_DOCUMENT.equals(getIntent().getAction())) {
+        if (mMode == MODE_CREATE) {
             intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
         }
 
@@ -66,50 +246,37 @@
         finish();
     }
 
-    public static class SourceFragment extends ListFragment {
-        private ArrayList<ProviderInfo> mProviders = Lists.newArrayList();
-        private ArrayAdapter<ProviderInfo> mAdapter;
+    public static class Document {
+        public Uri uri;
+        public String mimeType;
+        public String displayName;
 
-        public static void show(FragmentManager fm) {
-            final SourceFragment fragment = new SourceFragment();
-
-            final FragmentTransaction ft = fm.beginTransaction();
-            ft.replace(android.R.id.content, fragment);
-            ft.setBreadCrumbTitle("TOP");
-            ft.commitAllowingStateLoss();
+        public static Document fromCursor(String authority, Cursor cursor) {
+            final Document doc = new Document();
+            final String guid = getCursorString(cursor, DocumentColumns.GUID);
+            doc.uri = DocumentsContract.buildDocumentUri(authority, guid);
+            doc.mimeType = getCursorString(cursor, DocumentColumns.MIME_TYPE);
+            doc.displayName = getCursorString(cursor, DocumentColumns.DISPLAY_NAME);
+            return doc;
         }
+    }
 
-        @Override
-        public View onCreateView(
-                LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
-            final Context context = inflater.getContext();
+    public static Drawable resolveDocumentIcon(Context context, String mimeType) {
+        // TODO: allow backends to provide custom MIME icons
+        if (DocumentsContract.MIME_TYPE_DIRECTORY.equals(mimeType)) {
+            return context.getResources().getDrawable(R.drawable.ic_dir);
+        } else {
+            final PackageManager pm = context.getPackageManager();
+            final Intent intent = new Intent(Intent.ACTION_VIEW);
+            intent.setType(mimeType);
 
-            // Gather known storage providers
-            mProviders.clear();
-            final List<ProviderInfo> providers = context.getPackageManager()
-                    .queryContentProviders(null, -1, PackageManager.GET_META_DATA);
-            for (ProviderInfo info : providers) {
-                if (info.metaData != null
-                        && info.metaData.containsKey(
-                                DocumentsContract.META_DATA_DOCUMENT_PROVIDER)) {
-                    mProviders.add(info);
-                }
+            final ResolveInfo info = pm.resolveActivity(
+                    intent, PackageManager.MATCH_DEFAULT_ONLY);
+            if (info != null) {
+                return info.loadIcon(pm);
+            } else {
+                return null;
             }
-
-            mAdapter = new ArrayAdapter<ProviderInfo>(
-                    context, android.R.layout.simple_list_item_1, mProviders);
-            setListAdapter(mAdapter);
-
-            return super.onCreateView(inflater, container, savedInstanceState);
-        }
-
-        @Override
-        public void onListItemClick(ListView l, View v, int position, long id) {
-            final ProviderInfo info = mAdapter.getItem(position);
-            final Uri uri = DocumentsContract.buildContentsUri(DocumentsContract.buildDocumentUri(
-                    info.authority, DocumentsContract.ROOT_GUID));
-            final String displayName = info.name;
-            DirectoryFragment.show(getFragmentManager(), uri, displayName);
         }
     }
 }
diff --git a/packages/DocumentsUI/src/com/android/documentsui/SaveFragment.java b/packages/DocumentsUI/src/com/android/documentsui/SaveFragment.java
new file mode 100644
index 0000000..19a1d2a
--- /dev/null
+++ b/packages/DocumentsUI/src/com/android/documentsui/SaveFragment.java
@@ -0,0 +1,98 @@
+/*
+ * 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.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.ImageView;
+
+/**
+ * Display document title editor and save button.
+ */
+public class SaveFragment extends Fragment {
+    public static final String TAG = "SaveFragment";
+
+    private EditText mDisplayName;
+    private Button mSave;
+
+    private static final String EXTRA_MIME_TYPE = "mime_type";
+    private static final String EXTRA_DISPLAY_NAME = "display_name";
+
+    public static void show(FragmentManager fm, String mimeType, String displayName) {
+        final Bundle args = new Bundle();
+        args.putString(EXTRA_MIME_TYPE, mimeType);
+        args.putString(EXTRA_DISPLAY_NAME, displayName);
+
+        final SaveFragment fragment = new SaveFragment();
+        fragment.setArguments(args);
+
+        final FragmentTransaction ft = fm.beginTransaction();
+        ft.replace(R.id.save, fragment, TAG);
+        ft.commitAllowingStateLoss();
+    }
+
+    public static SaveFragment get(FragmentManager fm) {
+        return (SaveFragment) fm.findFragmentByTag(TAG);
+    }
+
+    @Override
+    public View onCreateView(
+            LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+        final Context context = inflater.getContext();
+
+        final View view = inflater.inflate(R.layout.fragment_save, container, false);
+
+        final ImageView icon = (ImageView) view.findViewById(android.R.id.icon);
+        icon.setImageDrawable(DocumentsActivity.resolveDocumentIcon(
+                context, getArguments().getString(EXTRA_MIME_TYPE)));
+
+        mDisplayName = (EditText) view.findViewById(android.R.id.title);
+        mDisplayName.setText(getArguments().getString(EXTRA_DISPLAY_NAME));
+
+        mSave = (Button) view.findViewById(android.R.id.button1);
+        mSave.setOnClickListener(mSaveListener);
+        mSave.setEnabled(false);
+
+        return view;
+    }
+
+    private View.OnClickListener mSaveListener = new View.OnClickListener() {
+        @Override
+        public void onClick(View v) {
+            final String mimeType = getArguments().getString(EXTRA_MIME_TYPE);
+            final String displayName = getArguments().getString(EXTRA_DISPLAY_NAME);
+            ((DocumentsActivity) getActivity()).onSaveRequested(mimeType, displayName);
+        }
+    };
+
+    public void setDisplayName(String displayName) {
+        getArguments().putString(EXTRA_DISPLAY_NAME, displayName);
+        mDisplayName.setText(displayName);
+    }
+
+    public void setSaveEnabled(boolean enabled) {
+        mSave.setEnabled(enabled);
+    }
+}