Sliding drawer for roots, move sorting, search.
Move backend root exploration into sliding drawer, and adjust action
bar when moving between modes. Moves sorting into action bar spinner
instead of separate dialog. Also add initial search support.
Change-Id: I70189911ba56ae6bd93d5c503a8600acd6d6c0c5
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) {