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