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 static com.android.documentsui.Shared.DEBUG; 20 import static com.android.documentsui.State.MODE_GRID; 21 import static com.android.documentsui.State.MODE_LIST; 22 import static com.android.documentsui.model.DocumentInfo.getCursorInt; 23 import static com.android.documentsui.model.DocumentInfo.getCursorString; 24 25 import android.database.Cursor; 26 import android.provider.DocumentsContract.Document; 27 import android.util.Log; 28 import android.util.SparseArray; 29 import android.view.ViewGroup; 30 31 import com.android.documentsui.State; 32 import com.google.common.collect.Sets; 33 34 import java.util.ArrayList; 35 import java.util.HashSet; 36 import java.util.List; 37 import java.util.Set; 38 39 /** 40 * Adapts from dirlist.Model to something RecyclerView understands. 41 */ 42 final class ModelBackedDocumentsAdapter extends DocumentsAdapter { 43 44 private static final String TAG = "ModelBackedDocuments"; 45 public static final int ITEM_TYPE_DOCUMENT = 1; 46 public static final int ITEM_TYPE_DIRECTORY = 2; 47 48 // Provides access to information needed when creating and view holders. This 49 // isn't an ideal pattern (more transitive dependency stuff) but good enough for now. 50 private final Environment mEnv; 51 private final IconHelper mIconHelper; // a transitive dependency of the holders. 52 53 /** 54 * An ordered list of model IDs. This is the data structure that determines what shows up in 55 * the UI, and where. 56 */ 57 private List<String> mModelIds = new ArrayList<>(); 58 59 // List of files that have been deleted. Some transient directory updates 60 // may happen while files are being deleted. During this time we don't 61 // want once-hidden files to be re-shown. We only remove 62 // items from this list when we get a model update where the model 63 // does not contain a corresponding id. This ensures hidden entries 64 // don't momentarily re-appear if we get intermediate updates from 65 // the file system. 66 private Set<String> mHiddenIds = new HashSet<>(); 67 68 public ModelBackedDocumentsAdapter(Environment env, IconHelper iconHelper) { 69 mEnv = env; 70 mIconHelper = iconHelper; 71 } 72 73 @Override 74 public DocumentHolder onCreateViewHolder(ViewGroup parent, int viewType) { 75 DocumentHolder holder = null; 76 final State state = mEnv.getDisplayState(); 77 switch (state.derivedMode) { 78 case MODE_GRID: 79 switch (viewType) { 80 case ITEM_TYPE_DIRECTORY: 81 holder = new GridDirectoryHolder(mEnv.getContext(), parent); 82 break; 83 case ITEM_TYPE_DOCUMENT: 84 holder = new GridDocumentHolder(mEnv.getContext(), parent, mIconHelper); 85 break; 86 default: 87 throw new IllegalStateException("Unsupported layout type."); 88 } 89 break; 90 case MODE_LIST: 91 holder = new ListDocumentHolder(mEnv.getContext(), parent, mIconHelper); 92 break; 93 default: 94 throw new IllegalStateException("Unsupported layout mode."); 95 } 96 97 mEnv.initDocumentHolder(holder); 98 return holder; 99 } 100 101 @Override 102 public void onBindViewHolder(DocumentHolder holder, int position, List<Object> payload) { 103 if (payload.contains(SELECTION_CHANGED_MARKER)) { 104 final boolean selected = mEnv.isSelected(mModelIds.get(position)); 105 holder.setSelected(selected, true); 106 } else { 107 onBindViewHolder(holder, position); 108 } 109 } 110 111 @Override 112 public void onBindViewHolder(DocumentHolder holder, int position) { 113 String modelId = mModelIds.get(position); 114 Cursor cursor = mEnv.getModel().getItem(modelId); 115 holder.bind(cursor, modelId, mEnv.getDisplayState()); 116 117 final String docMimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE); 118 final int docFlags = getCursorInt(cursor, Document.COLUMN_FLAGS); 119 120 boolean enabled = mEnv.isDocumentEnabled(docMimeType, docFlags); 121 boolean selected = mEnv.isSelected(modelId); 122 if (!enabled) { 123 assert(!selected); 124 } 125 holder.setEnabled(enabled); 126 holder.setSelected(mEnv.isSelected(modelId), false); 127 128 mEnv.onBindDocumentHolder(holder, cursor); 129 } 130 131 @Override 132 public int getItemCount() { 133 return mModelIds.size(); 134 } 135 136 @Override 137 public void onModelUpdate(Model model) { 138 if (DEBUG && mHiddenIds.size() > 0) { 139 Log.d(TAG, "Updating model with hidden ids: " + mHiddenIds); 140 } 141 142 String[] modelIds = model.getModelIds(); 143 mModelIds = new ArrayList<>(modelIds.length); 144 for (String id : modelIds) { 145 if (!mHiddenIds.contains(id)) { 146 mModelIds.add(id); 147 } else { 148 if (DEBUG) Log.d(TAG, "Omitting hidden id from model during update: " + id); 149 } 150 } 151 152 // Finally remove any hidden ids that aren't present in the model. 153 // This assumes that model updates represent a complete set of files. 154 mHiddenIds.retainAll(mModelIds); 155 } 156 157 @Override 158 public void onModelUpdateFailed(Exception e) { 159 Log.w(TAG, "Model update failed.", e); 160 mModelIds.clear(); 161 } 162 163 @Override 164 public String getModelId(int adapterPosition) { 165 return mModelIds.get(adapterPosition); 166 } 167 168 @Override 169 public SparseArray<String> hide(String... ids) { 170 if (DEBUG) Log.d(TAG, "Hiding ids: " + ids); 171 Set<String> toHide = Sets.newHashSet(ids); 172 173 // Proceed backwards through the list of items, because each removal causes the 174 // positions of all subsequent items to change. 175 SparseArray<String> hiddenItems = new SparseArray<>(); 176 for (int i = mModelIds.size() - 1; i >= 0; --i) { 177 String id = mModelIds.get(i); 178 if (toHide.contains(id)) { 179 mHiddenIds.add(id); 180 hiddenItems.put(i, mModelIds.remove(i)); 181 notifyItemRemoved(i); 182 } 183 } 184 185 return hiddenItems; 186 } 187 188 @Override 189 public List<String> getModelIds() { 190 return mModelIds; 191 } 192 193 @Override 194 public int getItemViewType(int position) { 195 return isDirectory(mEnv.getModel(), position) 196 ? ITEM_TYPE_DIRECTORY 197 : ITEM_TYPE_DOCUMENT; 198 } 199 200 @Override 201 public void onItemSelectionChanged(String id) { 202 int position = mModelIds.indexOf(id); 203 204 if (position >= 0) { 205 notifyItemChanged(position, SELECTION_CHANGED_MARKER); 206 } else { 207 Log.w(TAG, "Item change notification received for unknown item: " + id); 208 } 209 } 210 } 211