blob: 9cc6972094a6aee9b7fbd4af851732b09b62d7e1 [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;
Steve McKayd9caa6a2016-09-15 16:36:45 -070020import static com.android.documentsui.base.Shared.DEBUG;
Ben Linf8f06e92017-01-27 17:15:48 -080021import static com.android.documentsui.base.Shared.VERBOSE;
Ben Kwa0497da82015-11-30 23:00:02 -080022
Steve McKay990f76e2016-09-16 12:36:58 -070023import android.annotation.IntDef;
Ben Linf8f06e92017-01-27 17:15:48 -080024import android.app.RecoverableSecurityException;
Ben Kwa0497da82015-11-30 23:00:02 -080025import android.database.Cursor;
Tomasz Mikolajewski20c04c52016-03-15 15:55:46 +090026import android.database.MergeCursor;
Steve McKay84769b82016-06-09 10:46:07 -070027import android.net.Uri;
Ben Kwa0497da82015-11-30 23:00:02 -080028import android.os.Bundle;
Ben Kwa0497da82015-11-30 23:00:02 -080029import android.provider.DocumentsContract;
30import android.provider.DocumentsContract.Document;
31import android.support.annotation.Nullable;
32import android.support.annotation.VisibleForTesting;
Ben Kwa0497da82015-11-30 23:00:02 -080033import android.util.Log;
Ben Kwa0497da82015-11-30 23:00:02 -080034
Ben Kwa0497da82015-11-30 23:00:02 -080035import com.android.documentsui.DirectoryResult;
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;
Steve McKayd9caa6a2016-09-15 16:36:45 -070040import com.android.documentsui.roots.RootCursorWrapper;
Steve McKay4f78ba62016-10-04 16:48:49 -070041import com.android.documentsui.selection.Selection;
Ben Kwa0497da82015-11-30 23:00:02 -080042
Steve McKay990f76e2016-09-16 12:36:58 -070043import java.lang.annotation.Retention;
44import java.lang.annotation.RetentionPolicy;
Ben Kwa0497da82015-11-30 23:00:02 -080045import java.util.ArrayList;
Ben Kwa0497da82015-11-30 23:00:02 -080046import java.util.HashMap;
Jon Mann38f14392017-01-25 13:51:55 -080047import java.util.HashSet;
Ben Kwa0497da82015-11-30 23:00:02 -080048import java.util.List;
Ben Kwab8a5e082015-12-07 13:25:27 -080049import java.util.Map;
Jon Mann38f14392017-01-25 13:51:55 -080050import java.util.Set;
Steve McKayd0718952016-10-10 13:43:36 -070051import java.util.function.Predicate;
Ben Kwa0497da82015-11-30 23:00:02 -080052
53/**
54 * The data model for the current loaded directory.
55 */
56@VisibleForTesting
Tomasz Mikolajewskid71bd612016-02-16 12:28:43 +090057public class Model {
Steve McKayd0718952016-10-10 13:43:36 -070058
Ben Kwa0497da82015-11-30 23:00:02 -080059 private static final String TAG = "Model";
Ben Kwab8a5e082015-12-07 13:25:27 -080060
Garfield Tane9670332017-03-06 18:33:23 -080061 public @Nullable String info;
62 public @Nullable String error;
63 public @Nullable DocumentInfo doc;
Steve McKay98f8c5f2017-03-03 13:52:14 -080064
65 private final Features mFeatures;
Garfield Tane9670332017-03-06 18:33:23 -080066
Jon Mann38f14392017-01-25 13:51:55 -080067 /** Maps Model ID to cursor positions, for looking up items by Model ID. */
68 private final Map<String, Integer> mPositions = new HashMap<>();
69 private final Set<String> mFileNames = new HashSet<>();
70
Ben Kwa0497da82015-11-30 23:00:02 -080071 private boolean mIsLoading;
Steve McKay990f76e2016-09-16 12:36:58 -070072 private List<EventListener<Update>> mUpdateListeners = new ArrayList<>();
Steve McKay98f8c5f2017-03-03 13:52:14 -080073 private @Nullable Cursor mCursor;
Ben Kwab8a5e082015-12-07 13:25:27 -080074 private int mCursorCount;
Tomasz Mikolajewskif27c2742016-03-07 18:01:45 +090075 private String mIds[] = new String[0];
Ben Kwab8a5e082015-12-07 13:25:27 -080076
Steve McKay98f8c5f2017-03-03 13:52:14 -080077 public Model(Features features) {
78 mFeatures = features;
79 }
Ben Kwa0497da82015-11-30 23:00:02 -080080
Steve McKay990f76e2016-09-16 12:36:58 -070081 public void addUpdateListener(EventListener<Update> listener) {
82 mUpdateListeners.add(listener);
83 }
84
85 public void removeUpdateListener(EventListener<Update> listener) {
86 mUpdateListeners.remove(listener);
87 }
88
Ben Kwad72a1da2015-12-01 19:56:57 -080089 private void notifyUpdateListeners() {
Steve McKay990f76e2016-09-16 12:36:58 -070090 for (EventListener<Update> handler: mUpdateListeners) {
91 handler.accept(Update.UPDATE);
Ben Kwad72a1da2015-12-01 19:56:57 -080092 }
93 }
94
95 private void notifyUpdateListeners(Exception e) {
Steve McKay98f8c5f2017-03-03 13:52:14 -080096 Update error = new Update(e, mFeatures.isRemoteActionsEnabled());
Steve McKay990f76e2016-09-16 12:36:58 -070097 for (EventListener<Update> handler: mUpdateListeners) {
98 handler.accept(error);
Ben Kwad72a1da2015-12-01 19:56:57 -080099 }
100 }
101
Garfield Tane9670332017-03-06 18:33:23 -0800102 public void reset() {
Steve McKay9de0da62016-08-25 15:18:23 -0700103 mCursor = null;
104 mCursorCount = 0;
105 mIds = new String[0];
106 mPositions.clear();
107 info = null;
108 error = null;
109 doc = null;
110 mIsLoading = false;
Jon Mann38f14392017-01-25 13:51:55 -0800111 mFileNames.clear();
Steve McKay9de0da62016-08-25 15:18:23 -0700112 notifyUpdateListeners();
113 }
114
Garfield Tane9670332017-03-06 18:33:23 -0800115 @VisibleForTesting
116 protected void update(DirectoryResult result) {
Steve McKay9de0da62016-08-25 15:18:23 -0700117 assert(result != null);
118
119 if (DEBUG) Log.i(TAG, "Updating model with new result set.");
Ben Kwa0497da82015-11-30 23:00:02 -0800120
121 if (result.exception != null) {
122 Log.e(TAG, "Error while loading directory contents", result.exception);
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() {
Ben Kwada858bf2015-12-09 14:33:49 -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() {
Tomasz Mikolajewskif27c2742016-03-07 18:01:45 +0900153 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.
Tomasz Mikolajewski20c04c52016-03-15 15:55:46 +0900163 // If the cursor is a merged cursor over multiple authorities, then prefix the ids
164 // with the authority to avoid collisions.
165 if (mCursor instanceof MergeCursor) {
Garfield Tan2010ff72016-09-30 14:55:32 -0700166 mIds[pos] = getCursorString(mCursor, RootCursorWrapper.COLUMN_AUTHORITY)
167 + "|" + getCursorString(mCursor, Document.COLUMN_DOCUMENT_ID);
Tomasz Mikolajewski20c04c52016-03-15 15:55:46 +0900168 } else {
Tomasz Mikolajewski06b036f2016-04-26 11:11:17 +0900169 mIds[pos] = getCursorString(mCursor, Document.COLUMN_DOCUMENT_ID);
Tomasz Mikolajewski20c04c52016-03-15 15:55:46 +0900170 }
Jon Mann38f14392017-01-25 13:51:55 -0800171 mFileNames.add(getCursorString(mCursor, Document.COLUMN_DISPLAY_NAME));
Ben Kwab8a5e082015-12-07 13:25:27 -0800172 }
173
174 // Populate the positions.
175 mPositions.clear();
176 for (int i = 0; i < mCursorCount; ++i) {
Garfield Tan2010ff72016-09-30 14:55:32 -0700177 mPositions.put(mIds[i], i);
Ben Kwab8a5e082015-12-07 13:25:27 -0800178 }
179 }
180
Jon Mann38f14392017-01-25 13:51:55 -0800181 public boolean hasFileWithName(String name) {
182 return mFileNames.contains(name);
183 }
184
Tomasz Mikolajewskid71bd612016-02-16 12:28:43 +0900185 public @Nullable Cursor getItem(String modelId) {
Ben Kwa0497da82015-11-30 23:00:02 -0800186 Integer pos = mPositions.get(modelId);
Steve McKay5a22a112016-04-12 11:29:10 -0700187 if (pos == null) {
188 if (DEBUG) Log.d(TAG, "Unabled to find cursor position for modelId: " + modelId);
189 return null;
Ben Kwa0497da82015-11-30 23:00:02 -0800190 }
Steve McKay5a22a112016-04-12 11:29:10 -0700191
192 if (!mCursor.moveToPosition(pos)) {
193 if (DEBUG) Log.d(TAG,
194 "Unabled to move cursor to position " + pos + " for modelId: " + modelId);
195 return null;
196 }
197
198 return mCursor;
Ben Kwa0497da82015-11-30 23:00:02 -0800199 }
200
Steve McKay990f76e2016-09-16 12:36:58 -0700201 public boolean isEmpty() {
Ben Kwa0497da82015-11-30 23:00:02 -0800202 return mCursorCount == 0;
203 }
204
Garfield Tane9670332017-03-06 18:33:23 -0800205 public boolean isLoading() {
Ben Kwa0497da82015-11-30 23:00:02 -0800206 return mIsLoading;
207 }
208
Steve McKayc8889af2016-09-23 11:22:41 -0700209 public List<DocumentInfo> getDocuments(Selection selection) {
Steve McKay98f8c5f2017-03-03 13:52:14 -0800210 return loadDocuments(selection, DocumentFilters.ANY);
Ben Kwa0497da82015-11-30 23:00:02 -0800211 }
212
Steve McKay990f76e2016-09-16 12:36:58 -0700213 public @Nullable DocumentInfo getDocument(String modelId) {
214 final Cursor cursor = getItem(modelId);
215 return (cursor == null)
216 ? null
217 : DocumentInfo.fromDirectoryCursor(cursor);
218 }
219
Steve McKayd0718952016-10-10 13:43:36 -0700220 public List<DocumentInfo> loadDocuments(Selection selection, Predicate<Cursor> filter) {
221 final int size = (selection != null) ? selection.size() : 0;
222
223 final List<DocumentInfo> docs = new ArrayList<>(size);
Tomasz Mikolajewski1abcfd12016-12-12 10:08:49 +0900224 DocumentInfo doc;
Steve McKayd0718952016-10-10 13:43:36 -0700225 for (String modelId: selection) {
Tomasz Mikolajewski1abcfd12016-12-12 10:08:49 +0900226 doc = loadDocument(modelId, filter);
227 if (doc != null) {
228 docs.add(doc);
229 }
Steve McKayd0718952016-10-10 13:43:36 -0700230 }
231 return docs;
232 }
233
Tomasz Mikolajewski1abcfd12016-12-12 10:08:49 +0900234 public boolean hasDocuments(Selection selection, Predicate<Cursor> filter) {
235 for (String modelId: selection) {
236 if (loadDocument(modelId, filter) != null) {
237 return true;
238 }
239 }
240 return false;
241 }
242
Steve McKayd0718952016-10-10 13:43:36 -0700243 /**
Steve McKayd0718952016-10-10 13:43:36 -0700244 * @return DocumentInfo, or null. If filter returns false, null will be returned.
245 */
Tomasz Mikolajewski1abcfd12016-12-12 10:08:49 +0900246 private @Nullable DocumentInfo loadDocument(String modelId, Predicate<Cursor> filter) {
Steve McKayd0718952016-10-10 13:43:36 -0700247 final Cursor cursor = getItem(modelId);
248
249 if (cursor == null) {
250 Log.w(TAG, "Unable to obtain document for modelId: " + modelId);
Tomasz Mikolajewski1abcfd12016-12-12 10:08:49 +0900251 return null;
Steve McKayd0718952016-10-10 13:43:36 -0700252 }
253
254 if (filter.test(cursor)) {
Tomasz Mikolajewski1abcfd12016-12-12 10:08:49 +0900255 return DocumentInfo.fromDirectoryCursor(cursor);
Steve McKayd0718952016-10-10 13:43:36 -0700256 }
Tomasz Mikolajewski1abcfd12016-12-12 10:08:49 +0900257
258 if (VERBOSE) Log.v(TAG, "Filtered out document from results: " + modelId);
259 return null;
Steve McKayd0718952016-10-10 13:43:36 -0700260 }
261
Steve McKay84769b82016-06-09 10:46:07 -0700262 public Uri getItemUri(String modelId) {
263 final Cursor cursor = getItem(modelId);
264 return DocumentInfo.getUri(cursor);
265 }
266
Garfield, Tan11d23482016-08-05 09:33:29 -0700267 /**
268 * @return An ordered array of model IDs representing the documents in the model. It is sorted
269 * according to the current sort order, which was set by the last model update.
270 */
271 public String[] getModelIds() {
272 return mIds;
273 }
274
Steve McKay990f76e2016-09-16 12:36:58 -0700275 public static class Update {
Ben Kwa0497da82015-11-30 23:00:02 -0800276
Steve McKay990f76e2016-09-16 12:36:58 -0700277 public static final Update UPDATE = new Update();
Ben Kwa472103f2016-02-10 15:48:25 -0800278
Steve McKay990f76e2016-09-16 12:36:58 -0700279 @IntDef(value = {
280 TYPE_UPDATE,
Ben Linf8f06e92017-01-27 17:15:48 -0800281 TYPE_UPDATE_EXCEPTION
Steve McKay990f76e2016-09-16 12:36:58 -0700282 })
283 @Retention(RetentionPolicy.SOURCE)
284 public @interface UpdateType {}
285 public static final int TYPE_UPDATE = 0;
Ben Linf8f06e92017-01-27 17:15:48 -0800286 public static final int TYPE_UPDATE_EXCEPTION = 1;
Ben Kwa0497da82015-11-30 23:00:02 -0800287
Ben Linf8f06e92017-01-27 17:15:48 -0800288 private final @UpdateType int mUpdateType;
Steve McKay990f76e2016-09-16 12:36:58 -0700289 private final @Nullable Exception mException;
Steve McKay98f8c5f2017-03-03 13:52:14 -0800290 private final boolean mRemoteActionEnabled;
Steve McKay990f76e2016-09-16 12:36:58 -0700291
292 private Update() {
Ben Linf8f06e92017-01-27 17:15:48 -0800293 mUpdateType = TYPE_UPDATE;
Steve McKay990f76e2016-09-16 12:36:58 -0700294 mException = null;
Steve McKay98f8c5f2017-03-03 13:52:14 -0800295 mRemoteActionEnabled = false;
Steve McKay990f76e2016-09-16 12:36:58 -0700296 }
297
Steve McKay98f8c5f2017-03-03 13:52:14 -0800298 public Update(Exception exception, boolean remoteActionsEnabled) {
Steve McKay990f76e2016-09-16 12:36:58 -0700299 assert(exception != null);
Ben Linf8f06e92017-01-27 17:15:48 -0800300 mUpdateType = TYPE_UPDATE_EXCEPTION;
Steve McKay990f76e2016-09-16 12:36:58 -0700301 mException = exception;
Steve McKay98f8c5f2017-03-03 13:52:14 -0800302 mRemoteActionEnabled = remoteActionsEnabled;
Steve McKay990f76e2016-09-16 12:36:58 -0700303 }
304
305 public boolean isUpdate() {
Ben Linf8f06e92017-01-27 17:15:48 -0800306 return mUpdateType == TYPE_UPDATE;
Steve McKay990f76e2016-09-16 12:36:58 -0700307 }
308
Ben Linf8f06e92017-01-27 17:15:48 -0800309 public boolean hasException() {
310 return mUpdateType == TYPE_UPDATE_EXCEPTION;
Steve McKay990f76e2016-09-16 12:36:58 -0700311 }
312
Ben Linf8f06e92017-01-27 17:15:48 -0800313 public boolean hasRecoverableException() {
Steve McKay98f8c5f2017-03-03 13:52:14 -0800314 return mRemoteActionEnabled
315 && hasException()
Ben Lin38b41122017-02-03 17:52:43 -0800316 && mException instanceof RecoverableSecurityException;
Ben Linf8f06e92017-01-27 17:15:48 -0800317 }
318
319 public @Nullable Exception getException() {
Steve McKay990f76e2016-09-16 12:36:58 -0700320 return mException;
321 }
Ben Kwa0497da82015-11-30 23:00:02 -0800322 }
Ben Kwa0497da82015-11-30 23:00:02 -0800323}