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