diff options
11 files changed, 939 insertions, 306 deletions
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java index 0d5e34e87ebb..13cfbe4a16e5 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java +++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java @@ -53,6 +53,7 @@ import android.provider.DocumentsContract.Document; import android.support.annotation.Nullable; import android.support.design.widget.Snackbar; import android.support.v7.widget.GridLayoutManager; +import android.support.v7.widget.GridLayoutManager.SpanSizeLookup; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView.LayoutManager; @@ -99,18 +100,17 @@ import com.android.documentsui.dirlist.MultiSelectManager.Selection; import com.android.documentsui.model.DocumentInfo; import com.android.documentsui.model.DocumentStack; import com.android.documentsui.model.RootInfo; + import com.google.common.collect.Lists; -import com.google.common.collect.Sets; import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.Set; /** * Display the documents inside a single directory. */ -public class DirectoryFragment extends Fragment { +public class DirectoryFragment extends Fragment implements DocumentsAdapter.Environment { public static final int TYPE_NORMAL = 1; public static final int TYPE_SEARCH = 2; @@ -126,7 +126,7 @@ public class DirectoryFragment extends Fragment { private static final String TAG = "DirectoryFragment"; private static final int LOADER_ID = 42; - private static final boolean DEBUG_ENABLE_DND = true; + static final boolean DEBUG_ENABLE_DND = true; private static final String EXTRA_TYPE = "type"; private static final String EXTRA_ROOT = "root"; @@ -289,7 +289,11 @@ public class DirectoryFragment extends Fragment { final RootInfo root = getArguments().getParcelable(EXTRA_ROOT); final DocumentInfo doc = getArguments().getParcelable(EXTRA_DOC); - mAdapter = new DocumentsAdapter(); + mIconHelper = new IconHelper(context, state.derivedMode); + + mAdapter = new SectionBreakDocumentsAdapterWrapper( + this, new ModelBackedDocumentsAdapter(this, mIconHelper)); + mRecView.setAdapter(mAdapter); GestureDetector.SimpleOnGestureListener listener = @@ -333,7 +337,7 @@ public class DirectoryFragment extends Fragment { : MultiSelectManager.MODE_SINGLE); mSelectionManager.addCallback(new SelectionModeListener()); - mModel = new Model(context, mAdapter); + mModel = new Model(context); mModel.addUpdateListener(mAdapter); mModel.addUpdateListener(mModelUpdateListener); @@ -343,8 +347,6 @@ public class DirectoryFragment extends Fragment { mTuner = FragmentTuner.pick(state); mClipper = new DocumentClipper(context); - mIconHelper = new IconHelper(context, state.derivedMode); - boolean hideGridTitles; if (mType == TYPE_RECENT_OPEN) { // Hide titles when showing recents for picking images/videos @@ -574,7 +576,10 @@ public class DirectoryFragment extends Fragment { case MODE_GRID: if (mGridLayout == null) { mGridLayout = new GridLayoutManager(getContext(), mColumnCount); - mGridLayout.setSpanSizeLookup(mAdapter.createSpanSizeLookup()); + SpanSizeLookup lookup = mAdapter.createSpanSizeLookup(); + if (lookup != null) { + mGridLayout.setSpanSizeLookup(lookup); + } } layout = mGridLayout; break; @@ -609,6 +614,11 @@ public class DirectoryFragment extends Fragment { return columnCount; } + @Override + public int getColumnCount() { + return mColumnCount; + } + /** * Manages the integration between our ActionMode and MultiSelectManager, initiating * ActionMode when there is a selection, canceling it when there is no selection, @@ -893,10 +903,34 @@ public class DirectoryFragment extends Fragment { }.execute(selected); } - private State getDisplayState() { + @Override + public void initDocumentHolder(DocumentHolder holder) { + holder.addClickListener(mItemClickListener); + holder.addOnKeyListener(mSelectionManager); + } + + @Override + public void onBindDocumentHolder(DocumentHolder holder, Cursor cursor) { + if (DEBUG_ENABLE_DND) { + setupDragAndDropOnDocumentView(holder.itemView, cursor); + } + } + + @Override + public State getDisplayState() { return ((BaseActivity) getActivity()).getDisplayState(); } + @Override + public Model getModel() { + return mModel; + } + + @Override + public boolean isDocumentEnabled(String docMimeType, int docFlags) { + return mTuner.isDocumentEnabled(docMimeType, docFlags); + } + void showEmptyView() { mEmptyView.setVisibility(View.VISIBLE); mRecView.setVisibility(View.GONE); @@ -920,240 +954,6 @@ public class DirectoryFragment extends Fragment { mRecView.setVisibility(View.VISIBLE); } - final class DocumentsAdapter - extends RecyclerView.Adapter<DocumentHolder> - implements Model.UpdateListener { - - private static final String TAG = "DocumentsAdapter"; - public static final int ITEM_TYPE_LAYOUT_DIVIDER = 0; - public static final int ITEM_TYPE_DOCUMENT = 1; - public static final int ITEM_TYPE_DIRECTORY = 2; - - /** - * An ordered list of model IDs. This is the data structure that determines what shows up in - * the UI, and where. - */ - private List<String> mModelIds = new ArrayList<>(); - - // The list is divided into two segments - directories, and everything else. Record the - // position where the transition happens. - private int mDividerPosition; - - public GridLayoutManager.SpanSizeLookup createSpanSizeLookup() { - return new GridLayoutManager.SpanSizeLookup() { - @Override - public int getSpanSize(int position) { - // Make layout whitespace span the grid. This has the effect of breaking - // grid rows whenever layout whitespace is encountered. - if (getItemViewType(position) == ITEM_TYPE_LAYOUT_DIVIDER) { - return mColumnCount; - } else { - return 1; - } - } - }; - } - - @Override - public DocumentHolder onCreateViewHolder(ViewGroup parent, int viewType) { - if (viewType == ITEM_TYPE_LAYOUT_DIVIDER) { - return new EmptyDocumentHolder(getContext()); - }; - - DocumentHolder holder = null; - final State state = getDisplayState(); - switch (state.derivedMode) { - case MODE_GRID: - switch (viewType) { - case ITEM_TYPE_DIRECTORY: - holder = new GridDirectoryHolder(getContext(), parent); - break; - case ITEM_TYPE_DOCUMENT: - holder = new GridDocumentHolder(getContext(), parent, mIconHelper); - break; - default: - throw new IllegalStateException("Unsupported layout type."); - } - break; - case MODE_LIST: - holder = new ListDocumentHolder(getContext(), parent, mIconHelper); - break; - case MODE_UNKNOWN: - default: - throw new IllegalStateException("Unsupported layout mode."); - } - - holder.addClickListener(mItemClickListener); - holder.addOnKeyListener(mSelectionManager); - return holder; - } - - /** - * Deal with selection changed events by using a custom ItemAnimator that just changes the - * background color. This works around focus issues (otherwise items lose focus when their - * selection state changes) but also optimizes change animations for selection. - */ - @Override - public void onBindViewHolder(DocumentHolder holder, int position, List<Object> payload) { - if (holder.getItemViewType() == ITEM_TYPE_LAYOUT_DIVIDER) { - // Whitespace items are hidden elements with no data to bind. - return; - } - - final View itemView = holder.itemView; - - if (payload.contains(MultiSelectManager.SELECTION_CHANGED_MARKER)) { - final boolean selected = isSelected(mModelIds.get(position)); - itemView.setActivated(selected); - return; - } else { - onBindViewHolder(holder, position); - } - } - - @Override - public void onBindViewHolder(DocumentHolder holder, int position) { - if (holder.getItemViewType() == ITEM_TYPE_LAYOUT_DIVIDER) { - // Whitespace items are hidden elements with no data to bind. - return; - } - - String modelId = mModelIds.get(position); - Cursor cursor = mModel.getItem(modelId); - holder.bind(cursor, modelId, getDisplayState()); - - final String docMimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE); - final int docFlags = getCursorInt(cursor, Document.COLUMN_FLAGS); - - holder.setSelected(isSelected(modelId)); - holder.setEnabled(mTuner.isDocumentEnabled(docMimeType, docFlags)); - if (DEBUG_ENABLE_DND) { - setupDragAndDropOnDocumentView(holder.itemView, cursor); - } - } - - @Override - public int getItemCount() { - return mModelIds.size(); - } - - @Override - public void onModelUpdate(Model model) { - mModelIds = Lists.newArrayList(model.getModelIds()); - // Start the divider at the end. That way if the code below encounters no documents - // (i.e. in a directory containing only directories), the divider is placed at the end - // of the list, as expected. - mDividerPosition = mModelIds.size(); - - // Walk down the list of IDs till we encounter something that's not a directory, and - // insert a whitespace element - this introduces a visual break in the grid between - // folders and documents. - // TODO: This code makes assumptions about the model, namely, that it performs a - // bucketed sort where directories will always be ordered before other files. CBB. - for (int i = 0; i < mModelIds.size(); ++i) { - final String mimeType = getCursorString( - model.getItem(mModelIds.get(i)), Document.COLUMN_MIME_TYPE); - if (!Document.MIME_TYPE_DIR.equals(mimeType)) { - mDividerPosition = i; - break; - } - } - - mModelIds.add(mDividerPosition, null); - } - - @Override - public void onModelUpdateFailed(Exception e) { - if (DEBUG) Log.d(TAG, "onModelUpdateFailed called "); - mModelIds.clear(); - } - - /** - * @return The model ID of the item at the given adapter position. - */ - public String getModelId(int adapterPosition) { - return mModelIds.get(adapterPosition); - } - - /** - * Hides a set of items from the associated RecyclerView. - * - * @param ids The Model IDs of the items to hide. - * @return A SparseArray that maps the hidden IDs to their old positions. This can be used - * to {@link #unhide} the items if necessary. - */ - public SparseArray<String> hide(String... ids) { - Set<String> toHide = Sets.newHashSet(ids); - - // Proceed backwards through the list of items, because each removal causes the - // positions of all subsequent items to change. - SparseArray<String> hiddenItems = new SparseArray<>(); - for (int i = mModelIds.size() - 1; i >= 0; --i) { - String id = mModelIds.get(i); - if (toHide.contains(id)) { - hiddenItems.put(i, mModelIds.remove(i)); - notifyItemRemoved(i); - } - } - - return hiddenItems; - } - - /** - * Unhides a set of previously hidden items. - * - * @param ids A sparse array of IDs from a previous call to {@link #hide}. - */ - public void unhide(SparseArray<String> ids) { - // Proceed backwards through the list of items, because each addition causes the - // positions of all subsequent items to change. - for (int i = ids.size() - 1; i >= 0; --i) { - int pos = ids.keyAt(i); - String id = ids.get(pos); - mModelIds.add(pos, id); - notifyItemInserted(pos); - } - } - - /** - * Returns a list of model IDs of items currently in the adapter. Excludes items that are - * currently hidden (see {@link #hide(String...)}). - * - * @return A list of Model IDs. - */ - public List<String> getModelIds() { - return mModelIds; - } - - @Override - public int getItemViewType(int position) { - if (position < mDividerPosition) { - return ITEM_TYPE_DIRECTORY; - } else if (position == mDividerPosition) { - return ITEM_TYPE_LAYOUT_DIVIDER; - } else { - return ITEM_TYPE_DOCUMENT; - } - } - - /** - * Triggers item-change notifications by stable ID. Passing an unrecognized ID will result - * in a warning in logcat, but no other error. - * - * @param id - * @param selectionChangedMarker - */ - public void notifyItemChanged(String id, String selectionChangedMarker) { - int position = mModelIds.indexOf(id); - - if (position >= 0) { - notifyItemChanged(position, selectionChangedMarker); - } else { - Log.w(TAG, "Item change notification received for unknown item: " + id); - } - } - } - private String findCommonMimeType(List<String> mimeTypes) { String[] commonType = mimeTypes.get(0).split("/"); if (commonType.length != 2) { @@ -1504,7 +1304,8 @@ public class DirectoryFragment extends Fragment { abstract void onDocumentsReady(List<DocumentInfo> docs); } - boolean isSelected(String modelId) { + @Override + public boolean isSelected(String modelId) { return mSelectionManager.getSelection().contains(modelId); } @@ -1520,7 +1321,7 @@ public class DirectoryFragment extends Fragment { } } - private class ModelUpdateListener implements Model.UpdateListener { + private final class ModelUpdateListener implements Model.UpdateListener { @Override public void onModelUpdate(Model model) { if (model.info != null || model.error != null) { diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DocumentsAdapter.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DocumentsAdapter.java new file mode 100644 index 000000000000..2001b29c5d97 --- /dev/null +++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DocumentsAdapter.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2015 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.dirlist; + +import static com.android.documentsui.model.DocumentInfo.getCursorString; + +import android.content.Context; +import android.database.Cursor; +import android.provider.DocumentsContract.Document; +import android.support.v7.widget.GridLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.util.SparseArray; + +import com.android.documentsui.State; + +import java.nio.channels.UnsupportedAddressTypeException; +import java.util.List; + +/** + * DocumentsAdapter provides glue between a directory Model, and RecylcerView. We've + * abstracted this a bit in order to decompose some specialized support + * for adding dummy layout objects (@see SectionBreakDocumentsAdapter). Handling of the + * dummy layout objects was error prone when interspersed with the core mode / adapter code. + * + * @see ModelBackedDocumentsAdapter + * @see SectionBreakDocumentsAdapter + */ +abstract class DocumentsAdapter + extends RecyclerView.Adapter<DocumentHolder> + implements Model.UpdateListener { + + // Payloads for notifyItemChange to distinguish between selection and other events. + static final String SELECTION_CHANGED_MARKER = "Selection-Changed"; + + /** + * Returns a list of model IDs of items currently in the adapter. Excludes items that are + * currently hidden (see {@link #hide(String...)}). + * + * @return A list of Model IDs. + */ + abstract List<String> getModelIds(); + + /** + * Triggers item-change notifications by stable ID (as opposed to position). + * Passing an unrecognized ID will result in a warning in logcat, but no other error. + */ + abstract void notifyItemSelectionChanged(String id); + + /** + * @return The model ID of the item at the given adapter position. + */ + abstract String getModelId(int position); + + /** + * Hides a set of items from the associated RecyclerView. + * + * @param ids The Model IDs of the items to hide. + * @return A SparseArray that maps the hidden IDs to their old positions. This can be used + * to {@link #unhide} the items if necessary. + */ + abstract public SparseArray<String> hide(String... ids); + + /** + * Unhides a set of previously hidden items. + * + * @param ids A sparse array of IDs from a previous call to {@link #hide}. + */ + abstract void unhide(SparseArray<String> ids); + + /** + * Returns a class that yields the span size for a particular element. This is + * primarily useful in {@link SectionBreakDocumentsAdapterWrapper} where + * we adjust sizes. + */ + GridLayoutManager.SpanSizeLookup createSpanSizeLookup() { + throw new UnsupportedAddressTypeException(); + } + + static boolean isDirectory(Cursor cursor) { + final String mimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE); + return Document.MIME_TYPE_DIR.equals(mimeType); + } + + boolean isDirectory(Model model, int position) { + String modelId = getModelIds().get(position); + Cursor cursor = model.getItem(modelId); + return isDirectory(cursor); + } + + /** + * Environmental access for View adapter implementations. + */ + interface Environment { + Context getContext(); + int getColumnCount(); + State getDisplayState(); + boolean isSelected(String id); + Model getModel(); + boolean isDocumentEnabled(String mimeType, int flags); + void initDocumentHolder(DocumentHolder holder); + void onBindDocumentHolder(DocumentHolder holder, Cursor cursor); + } +} diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/Model.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/Model.java index bea38c64ae68..864f4050e71c 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/Model.java +++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/Model.java @@ -35,7 +35,6 @@ import android.provider.DocumentsContract; import android.provider.DocumentsContract.Document; import android.support.annotation.Nullable; import android.support.annotation.VisibleForTesting; -import android.support.v7.widget.RecyclerView; import android.util.Log; import com.android.documentsui.BaseActivity.SiblingProvider; @@ -74,7 +73,7 @@ public class Model implements SiblingProvider { @Nullable String info; @Nullable String error; - Model(Context context, RecyclerView.Adapter<?> viewAdapter) { + Model(Context context) { mContext = context; } diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/ModelBackedDocumentsAdapter.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/ModelBackedDocumentsAdapter.java new file mode 100644 index 000000000000..bb0d7299aa32 --- /dev/null +++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/ModelBackedDocumentsAdapter.java @@ -0,0 +1,223 @@ +/* + * Copyright (C) 2015 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.dirlist; + +import static com.android.documentsui.Shared.DEBUG; +import static com.android.documentsui.State.MODE_GRID; +import static com.android.documentsui.State.MODE_LIST; +import static com.android.documentsui.State.MODE_UNKNOWN; +import static com.android.documentsui.model.DocumentInfo.getCursorInt; +import static com.android.documentsui.model.DocumentInfo.getCursorString; + +import android.database.Cursor; +import android.provider.DocumentsContract.Document; +import android.support.v7.widget.GridLayoutManager; +import android.util.Log; +import android.util.SparseArray; +import android.view.ViewGroup; + +import com.android.documentsui.State; + +import com.google.common.collect.Sets; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * Adapts from dirlist.Model to something RecyclerView understands. + */ +final class ModelBackedDocumentsAdapter extends DocumentsAdapter { + + private static final String TAG = "ModelBackedDocumentsAdapter"; + public static final int ITEM_TYPE_DOCUMENT = 1; + public static final int ITEM_TYPE_DIRECTORY = 2; + + // Provides access to information needed when creating and view holders. This + // isn't an ideal pattern (more transitive dependency stuff) but good enough for now. + private final Environment mEnv; + private final IconHelper mIconHelper; // a transitive dependency of the holders. + + /** + * An ordered list of model IDs. This is the data structure that determines what shows up in + * the UI, and where. + */ + private List<String> mModelIds = new ArrayList<>(); + + // List of files that have been deleted. Some transient directory updates + // may happen while files are being deleted. During this time we don't + // want once-hidden files to be re-shown. We only remove + // items from this list when we get a model update where the model + // does not contain a corresponding id. This ensures hidden entries + // don't momentarily re-appear if we get intermediate updates from + // the file system. + private Set<String> mHiddenIds = new HashSet<>(); + + public ModelBackedDocumentsAdapter(Environment env, IconHelper iconHelper) { + mEnv = env; + mIconHelper = iconHelper; + } + + @Override + public DocumentHolder onCreateViewHolder(ViewGroup parent, int viewType) { + DocumentHolder holder = null; + final State state = mEnv.getDisplayState(); + switch (state.derivedMode) { + case MODE_GRID: + switch (viewType) { + case ITEM_TYPE_DIRECTORY: + holder = new GridDirectoryHolder(mEnv.getContext(), parent); + break; + case ITEM_TYPE_DOCUMENT: + holder = new GridDocumentHolder(mEnv.getContext(), parent, mIconHelper); + break; + default: + throw new IllegalStateException("Unsupported layout type."); + } + break; + case MODE_LIST: + holder = new ListDocumentHolder(mEnv.getContext(), parent, mIconHelper); + break; + case MODE_UNKNOWN: + default: + throw new IllegalStateException("Unsupported layout mode."); + } + + mEnv.initDocumentHolder(holder); + return holder; + } + + @Override + public void onBindViewHolder(DocumentHolder holder, int position, List<Object> payload) { + if (payload.contains(SELECTION_CHANGED_MARKER)) { + final boolean selected = mEnv.isSelected(mModelIds.get(position)); + holder.setSelected(selected); + } else { + onBindViewHolder(holder, position); + } + } + + @Override + public void onBindViewHolder(DocumentHolder holder, int position) { + String modelId = mModelIds.get(position); + Cursor cursor = mEnv.getModel().getItem(modelId); + holder.bind(cursor, modelId, mEnv.getDisplayState()); + + final String docMimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE); + final int docFlags = getCursorInt(cursor, Document.COLUMN_FLAGS); + + holder.setSelected(mEnv.isSelected(modelId)); + holder.setEnabled(mEnv.isDocumentEnabled(docMimeType, docFlags)); + + mEnv.onBindDocumentHolder(holder, cursor); + } + + @Override + public int getItemCount() { + return mModelIds.size(); + } + + @Override + public void onModelUpdate(Model model) { + if (DEBUG && mHiddenIds.size() > 0) { + Log.d(TAG, "Updating model with hidden ids: " + mHiddenIds); + } + + List<String> modelIds = model.getModelIds(); + mModelIds = new ArrayList<>(modelIds.size()); + for (String id : modelIds) { + if (!mHiddenIds.contains(id)) { + mModelIds.add(id); + } else { + if (DEBUG) Log.d(TAG, "Omitting hidden id from model during update: " + id); + } + } + + // Finally remove any hidden ids that aren't present in the model. + // This assumes that model updates represent a complete set of files. + mHiddenIds.retainAll(mModelIds); + } + + @Override + public void onModelUpdateFailed(Exception e) { + Log.w(TAG, "Model update failed.", e); + mModelIds.clear(); + } + + @Override + public String getModelId(int adapterPosition) { + return mModelIds.get(adapterPosition); + } + + @Override + public SparseArray<String> hide(String... ids) { + if (DEBUG) Log.d(TAG, "Hiding ids: " + ids); + Set<String> toHide = Sets.newHashSet(ids); + + // Proceed backwards through the list of items, because each removal causes the + // positions of all subsequent items to change. + SparseArray<String> hiddenItems = new SparseArray<>(); + for (int i = mModelIds.size() - 1; i >= 0; --i) { + String id = mModelIds.get(i); + if (toHide.contains(id)) { + mHiddenIds.add(id); + hiddenItems.put(i, mModelIds.remove(i)); + notifyItemRemoved(i); + } + } + + return hiddenItems; + } + + @Override + public void unhide(SparseArray<String> ids) { + if (DEBUG) Log.d(TAG, "Un-iding ids: " + ids); + // Proceed backwards through the list of items, because each addition causes the + // positions of all subsequent items to change. + for (int i = ids.size() - 1; i >= 0; --i) { + int pos = ids.keyAt(i); + String id = ids.get(pos); + mHiddenIds.remove(id); + mModelIds.add(pos, id); + notifyItemInserted(pos); + } + } + + @Override + public List<String> getModelIds() { + return mModelIds; + } + + @Override + public int getItemViewType(int position) { + return isDirectory(mEnv.getModel(), position) + ? ITEM_TYPE_DIRECTORY + : ITEM_TYPE_DOCUMENT; + } + + @Override + public void notifyItemSelectionChanged(String id) { + int position = mModelIds.indexOf(id); + + if (position >= 0) { + notifyItemChanged(position, SELECTION_CHANGED_MARKER); + } else { + Log.w(TAG, "Item change notification received for unknown item: " + id); + } + } +} diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/MultiSelectManager.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/MultiSelectManager.java index e47af6710c9a..4b3bf1ef2d31 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/MultiSelectManager.java +++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/MultiSelectManager.java @@ -75,9 +75,6 @@ public final class MultiSelectManager implements View.OnKeyListener { private boolean mSingleSelect; - // Payloads for notifyItemChange to distinguish between selection and other events. - public static final String SELECTION_CHANGED_MARKER = "Selection-Changed"; - @Nullable private BandController mBandManager; /** @@ -339,7 +336,10 @@ public final class MultiSelectManager implements View.OnKeyListener { if (DEBUG) Log.d(TAG, "Ignoring toggle for element with no position."); return; } - toggleSelection(mEnvironment.getModelIdFromAdapterPosition(position)); + String id = mEnvironment.getModelIdFromAdapterPosition(position); + if (id != null) { + toggleSelection(id); + } } /** @@ -348,6 +348,7 @@ public final class MultiSelectManager implements View.OnKeyListener { * @param modelId */ public void toggleSelection(String modelId) { + checkNotNull(modelId); boolean changed = false; if (mSelection.contains(modelId)) { changed = attemptDeselect(modelId); @@ -405,6 +406,10 @@ public final class MultiSelectManager implements View.OnKeyListener { checkState(end >= begin); for (int i = begin; i <= end; i++) { String id = mEnvironment.getModelIdFromAdapterPosition(i); + if (id == null) { + continue; + } + if (selected) { boolean canSelect = notifyBeforeItemStateChange(id, true); if (canSelect) { @@ -436,6 +441,7 @@ public final class MultiSelectManager implements View.OnKeyListener { * @return True if the update was applied. */ private boolean attemptDeselect(String id) { + checkArgument(id != null); if (notifyBeforeItemStateChange(id, false)) { mSelection.remove(id); notifyItemStateChanged(id, false); @@ -462,6 +468,7 @@ public final class MultiSelectManager implements View.OnKeyListener { * (identified by {@code position}) changes. */ private void notifyItemStateChanged(String id, boolean selected) { + checkArgument(id != null); int lastListener = mCallbacks.size() - 1; for (int i = lastListener; i > -1; i--) { mCallbacks.get(i).onItemStateChanged(id, selected); @@ -613,7 +620,7 @@ public final class MultiSelectManager implements View.OnKeyListener { * @param id * @return true if the position is currently selected. */ - public boolean contains(String id) { + public boolean contains(@Nullable String id) { return mTotalSelection.contains(id); } @@ -804,7 +811,12 @@ public final class MultiSelectManager implements View.OnKeyListener { int getChildCount(); int getVisibleChildCount(); void focusItem(int position); - String getModelIdFromAdapterPosition(int position); + /** + * Returns null if non-useful item. + * @param position + * @return + */ + @Nullable String getModelIdFromAdapterPosition(int position); int getItemCount(); List<String> getModelIds(); void notifyItemChanged(String id); @@ -818,11 +830,11 @@ public final class MultiSelectManager implements View.OnKeyListener { private final Drawable mBand; private boolean mIsOverlayShown = false; - private DirectoryFragment.DocumentsAdapter mAdapter; + private DocumentsAdapter mAdapter; RuntimeSelectionEnvironment(RecyclerView rv) { mView = rv; - mAdapter = (DirectoryFragment.DocumentsAdapter) rv.getAdapter(); + mAdapter = (DocumentsAdapter) rv.getAdapter(); mBand = mView.getContext().getTheme().getDrawable(R.drawable.band_select_overlay); } @@ -841,7 +853,7 @@ public final class MultiSelectManager implements View.OnKeyListener { } @Override - public String getModelIdFromAdapterPosition(int position) { + public @Nullable String getModelIdFromAdapterPosition(int position) { return mAdapter.getModelId(position); } @@ -964,7 +976,7 @@ public final class MultiSelectManager implements View.OnKeyListener { @Override public void notifyItemChanged(String id) { - mAdapter.notifyItemChanged(id, SELECTION_CHANGED_MARKER); + mAdapter.notifyItemSelectionChanged(id); } @Override diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/SectionBreakDocumentsAdapterWrapper.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/SectionBreakDocumentsAdapterWrapper.java new file mode 100644 index 000000000000..ae6ada903205 --- /dev/null +++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/SectionBreakDocumentsAdapterWrapper.java @@ -0,0 +1,211 @@ +/* + * Copyright (C) 2015 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.dirlist; + +import static com.android.internal.util.Preconditions.checkArgument; + +import android.support.v7.widget.GridLayoutManager; +import android.support.v7.widget.RecyclerView.AdapterDataObserver; +import android.util.SparseArray; +import android.view.ViewGroup; + +import java.util.List; + +/** + * Adapter wrapper that inserts a sort of line break item between directories and regular files. + * Only needs to be used in GRID mode...at this time. + */ +final class SectionBreakDocumentsAdapterWrapper extends DocumentsAdapter { + + private static final String TAG = "SectionBreakDocumentsAdapterWrapper"; + private static final int ITEM_TYPE_SECTION_BREAK = Integer.MAX_VALUE; + + private final Environment mEnv; + private final DocumentsAdapter mDelegate; + + private int mBreakPosition = -1; + + SectionBreakDocumentsAdapterWrapper(Environment environment, DocumentsAdapter delegate) { + mEnv = environment; + mDelegate = delegate; + + // Events and information flows two ways between recycler view and adapter. + // So we need to listen to events on our delegate and forward them + // to our listeners with a corrected position. + AdapterDataObserver adapterDataObserver = new AdapterDataObserver() { + public void onChanged() { + throw new UnsupportedOperationException(); + } + + public void onItemRangeChanged(int positionStart, int itemCount) { + checkArgument(itemCount == 1); + } + + public void onItemRangeInserted(int positionStart, int itemCount) { + checkArgument(itemCount == 1); + if (positionStart < mBreakPosition) { + mBreakPosition++; + } + notifyItemRangeInserted(toViewPosition(positionStart), itemCount); + } + + public void onItemRangeRemoved(int positionStart, int itemCount) { + checkArgument(itemCount == 1); + if (positionStart < mBreakPosition) { + mBreakPosition--; + } + notifyItemRangeRemoved(toViewPosition(positionStart), itemCount); + } + + public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) { + throw new UnsupportedOperationException(); + } + }; + + mDelegate.registerAdapterDataObserver(adapterDataObserver); + } + + public GridLayoutManager.SpanSizeLookup createSpanSizeLookup() { + return new GridLayoutManager.SpanSizeLookup() { + @Override + public int getSpanSize(int position) { + // Make layout whitespace span the grid. This has the effect of breaking + // grid rows whenever layout whitespace is encountered. + if (getItemViewType(position) == ITEM_TYPE_SECTION_BREAK) { + return mEnv.getColumnCount(); + } else { + return 1; + } + } + }; + } + + @Override + public DocumentHolder onCreateViewHolder(ViewGroup parent, int viewType) { + if (viewType == ITEM_TYPE_SECTION_BREAK) { + return new EmptyDocumentHolder(mEnv.getContext()); + } else { + return mDelegate.createViewHolder(parent, viewType); + } + } + + @Override + public void onBindViewHolder(DocumentHolder holder, int p, List<Object> payload) { + if (holder.getItemViewType() != ITEM_TYPE_SECTION_BREAK) { + mDelegate.onBindViewHolder(holder, toDelegatePosition(p), payload); + } + } + + @Override + public void onBindViewHolder(DocumentHolder holder, int p) { + if (holder.getItemViewType() != ITEM_TYPE_SECTION_BREAK) { + mDelegate.onBindViewHolder(holder, toDelegatePosition(p)); + } + } + + @Override + public int getItemCount() { + return mBreakPosition == -1 + ? mDelegate.getItemCount() + : mDelegate.getItemCount() + 1; + } + + @Override + public void onModelUpdate(Model model) { + mDelegate.onModelUpdate(model); + mBreakPosition = -1; + + // Walk down the list of IDs till we encounter something that's not a directory, and + // insert a whitespace element - this introduces a visual break in the grid between + // folders and documents. + // TODO: This code makes assumptions about the model, namely, that it performs a + // bucketed sort where directories will always be ordered before other files. CBB. + List<String> modelIds = mDelegate.getModelIds(); + for (int i = 0; i < modelIds.size(); i++) { + if (!isDirectory(model, i)) { + mBreakPosition = i; + break; + } + } + } + + @Override + public void onModelUpdateFailed(Exception e) { + mDelegate.onModelUpdateFailed(e); + } + + @Override + public int getItemViewType(int p) { + if (p == mBreakPosition) { + return ITEM_TYPE_SECTION_BREAK; + } else { + return mDelegate.getItemViewType(toDelegatePosition(p)); + } + } + + /** + * Returns the position of an item in the delegate, adjusting + * values that are greater than the break position. + * + * @param p Position within the view + * @return Position within the delegate + */ + private int toDelegatePosition(int p) { + return (mBreakPosition != -1 && p > mBreakPosition) ? p - 1 : p; + } + + /** + * Returns the position of an item in the view, adjusting + * values that are greater than the break position. + * + * @param p Position within the delegate + * @return Position within the view + */ + private int toViewPosition(int p) { + // If position is greater than or equal to the break, increase by one. + return (mBreakPosition != -1 && p >= mBreakPosition) ? p + 1 : p; + } + + @Override + public SparseArray<String> hide(String... ids) { + // NOTE: We hear about these changes and adjust break position + // in our AdapterDataObserver. + return mDelegate.hide(ids); + } + + @Override + void unhide(SparseArray<String> ids) { + // NOTE: We hear about these changes and adjust break position + // in our AdapterDataObserver. + mDelegate.unhide(ids); + } + + @Override + List<String> getModelIds() { + return mDelegate.getModelIds(); + } + + @Override + String getModelId(int p) { + return (p == mBreakPosition) ? null : mDelegate.getModelId(toDelegatePosition(p)); + } + + @Override + public void notifyItemSelectionChanged(String id) { + mDelegate.notifyItemSelectionChanged(id); + } +} diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/ModelBackedDocumentsAdapterTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/ModelBackedDocumentsAdapterTest.java new file mode 100644 index 000000000000..92668a8cd20f --- /dev/null +++ b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/ModelBackedDocumentsAdapterTest.java @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2015 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.dirlist; + +import android.content.Context; +import android.database.Cursor; +import android.support.v7.widget.RecyclerView; +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.SmallTest; +import android.view.ViewGroup; + +import com.android.documentsui.State; + +import java.util.List; + +@SmallTest +public class ModelBackedDocumentsAdapterTest extends AndroidTestCase { + + private static final String AUTHORITY = "test_authority"; + private static final String[] NAMES = new String[] { + "4", + "foo", + "1", + "bar", + "*(Ljifl;a", + "0", + "baz", + "2", + "3", + "%$%VD" + }; + + private TestModel model; + private ModelBackedDocumentsAdapter adapter; + + public void setUp() { + + final Context testContext = TestContext.createStorageTestContext(getContext(), AUTHORITY); + model = new TestModel(testContext, AUTHORITY); + model.update(NAMES); + + DocumentsAdapter.Environment env = new TestEnvironment(testContext); + + adapter = new ModelBackedDocumentsAdapter( + env, new IconHelper(testContext, State.MODE_GRID)); + adapter.onModelUpdate(model); + } + + // Tests that the item count is correct. + public void testItemCount() { + assertEquals(model.getItemCount(), adapter.getItemCount()); + } + + // Tests that the item count is correct. + public void testHide_ItemCount() { + List<String> ids = model.getModelIds(); + adapter.hide(ids.get(0), ids.get(1)); + assertEquals(model.getItemCount() - 2, adapter.getItemCount()); + } + + private final class TestEnvironment implements DocumentsAdapter.Environment { + private final Context testContext; + + private TestEnvironment(Context testContext) { + this.testContext = testContext; + } + + @Override + public boolean isSelected(String id) { + return false; + } + + @Override + public boolean isDocumentEnabled(String mimeType, int flags) { + return true; + } + + @Override + public void initDocumentHolder(DocumentHolder holder) {} + + @Override + public Model getModel() { + return model; + } + + @Override + public State getDisplayState() { + return null; + } + + @Override + public Context getContext() { + return testContext; + } + + @Override + public int getColumnCount() { + return 4; + } + + @Override + public void onBindDocumentHolder(DocumentHolder holder, Cursor cursor) {} + } + + private static class DummyListener implements Model.UpdateListener { + public void onModelUpdate(Model model) {} + public void onModelUpdateFailed(Exception e) {} + } + + private static class DummyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { + public int getItemCount() { return 0; } + public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {} + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + return null; + } + } +} diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/ModelTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/ModelTest.java index 121eb41c4c2a..bed7c9c9a253 100644 --- a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/ModelTest.java +++ b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/ModelTest.java @@ -21,16 +21,10 @@ import android.content.Context; import android.content.ContextWrapper; import android.database.Cursor; import android.database.MatrixCursor; -import android.net.Uri; -import android.os.Bundle; -import android.provider.DocumentsContract; import android.provider.DocumentsContract.Document; -import android.support.v7.widget.RecyclerView; import android.test.AndroidTestCase; -import android.test.mock.MockContentProvider; import android.test.mock.MockContentResolver; import android.test.suitebuilder.annotation.SmallTest; -import android.view.ViewGroup; import com.android.documentsui.DirectoryResult; import com.android.documentsui.RootCursorWrapper; @@ -49,6 +43,7 @@ public class ModelTest extends AndroidTestCase { private static final int ITEM_COUNT = 10; private static final String AUTHORITY = "test_authority"; + private static final String[] COLUMNS = new String[]{ RootCursorWrapper.COLUMN_AUTHORITY, Document.COLUMN_DOCUMENT_ID, @@ -57,23 +52,24 @@ public class ModelTest extends AndroidTestCase { Document.COLUMN_SIZE, Document.COLUMN_MIME_TYPE }; - private static Cursor cursor; + private static final String[] NAMES = new String[] { + "4", + "foo", + "1", + "bar", + "*(Ljifl;a", + "0", + "baz", + "2", + "3", + "%$%VD" + }; + + private Cursor cursor; private Context context; private Model model; private TestContentProvider provider; - private static final String[] NAMES = new String[] { - "4", - "foo", - "1", - "bar", - "*(Ljifl;a", - "0", - "baz", - "2", - "3", - "%$%VD" - }; public void setUp() { setupTestContext(); @@ -97,7 +93,7 @@ public class ModelTest extends AndroidTestCase { r.cursor = cursor; // Instantiate the model with a dummy view adapter and listener that (for now) do nothing. - model = new Model(context, new DummyAdapter()); + model = new Model(context); model.addUpdateListener(new DummyListener()); model.update(r); } @@ -326,32 +322,4 @@ public class ModelTest extends AndroidTestCase { public void onModelUpdate(Model model) {} public void onModelUpdateFailed(Exception e) {} } - - private static class DummyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { - public int getItemCount() { return 0; } - public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {} - public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { - return null; - } - } - - private static class TestContentProvider extends MockContentProvider { - List<Uri> mDeleted = new ArrayList<>(); - - @Override - public Bundle call(String method, String arg, Bundle extras) { - // Intercept and log delete method calls. - if (DocumentsContract.METHOD_DELETE_DOCUMENT.equals(method)) { - final Uri documentUri = extras.getParcelable(DocumentsContract.EXTRA_URI); - mDeleted.add(documentUri); - return new Bundle(); - } else { - return super.call(method, arg, extras); - } - } - - public void assertWasDeleted(DocumentInfo doc) { - assertTrue(mDeleted.contains(doc.derivedUri)); - } - } } diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/TestContentProvider.java b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/TestContentProvider.java new file mode 100644 index 000000000000..c8d424f10eae --- /dev/null +++ b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/TestContentProvider.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2015 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.dirlist; + +import android.net.Uri; +import android.os.Bundle; +import android.provider.DocumentsContract; +import android.test.mock.MockContentProvider; + +import com.android.documentsui.model.DocumentInfo; + +import java.util.ArrayList; +import java.util.List; + +/** + * A very simple test double for ContentProvider. Useful in this package only. + */ +class TestContentProvider extends MockContentProvider { + List<Uri> mDeleted = new ArrayList<>(); + + @Override + public Bundle call(String method, String arg, Bundle extras) { + // Intercept and log delete method calls. + if (DocumentsContract.METHOD_DELETE_DOCUMENT.equals(method)) { + final Uri documentUri = extras.getParcelable(DocumentsContract.EXTRA_URI); + mDeleted.add(documentUri); + return new Bundle(); + } else { + return super.call(method, arg, extras); + } + } + + public void assertWasDeleted(DocumentInfo doc) { + ModelTest.assertTrue(mDeleted.contains(doc.derivedUri)); + } +}
\ No newline at end of file diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/TestContext.java b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/TestContext.java new file mode 100644 index 000000000000..714062d5007c --- /dev/null +++ b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/TestContext.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2015 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.dirlist; + +import android.content.ContentResolver; +import android.content.Context; +import android.content.ContextWrapper; +import android.test.mock.MockContentResolver; + +public final class TestContext { + + /** + * Returns a Context configured with test provider for authority. + */ + static Context createStorageTestContext(Context context, String authority) { + final MockContentResolver testResolver = new MockContentResolver(); + TestContentProvider provider = new TestContentProvider(); + testResolver.addProvider(authority, provider); + + return new ContextWrapper(context) { + @Override + public ContentResolver getContentResolver() { + return testResolver; + } + }; + } +} diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/TestModel.java b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/TestModel.java new file mode 100644 index 000000000000..f861c7352447 --- /dev/null +++ b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/TestModel.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2015 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.dirlist; + +import android.content.Context; +import android.database.MatrixCursor; +import android.provider.DocumentsContract.Document; + +import com.android.documentsui.DirectoryResult; +import com.android.documentsui.RootCursorWrapper; +import com.android.documentsui.dirlist.MultiSelectManager.Selection; + +import java.util.Random; +import java.util.Set; + +public class TestModel extends Model { + + private static final String[] COLUMNS = new String[]{ + RootCursorWrapper.COLUMN_AUTHORITY, + Document.COLUMN_DOCUMENT_ID, + Document.COLUMN_FLAGS, + Document.COLUMN_DISPLAY_NAME, + Document.COLUMN_SIZE, + Document.COLUMN_MIME_TYPE + }; + + private final String mAuthority; + private Set<String> mDeleted; + + /** + * Creates a new context. context must be configured with provider for authority. + * @see TestContext#createStorageTestContext(Context, String). + */ + public TestModel(Context context, String authority) { + super(context); + mAuthority = authority; + } + + void update(String... names) { + Random rand = new Random(); + + MatrixCursor c = new MatrixCursor(COLUMNS); + for (int i = 0; i < names.length; i++) { + MatrixCursor.RowBuilder row = c.newRow(); + row.add(RootCursorWrapper.COLUMN_AUTHORITY, mAuthority); + row.add(Document.COLUMN_DOCUMENT_ID, Integer.toString(i)); + row.add(Document.COLUMN_FLAGS, Document.FLAG_SUPPORTS_DELETE); + // Generate random document names and sizes. This forces the model's internal sort code + // to actually do something. + row.add(Document.COLUMN_DISPLAY_NAME, names[i]); + row.add(Document.COLUMN_SIZE, rand.nextInt()); + } + + DirectoryResult r = new DirectoryResult(); + r.cursor = c; + update(r); + } + + @Override + public void delete(Selection selected, DeletionListener listener) { + for (String id : selected.getAll()) { + mDeleted.add(id); + } + listener.onCompletion(); + } +} |