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