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