blob: 016346a2017abf07430802995e4eb4a8261736dd [file] [log] [blame]
Ben Kwa0497da82015-11-30 23:00:02 -08001/*
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
17package com.android.documentsui.dirlist;
18
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 Kwa0497da82015-11-30 23:00:02 -080021
Steve McKay990f76e2016-09-16 12:36:58 -070022import android.annotation.IntDef;
Ben Kwa0497da82015-11-30 23:00:02 -080023import android.database.Cursor;
Tomasz Mikolajewski20c04c52016-03-15 15:55:46 +090024import android.database.MergeCursor;
Steve McKay84769b82016-06-09 10:46:07 -070025import android.net.Uri;
Ben Kwa0497da82015-11-30 23:00:02 -080026import android.os.Bundle;
Ben Kwa0497da82015-11-30 23:00:02 -080027import android.provider.DocumentsContract;
28import android.provider.DocumentsContract.Document;
29import android.support.annotation.Nullable;
30import android.support.annotation.VisibleForTesting;
Ben Kwa0497da82015-11-30 23:00:02 -080031import android.util.Log;
Ben Kwa0497da82015-11-30 23:00:02 -080032
Ben Kwa0497da82015-11-30 23:00:02 -080033import com.android.documentsui.DirectoryResult;
Steve McKayd0805062016-09-15 14:30:38 -070034import com.android.documentsui.base.DocumentInfo;
Steve McKay990f76e2016-09-16 12:36:58 -070035import com.android.documentsui.base.EventListener;
Steve McKayd9caa6a2016-09-15 16:36:45 -070036import com.android.documentsui.roots.RootCursorWrapper;
Steve McKay4f78ba62016-10-04 16:48:49 -070037import com.android.documentsui.selection.Selection;
Ben Kwa0497da82015-11-30 23:00:02 -080038
Steve McKay990f76e2016-09-16 12:36:58 -070039import java.lang.annotation.Retention;
40import java.lang.annotation.RetentionPolicy;
Ben Kwa0497da82015-11-30 23:00:02 -080041import java.util.ArrayList;
Ben Kwa0497da82015-11-30 23:00:02 -080042import java.util.HashMap;
43import java.util.List;
Ben Kwab8a5e082015-12-07 13:25:27 -080044import java.util.Map;
Ben Kwa0497da82015-11-30 23:00:02 -080045
46/**
47 * The data model for the current loaded directory.
48 */
49@VisibleForTesting
Tomasz Mikolajewskid71bd612016-02-16 12:28:43 +090050public class Model {
Ben Kwa0497da82015-11-30 23:00:02 -080051 private static final String TAG = "Model";
Ben Kwab8a5e082015-12-07 13:25:27 -080052
Ben Kwa0497da82015-11-30 23:00:02 -080053 private boolean mIsLoading;
Steve McKay990f76e2016-09-16 12:36:58 -070054 private List<EventListener<Update>> mUpdateListeners = new ArrayList<>();
Ben Kwa0497da82015-11-30 23:00:02 -080055 @Nullable private Cursor mCursor;
Ben Kwab8a5e082015-12-07 13:25:27 -080056 private int mCursorCount;
57 /** Maps Model ID to cursor positions, for looking up items by Model ID. */
58 private Map<String, Integer> mPositions = new HashMap<>();
Tomasz Mikolajewskif27c2742016-03-07 18:01:45 +090059 private String mIds[] = new String[0];
Ben Kwab8a5e082015-12-07 13:25:27 -080060
Ben Kwa0497da82015-11-30 23:00:02 -080061 @Nullable String info;
62 @Nullable String error;
Tomasz Mikolajewskie29e3412016-02-24 12:53:44 +090063 @Nullable DocumentInfo doc;
Ben Kwa0497da82015-11-30 23:00:02 -080064
Steve McKay990f76e2016-09-16 12:36:58 -070065 public void addUpdateListener(EventListener<Update> listener) {
66 mUpdateListeners.add(listener);
67 }
68
69 public void removeUpdateListener(EventListener<Update> listener) {
70 mUpdateListeners.remove(listener);
71 }
72
Ben Kwad72a1da2015-12-01 19:56:57 -080073 private void notifyUpdateListeners() {
Steve McKay990f76e2016-09-16 12:36:58 -070074 for (EventListener<Update> handler: mUpdateListeners) {
75 handler.accept(Update.UPDATE);
Ben Kwad72a1da2015-12-01 19:56:57 -080076 }
77 }
78
79 private void notifyUpdateListeners(Exception e) {
Steve McKay990f76e2016-09-16 12:36:58 -070080 Update error = new Update(e);
81 for (EventListener<Update> handler: mUpdateListeners) {
82 handler.accept(error);
Ben Kwad72a1da2015-12-01 19:56:57 -080083 }
84 }
85
Steve McKay9de0da62016-08-25 15:18:23 -070086 void onLoaderReset() {
87 if (mIsLoading) {
Steve McKay7c662092016-08-26 12:17:41 -070088 Log.w(TAG, "Received unexpected loader reset while in loading state for doc: "
89 + DocumentInfo.debugString(doc));
Ben Kwa0497da82015-11-30 23:00:02 -080090 }
Steve McKay7c662092016-08-26 12:17:41 -070091
92 reset();
Steve McKay9de0da62016-08-25 15:18:23 -070093 }
94
95 private void reset() {
96 mCursor = null;
97 mCursorCount = 0;
98 mIds = new String[0];
99 mPositions.clear();
100 info = null;
101 error = null;
102 doc = null;
103 mIsLoading = false;
104 notifyUpdateListeners();
105 }
106
107 void update(DirectoryResult result) {
108 assert(result != null);
109
110 if (DEBUG) Log.i(TAG, "Updating model with new result set.");
Ben Kwa0497da82015-11-30 23:00:02 -0800111
112 if (result.exception != null) {
113 Log.e(TAG, "Error while loading directory contents", result.exception);
Ben Kwad72a1da2015-12-01 19:56:57 -0800114 notifyUpdateListeners(result.exception);
Ben Kwa0497da82015-11-30 23:00:02 -0800115 return;
116 }
117
118 mCursor = result.cursor;
119 mCursorCount = mCursor.getCount();
Tomasz Mikolajewskie29e3412016-02-24 12:53:44 +0900120 doc = result.doc;
Ben Kwa0497da82015-11-30 23:00:02 -0800121
Ben Kwab8a5e082015-12-07 13:25:27 -0800122 updateModelData();
Ben Kwa0497da82015-11-30 23:00:02 -0800123
124 final Bundle extras = mCursor.getExtras();
125 if (extras != null) {
126 info = extras.getString(DocumentsContract.EXTRA_INFO);
127 error = extras.getString(DocumentsContract.EXTRA_ERROR);
128 mIsLoading = extras.getBoolean(DocumentsContract.EXTRA_LOADING, false);
129 }
130
Ben Kwad72a1da2015-12-01 19:56:57 -0800131 notifyUpdateListeners();
Ben Kwa0497da82015-11-30 23:00:02 -0800132 }
133
Ben Kwad72a1da2015-12-01 19:56:57 -0800134 @VisibleForTesting
Ben Kwa0497da82015-11-30 23:00:02 -0800135 int getItemCount() {
Ben Kwada858bf2015-12-09 14:33:49 -0800136 return mCursorCount;
Ben Kwa0497da82015-11-30 23:00:02 -0800137 }
138
139 /**
Ben Kwab8a5e082015-12-07 13:25:27 -0800140 * Scan over the incoming cursor data, generate Model IDs for each row, and sort the IDs
141 * according to the current sort order.
Ben Kwa0497da82015-11-30 23:00:02 -0800142 */
Ben Kwab8a5e082015-12-07 13:25:27 -0800143 private void updateModelData() {
Tomasz Mikolajewskif27c2742016-03-07 18:01:45 +0900144 mIds = new String[mCursorCount];
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900145
Ben Kwa0497da82015-11-30 23:00:02 -0800146 mCursor.moveToPosition(-1);
147 for (int pos = 0; pos < mCursorCount; ++pos) {
Ben Lin2df30e52016-04-22 16:12:50 -0700148 if (!mCursor.moveToNext()) {
149 Log.e(TAG, "Fail to move cursor to next pos: " + pos);
150 return;
151 }
Tomasz Mikolajewski985df3d2016-03-15 15:38:54 +0900152 // Generates a Model ID for a cursor entry that refers to a document. The Model ID is a
153 // unique string that can be used to identify the document referred to by the cursor.
Tomasz Mikolajewski20c04c52016-03-15 15:55:46 +0900154 // If the cursor is a merged cursor over multiple authorities, then prefix the ids
155 // with the authority to avoid collisions.
156 if (mCursor instanceof MergeCursor) {
Garfield Tan2010ff72016-09-30 14:55:32 -0700157 mIds[pos] = getCursorString(mCursor, RootCursorWrapper.COLUMN_AUTHORITY)
158 + "|" + getCursorString(mCursor, Document.COLUMN_DOCUMENT_ID);
Tomasz Mikolajewski20c04c52016-03-15 15:55:46 +0900159 } else {
Tomasz Mikolajewski06b036f2016-04-26 11:11:17 +0900160 mIds[pos] = getCursorString(mCursor, Document.COLUMN_DOCUMENT_ID);
Tomasz Mikolajewski20c04c52016-03-15 15:55:46 +0900161 }
Ben Kwab8a5e082015-12-07 13:25:27 -0800162 }
163
164 // Populate the positions.
165 mPositions.clear();
166 for (int i = 0; i < mCursorCount; ++i) {
Garfield Tan2010ff72016-09-30 14:55:32 -0700167 mPositions.put(mIds[i], i);
Ben Kwab8a5e082015-12-07 13:25:27 -0800168 }
169 }
170
Tomasz Mikolajewskid71bd612016-02-16 12:28:43 +0900171 public @Nullable Cursor getItem(String modelId) {
Ben Kwa0497da82015-11-30 23:00:02 -0800172 Integer pos = mPositions.get(modelId);
Steve McKay5a22a112016-04-12 11:29:10 -0700173 if (pos == null) {
174 if (DEBUG) Log.d(TAG, "Unabled to find cursor position for modelId: " + modelId);
175 return null;
Ben Kwa0497da82015-11-30 23:00:02 -0800176 }
Steve McKay5a22a112016-04-12 11:29:10 -0700177
178 if (!mCursor.moveToPosition(pos)) {
179 if (DEBUG) Log.d(TAG,
180 "Unabled to move cursor to position " + pos + " for modelId: " + modelId);
181 return null;
182 }
183
184 return mCursor;
Ben Kwa0497da82015-11-30 23:00:02 -0800185 }
186
Steve McKay990f76e2016-09-16 12:36:58 -0700187 public boolean isEmpty() {
Ben Kwa0497da82015-11-30 23:00:02 -0800188 return mCursorCount == 0;
189 }
190
191 boolean isLoading() {
192 return mIsLoading;
193 }
194
Steve McKayc8889af2016-09-23 11:22:41 -0700195 public List<DocumentInfo> getDocuments(Selection selection) {
Steve McKay84769b82016-06-09 10:46:07 -0700196 final int size = (selection != null) ? selection.size() : 0;
Ben Kwa0497da82015-11-30 23:00:02 -0800197
198 final List<DocumentInfo> docs = new ArrayList<>(size);
Steve McKay84769b82016-06-09 10:46:07 -0700199 // NOTE: That as this now iterates over only final (non-provisional) selection.
200 for (String modelId: selection) {
Steve McKay990f76e2016-09-16 12:36:58 -0700201 DocumentInfo doc = getDocument(modelId);
202 if (doc == null) {
203 Log.w(TAG, "Unable to obtain document for modelId: " + modelId);
Steve McKay5a22a112016-04-12 11:29:10 -0700204 continue;
205 }
Steve McKay990f76e2016-09-16 12:36:58 -0700206 docs.add(doc);
Ben Kwa0497da82015-11-30 23:00:02 -0800207 }
208 return docs;
209 }
210
Steve McKay990f76e2016-09-16 12:36:58 -0700211 public @Nullable DocumentInfo getDocument(String modelId) {
212 final Cursor cursor = getItem(modelId);
213 return (cursor == null)
214 ? null
215 : DocumentInfo.fromDirectoryCursor(cursor);
216 }
217
Steve McKay84769b82016-06-09 10:46:07 -0700218 public Uri getItemUri(String modelId) {
219 final Cursor cursor = getItem(modelId);
220 return DocumentInfo.getUri(cursor);
221 }
222
Garfield, Tan11d23482016-08-05 09:33:29 -0700223 /**
224 * @return An ordered array of model IDs representing the documents in the model. It is sorted
225 * according to the current sort order, which was set by the last model update.
226 */
227 public String[] getModelIds() {
228 return mIds;
229 }
230
Steve McKay990f76e2016-09-16 12:36:58 -0700231 public static class Update {
Ben Kwa0497da82015-11-30 23:00:02 -0800232
Steve McKay990f76e2016-09-16 12:36:58 -0700233 public static final Update UPDATE = new Update();
Ben Kwa472103f2016-02-10 15:48:25 -0800234
Steve McKay990f76e2016-09-16 12:36:58 -0700235 @IntDef(value = {
236 TYPE_UPDATE,
237 TYPE_UPDATE_ERROR
238 })
239 @Retention(RetentionPolicy.SOURCE)
240 public @interface UpdateType {}
241 public static final int TYPE_UPDATE = 0;
242 public static final int TYPE_UPDATE_ERROR = 1;
Ben Kwa0497da82015-11-30 23:00:02 -0800243
Steve McKay990f76e2016-09-16 12:36:58 -0700244 private final @UpdateType int mType;
245 private final @Nullable Exception mException;
246
247 private Update() {
248 mType = TYPE_UPDATE;
249 mException = null;
250 }
251
252 public Update(Exception exception) {
253 assert(exception != null);
254 mType = TYPE_UPDATE_ERROR;
255 mException = exception;
256 }
257
258 public boolean isUpdate() {
259 return mType == TYPE_UPDATE;
260 }
261
262 public boolean hasError() {
263 return mType == TYPE_UPDATE_ERROR;
264 }
265
266 public @Nullable Exception getError() {
267 return mException;
268 }
Ben Kwa0497da82015-11-30 23:00:02 -0800269 }
Ben Kwa0497da82015-11-30 23:00:02 -0800270}