• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
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 
17 package com.android.documentsui;
18 
19 import static com.android.documentsui.base.Shared.VERBOSE;
20 
21 import android.content.AsyncTaskLoader;
22 import android.content.ContentProviderClient;
23 import android.content.ContentResolver;
24 import android.content.Context;
25 import android.content.res.Resources;
26 import android.database.ContentObserver;
27 import android.database.Cursor;
28 import android.net.Uri;
29 import android.os.Bundle;
30 import android.os.CancellationSignal;
31 import android.os.Handler;
32 import android.os.Looper;
33 import android.os.OperationCanceledException;
34 import android.os.RemoteException;
35 import android.provider.DocumentsContract.Document;
36 import android.util.Log;
37 
38 import com.android.documentsui.archives.ArchivesProvider;
39 import com.android.documentsui.base.DebugFlags;
40 import com.android.documentsui.base.DocumentInfo;
41 import com.android.documentsui.base.Features;
42 import com.android.documentsui.base.FilteringCursorWrapper;
43 import com.android.documentsui.base.Lookup;
44 import com.android.documentsui.base.RootInfo;
45 import com.android.documentsui.roots.RootCursorWrapper;
46 import com.android.documentsui.sorting.SortModel;
47 
48 import libcore.io.IoUtils;
49 
50 public class DirectoryLoader extends AsyncTaskLoader<DirectoryResult> {
51 
52     private static final String TAG = "DirectoryLoader";
53 
54     private static final String[] SEARCH_REJECT_MIMES = new String[] { Document.MIME_TYPE_DIR };
55 
56     private final LockingContentObserver mObserver;
57     private final RootInfo mRoot;
58     private final Uri mUri;
59     private final SortModel mModel;
60     private final Lookup<String, String> mFileTypeLookup;
61     private final boolean mSearchMode;
62 
63     private DocumentInfo mDoc;
64     private CancellationSignal mSignal;
65     private DirectoryResult mResult;
66 
67     private Features mFeatures;
68 
DirectoryLoader( Features features, Context context, RootInfo root, DocumentInfo doc, Uri uri, SortModel model, Lookup<String, String> fileTypeLookup, DirectoryReloadLock lock, boolean inSearchMode)69     public DirectoryLoader(
70             Features features,
71             Context context,
72             RootInfo root,
73             DocumentInfo doc,
74             Uri uri,
75             SortModel model,
76             Lookup<String, String> fileTypeLookup,
77             DirectoryReloadLock lock,
78             boolean inSearchMode) {
79 
80         super(context, ProviderExecutor.forAuthority(root.authority));
81         mFeatures = features;
82         mRoot = root;
83         mUri = uri;
84         mModel = model;
85         mDoc = doc;
86         mFileTypeLookup = fileTypeLookup;
87         mSearchMode = inSearchMode;
88         mObserver = new LockingContentObserver(lock, this::onContentChanged);
89     }
90 
91     @Override
loadInBackground()92     public final DirectoryResult loadInBackground() {
93         synchronized (this) {
94             if (isLoadInBackgroundCanceled()) {
95                 throw new OperationCanceledException();
96             }
97             mSignal = new CancellationSignal();
98         }
99 
100         final ContentResolver resolver = getContext().getContentResolver();
101         final String authority = mUri.getAuthority();
102 
103         final DirectoryResult result = new DirectoryResult();
104         result.doc = mDoc;
105 
106         ContentProviderClient client = null;
107         Cursor cursor;
108         try {
109             client = DocumentsApplication.acquireUnstableProviderOrThrow(resolver, authority);
110             if (mDoc.isInArchive()) {
111                 ArchivesProvider.acquireArchive(client, mUri);
112             }
113             result.client = client;
114 
115             Resources resources = getContext().getResources();
116             if (mFeatures.isContentPagingEnabled()) {
117                 Bundle queryArgs = new Bundle();
118                 mModel.addQuerySortArgs(queryArgs);
119 
120                 // TODO: At some point we don't want forced flags to override real paging...
121                 // and that point is when we have real paging.
122                 DebugFlags.addForcedPagingArgs(queryArgs);
123 
124                 cursor = client.query(mUri, null, queryArgs, mSignal);
125             } else {
126                 cursor = client.query(
127                         mUri, null, null, null, mModel.getDocumentSortQuery(), mSignal);
128             }
129 
130             if (cursor == null) {
131                 throw new RemoteException("Provider returned null");
132             }
133 
134             cursor.registerContentObserver(mObserver);
135 
136             cursor = new RootCursorWrapper(mUri.getAuthority(), mRoot.rootId, cursor, -1);
137 
138             if (mSearchMode && !mFeatures.isFoldersInSearchResultsEnabled()) {
139                 // There is no findDocumentPath API. Enable filtering on folders in search mode.
140                 cursor = new FilteringCursorWrapper(cursor, null, SEARCH_REJECT_MIMES);
141             }
142 
143             // TODO: When API tweaks have landed, use ContentResolver.EXTRA_HONORED_ARGS
144             // instead of checking directly for ContentResolver.QUERY_ARG_SORT_COLUMNS (won't work)
145             if (mFeatures.isContentPagingEnabled()
146                         && cursor.getExtras().containsKey(ContentResolver.QUERY_ARG_SORT_COLUMNS)) {
147                 if (VERBOSE) Log.d(TAG, "Skipping sort of pre-sorted cursor. Booya!");
148             } else {
149                 cursor = mModel.sortCursor(cursor, mFileTypeLookup);
150             }
151             result.cursor = cursor;
152         } catch (Exception e) {
153             Log.w(TAG, "Failed to query", e);
154             result.exception = e;
155         } finally {
156             synchronized (this) {
157                 mSignal = null;
158             }
159             // TODO: Remove this call.
160             ContentProviderClient.releaseQuietly(client);
161         }
162 
163         return result;
164     }
165 
166     @Override
cancelLoadInBackground()167     public void cancelLoadInBackground() {
168         super.cancelLoadInBackground();
169 
170         synchronized (this) {
171             if (mSignal != null) {
172                 mSignal.cancel();
173             }
174         }
175     }
176 
177     @Override
deliverResult(DirectoryResult result)178     public void deliverResult(DirectoryResult result) {
179         if (isReset()) {
180             IoUtils.closeQuietly(result);
181             return;
182         }
183         DirectoryResult oldResult = mResult;
184         mResult = result;
185 
186         if (isStarted()) {
187             super.deliverResult(result);
188         }
189 
190         if (oldResult != null && oldResult != result) {
191             IoUtils.closeQuietly(oldResult);
192         }
193     }
194 
195     @Override
onStartLoading()196     protected void onStartLoading() {
197         if (mResult != null) {
198             deliverResult(mResult);
199         }
200         if (takeContentChanged() || mResult == null) {
201             forceLoad();
202         }
203     }
204 
205     @Override
onStopLoading()206     protected void onStopLoading() {
207         cancelLoad();
208     }
209 
210     @Override
onCanceled(DirectoryResult result)211     public void onCanceled(DirectoryResult result) {
212         IoUtils.closeQuietly(result);
213     }
214 
215     @Override
onReset()216     protected void onReset() {
217         super.onReset();
218 
219         // Ensure the loader is stopped
220         onStopLoading();
221 
222         IoUtils.closeQuietly(mResult);
223         mResult = null;
224 
225         getContext().getContentResolver().unregisterContentObserver(mObserver);
226     }
227 
228     private static final class LockingContentObserver extends ContentObserver {
229         private final DirectoryReloadLock mLock;
230         private final Runnable mContentChangedCallback;
231 
LockingContentObserver(DirectoryReloadLock lock, Runnable contentChangedCallback)232         public LockingContentObserver(DirectoryReloadLock lock, Runnable contentChangedCallback) {
233             super(new Handler(Looper.getMainLooper()));
234             mLock = lock;
235             mContentChangedCallback = contentChangedCallback;
236         }
237 
238         @Override
deliverSelfNotifications()239         public boolean deliverSelfNotifications() {
240             return true;
241         }
242 
243         @Override
onChange(boolean selfChange)244         public void onChange(boolean selfChange) {
245             mLock.tryUpdate(mContentChangedCallback);
246         }
247     }
248 }
249