diff --git a/packages/DocumentsUI/AndroidManifest.xml b/packages/DocumentsUI/AndroidManifest.xml
index 84c5474a259f9..69ee466ee7494 100644
--- a/packages/DocumentsUI/AndroidManifest.xml
+++ b/packages/DocumentsUI/AndroidManifest.xml
@@ -7,13 +7,15 @@
+ android:excludeFromRecents="true"
+ android:theme="@android:style/Theme.Holo.Light">
+
diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_dir.png b/packages/DocumentsUI/res/drawable-hdpi/ic_dir.png
new file mode 100644
index 0000000000000..d02534f1ecaf8
Binary files /dev/null and b/packages/DocumentsUI/res/drawable-hdpi/ic_dir.png differ
diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_menu_create_dir.png b/packages/DocumentsUI/res/drawable-hdpi/ic_menu_create_dir.png
new file mode 100644
index 0000000000000..6eb31f1cdbbd0
Binary files /dev/null and b/packages/DocumentsUI/res/drawable-hdpi/ic_menu_create_dir.png differ
diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_menu_grid.png b/packages/DocumentsUI/res/drawable-hdpi/ic_menu_grid.png
new file mode 100644
index 0000000000000..d1326e5e3d4bc
Binary files /dev/null and b/packages/DocumentsUI/res/drawable-hdpi/ic_menu_grid.png differ
diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_menu_list.png b/packages/DocumentsUI/res/drawable-hdpi/ic_menu_list.png
new file mode 100644
index 0000000000000..e03e3454f08ee
Binary files /dev/null and b/packages/DocumentsUI/res/drawable-hdpi/ic_menu_list.png differ
diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_menu_sort.png b/packages/DocumentsUI/res/drawable-hdpi/ic_menu_sort.png
new file mode 100644
index 0000000000000..680d4826483ad
Binary files /dev/null and b/packages/DocumentsUI/res/drawable-hdpi/ic_menu_sort.png differ
diff --git a/packages/DocumentsUI/res/layout/activity.xml b/packages/DocumentsUI/res/layout/activity.xml
new file mode 100644
index 0000000000000..f96d459bf1ced
--- /dev/null
+++ b/packages/DocumentsUI/res/layout/activity.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/DocumentsUI/res/layout/fragment_backend.xml b/packages/DocumentsUI/res/layout/fragment_backend.xml
new file mode 100644
index 0000000000000..2648de2ea6754
--- /dev/null
+++ b/packages/DocumentsUI/res/layout/fragment_backend.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
diff --git a/packages/DocumentsUI/res/layout/fragment_directory.xml b/packages/DocumentsUI/res/layout/fragment_directory.xml
new file mode 100644
index 0000000000000..8085fa81c287f
--- /dev/null
+++ b/packages/DocumentsUI/res/layout/fragment_directory.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/DocumentsUI/res/layout/fragment_save.xml b/packages/DocumentsUI/res/layout/fragment_save.xml
new file mode 100644
index 0000000000000..5a4ce949e4a5e
--- /dev/null
+++ b/packages/DocumentsUI/res/layout/fragment_save.xml
@@ -0,0 +1,51 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/DocumentsUI/res/layout/item_backend.xml b/packages/DocumentsUI/res/layout/item_backend.xml
new file mode 100644
index 0000000000000..6ec7566685a7f
--- /dev/null
+++ b/packages/DocumentsUI/res/layout/item_backend.xml
@@ -0,0 +1,58 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/DocumentsUI/res/layout/item_doc_grid.xml b/packages/DocumentsUI/res/layout/item_doc_grid.xml
new file mode 100644
index 0000000000000..93666ab90e148
--- /dev/null
+++ b/packages/DocumentsUI/res/layout/item_doc_grid.xml
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/DocumentsUI/res/layout/item_doc_list.xml b/packages/DocumentsUI/res/layout/item_doc_list.xml
new file mode 100644
index 0000000000000..85fca79905559
--- /dev/null
+++ b/packages/DocumentsUI/res/layout/item_doc_list.xml
@@ -0,0 +1,61 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/DocumentsUI/res/menu/activity.xml b/packages/DocumentsUI/res/menu/activity.xml
new file mode 100644
index 0000000000000..c675fb8ea0ff9
--- /dev/null
+++ b/packages/DocumentsUI/res/menu/activity.xml
@@ -0,0 +1,22 @@
+
+
+
+
diff --git a/packages/DocumentsUI/res/menu/directory.xml b/packages/DocumentsUI/res/menu/directory.xml
new file mode 100644
index 0000000000000..52c8f5c31f885
--- /dev/null
+++ b/packages/DocumentsUI/res/menu/directory.xml
@@ -0,0 +1,30 @@
+
+
+
+
diff --git a/packages/DocumentsUI/res/values/colors.xml b/packages/DocumentsUI/res/values/colors.xml
new file mode 100644
index 0000000000000..ff3e999b9ccf3
--- /dev/null
+++ b/packages/DocumentsUI/res/values/colors.xml
@@ -0,0 +1,19 @@
+
+
+
+
+ #ddd
+
diff --git a/packages/DocumentsUI/res/values/strings.xml b/packages/DocumentsUI/res/values/strings.xml
index 89f64964a0deb..65f8da82b67d7 100644
--- a/packages/DocumentsUI/res/values/strings.xml
+++ b/packages/DocumentsUI/res/values/strings.xml
@@ -16,4 +16,15 @@
Documents
+
+ Open
+ Save
+
+ Create folder
+ Grid view
+ List view
+ Sort by
+
+ Save
+
diff --git a/packages/DocumentsUI/src/com/android/documentsui/BackendFragment.java b/packages/DocumentsUI/src/com/android/documentsui/BackendFragment.java
new file mode 100644
index 0000000000000..2980e2338a33d
--- /dev/null
+++ b/packages/DocumentsUI/src/com/android/documentsui/BackendFragment.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.documentsui;
+
+import android.app.Fragment;
+import android.app.FragmentManager;
+import android.app.FragmentTransaction;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.ProviderInfo;
+import android.os.Bundle;
+import android.provider.DocumentsContract;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.ArrayAdapter;
+import android.widget.GridView;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.google.android.collect.Lists;
+
+import java.util.List;
+
+/**
+ * Display all known storage backends.
+ */
+public class BackendFragment extends Fragment {
+
+ // TODO: handle multiple accounts from single backend
+
+ private GridView mGridView;
+ private BackendAdapter mAdapter;
+
+ public static void show(FragmentManager fm) {
+ final BackendFragment fragment = new BackendFragment();
+
+ final FragmentTransaction ft = fm.beginTransaction();
+ ft.replace(R.id.directory, fragment);
+ ft.setBreadCrumbTitle("TOP");
+ ft.commitAllowingStateLoss();
+ }
+
+ @Override
+ public View onCreateView(
+ LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ final Context context = inflater.getContext();
+
+ // Gather known storage providers
+ final List providers = context.getPackageManager()
+ .queryContentProviders(null, -1, PackageManager.GET_META_DATA);
+ final List backends = Lists.newArrayList();
+ for (ProviderInfo info : providers) {
+ if (info.metaData != null
+ && info.metaData.containsKey(DocumentsContract.META_DATA_DOCUMENT_PROVIDER)) {
+ backends.add(info);
+ }
+ }
+
+ final View view = inflater.inflate(R.layout.fragment_backend, container, false);
+
+ mGridView = (GridView) view.findViewById(R.id.grid);
+ mGridView.setOnItemClickListener(mItemListener);
+
+ mAdapter = new BackendAdapter(context, backends);
+ mGridView.setAdapter(mAdapter);
+ mGridView.setNumColumns(GridView.AUTO_FIT);
+
+ return view;
+ }
+
+ private OnItemClickListener mItemListener = new OnItemClickListener() {
+ @Override
+ public void onItemClick(AdapterView> parent, View view, int position, long id) {
+ final ProviderInfo info = mAdapter.getItem(position);
+ ((DocumentsActivity) getActivity()).onBackendPicked(info);
+ }
+ };
+
+ public static class BackendAdapter extends ArrayAdapter {
+ 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 ProviderInfo info = getItem(position);
+ icon.setImageDrawable(info.loadIcon(pm));
+ text1.setText(info.loadLabel(pm));
+
+ return convertView;
+ }
+ }
+}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/ColumnAdapter.java b/packages/DocumentsUI/src/com/android/documentsui/ColumnAdapter.java
new file mode 100644
index 0000000000000..092b5dba2acb9
--- /dev/null
+++ b/packages/DocumentsUI/src/com/android/documentsui/ColumnAdapter.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.documentsui;
+
+import android.database.DataSetObserver;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.LinearLayout;
+import android.widget.ListAdapter;
+import android.widget.ListView;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * Adapter that wraps an existing adapter, presenting its contents in multiple
+ * equally-sized horizontal columns.
+ */
+public class ColumnAdapter extends BaseAdapter {
+ private final ListAdapter mWrapped;
+ private final OnItemClickListener mListener;
+
+ private int mColumns = 1;
+
+ public interface OnItemClickListener {
+ public void onItemClick(ListAdapter adapter, int position);
+ }
+
+ public ColumnAdapter(ListAdapter wrapped, OnItemClickListener listener) {
+ mWrapped = Preconditions.checkNotNull(wrapped);
+ mListener = Preconditions.checkNotNull(listener);
+
+ if (!wrapped.areAllItemsEnabled()) {
+ throw new IllegalStateException("All items must be enabled");
+ }
+ if (wrapped.getViewTypeCount() > 1) {
+ throw new IllegalStateException("All items must be identical");
+ }
+ }
+
+ public static void prepare(ListView list) {
+ list.setItemsCanFocus(true);
+ }
+
+ public void setColumns(int columns) {
+ mColumns = columns;
+ notifyDataSetChanged();
+ }
+
+ private View.OnClickListener mItemListener = new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ final int position = (Integer) v.getTag();
+ mListener.onItemClick(mWrapped, position);
+ }
+ };
+
+ @Override
+ public int getCount() {
+ return (mWrapped.getCount() + mColumns - 1) / mColumns;
+ }
+
+ @Override
+ public Object getItem(int position) {
+ return position;
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ if (convertView == null) {
+ convertView = new LinearLayout(parent.getContext());
+ }
+
+ final LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
+ 0, LinearLayout.LayoutParams.WRAP_CONTENT);
+ params.weight = 1f / mColumns;
+
+ final LinearLayout row = (LinearLayout) convertView;
+ final int first = position * mColumns;
+ final int last = mWrapped.getCount() - 1;
+
+ for (int i = 0; i < mColumns; i++) {
+ View convertItem = null;
+ if (i < row.getChildCount()) {
+ convertItem = row.getChildAt(i);
+ }
+
+ final int pos = first + i;
+ final int validPos = Math.min(pos, last);
+ final View item = mWrapped.getView(validPos, convertItem, row);
+ item.setTag(validPos);
+ item.setOnClickListener(mItemListener);
+ item.setFocusable(true);
+
+ if (pos == validPos) {
+ item.setVisibility(View.VISIBLE);
+ } else {
+ item.setVisibility(View.INVISIBLE);
+ }
+
+ if (convertItem == null) {
+ row.addView(item, params);
+ }
+ }
+
+ return convertView;
+ }
+
+ @Override
+ public void registerDataSetObserver(DataSetObserver observer) {
+ super.registerDataSetObserver(observer);
+ mWrapped.registerDataSetObserver(observer);
+ }
+
+ @Override
+ public void unregisterDataSetObserver(DataSetObserver observer) {
+ super.unregisterDataSetObserver(observer);
+ mWrapped.unregisterDataSetObserver(observer);
+ }
+}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
index d43abded6ead8..5ba1930261db9 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
@@ -16,9 +16,9 @@
package com.android.documentsui;
+import android.app.Fragment;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
-import android.app.ListFragment;
import android.app.LoaderManager.LoaderCallbacks;
import android.content.Context;
import android.content.CursorLoader;
@@ -28,49 +28,94 @@ import android.net.Uri;
import android.os.Bundle;
import android.provider.DocumentsContract;
import android.provider.DocumentsContract.DocumentColumns;
+import android.text.format.DateUtils;
import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
import android.widget.CursorAdapter;
+import android.widget.GridView;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
-public class DirectoryFragment extends ListFragment {
+import com.android.documentsui.DocumentsActivity.Document;
+
+/**
+ * Display the documents inside a single directory.
+ */
+public class DirectoryFragment extends Fragment {
+
+ // TODO: show storage backend in item views when requested
+ // TODO: implement sorting dialog
+ // TODO: support multiple selection with actionmode
+
+ private ListView mListView;
+ private GridView mGridView;
+
private DocumentsAdapter mAdapter;
private LoaderCallbacks mCallbacks;
+ private int mFlags;
+
private static final String EXTRA_URI = "uri";
+ private static final String EXTRA_MODE = "display_mode";
+
+ private static final int MODE_LIST = 1;
+ private static final int MODE_GRID = 2;
private static final int LOADER_DOCUMENTS = 2;
- public static void show(FragmentManager fm, Uri uri, CharSequence title) {
+ public static void show(FragmentManager fm, Uri uri, String displayName) {
final Bundle args = new Bundle();
args.putParcelable(EXTRA_URI, uri);
+ args.putInt(EXTRA_MODE, MODE_LIST);
final DirectoryFragment fragment = new DirectoryFragment();
fragment.setArguments(args);
final FragmentTransaction ft = fm.beginTransaction();
- ft.replace(android.R.id.content, fragment);
- ft.addToBackStack(title.toString());
- ft.setBreadCrumbTitle(title);
+ ft.replace(R.id.directory, fragment);
+ ft.addToBackStack(displayName);
+ ft.setBreadCrumbTitle(displayName);
ft.commitAllowingStateLoss();
}
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setHasOptionsMenu(true);
+ }
+
@Override
public View onCreateView(
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
final Context context = inflater.getContext();
+ final View view = inflater.inflate(R.layout.fragment_directory, container, false);
+
+ mListView = (ListView) view.findViewById(R.id.list);
+ mListView.setOnItemClickListener(mItemListener);
+
+ mGridView = (GridView) view.findViewById(R.id.grid);
+ mGridView.setOnItemClickListener(mItemListener);
+
mAdapter = new DocumentsAdapter(context);
- setListAdapter(mAdapter);
+ updateMode();
+
+ // TODO: migrate flags query to loader
+ final Uri uri = getArguments().getParcelable(EXTRA_URI);
+ mFlags = getDocumentFlags(context, uri);
mCallbacks = new LoaderCallbacks() {
@Override
public Loader onCreateLoader(int id, Bundle args) {
- final Uri uri = args.getParcelable(EXTRA_URI);
- return new CursorLoader(context, uri, null, null, null, null);
+ final Uri contentsUri = DocumentsContract.buildContentsUri(uri);
+ return new CursorLoader(context, contentsUri, null, null, null, null);
}
@Override
@@ -84,13 +129,17 @@ public class DirectoryFragment extends ListFragment {
}
};
- return super.onCreateView(inflater, container, savedInstanceState);
+ return view;
}
@Override
public void onStart() {
super.onStart();
getLoaderManager().restartLoader(LOADER_DOCUMENTS, getArguments(), mCallbacks);
+
+ // TODO: clean up tracking of current directory
+ final Uri uri = getArguments().getParcelable(EXTRA_URI);
+ ((DocumentsActivity) getActivity()).onDirectoryChanged(uri, mFlags);
}
@Override
@@ -100,26 +149,70 @@ public class DirectoryFragment extends ListFragment {
}
@Override
- public void onListItemClick(ListView l, View v, int position, long id) {
- final Cursor cursor = (Cursor) mAdapter.getItem(position);
- final String guid = getCursorString(cursor, DocumentColumns.GUID);
- final String mimeType = getCursorString(cursor, DocumentColumns.MIME_TYPE);
+ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+ super.onCreateOptionsMenu(menu, inflater);
+ inflater.inflate(R.menu.directory, menu);
+ }
- final Uri uri = getArguments().getParcelable(EXTRA_URI);
- final Uri childUri = DocumentsContract.buildDocumentUri(uri.getAuthority(), guid);
+ @Override
+ public void onPrepareOptionsMenu(Menu menu) {
+ super.onPrepareOptionsMenu(menu);
+ final int mode = getMode();
+ menu.findItem(R.id.menu_grid).setVisible(mode != MODE_GRID);
+ menu.findItem(R.id.menu_list).setVisible(mode != MODE_LIST);
+ }
- if (DocumentsContract.MIME_TYPE_DIRECTORY.equals(mimeType)) {
- // Nested directory picked, recurse using new fragment
- final Uri childContentsUri = DocumentsContract.buildContentsUri(childUri);
- final String displayName = cursor.getString(
- cursor.getColumnIndex(DocumentColumns.DISPLAY_NAME));
- DirectoryFragment.show(getFragmentManager(), childContentsUri, displayName);
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ final int id = item.getItemId();
+ if (id == R.id.menu_grid) {
+ getArguments().putInt(EXTRA_MODE, MODE_GRID);
+ updateMode();
+ return true;
+ } else if (id == R.id.menu_list) {
+ getArguments().putInt(EXTRA_MODE, MODE_LIST);
+ updateMode();
+ return true;
} else {
- // Explicit file picked, return
- ((DocumentsActivity) getActivity()).onDocumentPicked(childUri);
+ return super.onOptionsItemSelected(item);
}
}
+ private void updateMode() {
+ final int mode = getMode();
+
+ mListView.setVisibility(mode == MODE_LIST ? View.VISIBLE : View.GONE);
+ mGridView.setVisibility(mode == MODE_GRID ? View.VISIBLE : View.GONE);
+
+ if (mode == MODE_GRID) {
+ mListView.setAdapter(null);
+ mGridView.setAdapter(mAdapter);
+ mGridView.setNumColumns(GridView.AUTO_FIT);
+ } else {
+ mGridView.setAdapter(null);
+ mListView.setAdapter(mAdapter);
+ }
+ }
+
+ private OnItemClickListener mItemListener = new OnItemClickListener() {
+ @Override
+ public void onItemClick(AdapterView> parent, View view, int position, long id) {
+ final Cursor cursor = (Cursor) mAdapter.getItem(position);
+
+ final Uri uri = getArguments().getParcelable(EXTRA_URI);
+ final Document doc = Document.fromCursor(uri.getAuthority(), cursor);
+ ((DocumentsActivity) getActivity()).onDocumentPicked(doc);
+ }
+ };
+
+ private boolean getSupportsCreate() {
+ return (mFlags & DocumentsContract.FLAG_SUPPORTS_CREATE) != 0;
+ }
+
+ private int getMode() {
+ return getArguments().getInt(EXTRA_MODE, MODE_LIST);
+ }
+
private class DocumentsAdapter extends CursorAdapter {
public DocumentsAdapter(Context context) {
super(context, null, false);
@@ -127,8 +220,15 @@ public class DirectoryFragment extends ListFragment {
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
- return LayoutInflater.from(context)
- .inflate(com.android.internal.R.layout.preference, parent, false);
+ final LayoutInflater inflater = LayoutInflater.from(context);
+ final int mode = getMode();
+ if (mode == MODE_LIST) {
+ return inflater.inflate(R.layout.item_doc_list, parent, false);
+ } else if (mode == MODE_GRID) {
+ return inflater.inflate(R.layout.item_doc_grid, parent, false);
+ } else {
+ throw new IllegalStateException();
+ }
}
@Override
@@ -137,12 +237,10 @@ public class DirectoryFragment extends ListFragment {
final TextView summary = (TextView) view.findViewById(android.R.id.summary);
final ImageView icon = (ImageView) view.findViewById(android.R.id.icon);
- icon.setMaxWidth(128);
- icon.setMaxHeight(128);
-
final String guid = getCursorString(cursor, DocumentColumns.GUID);
final String displayName = getCursorString(cursor, DocumentColumns.DISPLAY_NAME);
final String mimeType = getCursorString(cursor, DocumentColumns.MIME_TYPE);
+ final long lastModified = getCursorLong(cursor, DocumentColumns.LAST_MODIFIED);
final int flags = getCursorInt(cursor, DocumentColumns.FLAGS);
if ((flags & DocumentsContract.FLAG_SUPPORTS_THUMBNAIL) != 0) {
@@ -150,19 +248,39 @@ public class DirectoryFragment extends ListFragment {
final Uri childUri = DocumentsContract.buildDocumentUri(uri.getAuthority(), guid);
icon.setImageURI(childUri);
} else {
- icon.setImageURI(null);
+ icon.setImageDrawable(DocumentsActivity.resolveDocumentIcon(context, mimeType));
}
title.setText(displayName);
- summary.setText(mimeType);
+ if (summary != null) {
+ summary.setText(DateUtils.getRelativeTimeSpanString(lastModified));
+ }
}
}
-
- private static String getCursorString(Cursor cursor, String columnName) {
+
+ private static int getDocumentFlags(Context context, Uri uri) {
+ final Cursor cursor = context.getContentResolver().query(uri, new String[] {
+ DocumentColumns.FLAGS }, null, null, null);
+ try {
+ if (cursor.moveToFirst()) {
+ return getCursorInt(cursor, DocumentColumns.FLAGS);
+ } else {
+ return 0;
+ }
+ } finally {
+ cursor.close();
+ }
+ }
+
+ public static String getCursorString(Cursor cursor, String columnName) {
return cursor.getString(cursor.getColumnIndex(columnName));
}
- private static int getCursorInt(Cursor cursor, String columnName) {
+ public static long getCursorLong(Cursor cursor, String columnName) {
+ return cursor.getLong(cursor.getColumnIndex(columnName));
+ }
+
+ public static int getCursorInt(Cursor cursor, String columnName) {
return cursor.getInt(cursor.getColumnIndex(columnName));
}
}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
index 196776b7ae5e5..c83edc4014faf 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
@@ -16,49 +16,229 @@
package com.android.documentsui;
+import static com.android.documentsui.DirectoryFragment.getCursorString;
+
+import android.app.ActionBar;
+import android.app.ActionBar.OnNavigationListener;
import android.app.Activity;
import android.app.FragmentManager;
-import android.app.FragmentTransaction;
-import android.app.ListFragment;
+import android.app.FragmentManager.BackStackEntry;
+import android.app.FragmentManager.OnBackStackChangedListener;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ProviderInfo;
+import android.content.pm.ResolveInfo;
+import android.database.Cursor;
+import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
import android.provider.DocumentsContract;
+import android.provider.DocumentsContract.DocumentColumns;
import android.util.Log;
import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
-import android.widget.ArrayAdapter;
-import android.widget.ListView;
-
-import com.google.android.collect.Lists;
-
-import java.util.ArrayList;
-import java.util.List;
+import android.widget.BaseAdapter;
+import android.widget.TextView;
public class DocumentsActivity extends Activity {
private static final String TAG = "Documents";
+ // TODO: fragment to show recently opened documents
+ // TODO: pull actionbar icon from current backend
+
+ private static final int MODE_OPEN = 1;
+ private static final int MODE_CREATE = 2;
+
+ private int mMode;
+ private boolean mIgnoreNextNavigation;
+
+ private Uri mCurrentDir;
+ private boolean mCurrentSupportsCreate;
+
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
- SourceFragment.show(getFragmentManager());
+ final String action = getIntent().getAction();
+ if (Intent.ACTION_OPEN_DOCUMENT.equals(action)) {
+ mMode = MODE_OPEN;
+ } else if (Intent.ACTION_CREATE_DOCUMENT.equals(action)) {
+ mMode = MODE_CREATE;
+ }
+
setResult(Activity.RESULT_CANCELED);
+ setContentView(R.layout.activity);
+
+ getFragmentManager().addOnBackStackChangedListener(mStackListener);
+ BackendFragment.show(getFragmentManager());
+
+ updateActionBar();
+
+ if (mMode == MODE_CREATE) {
+ final String mimeType = getIntent().getType();
+ final String title = getIntent().getStringExtra(Intent.EXTRA_TITLE);
+ SaveFragment.show(getFragmentManager(), mimeType, title);
+ }
}
- public void onDocumentPicked(Uri uri) {
- Log.d(TAG, "onDocumentPicked() " + uri);
+ public void updateActionBar() {
+ final FragmentManager fm = getFragmentManager();
+ final ActionBar actionBar = getActionBar();
+
+ if (fm.getBackStackEntryCount() > 0) {
+ actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST);
+ actionBar.setDisplayShowHomeEnabled(true);
+ actionBar.setDisplayHomeAsUpEnabled(true);
+ actionBar.setTitle(null);
+ actionBar.setListNavigationCallbacks(mStackAdapter, mNavigationListener);
+ actionBar.setSelectedNavigationItem(mStackAdapter.getCount() - 1);
+ mIgnoreNextNavigation = true;
+
+ } else {
+ actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD);
+ actionBar.setDisplayShowHomeEnabled(false);
+ actionBar.setDisplayHomeAsUpEnabled(false);
+
+ if (mMode == MODE_OPEN) {
+ actionBar.setTitle(R.string.title_open);
+ } else if (mMode == MODE_CREATE) {
+ actionBar.setTitle(R.string.title_save);
+ }
+ }
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ super.onCreateOptionsMenu(menu);
+ getMenuInflater().inflate(R.menu.activity, menu);
+ return true;
+ }
+
+ @Override
+ public boolean onPrepareOptionsMenu(Menu menu) {
+ super.onPrepareOptionsMenu(menu);
+
+ final MenuItem createDir = menu.findItem(R.id.menu_create_dir);
+ createDir.setVisible(mMode == MODE_CREATE);
+ createDir.setEnabled(mCurrentSupportsCreate);
+
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ final int id = item.getItemId();
+ if (id == android.R.id.home) {
+ getFragmentManager().popBackStack();
+ updateActionBar();
+ } else if (id == R.id.menu_create_dir) {
+ // TODO: show dialog to create directory
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ private OnBackStackChangedListener mStackListener = new OnBackStackChangedListener() {
+ @Override
+ public void onBackStackChanged() {
+ updateActionBar();
+ }
+ };
+
+ private BaseAdapter mStackAdapter = new BaseAdapter() {
+ @Override
+ public int getCount() {
+ return getFragmentManager().getBackStackEntryCount();
+ }
+
+ @Override
+ public Object getItem(int position) {
+ return getFragmentManager().getBackStackEntryAt(position);
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return getFragmentManager().getBackStackEntryAt(position).getId();
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ if (convertView == null) {
+ convertView = LayoutInflater.from(parent.getContext())
+ .inflate(android.R.layout.simple_dropdown_item_1line, parent, false);
+ }
+
+ final BackStackEntry entry = getFragmentManager().getBackStackEntryAt(position);
+ final TextView text1 = (TextView) convertView.findViewById(android.R.id.text1);
+ text1.setText(entry.getBreadCrumbTitle());
+
+ return convertView;
+ }
+ };
+
+ private OnNavigationListener mNavigationListener = new OnNavigationListener() {
+ @Override
+ public boolean onNavigationItemSelected(int itemPosition, long itemId) {
+ if (mIgnoreNextNavigation) {
+ mIgnoreNextNavigation = false;
+ return false;
+ }
+
+ getFragmentManager().popBackStack((int) itemId, 0);
+ return true;
+ }
+ };
+
+ public void onDirectoryChanged(Uri uri, int flags) {
+ mCurrentDir = uri;
+ mCurrentSupportsCreate = (flags & DocumentsContract.FLAG_SUPPORTS_CREATE) != 0;
+
+ if (mMode == MODE_CREATE) {
+ final FragmentManager fm = getFragmentManager();
+ SaveFragment.get(fm).setSaveEnabled(mCurrentSupportsCreate);
+ }
+
+ invalidateOptionsMenu();
+ }
+
+ public void onBackendPicked(ProviderInfo info) {
+ final Uri uri = DocumentsContract.buildDocumentUri(
+ info.authority, DocumentsContract.ROOT_GUID);
+ final CharSequence displayName = info.loadLabel(getPackageManager());
+ DirectoryFragment.show(getFragmentManager(), uri, displayName.toString());
+ }
+
+ public void onDocumentPicked(Document doc) {
+ final FragmentManager fm = getFragmentManager();
+ if (DocumentsContract.MIME_TYPE_DIRECTORY.equals(doc.mimeType)) {
+ // Nested directory picked, recurse using new fragment
+ DirectoryFragment.show(fm, doc.uri, doc.displayName);
+ } else if (mMode == MODE_OPEN) {
+ // Explicit file picked, return
+ onFinished(doc.uri);
+ } else if (mMode == MODE_CREATE) {
+ // Overwrite current filename
+ SaveFragment.get(fm).setDisplayName(doc.displayName);
+ }
+ }
+
+ public void onSaveRequested(String mimeType, String displayName) {
+ // TODO: create file, confirming before overwriting
+ onFinished(null);
+ }
+
+ private void onFinished(Uri uri) {
+ Log.d(TAG, "onFinished() " + uri);
final Intent intent = new Intent();
intent.setData(uri);
intent.addFlags(
Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_PERSIST_GRANT_URI_PERMISSION);
- if (Intent.ACTION_CREATE_DOCUMENT.equals(getIntent().getAction())) {
+ if (mMode == MODE_CREATE) {
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
}
@@ -66,50 +246,37 @@ public class DocumentsActivity extends Activity {
finish();
}
- public static class SourceFragment extends ListFragment {
- private ArrayList mProviders = Lists.newArrayList();
- private ArrayAdapter mAdapter;
+ public static class Document {
+ public Uri uri;
+ public String mimeType;
+ public String displayName;
- public static void show(FragmentManager fm) {
- final SourceFragment fragment = new SourceFragment();
-
- final FragmentTransaction ft = fm.beginTransaction();
- ft.replace(android.R.id.content, fragment);
- ft.setBreadCrumbTitle("TOP");
- ft.commitAllowingStateLoss();
+ public static Document fromCursor(String authority, Cursor cursor) {
+ final Document doc = new Document();
+ final String guid = getCursorString(cursor, DocumentColumns.GUID);
+ doc.uri = DocumentsContract.buildDocumentUri(authority, guid);
+ doc.mimeType = getCursorString(cursor, DocumentColumns.MIME_TYPE);
+ doc.displayName = getCursorString(cursor, DocumentColumns.DISPLAY_NAME);
+ return doc;
}
+ }
- @Override
- public View onCreateView(
- LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
- final Context context = inflater.getContext();
+ public static Drawable resolveDocumentIcon(Context context, String mimeType) {
+ // TODO: allow backends to provide custom MIME icons
+ if (DocumentsContract.MIME_TYPE_DIRECTORY.equals(mimeType)) {
+ return context.getResources().getDrawable(R.drawable.ic_dir);
+ } else {
+ final PackageManager pm = context.getPackageManager();
+ final Intent intent = new Intent(Intent.ACTION_VIEW);
+ intent.setType(mimeType);
- // Gather known storage providers
- mProviders.clear();
- final List providers = context.getPackageManager()
- .queryContentProviders(null, -1, PackageManager.GET_META_DATA);
- for (ProviderInfo info : providers) {
- if (info.metaData != null
- && info.metaData.containsKey(
- DocumentsContract.META_DATA_DOCUMENT_PROVIDER)) {
- mProviders.add(info);
- }
+ final ResolveInfo info = pm.resolveActivity(
+ intent, PackageManager.MATCH_DEFAULT_ONLY);
+ if (info != null) {
+ return info.loadIcon(pm);
+ } else {
+ return null;
}
-
- mAdapter = new ArrayAdapter(
- context, android.R.layout.simple_list_item_1, mProviders);
- setListAdapter(mAdapter);
-
- return super.onCreateView(inflater, container, savedInstanceState);
- }
-
- @Override
- public void onListItemClick(ListView l, View v, int position, long id) {
- final ProviderInfo info = mAdapter.getItem(position);
- final Uri uri = DocumentsContract.buildContentsUri(DocumentsContract.buildDocumentUri(
- info.authority, DocumentsContract.ROOT_GUID));
- final String displayName = info.name;
- DirectoryFragment.show(getFragmentManager(), uri, displayName);
}
}
}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/SaveFragment.java b/packages/DocumentsUI/src/com/android/documentsui/SaveFragment.java
new file mode 100644
index 0000000000000..19a1d2a45baa0
--- /dev/null
+++ b/packages/DocumentsUI/src/com/android/documentsui/SaveFragment.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.documentsui;
+
+import android.app.Fragment;
+import android.app.FragmentManager;
+import android.app.FragmentTransaction;
+import android.content.Context;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.ImageView;
+
+/**
+ * Display document title editor and save button.
+ */
+public class SaveFragment extends Fragment {
+ public static final String TAG = "SaveFragment";
+
+ private EditText mDisplayName;
+ private Button mSave;
+
+ private static final String EXTRA_MIME_TYPE = "mime_type";
+ private static final String EXTRA_DISPLAY_NAME = "display_name";
+
+ public static void show(FragmentManager fm, String mimeType, String displayName) {
+ final Bundle args = new Bundle();
+ args.putString(EXTRA_MIME_TYPE, mimeType);
+ args.putString(EXTRA_DISPLAY_NAME, displayName);
+
+ final SaveFragment fragment = new SaveFragment();
+ fragment.setArguments(args);
+
+ final FragmentTransaction ft = fm.beginTransaction();
+ ft.replace(R.id.save, fragment, TAG);
+ ft.commitAllowingStateLoss();
+ }
+
+ public static SaveFragment get(FragmentManager fm) {
+ return (SaveFragment) fm.findFragmentByTag(TAG);
+ }
+
+ @Override
+ public View onCreateView(
+ LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ final Context context = inflater.getContext();
+
+ final View view = inflater.inflate(R.layout.fragment_save, container, false);
+
+ final ImageView icon = (ImageView) view.findViewById(android.R.id.icon);
+ icon.setImageDrawable(DocumentsActivity.resolveDocumentIcon(
+ context, getArguments().getString(EXTRA_MIME_TYPE)));
+
+ mDisplayName = (EditText) view.findViewById(android.R.id.title);
+ mDisplayName.setText(getArguments().getString(EXTRA_DISPLAY_NAME));
+
+ mSave = (Button) view.findViewById(android.R.id.button1);
+ mSave.setOnClickListener(mSaveListener);
+ mSave.setEnabled(false);
+
+ return view;
+ }
+
+ private View.OnClickListener mSaveListener = new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ final String mimeType = getArguments().getString(EXTRA_MIME_TYPE);
+ final String displayName = getArguments().getString(EXTRA_DISPLAY_NAME);
+ ((DocumentsActivity) getActivity()).onSaveRequested(mimeType, displayName);
+ }
+ };
+
+ public void setDisplayName(String displayName) {
+ getArguments().putString(EXTRA_DISPLAY_NAME, displayName);
+ mDisplayName.setText(displayName);
+ }
+
+ public void setSaveEnabled(boolean enabled) {
+ mSave.setEnabled(enabled);
+ }
+}