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 17package com.android.documentsui; 18 19import static com.android.documentsui.Shared.DEBUG; 20import static com.android.documentsui.Shared.TAG; 21import static com.android.documentsui.State.SORT_ORDER_DISPLAY_NAME; 22import static com.android.documentsui.State.SORT_ORDER_LAST_MODIFIED; 23import static com.android.documentsui.State.SORT_ORDER_SIZE; 24 25import android.content.AsyncTaskLoader; 26import android.content.ContentProviderClient; 27import android.content.ContentResolver; 28import android.content.Context; 29import android.database.Cursor; 30import android.net.Uri; 31import android.os.CancellationSignal; 32import android.os.OperationCanceledException; 33import android.os.RemoteException; 34import android.provider.DocumentsContract; 35import android.provider.DocumentsContract.Document; 36import android.util.Log; 37 38import com.android.documentsui.dirlist.DirectoryFragment; 39import com.android.documentsui.model.DocumentInfo; 40import com.android.documentsui.model.RootInfo; 41 42import libcore.io.IoUtils; 43 44import java.io.FileNotFoundException; 45 46public class DirectoryLoader extends AsyncTaskLoader<DirectoryResult> { 47 48 private static final String[] SEARCH_REJECT_MIMES = new String[] { Document.MIME_TYPE_DIR }; 49 50 private final ForceLoadContentObserver mObserver = new ForceLoadContentObserver(); 51 52 private final int mType; 53 private final RootInfo mRoot; 54 private final Uri mUri; 55 private final int mUserSortOrder; 56 private final boolean mSearchMode; 57 58 private DocumentInfo mDoc; 59 private CancellationSignal mSignal; 60 private DirectoryResult mResult; 61 62 public DirectoryLoader(Context context, int type, RootInfo root, DocumentInfo doc, Uri uri, 63 int userSortOrder, boolean inSearchMode) { 64 super(context, ProviderExecutor.forAuthority(root.authority)); 65 mType = type; 66 mRoot = root; 67 mUri = uri; 68 mUserSortOrder = userSortOrder; 69 mDoc = doc; 70 mSearchMode = inSearchMode; 71 } 72 73 @Override 74 public final DirectoryResult loadInBackground() { 75 synchronized (this) { 76 if (isLoadInBackgroundCanceled()) { 77 throw new OperationCanceledException(); 78 } 79 mSignal = new CancellationSignal(); 80 } 81 82 final ContentResolver resolver = getContext().getContentResolver(); 83 final String authority = mUri.getAuthority(); 84 85 final DirectoryResult result = new DirectoryResult(); 86 result.doc = mDoc; 87 88 // Use default document when searching 89 if (mSearchMode) { 90 final Uri docUri = DocumentsContract.buildDocumentUri( 91 mRoot.authority, mRoot.documentId); 92 try { 93 mDoc = DocumentInfo.fromUri(resolver, docUri); 94 } catch (FileNotFoundException e) { 95 Log.w(TAG, "Failed to query", e); 96 result.exception = e; 97 return result; 98 } 99 } 100 101 if (mUserSortOrder != State.SORT_ORDER_UNKNOWN) { 102 result.sortOrder = mUserSortOrder; 103 } else { 104 if ((mDoc.flags & Document.FLAG_DIR_PREFERS_LAST_MODIFIED) != 0) { 105 result.sortOrder = State.SORT_ORDER_LAST_MODIFIED; 106 } else { 107 result.sortOrder = State.SORT_ORDER_DISPLAY_NAME; 108 } 109 } 110 111 // Search always uses ranking from provider 112 if (mSearchMode) { 113 result.sortOrder = State.SORT_ORDER_UNKNOWN; 114 } 115 116 if (DEBUG) 117 Log.d(TAG, "userSortOrder=" + mUserSortOrder + ", sortOrder=" + result.sortOrder); 118 119 ContentProviderClient client = null; 120 Cursor cursor = null; 121 try { 122 client = DocumentsApplication.acquireUnstableProviderOrThrow(resolver, authority); 123 cursor = client.query( 124 mUri, null, null, null, getQuerySortOrder(result.sortOrder), mSignal); 125 if (cursor == null) { 126 throw new RemoteException("Provider returned null"); 127 } 128 129 cursor.registerContentObserver(mObserver); 130 131 cursor = new RootCursorWrapper(mUri.getAuthority(), mRoot.rootId, cursor, -1); 132 133 if (mSearchMode) { 134 // Filter directories out of search results, for now 135 cursor = new FilteringCursorWrapper(cursor, null, SEARCH_REJECT_MIMES); 136 } 137 138 result.client = client; 139 result.cursor = cursor; 140 } catch (Exception e) { 141 Log.w(TAG, "Failed to query", e); 142 result.exception = e; 143 ContentProviderClient.releaseQuietly(client); 144 } finally { 145 synchronized (this) { 146 mSignal = null; 147 } 148 } 149 150 return result; 151 } 152 153 @Override 154 public void cancelLoadInBackground() { 155 super.cancelLoadInBackground(); 156 157 synchronized (this) { 158 if (mSignal != null) { 159 mSignal.cancel(); 160 } 161 } 162 } 163 164 @Override 165 public void deliverResult(DirectoryResult result) { 166 if (isReset()) { 167 IoUtils.closeQuietly(result); 168 return; 169 } 170 DirectoryResult oldResult = mResult; 171 mResult = result; 172 173 if (isStarted()) { 174 super.deliverResult(result); 175 } 176 177 if (oldResult != null && oldResult != result) { 178 IoUtils.closeQuietly(oldResult); 179 } 180 } 181 182 @Override 183 protected void onStartLoading() { 184 if (mResult != null) { 185 deliverResult(mResult); 186 } 187 if (takeContentChanged() || mResult == null) { 188 forceLoad(); 189 } 190 } 191 192 @Override 193 protected void onStopLoading() { 194 cancelLoad(); 195 } 196 197 @Override 198 public void onCanceled(DirectoryResult result) { 199 IoUtils.closeQuietly(result); 200 } 201 202 @Override 203 protected void onReset() { 204 super.onReset(); 205 206 // Ensure the loader is stopped 207 onStopLoading(); 208 209 IoUtils.closeQuietly(mResult); 210 mResult = null; 211 212 getContext().getContentResolver().unregisterContentObserver(mObserver); 213 } 214 215 public static String getQuerySortOrder(int sortOrder) { 216 switch (sortOrder) { 217 case SORT_ORDER_DISPLAY_NAME: 218 return Document.COLUMN_DISPLAY_NAME + " ASC"; 219 case SORT_ORDER_LAST_MODIFIED: 220 return Document.COLUMN_LAST_MODIFIED + " DESC"; 221 case SORT_ORDER_SIZE: 222 return Document.COLUMN_SIZE + " DESC"; 223 default: 224 return null; 225 } 226 } 227} 228