diff options
author | Jeff Sharkey <jsharkey@android.com> | 2013-07-01 16:56:54 -0700 |
---|---|---|
committer | Jeff Sharkey <jsharkey@android.com> | 2013-07-01 17:00:14 -0700 |
commit | be8b12e687bd10a526b1f54c2d8a52abdad15d85 (patch) | |
tree | 1ef49eac10010b487799de3cdc625d0783bfe3d5 | |
parent | 54e55b740fef1be654c3959aee41ef5ddfa61293 (diff) | |
download | frameworks_base-be8b12e687bd10a526b1f54c2d8a52abdad15d85.zip frameworks_base-be8b12e687bd10a526b1f54c2d8a52abdad15d85.tar.gz frameworks_base-be8b12e687bd10a526b1f54c2d8a52abdad15d85.tar.bz2 |
Support multi-select in storage UI.
When caller has specified that multiple documents are okay, enable
multi-select action mode. Currently only allows document selection,
not directories. Returns multiple documents through ClipData.
Fix bug where GridView was stuck with 2 columns on tablets.
Change-Id: Id49b29a86330639b56fa116d37e7f0d874980c5b
11 files changed, 208 insertions, 16 deletions
diff --git a/packages/DocumentsUI/res/drawable/item_background.xml b/packages/DocumentsUI/res/drawable/item_background.xml new file mode 100644 index 0000000..4fb32fc --- /dev/null +++ b/packages/DocumentsUI/res/drawable/item_background.xml @@ -0,0 +1,26 @@ +<?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. +--> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_activated="true" android:state_pressed="true" android:drawable="@*android:drawable/list_activated_holo" /> + <item android:state_activated="true" android:drawable="@*android:drawable/list_activated_holo" /> + <item android:state_focused="true" android:state_enabled="false" android:state_pressed="true" android:drawable="@*android:drawable/list_selector_disabled_holo_light" /> + <item android:state_focused="true" android:state_enabled="false" android:drawable="@*android:drawable/list_selector_disabled_holo_light" /> + <item android:state_focused="true" android:state_pressed="true" android:drawable="@*android:drawable/list_selector_background_transition_holo_light" /> + <item android:state_focused="false" android:state_pressed="true" android:drawable="@*android:drawable/list_selector_background_transition_holo_light" /> + <item android:state_focused="true" android:drawable="@*android:drawable/list_focused_holo" /> + <item android:drawable="@android:color/transparent" /> +</selector> diff --git a/packages/DocumentsUI/res/layout/fragment_directory.xml b/packages/DocumentsUI/res/layout/fragment_directory.xml index 8085fa8..638ac92 100644 --- a/packages/DocumentsUI/res/layout/fragment_directory.xml +++ b/packages/DocumentsUI/res/layout/fragment_directory.xml @@ -21,7 +21,8 @@ <ListView android:id="@+id/list" android:layout_width="match_parent" - android:layout_height="match_parent" /> + android:layout_height="match_parent" + android:listSelector="@android:color/transparent" /> <GridView android:id="@+id/grid" diff --git a/packages/DocumentsUI/res/layout/fragment_save.xml b/packages/DocumentsUI/res/layout/fragment_save.xml index 5a4ce94..85c48b1 100644 --- a/packages/DocumentsUI/res/layout/fragment_save.xml +++ b/packages/DocumentsUI/res/layout/fragment_save.xml @@ -43,7 +43,7 @@ android:layout_width="wrap_content" android:layout_height="match_parent" android:background="?android:attr/selectableItemBackground" - android:text="@string/btn_save" + android:text="@string/menu_save" android:textAllCaps="true" android:textAppearance="?android:attr/textAppearanceSmall" android:padding="8dp" /> diff --git a/packages/DocumentsUI/res/layout/item_doc_grid.xml b/packages/DocumentsUI/res/layout/item_doc_grid.xml index 93666ab..caa9db6 100644 --- a/packages/DocumentsUI/res/layout/item_doc_grid.xml +++ b/packages/DocumentsUI/res/layout/item_doc_grid.xml @@ -24,7 +24,7 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/chip" - android:foreground="?android:attr/selectableItemBackground" + android:foreground="@drawable/item_background" android:duplicateParentState="true"> <GridLayout diff --git a/packages/DocumentsUI/res/layout/item_doc_list.xml b/packages/DocumentsUI/res/layout/item_doc_list.xml index 85fca79..39e55be 100644 --- a/packages/DocumentsUI/res/layout/item_doc_list.xml +++ b/packages/DocumentsUI/res/layout/item_doc_list.xml @@ -17,6 +17,7 @@ <GridLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" + android:background="@drawable/item_background" android:minHeight="?android:attr/listPreferredItemHeight" android:paddingStart="?android:attr/listPreferredItemPaddingStart" android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" diff --git a/packages/DocumentsUI/res/menu/activity.xml b/packages/DocumentsUI/res/menu/activity.xml index c675fb8..bf7c161 100644 --- a/packages/DocumentsUI/res/menu/activity.xml +++ b/packages/DocumentsUI/res/menu/activity.xml @@ -18,5 +18,6 @@ <item android:id="@+id/menu_create_dir" android:title="@string/menu_create_dir" - android:icon="@drawable/ic_menu_create_dir" /> + android:icon="@drawable/ic_menu_create_dir" + android:showAsAction="always" /> </menu> diff --git a/packages/DocumentsUI/res/menu/mode_directory.xml b/packages/DocumentsUI/res/menu/mode_directory.xml new file mode 100644 index 0000000..6b6d7e9 --- /dev/null +++ b/packages/DocumentsUI/res/menu/mode_directory.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2013 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<menu xmlns:android="http://schemas.android.com/apk/res/android"> + <item + android:id="@+id/menu_open" + android:title="@string/menu_open" + android:showAsAction="always" /> +</menu> diff --git a/packages/DocumentsUI/res/values/dimens.xml b/packages/DocumentsUI/res/values/dimens.xml new file mode 100644 index 0000000..e5c4138 --- /dev/null +++ b/packages/DocumentsUI/res/values/dimens.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2013 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<resources> + <dimen name="grid_width">180dp</dimen> +</resources> diff --git a/packages/DocumentsUI/res/values/strings.xml b/packages/DocumentsUI/res/values/strings.xml index 65f8da8..141ba80 100644 --- a/packages/DocumentsUI/res/values/strings.xml +++ b/packages/DocumentsUI/res/values/strings.xml @@ -14,7 +14,7 @@ limitations under the License. --> -<resources> +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_label">Documents</string> <string name="title_open">Open</string> @@ -25,6 +25,9 @@ <string name="menu_list">List view</string> <string name="menu_sort">Sort by</string> - <string name="btn_save">Save</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> </resources> diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java index 5ba1930..bae42d5 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java +++ b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java @@ -29,12 +29,16 @@ import android.os.Bundle; import android.provider.DocumentsContract; import android.provider.DocumentsContract.DocumentColumns; import android.text.format.DateUtils; +import android.util.SparseBooleanArray; +import android.view.ActionMode; 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.AbsListView; +import android.widget.AbsListView.MultiChoiceModeListener; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.CursorAdapter; @@ -44,6 +48,9 @@ import android.widget.ListView; import android.widget.TextView; import com.android.documentsui.DocumentsActivity.Document; +import com.google.android.collect.Lists; + +import java.util.ArrayList; /** * Display the documents inside a single directory. @@ -52,11 +59,12 @@ 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 AbsListView mCurrentView; + private DocumentsAdapter mAdapter; private LoaderCallbacks<Cursor> mCallbacks; @@ -64,16 +72,19 @@ public class DirectoryFragment extends Fragment { private static final String EXTRA_URI = "uri"; private static final String EXTRA_MODE = "display_mode"; + private static final String EXTRA_ALLOW_MULTIPLE = "allow_multiple"; 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, String displayName) { + public static void show( + FragmentManager fm, Uri uri, String displayName, boolean allowMultiple) { final Bundle args = new Bundle(); args.putParcelable(EXTRA_URI, uri); args.putInt(EXTRA_MODE, MODE_LIST); + args.putBoolean(EXTRA_ALLOW_MULTIPLE, allowMultiple); final DirectoryFragment fragment = new DirectoryFragment(); fragment.setArguments(args); @@ -100,9 +111,11 @@ public class DirectoryFragment extends Fragment { mListView = (ListView) view.findViewById(R.id.list); mListView.setOnItemClickListener(mItemListener); + mListView.setMultiChoiceModeListener(mMultiListener); mGridView = (GridView) view.findViewById(R.id.grid); mGridView.setOnItemClickListener(mItemListener); + mGridView.setMultiChoiceModeListener(mMultiListener); mAdapter = new DocumentsAdapter(context); updateMode(); @@ -184,13 +197,27 @@ public class DirectoryFragment extends Fragment { mListView.setVisibility(mode == MODE_LIST ? View.VISIBLE : View.GONE); mGridView.setVisibility(mode == MODE_GRID ? View.VISIBLE : View.GONE); + final int choiceMode; + if (getArguments().getBoolean(EXTRA_ALLOW_MULTIPLE)) { + choiceMode = ListView.CHOICE_MODE_MULTIPLE_MODAL; + } else { + choiceMode = ListView.CHOICE_MODE_NONE; + } + if (mode == MODE_GRID) { mListView.setAdapter(null); + mListView.setChoiceMode(ListView.CHOICE_MODE_NONE); mGridView.setAdapter(mAdapter); + mGridView.setColumnWidth(getResources().getDimensionPixelSize(R.dimen.grid_width)); mGridView.setNumColumns(GridView.AUTO_FIT); + mGridView.setChoiceMode(choiceMode); + mCurrentView = mGridView; } else { mGridView.setAdapter(null); + mGridView.setChoiceMode(ListView.CHOICE_MODE_NONE); mListView.setAdapter(mAdapter); + mListView.setChoiceMode(choiceMode); + mCurrentView = mListView; } } @@ -198,13 +225,69 @@ public class DirectoryFragment extends Fragment { @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 MultiChoiceModeListener mMultiListener = new MultiChoiceModeListener() { + @Override + public boolean onCreateActionMode(ActionMode mode, Menu menu) { + mode.getMenuInflater().inflate(R.menu.mode_directory, menu); + return true; + } + + @Override + public boolean onPrepareActionMode(ActionMode mode, Menu menu) { + return true; + } + + @Override + public boolean onActionItemClicked(ActionMode mode, MenuItem item) { + if (item.getItemId() == R.id.menu_open) { + final Uri uri = getArguments().getParcelable(EXTRA_URI); + final SparseBooleanArray checked = mCurrentView.getCheckedItemPositions(); + final ArrayList<Document> docs = Lists.newArrayList(); + + final int size = checked.size(); + for (int i = 0; i < size; i++) { + if (checked.valueAt(i)) { + final Cursor cursor = (Cursor) mAdapter.getItem(checked.keyAt(i)); + docs.add(Document.fromCursor(uri.getAuthority(), cursor)); + } + } + + ((DocumentsActivity) getActivity()).onDocumentsPicked(docs); + return true; + } else { + return false; + } + } + + @Override + public void onDestroyActionMode(ActionMode mode) { + // ignored + } + + @Override + public void onItemCheckedStateChanged( + ActionMode mode, int position, long id, boolean checked) { + if (checked) { + final Cursor cursor = (Cursor) mAdapter.getItem(position); + final String mimeType = getCursorString(cursor, DocumentColumns.MIME_TYPE); + + // Directories cannot be checked + if (DocumentsContract.MIME_TYPE_DIRECTORY.equals(mimeType)) { + mCurrentView.setItemChecked(position, false); + } + } + + mode.setTitle(getResources() + .getString(R.string.mode_selected_count, mCurrentView.getCheckedItemCount())); + } + }; + private boolean getSupportsCreate() { return (mFlags & DocumentsContract.FLAG_SUPPORTS_CREATE) != 0; } diff --git a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java index c83edc4..ca4ea82 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java +++ b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java @@ -24,6 +24,8 @@ import android.app.Activity; import android.app.FragmentManager; import android.app.FragmentManager.BackStackEntry; import android.app.FragmentManager.OnBackStackChangedListener; +import android.content.ClipData; +import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; @@ -44,6 +46,9 @@ import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.TextView; +import java.util.Arrays; +import java.util.List; + public class DocumentsActivity extends Activity { private static final String TAG = "Documents"; @@ -54,6 +59,9 @@ public class DocumentsActivity extends Activity { private static final int MODE_CREATE = 2; private int mMode; + private boolean mAllowMultiple; + private String[] mAcceptMimes; + private boolean mIgnoreNextNavigation; private Uri mCurrentDir; @@ -63,11 +71,20 @@ public class DocumentsActivity extends Activity { public void onCreate(Bundle icicle) { super.onCreate(icicle); - final String action = getIntent().getAction(); + final Intent intent = getIntent(); + final String action = intent.getAction(); if (Intent.ACTION_OPEN_DOCUMENT.equals(action)) { mMode = MODE_OPEN; + mAllowMultiple = intent.getBooleanExtra(Intent.EXTRA_ALLOW_MULTIPLE, false); } else if (Intent.ACTION_CREATE_DOCUMENT.equals(action)) { mMode = MODE_CREATE; + mAllowMultiple = false; + } + + if (intent.hasExtra(Intent.EXTRA_MIME_TYPES)) { + mAcceptMimes = intent.getStringArrayExtra(Intent.EXTRA_MIME_TYPES); + } else { + mAcceptMimes = new String[] { intent.getType() }; } setResult(Activity.RESULT_CANCELED); @@ -208,14 +225,14 @@ public class DocumentsActivity extends Activity { final Uri uri = DocumentsContract.buildDocumentUri( info.authority, DocumentsContract.ROOT_GUID); final CharSequence displayName = info.loadLabel(getPackageManager()); - DirectoryFragment.show(getFragmentManager(), uri, displayName.toString()); + DirectoryFragment.show(getFragmentManager(), uri, displayName.toString(), mAllowMultiple); } 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, mAllowMultiple); } else if (mMode == MODE_OPEN) { // Explicit file picked, return onFinished(doc.uri); @@ -225,16 +242,35 @@ public class DocumentsActivity extends Activity { } } + public void onDocumentsPicked(List<Document> docs) { + final int size = docs.size(); + final Uri[] uris = new Uri[size]; + for (int i = 0; i < size; i++) { + uris[i] = docs.get(i).uri; + } + onFinished(uris); + } + public void onSaveRequested(String mimeType, String displayName) { // TODO: create file, confirming before overwriting - onFinished(null); + final Uri uri = null; + onFinished(uri); } - private void onFinished(Uri uri) { - Log.d(TAG, "onFinished() " + uri); + private void onFinished(Uri... uris) { + Log.d(TAG, "onFinished() " + Arrays.toString(uris)); final Intent intent = new Intent(); - intent.setData(uri); + if (uris.length == 1) { + intent.setData(uris[0]); + } else if (uris.length > 1) { + final ContentResolver resolver = getContentResolver(); + final ClipData clipData = new ClipData(null, mAcceptMimes, new ClipData.Item(uris[0])); + for (int i = 1; i < uris.length; i++) { + clipData.addItem(new ClipData.Item(uris[i])); + } + intent.setClipData(clipData); + } intent.addFlags( Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_PERSIST_GRANT_URI_PERMISSION); |