RootsCache.java revision 923396b7720367ec99c889725fddfa556e703971
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;
20
21import android.content.ContentProviderClient;
22import android.content.ContentResolver;
23import android.content.Context;
24import android.content.pm.PackageManager;
25import android.content.pm.ProviderInfo;
26import android.database.Cursor;
27import android.net.Uri;
28import android.provider.DocumentsContract;
29import android.provider.DocumentsContract.Root;
30import android.util.Log;
31
32import com.android.documentsui.DocumentsActivity.State;
33import com.android.documentsui.model.RootInfo;
34import com.android.internal.annotations.GuardedBy;
35import com.android.internal.util.Objects;
36import com.google.android.collect.Lists;
37
38import libcore.io.IoUtils;
39
40import java.util.ArrayList;
41import java.util.List;
42
43/**
44 * Cache of known storage backends and their roots.
45 */
46public class RootsCache {
47
48    // TODO: cache roots in local provider to avoid spinning up backends
49    // TODO: root updates should trigger UI refresh
50
51    private static final boolean RECENTS_ENABLED = true;
52
53    private final Context mContext;
54
55    public List<RootInfo> mRoots = Lists.newArrayList();
56
57    private RootInfo mRecentsRoot;
58
59    public RootsCache(Context context) {
60        mContext = context;
61        update();
62    }
63
64    /**
65     * Gather roots from all known storage providers.
66     */
67    @GuardedBy("ActivityThread")
68    public void update() {
69        mRoots.clear();
70
71        if (RECENTS_ENABLED) {
72            // Create special root for recents
73            final RootInfo root = new RootInfo();
74            root.rootType = Root.ROOT_TYPE_SHORTCUT;
75            root.icon = R.drawable.ic_dir;
76            root.flags = Root.FLAG_LOCAL_ONLY | Root.FLAG_SUPPORTS_CREATE;
77            root.title = mContext.getString(R.string.root_recent);
78            root.availableBytes = -1;
79
80            mRoots.add(root);
81            mRecentsRoot = root;
82        }
83
84        // Query for other storage backends
85        final ContentResolver resolver = mContext.getContentResolver();
86        final PackageManager pm = mContext.getPackageManager();
87        final List<ProviderInfo> providers = pm.queryContentProviders(
88                null, -1, PackageManager.GET_META_DATA);
89        for (ProviderInfo info : providers) {
90            if (info.metaData != null && info.metaData.containsKey(
91                    DocumentsContract.META_DATA_DOCUMENT_PROVIDER)) {
92
93                // TODO: remove deprecated customRoots flag
94                // TODO: populate roots on background thread, and cache results
95                final Uri rootsUri = DocumentsContract.buildRootsUri(info.authority);
96                final ContentProviderClient client = resolver
97                        .acquireUnstableContentProviderClient(info.authority);
98                Cursor cursor = null;
99                try {
100                    cursor = client.query(rootsUri, null, null, null, null);
101                    while (cursor.moveToNext()) {
102                        final RootInfo root = RootInfo.fromRootsCursor(info.authority, cursor);
103                        mRoots.add(root);
104                    }
105                } catch (Exception e) {
106                    Log.w(TAG, "Failed to load some roots from " + info.authority + ": " + e);
107                } finally {
108                    IoUtils.closeQuietly(cursor);
109                    ContentProviderClient.closeQuietly(client);
110                }
111            }
112        }
113    }
114
115    @Deprecated
116    public RootInfo findRoot(Uri uri) {
117        final String authority = uri.getAuthority();
118        final String docId = DocumentsContract.getDocumentId(uri);
119        for (RootInfo root : mRoots) {
120            if (Objects.equal(root.authority, authority) && Objects.equal(root.documentId, docId)) {
121                return root;
122            }
123        }
124        return null;
125    }
126
127    @GuardedBy("ActivityThread")
128    public RootInfo getRoot(String authority, String rootId) {
129        for (RootInfo root : mRoots) {
130            if (Objects.equal(root.authority, authority) && Objects.equal(root.rootId, rootId)) {
131                return root;
132            }
133        }
134        return null;
135    }
136
137    @GuardedBy("ActivityThread")
138    public RootInfo getRecentsRoot() {
139        return mRecentsRoot;
140    }
141
142    @GuardedBy("ActivityThread")
143    public boolean isRecentsRoot(RootInfo root) {
144        return mRecentsRoot == root;
145    }
146
147    @GuardedBy("ActivityThread")
148    public List<RootInfo> getRoots() {
149        return mRoots;
150    }
151
152    @GuardedBy("ActivityThread")
153    public List<RootInfo> getMatchingRoots(State state) {
154        ArrayList<RootInfo> matching = Lists.newArrayList();
155        for (RootInfo root : mRoots) {
156            final boolean supportsCreate = (root.flags & Root.FLAG_SUPPORTS_CREATE) != 0;
157            final boolean advanced = (root.flags & Root.FLAG_ADVANCED) != 0;
158            final boolean localOnly = (root.flags & Root.FLAG_LOCAL_ONLY) != 0;
159
160            // Exclude read-only devices when creating
161            if (state.action == State.ACTION_CREATE && !supportsCreate) continue;
162            // Exclude advanced devices when not requested
163            if (!state.showAdvanced && advanced) continue;
164            // Exclude non-local devices when local only
165            if (state.localOnly && !localOnly) continue;
166
167            // Only include roots that serve requested content
168            if (!MimePredicate.mimeMatches(root.mimeTypes, state.acceptMimes)) {
169                continue;
170            }
171
172            matching.add(root);
173        }
174        return matching;
175    }
176}
177