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.base.DocumentInfo.getCursorInt; 20 import static com.android.documentsui.base.DocumentInfo.getCursorString; 21 import static com.android.documentsui.base.State.MODE_GRID; 22 import static com.android.documentsui.base.State.MODE_LIST; 23 24 import android.database.Cursor; 25 import android.provider.DocumentsContract.Document; 26 import android.util.Log; 27 import android.view.ViewGroup; 28 29 import androidx.recyclerview.selection.SelectionTracker; 30 import androidx.recyclerview.widget.RecyclerView; 31 32 import com.android.documentsui.ConfigStore; 33 import com.android.documentsui.Model; 34 import com.android.documentsui.Model.Update; 35 import com.android.documentsui.base.EventListener; 36 import com.android.documentsui.base.Lookup; 37 import com.android.documentsui.base.State; 38 import com.android.documentsui.roots.RootCursorWrapper; 39 import com.android.modules.utils.build.SdkLevel; 40 41 import java.util.ArrayList; 42 import java.util.List; 43 44 /** 45 * Adapts from dirlist.Model to something RecyclerView understands. 46 */ 47 final class ModelBackedDocumentsAdapter extends DocumentsAdapter { 48 49 private static final String TAG = "ModelBackedDocuments"; 50 51 // Provides access to information needed when creating and view holders. This 52 // isn't an ideal pattern (more transitive dependency stuff) but good enough for now. 53 private final Environment mEnv; 54 private final IconHelper mIconHelper; // a transitive dependency of the holders. 55 private final Lookup<String, String> mFileTypeLookup; 56 private final ConfigStore mConfigStore; 57 58 /** 59 * An ordered list of model IDs. This is the data structure that determines what shows up in 60 * the UI, and where. 61 */ 62 private List<String> mModelIds = new ArrayList<>(); 63 private EventListener<Model.Update> mModelUpdateListener; 64 ModelBackedDocumentsAdapter( Environment env, IconHelper iconHelper, Lookup<String, String> fileTypeLookup, ConfigStore configStore)65 public ModelBackedDocumentsAdapter( 66 Environment env, IconHelper iconHelper, Lookup<String, String> fileTypeLookup, 67 ConfigStore configStore) { 68 mEnv = env; 69 mIconHelper = iconHelper; 70 mFileTypeLookup = fileTypeLookup; 71 mConfigStore = configStore; 72 73 mModelUpdateListener = new EventListener<Model.Update>() { 74 @Override 75 public void accept(Update event) { 76 if (event.hasException()) { 77 onModelUpdateFailed(event.getException()); 78 } else { 79 onModelUpdate(mEnv.getModel()); 80 } 81 } 82 }; 83 } 84 85 @Override getModelUpdateListener()86 EventListener<Update> getModelUpdateListener() { 87 return mModelUpdateListener; 88 } 89 90 @Override onCreateViewHolder(ViewGroup parent, int viewType)91 public DocumentHolder onCreateViewHolder(ViewGroup parent, int viewType) { 92 DocumentHolder holder = null; 93 final State state = mEnv.getDisplayState(); 94 switch (state.derivedMode) { 95 case MODE_GRID: 96 switch (viewType) { 97 case ITEM_TYPE_DIRECTORY: 98 holder = 99 new GridDirectoryHolder( 100 mEnv.getContext(), parent, mIconHelper, mConfigStore); 101 break; 102 case ITEM_TYPE_DOCUMENT: 103 holder = state.isPhotoPicking() 104 ? new GridPhotoHolder(mEnv.getContext(), parent, mIconHelper, 105 mConfigStore) 106 : new GridDocumentHolder(mEnv.getContext(), parent, mIconHelper, 107 mConfigStore); 108 break; 109 default: 110 throw new IllegalStateException("Unsupported layout type."); 111 } 112 break; 113 case MODE_LIST: 114 holder = new ListDocumentHolder( 115 mEnv.getContext(), parent, mIconHelper, mFileTypeLookup, mConfigStore); 116 break; 117 default: 118 throw new IllegalStateException("Unsupported layout mode."); 119 } 120 121 mEnv.initDocumentHolder(holder); 122 return holder; 123 } 124 125 @Override onBindViewHolder(DocumentHolder holder, int position, List<Object> payload)126 public void onBindViewHolder(DocumentHolder holder, int position, List<Object> payload) { 127 if (payload.contains(SelectionTracker.SELECTION_CHANGED_MARKER)) { 128 final boolean selected = mEnv.isSelected(mModelIds.get(position)); 129 holder.setSelected(selected, true); 130 } else { 131 onBindViewHolder(holder, position); 132 } 133 } 134 135 @Override onBindViewHolder(DocumentHolder holder, int position)136 public void onBindViewHolder(DocumentHolder holder, int position) { 137 String modelId = mModelIds.get(position); 138 Cursor cursor = mEnv.getModel().getItem(modelId); 139 holder.bind(cursor, modelId); 140 141 final String docMimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE); 142 final int docFlags = getCursorInt(cursor, Document.COLUMN_FLAGS); 143 final int userIdIdentifier = getCursorInt(cursor, RootCursorWrapper.COLUMN_USER_ID); 144 145 boolean enabled = mEnv.isDocumentEnabled(docMimeType, docFlags); 146 boolean selected = mEnv.isSelected(modelId); 147 if (!enabled) { 148 assert (!selected); 149 } 150 holder.setEnabled(enabled); 151 holder.setSelected(mEnv.isSelected(modelId), false); 152 holder.setAction(mEnv.getDisplayState().action); 153 holder.bindPreviewIcon(mEnv.getDisplayState().shouldShowPreview() && enabled, 154 view -> mEnv.getActionHandler().previewItem(holder.getItemDetails())); 155 if (mConfigStore.isPrivateSpaceInDocsUIEnabled() && SdkLevel.isAtLeastS()) { 156 holder.bindProfileIcon(mIconHelper.shouldShowBadge(userIdIdentifier), userIdIdentifier); 157 } else { 158 holder.bindBriefcaseIcon(mIconHelper.shouldShowBadge(userIdIdentifier)); 159 } 160 161 mEnv.onBindDocumentHolder(holder, cursor); 162 } 163 164 @Override getItemCount()165 public int getItemCount() { 166 return mModelIds.size(); 167 } 168 onModelUpdate(Model model)169 private void onModelUpdate(Model model) { 170 String[] modelIds = model.getModelIds(); 171 mModelIds = new ArrayList<>(modelIds.length); 172 for (String id : modelIds) { 173 mModelIds.add(id); 174 } 175 } 176 onModelUpdateFailed(Exception e)177 private void onModelUpdateFailed(Exception e) { 178 Log.w(TAG, "Model update failed.", e); 179 mModelIds.clear(); 180 } 181 182 @Override getStableId(int adapterPosition)183 public String getStableId(int adapterPosition) { 184 return mModelIds.get(adapterPosition); 185 } 186 187 @Override getAdapterPosition(String modelId)188 public int getAdapterPosition(String modelId) { 189 return mModelIds.indexOf(modelId); 190 } 191 192 @Override getStableIds()193 public List<String> getStableIds() { 194 return mModelIds; 195 } 196 197 @Override getPosition(String id)198 public int getPosition(String id) { 199 int position = mModelIds.indexOf(id); 200 return position >= 0 ? position : RecyclerView.NO_POSITION; 201 } 202 203 @Override getItemViewType(int position)204 public int getItemViewType(int position) { 205 return isDirectory(mEnv.getModel(), position) 206 ? ITEM_TYPE_DIRECTORY 207 : ITEM_TYPE_DOCUMENT; 208 } 209 210 /** 211 * @return true if the item type is either document or directory, false for all other 212 * possible types. 213 */ isContentType(int type)214 public static boolean isContentType(int type) { 215 switch (type) { 216 case ModelBackedDocumentsAdapter.ITEM_TYPE_DOCUMENT: 217 case ModelBackedDocumentsAdapter.ITEM_TYPE_DIRECTORY: 218 return true; 219 } 220 return false; 221 } 222 } 223