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.releaseQuietly(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, 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