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