Home | History | Annotate | Download | only in dirlist
      1 /*
      2  * Copyright (C) 2015 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.documentsui.dirlist;
     18 
     19 import android.content.Context;
     20 import android.database.Cursor;
     21 import android.support.v7.widget.GridLayoutManager;
     22 import android.support.v7.widget.RecyclerView.AdapterDataObserver;
     23 import android.util.SparseArray;
     24 import android.view.ViewGroup;
     25 import android.widget.Space;
     26 
     27 import com.android.documentsui.R;
     28 import com.android.documentsui.State;
     29 
     30 import java.util.List;
     31 
     32 /**
     33  * Adapter wrapper that inserts a sort of line break item between directories and regular files.
     34  * Only needs to be used in GRID mode...at this time.
     35  */
     36 final class SectionBreakDocumentsAdapterWrapper extends DocumentsAdapter {
     37 
     38     private static final String TAG = "SectionBreakDocumentsAdapterWrapper";
     39     private static final int ITEM_TYPE_SECTION_BREAK = Integer.MAX_VALUE;
     40 
     41     private final Environment mEnv;
     42     private final DocumentsAdapter mDelegate;
     43 
     44     private int mBreakPosition = -1;
     45 
     46     SectionBreakDocumentsAdapterWrapper(Environment environment, DocumentsAdapter delegate) {
     47         mEnv = environment;
     48         mDelegate = delegate;
     49 
     50         // Relay events published by our delegate to our listeners (presumably RecyclerView)
     51         // with adjusted positions.
     52         mDelegate.registerAdapterDataObserver(new EventRelay());
     53     }
     54 
     55     public GridLayoutManager.SpanSizeLookup createSpanSizeLookup() {
     56         return new GridLayoutManager.SpanSizeLookup() {
     57             @Override
     58             public int getSpanSize(int position) {
     59                 // Make layout whitespace span the grid. This has the effect of breaking
     60                 // grid rows whenever layout whitespace is encountered.
     61                 if (getItemViewType(position) == ITEM_TYPE_SECTION_BREAK) {
     62                     return mEnv.getColumnCount();
     63                 } else {
     64                     return 1;
     65                 }
     66             }
     67         };
     68     }
     69 
     70     @Override
     71     public DocumentHolder onCreateViewHolder(ViewGroup parent, int viewType) {
     72         if (viewType == ITEM_TYPE_SECTION_BREAK) {
     73             return new EmptyDocumentHolder(mEnv.getContext());
     74         } else {
     75             return mDelegate.createViewHolder(parent, viewType);
     76         }
     77     }
     78 
     79     @Override
     80     public void onBindViewHolder(DocumentHolder holder, int p, List<Object> payload) {
     81         if (holder.getItemViewType() != ITEM_TYPE_SECTION_BREAK) {
     82             mDelegate.onBindViewHolder(holder, toDelegatePosition(p), payload);
     83         } else {
     84             ((EmptyDocumentHolder)holder).bind(mEnv.getDisplayState());
     85         }
     86     }
     87 
     88     @Override
     89     public void onBindViewHolder(DocumentHolder holder, int p) {
     90         if (holder.getItemViewType() != ITEM_TYPE_SECTION_BREAK) {
     91             mDelegate.onBindViewHolder(holder, toDelegatePosition(p));
     92         } else {
     93             ((EmptyDocumentHolder)holder).bind(mEnv.getDisplayState());
     94         }
     95     }
     96 
     97     @Override
     98     public int getItemCount() {
     99         return mBreakPosition == -1
    100                 ? mDelegate.getItemCount()
    101                 : mDelegate.getItemCount() + 1;
    102     }
    103 
    104     @Override
    105     public void onModelUpdate(Model model) {
    106         mDelegate.onModelUpdate(model);
    107         mBreakPosition = -1;
    108 
    109         // Walk down the list of IDs till we encounter something that's not a directory, and
    110         // insert a whitespace element - this introduces a visual break in the grid between
    111         // folders and documents.
    112         // TODO: This code makes assumptions about the model, namely, that it performs a
    113         // bucketed sort where directories will always be ordered before other files. CBB.
    114         List<String> modelIds = mDelegate.getModelIds();
    115         for (int i = 0; i < modelIds.size(); i++) {
    116             if (!isDirectory(model, i)) {
    117                 // If the break is the first thing in the list, then there are actually no
    118                 // directories. In that case, don't insert a break at all.
    119                 if (i > 0) {
    120                     mBreakPosition = i;
    121                 }
    122                 break;
    123             }
    124         }
    125     }
    126 
    127     @Override
    128     public void onModelUpdateFailed(Exception e) {
    129         mDelegate.onModelUpdateFailed(e);
    130     }
    131 
    132     @Override
    133     public int getItemViewType(int p) {
    134         if (p == mBreakPosition) {
    135             return ITEM_TYPE_SECTION_BREAK;
    136         } else {
    137             return mDelegate.getItemViewType(toDelegatePosition(p));
    138         }
    139     }
    140 
    141     /**
    142      * Returns the position of an item in the delegate, adjusting
    143      * values that are greater than the break position.
    144      *
    145      * @param p Position within the view
    146      * @return Position within the delegate
    147      */
    148     private int toDelegatePosition(int p) {
    149         return (mBreakPosition != -1 && p > mBreakPosition) ? p - 1 : p;
    150     }
    151 
    152     /**
    153      * Returns the position of an item in the view, adjusting
    154      * values that are greater than the break position.
    155      *
    156      * @param p Position within the delegate
    157      * @return Position within the view
    158      */
    159     private int toViewPosition(int p) {
    160         // If position is greater than or equal to the break, increase by one.
    161         return (mBreakPosition != -1 && p >= mBreakPosition) ? p + 1 : p;
    162     }
    163 
    164     @Override
    165     public SparseArray<String> hide(String... ids) {
    166         // NOTE: We hear about these changes and adjust break position
    167         // in our AdapterDataObserver.
    168         return mDelegate.hide(ids);
    169     }
    170 
    171     @Override
    172     List<String> getModelIds() {
    173         return mDelegate.getModelIds();
    174     }
    175 
    176     @Override
    177     String getModelId(int p) {
    178         return (p == mBreakPosition) ? null : mDelegate.getModelId(toDelegatePosition(p));
    179     }
    180 
    181     @Override
    182     public void onItemSelectionChanged(String id) {
    183         mDelegate.onItemSelectionChanged(id);
    184     }
    185 
    186     // Listener we add to our delegate. This allows us to relay events published
    187     // by the delegate to our listeners (presumably RecyclerView) with adjusted positions.
    188     private final class EventRelay extends AdapterDataObserver {
    189         public void onChanged() {
    190             throw new UnsupportedOperationException();
    191         }
    192 
    193         public void onItemRangeChanged(int positionStart, int itemCount) {
    194             throw new UnsupportedOperationException();
    195         }
    196 
    197         public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
    198             assert(itemCount == 1);
    199             notifyItemRangeChanged(toViewPosition(positionStart), itemCount, payload);
    200         }
    201 
    202         public void onItemRangeInserted(int positionStart, int itemCount) {
    203             assert(itemCount == 1);
    204             if (positionStart < mBreakPosition) {
    205                 mBreakPosition++;
    206             }
    207             notifyItemRangeInserted(toViewPosition(positionStart), itemCount);
    208         }
    209 
    210         public void onItemRangeRemoved(int positionStart, int itemCount) {
    211             assert(itemCount == 1);
    212             if (positionStart < mBreakPosition) {
    213                 mBreakPosition--;
    214             }
    215             notifyItemRangeRemoved(toViewPosition(positionStart), itemCount);
    216         }
    217 
    218         public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
    219             throw new UnsupportedOperationException();
    220         }
    221     }
    222 
    223     /**
    224      * The most elegant transparent blank box that spans N rows ever conceived.
    225      */
    226     private static final class EmptyDocumentHolder extends DocumentHolder {
    227         final int mVisibleHeight;
    228 
    229         public EmptyDocumentHolder(Context context) {
    230             super(context, new Space(context));
    231 
    232             // Per UX spec, this puts a bigger gap between the folders and documents in the grid.
    233             mVisibleHeight = context.getResources().getDimensionPixelSize(
    234                     R.dimen.grid_item_margin);
    235         }
    236 
    237         public void bind(State state) {
    238             bind(null, null, state);
    239         }
    240 
    241         @Override
    242         public void bind(Cursor cursor, String modelId, State state) {
    243             if (state.derivedMode == State.MODE_GRID) {
    244                 itemView.setMinimumHeight(mVisibleHeight);
    245             } else {
    246                 itemView.setMinimumHeight(0);
    247             }
    248             return;
    249         }
    250     }
    251 }
    252