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
This commit is contained in:
Jeff Sharkey
2013-07-31 20:53:22 -07:00
parent 20d96d8aff
commit bc2971d249
14 changed files with 435 additions and 337 deletions

View File

@@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 171 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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;
}
}
}

View File

@@ -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);

View File

@@ -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<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 @@ 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<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) {