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.DocumentsActivity.TAG; 20 import static com.android.documentsui.DocumentsActivity.State.MODE_UNKNOWN; 21 import static com.android.documentsui.DocumentsActivity.State.SORT_ORDER_DISPLAY_NAME; 22 import static com.android.documentsui.DocumentsActivity.State.SORT_ORDER_LAST_MODIFIED; 23 import static com.android.documentsui.DocumentsActivity.State.SORT_ORDER_SIZE; 24 import static com.android.documentsui.DocumentsActivity.State.SORT_ORDER_UNKNOWN; 25 import static com.android.documentsui.model.DocumentInfo.getCursorInt; 26 27 import android.content.AsyncTaskLoader; 28 import android.content.ContentProviderClient; 29 import android.content.ContentResolver; 30 import android.content.Context; 31 import android.database.Cursor; 32 import android.net.Uri; 33 import android.os.CancellationSignal; 34 import android.os.OperationCanceledException; 35 import android.provider.DocumentsContract; 36 import android.provider.DocumentsContract.Document; 37 import android.util.Log; 38 39 import com.android.documentsui.DocumentsActivity.State; 40 import com.android.documentsui.RecentsProvider.StateColumns; 41 import com.android.documentsui.model.DocumentInfo; 42 import com.android.documentsui.model.RootInfo; 43 44 import libcore.io.IoUtils; 45 46 import java.io.FileNotFoundException; 47 48 class 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.releaseQuietly(client); 60 cursor = null; 61 client = null; 62 } 63 } 64 65 public 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, ProviderExecutor.forAuthority(root.authority)); 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 ContentProviderClient client = null; 161 try { 162 client = DocumentsApplication.acquireUnstableProviderOrThrow(resolver, authority); 163 164 cursor = client.query( 165 mUri, null, null, null, getQuerySortOrder(result.sortOrder), mSignal); 166 cursor.registerContentObserver(mObserver); 167 168 cursor = new RootCursorWrapper(mUri.getAuthority(), mRoot.rootId, cursor, -1); 169 170 if (mType == DirectoryFragment.TYPE_SEARCH) { 171 // Filter directories out of search results, for now 172 cursor = new FilteringCursorWrapper(cursor, null, SEARCH_REJECT_MIMES); 173 } else { 174 // Normal directories should have sorting applied 175 cursor = new SortingCursorWrapper(cursor, result.sortOrder); 176 } 177 178 result.client = client; 179 result.cursor = cursor; 180 } catch (Exception e) { 181 Log.w(TAG, "Failed to query", e); 182 result.exception = e; 183 ContentProviderClient.releaseQuietly(client); 184 } finally { 185 synchronized (this) { 186 mSignal = null; 187 } 188 } 189 190 return result; 191 } 192 193 @Override 194 public void cancelLoadInBackground() { 195 super.cancelLoadInBackground(); 196 197 synchronized (this) { 198 if (mSignal != null) { 199 mSignal.cancel(); 200 } 201 } 202 } 203 204 @Override 205 public void deliverResult(DirectoryResult result) { 206 if (isReset()) { 207 IoUtils.closeQuietly(result); 208 return; 209 } 210 DirectoryResult oldResult = mResult; 211 mResult = result; 212 213 if (isStarted()) { 214 super.deliverResult(result); 215 } 216 217 if (oldResult != null && oldResult != result) { 218 IoUtils.closeQuietly(oldResult); 219 } 220 } 221 222 @Override 223 protected void onStartLoading() { 224 if (mResult != null) { 225 deliverResult(mResult); 226 } 227 if (takeContentChanged() || mResult == null) { 228 forceLoad(); 229 } 230 } 231 232 @Override 233 protected void onStopLoading() { 234 cancelLoad(); 235 } 236 237 @Override 238 public void onCanceled(DirectoryResult result) { 239 IoUtils.closeQuietly(result); 240 } 241 242 @Override 243 protected void onReset() { 244 super.onReset(); 245 246 // Ensure the loader is stopped 247 onStopLoading(); 248 249 IoUtils.closeQuietly(mResult); 250 mResult = null; 251 252 getContext().getContentResolver().unregisterContentObserver(mObserver); 253 } 254 255 public static String getQuerySortOrder(int sortOrder) { 256 switch (sortOrder) { 257 case SORT_ORDER_DISPLAY_NAME: 258 return Document.COLUMN_DISPLAY_NAME + " ASC"; 259 case SORT_ORDER_LAST_MODIFIED: 260 return Document.COLUMN_LAST_MODIFIED + " DESC"; 261 case SORT_ORDER_SIZE: 262 return Document.COLUMN_SIZE + " DESC"; 263 default: 264 return null; 265 } 266 } 267 } 268