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