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