blob: 7b50cc688d23f4e13cab1606d0831c9bd2f1ab2d [file] [log] [blame]
Jeff Sharkeya5defe32013-08-05 17:56:48 -07001/*
2 * Copyright (C) 2013 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;
18
Steve McKay98f8c5f2017-03-03 13:52:14 -080019import static com.android.documentsui.base.Shared.VERBOSE;
20
Jeff Sharkeyac9e6272013-08-31 21:27:44 -070021import android.content.AsyncTaskLoader;
22import android.content.ContentProviderClient;
Jeff Sharkeya4d1f222013-09-07 14:45:03 -070023import android.content.ContentResolver;
Jeff Sharkeya5defe32013-08-05 17:56:48 -070024import android.content.Context;
Steve McKay98f8c5f2017-03-03 13:52:14 -080025import android.content.res.Resources;
Ben Lin9fea3122016-10-10 18:32:26 -070026import android.database.ContentObserver;
Jeff Sharkeya5defe32013-08-05 17:56:48 -070027import android.database.Cursor;
28import android.net.Uri;
Steve McKay50b9bae2017-01-17 11:12:08 -080029import android.os.Bundle;
Jeff Sharkeya5defe32013-08-05 17:56:48 -070030import android.os.CancellationSignal;
Ben Lin9fea3122016-10-10 18:32:26 -070031import android.os.Handler;
Garfield Tane9670332017-03-06 18:33:23 -080032import android.os.Looper;
Jeff Sharkeyac9e6272013-08-31 21:27:44 -070033import android.os.OperationCanceledException;
Makoto Onuki77778752015-07-01 14:55:14 -070034import android.os.RemoteException;
Jeff Sharkeyac9e6272013-08-31 21:27:44 -070035import android.provider.DocumentsContract.Document;
Jeff Sharkeya4d1f222013-09-07 14:45:03 -070036import android.util.Log;
37
Tomasz Mikolajewskib19061c2017-02-13 17:33:42 +090038import com.android.documentsui.archives.ArchivesProvider;
Steve McKay50b9bae2017-01-17 11:12:08 -080039import com.android.documentsui.base.DebugFlags;
Steve McKayd0805062016-09-15 14:30:38 -070040import com.android.documentsui.base.DocumentInfo;
Steve McKay710248d2017-03-14 10:09:30 -070041import com.android.documentsui.base.Features;
Steve McKayd9caa6a2016-09-15 16:36:45 -070042import com.android.documentsui.base.FilteringCursorWrapper;
Steve McKayd0805062016-09-15 14:30:38 -070043import com.android.documentsui.base.RootInfo;
Steve McKayd9caa6a2016-09-15 16:36:45 -070044import com.android.documentsui.roots.RootCursorWrapper;
Garfield, Tan11d23482016-08-05 09:33:29 -070045import com.android.documentsui.sorting.SortModel;
Jeff Sharkeya5defe32013-08-05 17:56:48 -070046
Steve McKayc88f83c2016-08-31 12:01:43 -070047import libcore.io.IoUtils;
48
Jeff Sharkeyac9e6272013-08-31 21:27:44 -070049public class DirectoryLoader extends AsyncTaskLoader<DirectoryResult> {
Jeff Sharkey9dd02622013-09-27 16:44:11 -070050
Garfield, Tan11d23482016-08-05 09:33:29 -070051 private static final String TAG = "DirectoryLoader";
52
Jeff Sharkey9dd02622013-09-27 16:44:11 -070053 private static final String[] SEARCH_REJECT_MIMES = new String[] { Document.MIME_TYPE_DIR };
54
Ben Lin9fea3122016-10-10 18:32:26 -070055 private final LockingContentObserver mObserver;
Jeff Sharkeya4d1f222013-09-07 14:45:03 -070056 private final RootInfo mRoot;
Jeff Sharkeyac9e6272013-08-31 21:27:44 -070057 private final Uri mUri;
Garfield, Tan11d23482016-08-05 09:33:29 -070058 private final SortModel mModel;
Aga Wronskaaf5ace52016-02-17 13:50:42 -080059 private final boolean mSearchMode;
Jeff Sharkeya5defe32013-08-05 17:56:48 -070060
Steve McKay7776aa52016-01-25 19:00:22 -080061 private DocumentInfo mDoc;
Jeff Sharkeyac9e6272013-08-31 21:27:44 -070062 private CancellationSignal mSignal;
63 private DirectoryResult mResult;
64
Steve McKay710248d2017-03-14 10:09:30 -070065 private Features mFeatures;
66
Steve McKayc88f83c2016-08-31 12:01:43 -070067 public DirectoryLoader(
Steve McKay710248d2017-03-14 10:09:30 -070068 Features freatures,
Steve McKayc88f83c2016-08-31 12:01:43 -070069 Context context,
70 RootInfo root,
71 DocumentInfo doc,
72 Uri uri,
73 SortModel model,
Ben Lin9fea3122016-10-10 18:32:26 -070074 DirectoryReloadLock lock,
Steve McKayc88f83c2016-08-31 12:01:43 -070075 boolean inSearchMode) {
76
Jeff Sharkeyf63b7772013-10-01 17:57:41 -070077 super(context, ProviderExecutor.forAuthority(root.authority));
Steve McKay710248d2017-03-14 10:09:30 -070078 mFeatures = freatures;
Jeff Sharkeya4d1f222013-09-07 14:45:03 -070079 mRoot = root;
Jeff Sharkeyac9e6272013-08-31 21:27:44 -070080 mUri = uri;
Garfield, Tan11d23482016-08-05 09:33:29 -070081 mModel = model;
Steve McKay7776aa52016-01-25 19:00:22 -080082 mDoc = doc;
Aga Wronskaaf5ace52016-02-17 13:50:42 -080083 mSearchMode = inSearchMode;
Ben Lin9fea3122016-10-10 18:32:26 -070084 mObserver = new LockingContentObserver(lock, this::onContentChanged);
Jeff Sharkeya5defe32013-08-05 17:56:48 -070085 }
86
87 @Override
Jeff Sharkeyac9e6272013-08-31 21:27:44 -070088 public final DirectoryResult loadInBackground() {
89 synchronized (this) {
90 if (isLoadInBackgroundCanceled()) {
91 throw new OperationCanceledException();
92 }
93 mSignal = new CancellationSignal();
94 }
Jeff Sharkeya4d1f222013-09-07 14:45:03 -070095
96 final ContentResolver resolver = getContext().getContentResolver();
Jeff Sharkeyd82b26b2013-09-02 15:07:28 -070097 final String authority = mUri.getAuthority();
Jeff Sharkeya4d1f222013-09-07 14:45:03 -070098
99 final DirectoryResult result = new DirectoryResult();
Tomasz Mikolajewskie29e3412016-02-24 12:53:44 +0900100 result.doc = mDoc;
Jeff Sharkeya4d1f222013-09-07 14:45:03 -0700101
Jeff Sharkeyf63b7772013-10-01 17:57:41 -0700102 ContentProviderClient client = null;
Garfield, Tan11d23482016-08-05 09:33:29 -0700103 Cursor cursor;
Jeff Sharkeya4d1f222013-09-07 14:45:03 -0700104 try {
Jeff Sharkeyf63b7772013-10-01 17:57:41 -0700105 client = DocumentsApplication.acquireUnstableProviderOrThrow(resolver, authority);
Tomasz Mikolajewskib19061c2017-02-13 17:33:42 +0900106 if (mDoc.isInArchive()) {
107 ArchivesProvider.acquireArchive(client, mUri);
108 }
109 result.client = client;
Steve McKay50b9bae2017-01-17 11:12:08 -0800110
Steve McKay98f8c5f2017-03-03 13:52:14 -0800111 Resources resources = getContext().getResources();
Steve McKay710248d2017-03-14 10:09:30 -0700112 if (mFeatures.isContentPagingEnabled()) {
Steve McKayf208d842017-02-27 12:57:58 -0800113 Bundle queryArgs = new Bundle();
114 mModel.addQuerySortArgs(queryArgs);
Steve McKay50b9bae2017-01-17 11:12:08 -0800115
Steve McKayf208d842017-02-27 12:57:58 -0800116 // TODO: At some point we don't want forced flags to override real paging...
117 // and that point is when we have real paging.
118 DebugFlags.addForcedPagingArgs(queryArgs);
119
120 cursor = client.query(mUri, null, queryArgs, mSignal);
121 } else {
122 cursor = client.query(
123 mUri, null, null, null, mModel.getDocumentSortQuery(), mSignal);
Makoto Onuki77778752015-07-01 14:55:14 -0700124 }
125
Steve McKayf208d842017-02-27 12:57:58 -0800126 if (cursor == null) {
127 throw new RemoteException("Provider returned null");
Steve McKay50b9bae2017-01-17 11:12:08 -0800128 }
129
Jeff Sharkey20b32272013-09-03 15:25:52 -0700130 cursor.registerContentObserver(mObserver);
131
Jeff Sharkeya35ac2d2013-09-10 12:04:26 -0700132 cursor = new RootCursorWrapper(mUri.getAuthority(), mRoot.rootId, cursor, -1);
Jeff Sharkeyd82b26b2013-09-02 15:07:28 -0700133
Steve McKay710248d2017-03-14 10:09:30 -0700134 if (mSearchMode && !mFeatures.isFoldersInSearchResultsEnabled()) {
Garfield Tanb00bbc52016-11-01 14:23:35 -0700135 // There is no findDocumentPath API. Enable filtering on folders in search mode.
Jeff Sharkey9dd02622013-09-27 16:44:11 -0700136 cursor = new FilteringCursorWrapper(cursor, null, SEARCH_REJECT_MIMES);
Jeff Sharkeya35ac2d2013-09-10 12:04:26 -0700137 }
138
Steve McKay98f8c5f2017-03-03 13:52:14 -0800139 // TODO: When API tweaks have landed, use ContentResolver.EXTRA_HONORED_ARGS
140 // instead of checking directly for ContentResolver.QUERY_ARG_SORT_COLUMNS (won't work)
Steve McKay710248d2017-03-14 10:09:30 -0700141 if (mFeatures.isContentPagingEnabled()
Steve McKay98f8c5f2017-03-03 13:52:14 -0800142 && cursor.getExtras().containsKey(ContentResolver.QUERY_ARG_SORT_COLUMNS)) {
143 if (VERBOSE) Log.d(TAG, "Skipping sort of pre-sorted cursor. Booya!");
144 } else {
145 cursor = mModel.sortCursor(cursor);
146 }
Jeff Sharkeya35ac2d2013-09-10 12:04:26 -0700147 result.cursor = cursor;
Jeff Sharkey1d890e02013-08-15 11:24:03 -0700148 } catch (Exception e) {
Jeff Sharkey0e8c8712013-09-12 21:59:06 -0700149 Log.w(TAG, "Failed to query", e);
Jeff Sharkeyac9e6272013-08-31 21:27:44 -0700150 result.exception = e;
Jeff Sharkeyac9e6272013-08-31 21:27:44 -0700151 } finally {
152 synchronized (this) {
153 mSignal = null;
154 }
Tomasz Mikolajewskib19061c2017-02-13 17:33:42 +0900155 // TODO: Remove this call.
Ben Linf8f06e92017-01-27 17:15:48 -0800156 ContentProviderClient.releaseQuietly(client);
Jeff Sharkey1d890e02013-08-15 11:24:03 -0700157 }
Jeff Sharkeya4d1f222013-09-07 14:45:03 -0700158
Jeff Sharkey46899c82013-08-18 22:26:48 -0700159 return result;
Jeff Sharkey1d890e02013-08-15 11:24:03 -0700160 }
161
Jeff Sharkeyac9e6272013-08-31 21:27:44 -0700162 @Override
163 public void cancelLoadInBackground() {
164 super.cancelLoadInBackground();
Jeff Sharkeya5defe32013-08-05 17:56:48 -0700165
Jeff Sharkeyac9e6272013-08-31 21:27:44 -0700166 synchronized (this) {
167 if (mSignal != null) {
168 mSignal.cancel();
Jeff Sharkey46899c82013-08-18 22:26:48 -0700169 }
Jeff Sharkeya5defe32013-08-05 17:56:48 -0700170 }
Jeff Sharkeyac9e6272013-08-31 21:27:44 -0700171 }
Jeff Sharkeya5defe32013-08-05 17:56:48 -0700172
Jeff Sharkeyac9e6272013-08-31 21:27:44 -0700173 @Override
174 public void deliverResult(DirectoryResult result) {
175 if (isReset()) {
176 IoUtils.closeQuietly(result);
177 return;
178 }
179 DirectoryResult oldResult = mResult;
180 mResult = result;
181
182 if (isStarted()) {
183 super.deliverResult(result);
184 }
185
186 if (oldResult != null && oldResult != result) {
187 IoUtils.closeQuietly(oldResult);
188 }
189 }
190
191 @Override
192 protected void onStartLoading() {
193 if (mResult != null) {
194 deliverResult(mResult);
195 }
196 if (takeContentChanged() || mResult == null) {
197 forceLoad();
198 }
199 }
200
201 @Override
202 protected void onStopLoading() {
203 cancelLoad();
204 }
205
206 @Override
207 public void onCanceled(DirectoryResult result) {
208 IoUtils.closeQuietly(result);
209 }
210
211 @Override
212 protected void onReset() {
213 super.onReset();
214
215 // Ensure the loader is stopped
216 onStopLoading();
217
218 IoUtils.closeQuietly(mResult);
219 mResult = null;
220
221 getContext().getContentResolver().unregisterContentObserver(mObserver);
222 }
Ben Lin9fea3122016-10-10 18:32:26 -0700223
224 private static final class LockingContentObserver extends ContentObserver {
225 private final DirectoryReloadLock mLock;
226 private final Runnable mContentChangedCallback;
227
228 public LockingContentObserver(DirectoryReloadLock lock, Runnable contentChangedCallback) {
Garfield Tane9670332017-03-06 18:33:23 -0800229 super(new Handler(Looper.getMainLooper()));
Ben Lin9fea3122016-10-10 18:32:26 -0700230 mLock = lock;
231 mContentChangedCallback = contentChangedCallback;
232 }
233
234 @Override
235 public boolean deliverSelfNotifications() {
236 return true;
237 }
238
239 @Override
240 public void onChange(boolean selfChange) {
241 mLock.tryUpdate(mContentChangedCallback);
242 }
243 }
Jeff Sharkeya5defe32013-08-05 17:56:48 -0700244}