blob: b9c3d1d4da42a7bcc24022d9dda046c9185a71b3 [file] [log] [blame]
Ben Kwa0497da82015-11-30 23:00:02 -08001/*
Garfield Tane9670332017-03-06 18:33:23 -08002 * Copyright (C) 2017 The Android Open Source Project
Ben Kwa0497da82015-11-30 23:00:02 -08003 *
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
Garfield Tane9670332017-03-06 18:33:23 -080017package com.android.documentsui;
Ben Kwa0497da82015-11-30 23:00:02 -080018
Steve McKayd0805062016-09-15 14:30:38 -070019import static com.android.documentsui.base.DocumentInfo.getCursorString;
Felipe Leme9de58072018-01-19 16:40:04 -080020import static com.android.documentsui.base.SharedMinimal.DEBUG;
21import static com.android.documentsui.base.SharedMinimal.VERBOSE;
Ben Kwa0497da82015-11-30 23:00:02 -080022
Jeff Sharkeya4ff00f2018-07-09 14:57:51 -060023import androidx.annotation.IntDef;
Ben Lin339968c2017-03-21 11:35:14 -070024import android.app.AuthenticationRequiredException;
Ben Kwa0497da82015-11-30 23:00:02 -080025import android.database.Cursor;
Steve McKay84769b82016-06-09 10:46:07 -070026import android.net.Uri;
Ben Kwa0497da82015-11-30 23:00:02 -080027import android.os.Bundle;
Ben Kwa0497da82015-11-30 23:00:02 -080028import android.provider.DocumentsContract;
29import android.provider.DocumentsContract.Document;
Riddle Hsu0c375982018-06-21 22:06:43 +080030import android.util.Log;
31
KOUSHIK PANUGANTI6ca7acc2018-04-17 16:00:10 -070032import androidx.annotation.Nullable;
33import androidx.annotation.VisibleForTesting;
Riddle Hsu0c375982018-06-21 22:06:43 +080034import androidx.recyclerview.selection.Selection;
Ben Kwa0497da82015-11-30 23:00:02 -080035
Steve McKay98f8c5f2017-03-03 13:52:14 -080036import com.android.documentsui.base.DocumentFilters;
Steve McKayd0805062016-09-15 14:30:38 -070037import com.android.documentsui.base.DocumentInfo;
Steve McKay990f76e2016-09-16 12:36:58 -070038import com.android.documentsui.base.EventListener;
Steve McKay98f8c5f2017-03-03 13:52:14 -080039import com.android.documentsui.base.Features;
Ben Kwa0497da82015-11-30 23:00:02 -080040
Steve McKay990f76e2016-09-16 12:36:58 -070041import java.lang.annotation.Retention;
42import java.lang.annotation.RetentionPolicy;
Ben Kwa0497da82015-11-30 23:00:02 -080043import java.util.ArrayList;
Ben Kwa0497da82015-11-30 23:00:02 -080044import java.util.HashMap;
Jon Mann38f14392017-01-25 13:51:55 -080045import java.util.HashSet;
Ben Kwa0497da82015-11-30 23:00:02 -080046import java.util.List;
Ben Kwab8a5e082015-12-07 13:25:27 -080047import java.util.Map;
Jon Mann38f14392017-01-25 13:51:55 -080048import java.util.Set;
Steve McKayd0718952016-10-10 13:43:36 -070049import java.util.function.Predicate;
Ben Kwa0497da82015-11-30 23:00:02 -080050
51/**
52 * The data model for the current loaded directory.
53 */
54@VisibleForTesting
Tomasz Mikolajewskid71bd612016-02-16 12:28:43 +090055public class Model {
Steve McKayd0718952016-10-10 13:43:36 -070056
Ben Kwa0497da82015-11-30 23:00:02 -080057 private static final String TAG = "Model";
Ben Kwab8a5e082015-12-07 13:25:27 -080058
Garfield Tane9670332017-03-06 18:33:23 -080059 public @Nullable String info;
60 public @Nullable String error;
61 public @Nullable DocumentInfo doc;
Steve McKay98f8c5f2017-03-03 13:52:14 -080062
63 private final Features mFeatures;
Garfield Tane9670332017-03-06 18:33:23 -080064
Jon Mann38f14392017-01-25 13:51:55 -080065 /** Maps Model ID to cursor positions, for looking up items by Model ID. */
66 private final Map<String, Integer> mPositions = new HashMap<>();
67 private final Set<String> mFileNames = new HashSet<>();
68
Ben Kwa0497da82015-11-30 23:00:02 -080069 private boolean mIsLoading;
Steve McKay990f76e2016-09-16 12:36:58 -070070 private List<EventListener<Update>> mUpdateListeners = new ArrayList<>();
Steve McKay98f8c5f2017-03-03 13:52:14 -080071 private @Nullable Cursor mCursor;
Ben Kwab8a5e082015-12-07 13:25:27 -080072 private int mCursorCount;
Tomasz Mikolajewskif27c2742016-03-07 18:01:45 +090073 private String mIds[] = new String[0];
Ben Kwab8a5e082015-12-07 13:25:27 -080074
Steve McKay98f8c5f2017-03-03 13:52:14 -080075 public Model(Features features) {
76 mFeatures = features;
77 }
Ben Kwa0497da82015-11-30 23:00:02 -080078
Steve McKay990f76e2016-09-16 12:36:58 -070079 public void addUpdateListener(EventListener<Update> listener) {
80 mUpdateListeners.add(listener);
81 }
82
83 public void removeUpdateListener(EventListener<Update> listener) {
84 mUpdateListeners.remove(listener);
85 }
86
Ben Kwad72a1da2015-12-01 19:56:57 -080087 private void notifyUpdateListeners() {
Steve McKay990f76e2016-09-16 12:36:58 -070088 for (EventListener<Update> handler: mUpdateListeners) {
89 handler.accept(Update.UPDATE);
Ben Kwad72a1da2015-12-01 19:56:57 -080090 }
91 }
92
93 private void notifyUpdateListeners(Exception e) {
Steve McKay98f8c5f2017-03-03 13:52:14 -080094 Update error = new Update(e, mFeatures.isRemoteActionsEnabled());
Steve McKay990f76e2016-09-16 12:36:58 -070095 for (EventListener<Update> handler: mUpdateListeners) {
96 handler.accept(error);
Ben Kwad72a1da2015-12-01 19:56:57 -080097 }
98 }
99
Garfield Tane9670332017-03-06 18:33:23 -0800100 public void reset() {
Steve McKay9de0da62016-08-25 15:18:23 -0700101 mCursor = null;
102 mCursorCount = 0;
103 mIds = new String[0];
104 mPositions.clear();
105 info = null;
106 error = null;
107 doc = null;
108 mIsLoading = false;
Jon Mann38f14392017-01-25 13:51:55 -0800109 mFileNames.clear();
Steve McKay9de0da62016-08-25 15:18:23 -0700110 notifyUpdateListeners();
111 }
112
Garfield Tane9670332017-03-06 18:33:23 -0800113 @VisibleForTesting
Tony Huang166cd7c2019-03-11 16:31:57 +0800114 public void update(DirectoryResult result) {
Steve McKay9de0da62016-08-25 15:18:23 -0700115 assert(result != null);
Jason Chang96f886b2019-03-29 17:59:02 +0800116 if (DEBUG) {
117 Log.i(TAG, "Updating model with new result set.");
118 }
Ben Kwa0497da82015-11-30 23:00:02 -0800119
120 if (result.exception != null) {
121 Log.e(TAG, "Error while loading directory contents", result.exception);
Garfield Tan3c654712017-05-22 17:05:38 -0700122 reset(); // Resets this model to avoid access to old cursors.
Ben Kwad72a1da2015-12-01 19:56:57 -0800123 notifyUpdateListeners(result.exception);
Ben Kwa0497da82015-11-30 23:00:02 -0800124 return;
125 }
126
127 mCursor = result.cursor;
128 mCursorCount = mCursor.getCount();
Tomasz Mikolajewskie29e3412016-02-24 12:53:44 +0900129 doc = result.doc;
Ben Kwa0497da82015-11-30 23:00:02 -0800130
Ben Kwab8a5e082015-12-07 13:25:27 -0800131 updateModelData();
Ben Kwa0497da82015-11-30 23:00:02 -0800132
133 final Bundle extras = mCursor.getExtras();
134 if (extras != null) {
135 info = extras.getString(DocumentsContract.EXTRA_INFO);
136 error = extras.getString(DocumentsContract.EXTRA_ERROR);
137 mIsLoading = extras.getBoolean(DocumentsContract.EXTRA_LOADING, false);
138 }
139
Ben Kwad72a1da2015-12-01 19:56:57 -0800140 notifyUpdateListeners();
Ben Kwa0497da82015-11-30 23:00:02 -0800141 }
142
Ben Kwad72a1da2015-12-01 19:56:57 -0800143 @VisibleForTesting
Garfield Tane9670332017-03-06 18:33:23 -0800144 public int getItemCount() {
shawnlin48200cd2018-10-16 16:20:42 +0800145 return mCursorCount;
Ben Kwa0497da82015-11-30 23:00:02 -0800146 }
147
148 /**
Ben Kwab8a5e082015-12-07 13:25:27 -0800149 * Scan over the incoming cursor data, generate Model IDs for each row, and sort the IDs
150 * according to the current sort order.
Ben Kwa0497da82015-11-30 23:00:02 -0800151 */
Ben Kwab8a5e082015-12-07 13:25:27 -0800152 private void updateModelData() {
shawnlin48200cd2018-10-16 16:20:42 +0800153 mIds = new String[mCursorCount];
Jon Mann38f14392017-01-25 13:51:55 -0800154 mFileNames.clear();
Ben Kwa0497da82015-11-30 23:00:02 -0800155 mCursor.moveToPosition(-1);
156 for (int pos = 0; pos < mCursorCount; ++pos) {
Ben Lin2df30e52016-04-22 16:12:50 -0700157 if (!mCursor.moveToNext()) {
158 Log.e(TAG, "Fail to move cursor to next pos: " + pos);
159 return;
160 }
Tomasz Mikolajewski985df3d2016-03-15 15:38:54 +0900161 // Generates a Model ID for a cursor entry that refers to a document. The Model ID is a
162 // unique string that can be used to identify the document referred to by the cursor.
shawnlin65bbe792018-08-20 16:20:43 +0800163 // Prefix the ids with the authority to avoid collisions.
shawnlin48200cd2018-10-16 16:20:42 +0800164 mIds[pos] = ModelId.build(mCursor);
Jon Mann38f14392017-01-25 13:51:55 -0800165 mFileNames.add(getCursorString(mCursor, Document.COLUMN_DISPLAY_NAME));
Ben Kwab8a5e082015-12-07 13:25:27 -0800166 }
167
shawnlin48200cd2018-10-16 16:20:42 +0800168 // Populate the positions.
169 mPositions.clear();
Ben Kwab8a5e082015-12-07 13:25:27 -0800170 for (int i = 0; i < mCursorCount; ++i) {
shawnlin48200cd2018-10-16 16:20:42 +0800171 mPositions.put(mIds[i], i);
Ben Kwab8a5e082015-12-07 13:25:27 -0800172 }
173 }
174
Jon Mann38f14392017-01-25 13:51:55 -0800175 public boolean hasFileWithName(String name) {
176 return mFileNames.contains(name);
177 }
178
Tomasz Mikolajewskid71bd612016-02-16 12:28:43 +0900179 public @Nullable Cursor getItem(String modelId) {
Ben Kwa0497da82015-11-30 23:00:02 -0800180 Integer pos = mPositions.get(modelId);
Steve McKay5a22a112016-04-12 11:29:10 -0700181 if (pos == null) {
Jason Chang96f886b2019-03-29 17:59:02 +0800182 if (DEBUG) {
183 Log.d(TAG, "Unabled to find cursor position for modelId: " + modelId);
184 }
Steve McKay5a22a112016-04-12 11:29:10 -0700185 return null;
Ben Kwa0497da82015-11-30 23:00:02 -0800186 }
Steve McKay5a22a112016-04-12 11:29:10 -0700187
188 if (!mCursor.moveToPosition(pos)) {
Jason Chang96f886b2019-03-29 17:59:02 +0800189 if (DEBUG) {
190 Log.d(TAG,
Steve McKay5a22a112016-04-12 11:29:10 -0700191 "Unabled to move cursor to position " + pos + " for modelId: " + modelId);
Jason Chang96f886b2019-03-29 17:59:02 +0800192 }
Steve McKay5a22a112016-04-12 11:29:10 -0700193 return null;
194 }
195
196 return mCursor;
Ben Kwa0497da82015-11-30 23:00:02 -0800197 }
198
Garfield Tane9670332017-03-06 18:33:23 -0800199 public boolean isLoading() {
Ben Kwa0497da82015-11-30 23:00:02 -0800200 return mIsLoading;
201 }
202
Riddle Hsu0c375982018-06-21 22:06:43 +0800203 public List<DocumentInfo> getDocuments(Selection<String> selection) {
Steve McKay98f8c5f2017-03-03 13:52:14 -0800204 return loadDocuments(selection, DocumentFilters.ANY);
Ben Kwa0497da82015-11-30 23:00:02 -0800205 }
206
Steve McKay990f76e2016-09-16 12:36:58 -0700207 public @Nullable DocumentInfo getDocument(String modelId) {
208 final Cursor cursor = getItem(modelId);
209 return (cursor == null)
210 ? null
211 : DocumentInfo.fromDirectoryCursor(cursor);
212 }
213
Riddle Hsu0c375982018-06-21 22:06:43 +0800214 public List<DocumentInfo> loadDocuments(Selection<String> selection, Predicate<Cursor> filter) {
Steve McKayd0718952016-10-10 13:43:36 -0700215 final int size = (selection != null) ? selection.size() : 0;
216
217 final List<DocumentInfo> docs = new ArrayList<>(size);
Tomasz Mikolajewski1abcfd12016-12-12 10:08:49 +0900218 DocumentInfo doc;
Steve McKayd0718952016-10-10 13:43:36 -0700219 for (String modelId: selection) {
Tomasz Mikolajewski1abcfd12016-12-12 10:08:49 +0900220 doc = loadDocument(modelId, filter);
221 if (doc != null) {
222 docs.add(doc);
223 }
Steve McKayd0718952016-10-10 13:43:36 -0700224 }
225 return docs;
226 }
227
Riddle Hsu0c375982018-06-21 22:06:43 +0800228 public boolean hasDocuments(Selection<String> selection, Predicate<Cursor> filter) {
Tomasz Mikolajewski1abcfd12016-12-12 10:08:49 +0900229 for (String modelId: selection) {
230 if (loadDocument(modelId, filter) != null) {
231 return true;
232 }
233 }
234 return false;
235 }
236
Steve McKayd0718952016-10-10 13:43:36 -0700237 /**
Steve McKayd0718952016-10-10 13:43:36 -0700238 * @return DocumentInfo, or null. If filter returns false, null will be returned.
239 */
Tomasz Mikolajewski1abcfd12016-12-12 10:08:49 +0900240 private @Nullable DocumentInfo loadDocument(String modelId, Predicate<Cursor> filter) {
Steve McKayd0718952016-10-10 13:43:36 -0700241 final Cursor cursor = getItem(modelId);
242
243 if (cursor == null) {
244 Log.w(TAG, "Unable to obtain document for modelId: " + modelId);
Tomasz Mikolajewski1abcfd12016-12-12 10:08:49 +0900245 return null;
Steve McKayd0718952016-10-10 13:43:36 -0700246 }
247
248 if (filter.test(cursor)) {
Tomasz Mikolajewski1abcfd12016-12-12 10:08:49 +0900249 return DocumentInfo.fromDirectoryCursor(cursor);
Steve McKayd0718952016-10-10 13:43:36 -0700250 }
Tomasz Mikolajewski1abcfd12016-12-12 10:08:49 +0900251
252 if (VERBOSE) Log.v(TAG, "Filtered out document from results: " + modelId);
253 return null;
Steve McKayd0718952016-10-10 13:43:36 -0700254 }
255
Steve McKay84769b82016-06-09 10:46:07 -0700256 public Uri getItemUri(String modelId) {
257 final Cursor cursor = getItem(modelId);
258 return DocumentInfo.getUri(cursor);
259 }
260
Garfield, Tan11d23482016-08-05 09:33:29 -0700261 /**
262 * @return An ordered array of model IDs representing the documents in the model. It is sorted
263 * according to the current sort order, which was set by the last model update.
264 */
265 public String[] getModelIds() {
266 return mIds;
267 }
268
Steve McKay990f76e2016-09-16 12:36:58 -0700269 public static class Update {
Ben Kwa0497da82015-11-30 23:00:02 -0800270
Steve McKay990f76e2016-09-16 12:36:58 -0700271 public static final Update UPDATE = new Update();
Ben Kwa472103f2016-02-10 15:48:25 -0800272
Steve McKay990f76e2016-09-16 12:36:58 -0700273 @IntDef(value = {
274 TYPE_UPDATE,
Ben Linf8f06e92017-01-27 17:15:48 -0800275 TYPE_UPDATE_EXCEPTION
Steve McKay990f76e2016-09-16 12:36:58 -0700276 })
277 @Retention(RetentionPolicy.SOURCE)
278 public @interface UpdateType {}
279 public static final int TYPE_UPDATE = 0;
Ben Linf8f06e92017-01-27 17:15:48 -0800280 public static final int TYPE_UPDATE_EXCEPTION = 1;
Ben Kwa0497da82015-11-30 23:00:02 -0800281
Ben Linf8f06e92017-01-27 17:15:48 -0800282 private final @UpdateType int mUpdateType;
Steve McKay990f76e2016-09-16 12:36:58 -0700283 private final @Nullable Exception mException;
Steve McKay98f8c5f2017-03-03 13:52:14 -0800284 private final boolean mRemoteActionEnabled;
Steve McKay990f76e2016-09-16 12:36:58 -0700285
286 private Update() {
Ben Linf8f06e92017-01-27 17:15:48 -0800287 mUpdateType = TYPE_UPDATE;
Steve McKay990f76e2016-09-16 12:36:58 -0700288 mException = null;
Steve McKay98f8c5f2017-03-03 13:52:14 -0800289 mRemoteActionEnabled = false;
Steve McKay990f76e2016-09-16 12:36:58 -0700290 }
291
Steve McKay98f8c5f2017-03-03 13:52:14 -0800292 public Update(Exception exception, boolean remoteActionsEnabled) {
Steve McKay990f76e2016-09-16 12:36:58 -0700293 assert(exception != null);
Ben Linf8f06e92017-01-27 17:15:48 -0800294 mUpdateType = TYPE_UPDATE_EXCEPTION;
Steve McKay990f76e2016-09-16 12:36:58 -0700295 mException = exception;
Steve McKay98f8c5f2017-03-03 13:52:14 -0800296 mRemoteActionEnabled = remoteActionsEnabled;
Steve McKay990f76e2016-09-16 12:36:58 -0700297 }
298
299 public boolean isUpdate() {
Ben Linf8f06e92017-01-27 17:15:48 -0800300 return mUpdateType == TYPE_UPDATE;
Steve McKay990f76e2016-09-16 12:36:58 -0700301 }
302
Ben Linf8f06e92017-01-27 17:15:48 -0800303 public boolean hasException() {
304 return mUpdateType == TYPE_UPDATE_EXCEPTION;
Steve McKay990f76e2016-09-16 12:36:58 -0700305 }
306
Ben Lin339968c2017-03-21 11:35:14 -0700307 public boolean hasAuthenticationException() {
Steve McKay98f8c5f2017-03-03 13:52:14 -0800308 return mRemoteActionEnabled
309 && hasException()
Ben Lin339968c2017-03-21 11:35:14 -0700310 && mException instanceof AuthenticationRequiredException;
Ben Linf8f06e92017-01-27 17:15:48 -0800311 }
312
313 public @Nullable Exception getException() {
Steve McKay990f76e2016-09-16 12:36:58 -0700314 return mException;
315 }
Ben Kwa0497da82015-11-30 23:00:02 -0800316 }
Ben Kwa0497da82015-11-30 23:00:02 -0800317}