| /* |
| * 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.content.Context; |
| import android.database.Cursor; |
| import android.support.v7.widget.GridLayoutManager; |
| import android.support.v7.widget.RecyclerView.AdapterDataObserver; |
| import android.util.SparseArray; |
| import android.view.ViewGroup; |
| import android.widget.Space; |
| |
| import com.android.documentsui.R; |
| import com.android.documentsui.State; |
| |
| 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; |
| |
| // Relay events published by our delegate to our listeners (presumably RecyclerView) |
| // with adjusted positions. |
| mDelegate.registerAdapterDataObserver(new EventRelay()); |
| } |
| |
| 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); |
| } else { |
| ((EmptyDocumentHolder)holder).bind(mEnv.getDisplayState()); |
| } |
| } |
| |
| @Override |
| public void onBindViewHolder(DocumentHolder holder, int p) { |
| if (holder.getItemViewType() != ITEM_TYPE_SECTION_BREAK) { |
| mDelegate.onBindViewHolder(holder, toDelegatePosition(p)); |
| } else { |
| ((EmptyDocumentHolder)holder).bind(mEnv.getDisplayState()); |
| } |
| } |
| |
| @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)) { |
| // If the break is the first thing in the list, then there are actually no |
| // directories. In that case, don't insert a break at all. |
| if (i > 0) { |
| 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 onItemSelectionChanged(String id) { |
| mDelegate.onItemSelectionChanged(id); |
| } |
| |
| // Listener we add to our delegate. This allows us to relay events published |
| // by the delegate to our listeners (presumably RecyclerView) with adjusted positions. |
| private final class EventRelay extends AdapterDataObserver { |
| public void onChanged() { |
| throw new UnsupportedOperationException(); |
| } |
| |
| public void onItemRangeChanged(int positionStart, int itemCount) { |
| throw new UnsupportedOperationException(); |
| } |
| |
| public void onItemRangeChanged(int positionStart, int itemCount, Object payload) { |
| checkArgument(itemCount == 1); |
| notifyItemRangeChanged(toViewPosition(positionStart), itemCount, payload); |
| } |
| |
| 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(); |
| } |
| } |
| |
| /** |
| * The most elegant transparent blank box that spans N rows ever conceived. |
| */ |
| private static final class EmptyDocumentHolder extends DocumentHolder { |
| final int mVisibleHeight; |
| |
| public EmptyDocumentHolder(Context context) { |
| super(context, new Space(context)); |
| |
| // Per UX spec, this puts a bigger gap between the folders and documents in the grid. |
| mVisibleHeight = context.getResources().getDimensionPixelSize( |
| R.dimen.grid_item_margin); |
| } |
| |
| public void bind(State state) { |
| bind(null, null, state); |
| } |
| |
| @Override |
| public void bind(Cursor cursor, String modelId, State state) { |
| if (state.derivedMode == State.MODE_GRID) { |
| itemView.setMinimumHeight(mVisibleHeight); |
| } else { |
| itemView.setMinimumHeight(0); |
| } |
| return; |
| } |
| } |
| } |