• 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.RootInfo;
44 import com.android.documentsui.roots.RootCursorWrapper;
45 import com.android.documentsui.sorting.SortModel;
46 
47 import libcore.io.IoUtils;
48 
49 public class DirectoryLoader extends AsyncTaskLoader<DirectoryResult> {
50 
51     private static final String TAG = "DirectoryLoader";
52 
53     private static final String[] SEARCH_REJECT_MIMES = new String[] { Document.MIME_TYPE_DIR };
54 
55     private final LockingContentObserver mObserver;
56     private final RootInfo mRoot;
57     private final Uri mUri;
58     private final SortModel mModel;
59     private final boolean mSearchMode;
60 
61     private DocumentInfo mDoc;
62     private CancellationSignal mSignal;
63     private DirectoryResult mResult;
64 
65     private Features mFeatures;
66 
DirectoryLoader( Features freatures, Context context, RootInfo root, DocumentInfo doc, Uri uri, SortModel model, DirectoryReloadLock lock, boolean inSearchMode)67     public DirectoryLoader(
68             Features freatures,
69             Context context,
70             RootInfo root,
71             DocumentInfo doc,
72             Uri uri,
73             SortModel model,
74             DirectoryReloadLock lock,
75             boolean inSearchMode) {
76 
77         super(context, ProviderExecutor.forAuthority(root.authority));
78         mFeatures = freatures;
79         mRoot = root;
80         mUri = uri;
81         mModel = model;
82         mDoc = doc;
83         mSearchMode = inSearchMode;
84         mObserver = new LockingContentObserver(lock, this::onContentChanged);
85     }
86 
87     @Override
loadInBackground()88     public final DirectoryResult loadInBackground() {
89         synchronized (this) {
90             if (isLoadInBackgroundCanceled()) {
91                 throw new OperationCanceledException();
92             }
93             mSignal = new CancellationSignal();
94         }
95 
96         final ContentResolver resolver = getContext().getContentResolver();
97         final String authority = mUri.getAuthority();
98 
99         final DirectoryResult result = new DirectoryResult();
100         result.doc = mDoc;
101 
102         ContentProviderClient client = null;
103         Cursor cursor;
104         try {
105             client = DocumentsApplication.acquireUnstableProviderOrThrow(resolver, authority);
106             if (mDoc.isInArchive()) {
107                 ArchivesProvider.acquireArchive(client, mUri);
108             }
109             result.client = client;
110 
111             Resources resources = getContext().getResources();
112             if (mFeatures.isContentPagingEnabled()) {
113                 Bundle queryArgs = new Bundle();
114                 mModel.addQuerySortArgs(queryArgs);
115 
116                 // 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);
124             }
125 
126             if (cursor == null) {
127                 throw new RemoteException("Provider returned null");
128             }
129 
130             cursor.registerContentObserver(mObserver);
131 
132             cursor = new RootCursorWrapper(mUri.getAuthority(), mRoot.rootId, cursor, -1);
133 
134             if (mSearchMode && !mFeatures.isFoldersInSearchResultsEnabled()) {
135                 // There is no findDocumentPath API. Enable filtering on folders in search mode.
136                 cursor = new FilteringCursorWrapper(cursor, null, SEARCH_REJECT_MIMES);
137             }
138 
139             // 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)
141             if (mFeatures.isContentPagingEnabled()
142                         && 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             }
147             result.cursor = cursor;
148         } catch (Exception e) {
149             Log.w(TAG, "Failed to query", e);
150             result.exception = e;
151         } finally {
152             synchronized (this) {
153                 mSignal = null;
154             }
155             // TODO: Remove this call.
156             ContentProviderClient.releaseQuietly(client);
157         }
158 
159         return result;
160     }
161 
162     @Override
cancelLoadInBackground()163     public void cancelLoadInBackground() {
164         super.cancelLoadInBackground();
165 
166         synchronized (this) {
167             if (mSignal != null) {
168                 mSignal.cancel();
169             }
170         }
171     }
172 
173     @Override
deliverResult(DirectoryResult result)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
onStartLoading()192     protected void onStartLoading() {
193         if (mResult != null) {
194             deliverResult(mResult);
195         }
196         if (takeContentChanged() || mResult == null) {
197             forceLoad();
198         }
199     }
200 
201     @Override
onStopLoading()202     protected void onStopLoading() {
203         cancelLoad();
204     }
205 
206     @Override
onCanceled(DirectoryResult result)207     public void onCanceled(DirectoryResult result) {
208         IoUtils.closeQuietly(result);
209     }
210 
211     @Override
onReset()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     }
223 
224     private static final class LockingContentObserver extends ContentObserver {
225         private final DirectoryReloadLock mLock;
226         private final Runnable mContentChangedCallback;
227 
LockingContentObserver(DirectoryReloadLock lock, Runnable contentChangedCallback)228         public LockingContentObserver(DirectoryReloadLock lock, Runnable contentChangedCallback) {
229             super(new Handler(Looper.getMainLooper()));
230             mLock = lock;
231             mContentChangedCallback = contentChangedCallback;
232         }
233 
234         @Override
deliverSelfNotifications()235         public boolean deliverSelfNotifications() {
236             return true;
237         }
238 
239         @Override
onChange(boolean selfChange)240         public void onChange(boolean selfChange) {
241             mLock.tryUpdate(mContentChangedCallback);
242         }
243     }
244 }
245