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