From bc2971d2499f5bf5534ced7296ece630ca902738 Mon Sep 17 00:00:00 2001 From: Jeff Sharkey Date: Wed, 31 Jul 2013 20:53:22 -0700 Subject: [PATCH] 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 --- packages/DocumentsUI/Android.mk | 2 + .../res/drawable-hdpi/drawer_shadow.9.png | Bin 0 -> 171 bytes .../res/drawable-hdpi/ic_drawer.png | Bin 0 -> 2842 bytes .../res/drawable-hdpi/ic_menu_search.png | Bin 0 -> 1653 bytes packages/DocumentsUI/res/layout/activity.xml | 38 ++- .../DocumentsUI/res/layout/item_backend.xml | 58 ---- packages/DocumentsUI/res/layout/item_root.xml | 60 ++++ .../DocumentsUI/res/layout/item_title.xml | 41 +++ packages/DocumentsUI/res/menu/activity.xml | 9 +- packages/DocumentsUI/res/menu/directory.xml | 5 - packages/DocumentsUI/res/values/strings.xml | 12 +- .../android/documentsui/BackendFragment.java | 175 ---------- .../documentsui/DirectoryFragment.java | 59 +--- .../documentsui/DocumentsActivity.java | 313 ++++++++++++++++-- 14 files changed, 435 insertions(+), 337 deletions(-) create mode 100644 packages/DocumentsUI/res/drawable-hdpi/drawer_shadow.9.png create mode 100644 packages/DocumentsUI/res/drawable-hdpi/ic_drawer.png create mode 100644 packages/DocumentsUI/res/drawable-hdpi/ic_menu_search.png delete mode 100644 packages/DocumentsUI/res/layout/item_backend.xml create mode 100644 packages/DocumentsUI/res/layout/item_root.xml create mode 100644 packages/DocumentsUI/res/layout/item_title.xml delete mode 100644 packages/DocumentsUI/src/com/android/documentsui/BackendFragment.java diff --git a/packages/DocumentsUI/Android.mk b/packages/DocumentsUI/Android.mk index 1e458070591cb..853353d45fff8 100644 --- a/packages/DocumentsUI/Android.mk +++ b/packages/DocumentsUI/Android.mk @@ -5,6 +5,8 @@ LOCAL_MODULE_TAGS := optional 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 0000000000000000000000000000000000000000..224cc4ff43a29c546ae50c654c20c58c2f4cdb75 GIT binary patch literal 171 zcmeAS@N?(olHy`uVBq!ia0vp^JV0#3!3HEVSgovp6icy_X9x!n)NrJ90QsB+9+AZi z4BVX{%xHe{^je^xucwP+h)3t!Yl?gg4m>OeUfNFiFU`@U5Xr+-Y2h+^&Fz!7ypldm za#4)fkaYOplLMWvd)czen2t>^YG&h=GRe5G;NW`))xgl;_^?U481>l1!)FR53IYvg N@O1TaS?83{1OO&fGZ_E? literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..ff7b1def9ac3f86488a855f502b965ac75b633fb GIT binary patch literal 2842 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..cc661e3ae1f9b38ea833242fb06c942ec2422362 GIT binary patch literal 1653 zcmZ`(e>~IqAAiq`nRa92i^6`=Idx_u>@22X<2XMrRy04;4{d%$`pTAMN2$?WdT{cy zxHUK3SDZTMM1K>r{shy_6F1>^T|2we`#2=n&=EyLCex`dFx2@3)s*B$`T z835kuN}_oHvUFP(`2a8~0I(2Nb%uKC8V2!!{%o-MNosCiFV|I!gq-LM9diC_egj8# z1Ay@5vRM%+(=V?CmRLuc_2oZe&L)-(x?7T?Qb_jY`azUdN{NIdAz7;SdRv@M+}4Xx zXA6lE^6ndW@(VR-xE z^vRE!jXT-=pvMhm>`43VTrAJAUF;IHc>s789%0;xzIm*H|1sRV4QA#xNAurs=G#zV ztYL55hU%ej zBN?mc&nix+nQAu`P8R37#iS8<&Smi7D?5ZgmrjW8L(!@kP1aF^4x@@Xc_8&%m$sG;|c&U88L-B`1Y1Mu7SNP5+ zHI33{yVRo=!phxv!|wVNehhBpq+t3jvN_5_f#)urLtVOt5KY)EvlrhdsU2-fazl3m z(!}jyn8S=Th*QXmX{Uh^-oDkfE!clv;~c<(lh_60@?qQ28ngOy#^+^);Nv(N_8R3n z32m0D@`s)#s3~t?6^`1&wa5j7%o-z4F?GP+7kkTLu`ufV>91_`v93Kv>c9{7 zvUJ;&`iGwx$%xnG_B{b~^)c1U`(~Fsif1d_OY7ZBzJYDdS0#Awxg%p= zKgpCb4Lxs6_lq$XXa7LFdvK#EBXxS{r<}Tb8YG#C?rJfhVcJ&D#@-%7i{4GqAu{qR zKHRARX&38*oh~hJCp}7bQdE<;83@rSvfgM>byts=e`P#K&^-HqwA!~U#}UomW5d4i zLUe!Idrf>xIe{Y-NmKi4HGX-+(hwedcm!R%z6<^} z`Lg~m9QQxEDt*3$=NgRCU6C>AjfVotD7Ey~We=I(4~n9T!kd1F%3#~x!A9NM9e!13 zIi*dpcPLwPtU7+IyU{sjAqkk!!hxG^@@~!#E00PsqLuQz~UG)kHu{I!=xYh}n~}#^hYd zs+>xJ>3csBY&Y>3+x|CGK}Uu^rpDjoxVl{>CwDnEE%&EG=1#RcDK9QoJ>jO=c)_{~ zGUp{w+psGvM33Z;&BAUN4vavPEw6Wtd@udCnC7tI_E^8?0&mglAD2g^8j+K@N@1(u z$(JfkzVApT{#k4hY(UC>j>(PRSMIt%+SFT1nImd9xQBF~)h^RFDl;)IGl`Lqo}>dn zqdL=fQg!Mc;q1(yxiV-jx|l(w_T#w)|3gR>BpymW@&5_G`n>Ye5v)F42p1%0X2+!` k0al_QK8e6hjXRhWmK2wGB5fe))B6Wp-(Yr&5C8Ok0Q<(tEC2ui literal 0 HcmV?d00001 diff --git a/packages/DocumentsUI/res/layout/activity.xml b/packages/DocumentsUI/res/layout/activity.xml index f96d459bf1ced..eb6d803d9e87f 100644 --- a/packages/DocumentsUI/res/layout/activity.xml +++ b/packages/DocumentsUI/res/layout/activity.xml @@ -14,20 +14,34 @@ limitations under the License. --> - + android:layout_height="match_parent"> - + android:layout_height="match_parent" + android:orientation="vertical"> - + - + + + + + + + diff --git a/packages/DocumentsUI/res/layout/item_backend.xml b/packages/DocumentsUI/res/layout/item_backend.xml deleted file mode 100644 index 6ec7566685a7f..0000000000000 --- a/packages/DocumentsUI/res/layout/item_backend.xml +++ /dev/null @@ -1,58 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/packages/DocumentsUI/res/layout/item_root.xml b/packages/DocumentsUI/res/layout/item_root.xml new file mode 100644 index 0000000000000..e9cf3aaab7304 --- /dev/null +++ b/packages/DocumentsUI/res/layout/item_root.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + diff --git a/packages/DocumentsUI/res/layout/item_title.xml b/packages/DocumentsUI/res/layout/item_title.xml new file mode 100644 index 0000000000000..fe6c14d8c6ab7 --- /dev/null +++ b/packages/DocumentsUI/res/layout/item_title.xml @@ -0,0 +1,41 @@ + + + + + + + + + + diff --git a/packages/DocumentsUI/res/menu/activity.xml b/packages/DocumentsUI/res/menu/activity.xml index bf7c161a83ce7..a0d03b251699e 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" /> + diff --git a/packages/DocumentsUI/res/menu/directory.xml b/packages/DocumentsUI/res/menu/directory.xml index c1fa2287ca166..12d0324fb26e5 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" /> - diff --git a/packages/DocumentsUI/res/values/strings.xml b/packages/DocumentsUI/res/values/strings.xml index 6ae2d1212eb85..18e486dc95195 100644 --- a/packages/DocumentsUI/res/values/strings.xml +++ b/packages/DocumentsUI/res/values/strings.xml @@ -17,20 +17,24 @@ Documents - Open - Save + Open from + Save to Create folder Grid view List view Sort by + Search Open Save %1$d selected - Name - Date modified + By name + By date modified + + Open navigation drawer + Close navigation drawer 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 fc13487a35ff9..0000000000000 --- 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 providers = context.getPackageManager() - .queryContentProviders(null, -1, PackageManager.GET_META_DATA); - final List 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 { - public BackendAdapter(Context context, List 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 531eaf3c78634..8b3dd99179c35 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 @@ public class DirectoryFragment extends Fragment { // 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 @@ public class DirectoryFragment extends Fragment { 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 @@ public class DirectoryFragment extends Fragment { 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 @@ public class DirectoryFragment extends Fragment { 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 @@ public class DirectoryFragment extends Fragment { 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 @@ public class DirectoryFragment extends Fragment { } } - private void updateSortBy() { + public void updateSortBy() { getLoaderManager().restartLoader(LOADER_DOCUMENTS, getArguments(), mCallbacks); } @@ -358,38 +357,6 @@ public class DirectoryFragment extends Fragment { } } - 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 e61cea6d7e375..13def57e5931e 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.AlertDialog; 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; 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 @@ public class DocumentsActivity extends Activity { // 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 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 @@ public class DocumentsActivity extends Activity { 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 class DocumentsActivity extends Activity { 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 @@ public class DocumentsActivity extends Activity { 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 @@ public class DocumentsActivity extends Activity { } }; - 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 class DocumentsActivity extends Activity { 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 @@ public class DocumentsActivity extends Activity { 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 class DocumentsActivity extends Activity { 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 @@ public class DocumentsActivity extends Activity { } } - private static final String TAG_CREATE_DIRECTORY = "create_directory"; + /** + * Gather roots from all known storage providers. + */ + private void updateRoots() { + mRoots.clear(); + + final List 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 { + public RootsAdapter(Context context, List 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) {