Home | History | Annotate | Download | only in documentsui
      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