Jeff Sharkey | 7611221 | 2013-08-06 11:26:10 -0700 | [diff] [blame] | 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 | |
Steve McKay | 459bc2b | 2015-09-16 15:07:31 -0700 | [diff] [blame] | 19 | import static com.android.documentsui.Shared.DEBUG; |
Steve McKay | f8a5e08 | 2015-09-23 17:21:40 -0700 | [diff] [blame] | 20 | import static com.android.documentsui.Shared.TAG; |
Jeff Sharkey | 7611221 | 2013-08-06 11:26:10 -0700 | [diff] [blame] | 21 | |
Jeff Sharkey | b6a7f2c | 2013-08-27 18:26:48 -0700 | [diff] [blame] | 22 | import android.content.ContentProviderClient; |
| 23 | import android.content.ContentResolver; |
Jeff Sharkey | 7611221 | 2013-08-06 11:26:10 -0700 | [diff] [blame] | 24 | import android.content.Context; |
Jeff Sharkey | 85c273d | 2013-10-07 10:16:12 -0700 | [diff] [blame] | 25 | import android.content.Intent; |
Jeff Sharkey | 8b99704 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 26 | import android.content.pm.ApplicationInfo; |
Jeff Sharkey | 7611221 | 2013-08-06 11:26:10 -0700 | [diff] [blame] | 27 | import android.content.pm.PackageManager; |
| 28 | import android.content.pm.ProviderInfo; |
Jeff Sharkey | 85c273d | 2013-10-07 10:16:12 -0700 | [diff] [blame] | 29 | import android.content.pm.ResolveInfo; |
Jeff Sharkey | 8b99704 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 30 | import android.database.ContentObserver; |
Jeff Sharkey | 724deeb | 2013-08-31 15:02:20 -0700 | [diff] [blame] | 31 | import android.database.Cursor; |
Jeff Sharkey | 7611221 | 2013-08-06 11:26:10 -0700 | [diff] [blame] | 32 | import android.net.Uri; |
Jeff Sharkey | 8b99704 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 33 | import android.os.AsyncTask; |
| 34 | import android.os.Handler; |
| 35 | import android.os.SystemClock; |
Jeff Sharkey | 7611221 | 2013-08-06 11:26:10 -0700 | [diff] [blame] | 36 | import android.provider.DocumentsContract; |
Jeff Sharkey | 724deeb | 2013-08-31 15:02:20 -0700 | [diff] [blame] | 37 | import android.provider.DocumentsContract.Root; |
Steve McKay | f8a5e08 | 2015-09-23 17:21:40 -0700 | [diff] [blame] | 38 | import android.support.annotation.VisibleForTesting; |
Jeff Sharkey | 7611221 | 2013-08-06 11:26:10 -0700 | [diff] [blame] | 39 | import android.util.Log; |
Jeff Sharkey | 7611221 | 2013-08-06 11:26:10 -0700 | [diff] [blame] | 40 | |
Jeff Sharkey | 724deeb | 2013-08-31 15:02:20 -0700 | [diff] [blame] | 41 | import com.android.documentsui.model.RootInfo; |
Jeff Sharkey | 7611221 | 2013-08-06 11:26:10 -0700 | [diff] [blame] | 42 | import com.android.internal.annotations.GuardedBy; |
Steve McKay | fefcd70 | 2015-08-20 16:19:38 +0000 | [diff] [blame] | 43 | |
Jeff Sharkey | 8b99704 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 44 | import com.google.common.collect.ArrayListMultimap; |
| 45 | import com.google.common.collect.Multimap; |
Jeff Sharkey | 7611221 | 2013-08-06 11:26:10 -0700 | [diff] [blame] | 46 | |
Jeff Sharkey | 724deeb | 2013-08-31 15:02:20 -0700 | [diff] [blame] | 47 | import libcore.io.IoUtils; |
| 48 | |
Steve McKay | fefcd70 | 2015-08-20 16:19:38 +0000 | [diff] [blame] | 49 | import java.util.ArrayList; |
Jeff Sharkey | 8b99704 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 50 | import java.util.Collection; |
Tomasz Mikolajewski | 5a1e879 | 2016-01-27 17:36:51 +0900 | [diff] [blame] | 51 | import java.util.Collections; |
Jeff Sharkey | 8b99704 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 52 | import java.util.HashSet; |
Jeff Sharkey | 7611221 | 2013-08-06 11:26:10 -0700 | [diff] [blame] | 53 | import java.util.List; |
Kenny Root | 16d8598 | 2013-12-13 12:00:26 -0800 | [diff] [blame] | 54 | import java.util.Objects; |
Jeff Sharkey | 8b99704 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 55 | import java.util.concurrent.CountDownLatch; |
| 56 | import java.util.concurrent.TimeUnit; |
Jeff Sharkey | 7611221 | 2013-08-06 11:26:10 -0700 | [diff] [blame] | 57 | |
| 58 | /** |
| 59 | * Cache of known storage backends and their roots. |
| 60 | */ |
| 61 | public class RootsCache { |
Jeff Sharkey | 5916877 | 2013-10-23 09:59:06 -0700 | [diff] [blame] | 62 | public static final Uri sNotificationUri = Uri.parse( |
| 63 | "content://com.android.documentsui.roots/"); |
Jeff Sharkey | 7611221 | 2013-08-06 11:26:10 -0700 | [diff] [blame] | 64 | |
Jeff Sharkey | 873daa3 | 2013-08-18 17:38:20 -0700 | [diff] [blame] | 65 | private final Context mContext; |
Jeff Sharkey | 8b99704 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 66 | private final ContentObserver mObserver; |
Daichi Hirono | 3067d0d | 2015-12-25 11:08:42 +0900 | [diff] [blame] | 67 | private OnCacheUpdateListener mCacheUpdateListener; |
Jeff Sharkey | 7611221 | 2013-08-06 11:26:10 -0700 | [diff] [blame] | 68 | |
Steve McKay | 323dffb | 2016-02-17 18:25:47 -0800 | [diff] [blame] | 69 | private final RootInfo mRecentsRoot; |
Jeff Sharkey | 7611221 | 2013-08-06 11:26:10 -0700 | [diff] [blame] | 70 | |
Jeff Sharkey | 8b99704 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 71 | private final Object mLock = new Object(); |
| 72 | private final CountDownLatch mFirstLoad = new CountDownLatch(1); |
| 73 | |
| 74 | @GuardedBy("mLock") |
| 75 | private Multimap<String, RootInfo> mRoots = ArrayListMultimap.create(); |
| 76 | @GuardedBy("mLock") |
Steve McKay | fefcd70 | 2015-08-20 16:19:38 +0000 | [diff] [blame] | 77 | private HashSet<String> mStoppedAuthorities = new HashSet<>(); |
Jeff Sharkey | 8b99704 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 78 | |
| 79 | @GuardedBy("mObservedAuthorities") |
Steve McKay | fefcd70 | 2015-08-20 16:19:38 +0000 | [diff] [blame] | 80 | private final HashSet<String> mObservedAuthorities = new HashSet<>(); |
Jeff Sharkey | 873daa3 | 2013-08-18 17:38:20 -0700 | [diff] [blame] | 81 | |
| 82 | public RootsCache(Context context) { |
| 83 | mContext = context; |
Jeff Sharkey | 8b99704 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 84 | mObserver = new RootsChangedObserver(); |
Steve McKay | 323dffb | 2016-02-17 18:25:47 -0800 | [diff] [blame] | 85 | |
| 86 | // Create a new anonymous "Recents" RootInfo. It's a faker. |
| 87 | mRecentsRoot = new RootInfo() {{ |
Steve McKay | 55c00e7 | 2016-02-18 15:32:16 -0800 | [diff] [blame] | 88 | // Special root for recents |
| 89 | derivedIcon = R.drawable.ic_root_recent; |
| 90 | derivedType = RootInfo.TYPE_RECENTS; |
Steve McKay | 62d33a7 | 2016-02-26 15:30:19 -0800 | [diff] [blame] | 91 | flags = Root.FLAG_LOCAL_ONLY | Root.FLAG_SUPPORTS_IS_CHILD |
| 92 | | Root.FLAG_SUPPORTS_CREATE; |
Steve McKay | 55c00e7 | 2016-02-18 15:32:16 -0800 | [diff] [blame] | 93 | title = mContext.getString(R.string.root_recent); |
| 94 | availableBytes = -1; |
| 95 | }}; |
Jeff Sharkey | 8b99704 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 96 | } |
| 97 | |
| 98 | private class RootsChangedObserver extends ContentObserver { |
| 99 | public RootsChangedObserver() { |
| 100 | super(new Handler()); |
| 101 | } |
| 102 | |
| 103 | @Override |
| 104 | public void onChange(boolean selfChange, Uri uri) { |
Steve McKay | 459bc2b | 2015-09-16 15:07:31 -0700 | [diff] [blame] | 105 | if (DEBUG) Log.d(TAG, "Updating roots due to change at " + uri); |
Jeff Sharkey | 8b99704 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 106 | updateAuthorityAsync(uri.getAuthority()); |
| 107 | } |
Jeff Sharkey | 873daa3 | 2013-08-18 17:38:20 -0700 | [diff] [blame] | 108 | } |
Jeff Sharkey | 7611221 | 2013-08-06 11:26:10 -0700 | [diff] [blame] | 109 | |
Daichi Hirono | 3067d0d | 2015-12-25 11:08:42 +0900 | [diff] [blame] | 110 | static interface OnCacheUpdateListener { |
| 111 | void onCacheUpdate(); |
| 112 | } |
| 113 | |
Jeff Sharkey | 7611221 | 2013-08-06 11:26:10 -0700 | [diff] [blame] | 114 | /** |
| 115 | * Gather roots from all known storage providers. |
| 116 | */ |
Jeff Sharkey | 8b99704 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 117 | public void updateAsync() { |
Steve McKay | 8d8a0a5 | 2016-03-01 12:57:44 -0800 | [diff] [blame] | 118 | |
| 119 | // NOTE: This method is called when the UI language changes. |
| 120 | // For that reason we upadte our RecentsRoot to reflect |
| 121 | // the current language. |
| 122 | mRecentsRoot.title = mContext.getString(R.string.root_recent); |
| 123 | |
| 124 | // Nothing else about the root should ever change. |
Steve McKay | 0af8afd | 2016-02-25 13:34:03 -0800 | [diff] [blame] | 125 | assert(mRecentsRoot.authority == null); |
| 126 | assert(mRecentsRoot.rootId == null); |
| 127 | assert(mRecentsRoot.derivedIcon == R.drawable.ic_root_recent); |
| 128 | assert(mRecentsRoot.derivedType == RootInfo.TYPE_RECENTS); |
| 129 | assert(mRecentsRoot.flags == (Root.FLAG_LOCAL_ONLY |
| 130 | | Root.FLAG_SUPPORTS_IS_CHILD |
| 131 | | Root.FLAG_SUPPORTS_CREATE)); |
Steve McKay | 0af8afd | 2016-02-25 13:34:03 -0800 | [diff] [blame] | 132 | assert(mRecentsRoot.availableBytes == -1); |
| 133 | |
Jeff Sharkey | 8b99704 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 134 | new UpdateTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); |
| 135 | } |
Jeff Sharkey | b6a7f2c | 2013-08-27 18:26:48 -0700 | [diff] [blame] | 136 | |
Jeff Sharkey | 8b99704 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 137 | /** |
| 138 | * Gather roots from storage providers belonging to given package name. |
| 139 | */ |
| 140 | public void updatePackageAsync(String packageName) { |
Jeff Sharkey | 8b99704 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 141 | new UpdateTask(packageName).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); |
| 142 | } |
| 143 | |
| 144 | /** |
| 145 | * Gather roots from storage providers belonging to given authority. |
| 146 | */ |
| 147 | public void updateAuthorityAsync(String authority) { |
| 148 | final ProviderInfo info = mContext.getPackageManager().resolveContentProvider(authority, 0); |
| 149 | if (info != null) { |
| 150 | updatePackageAsync(info.packageName); |
| 151 | } |
| 152 | } |
| 153 | |
| 154 | private void waitForFirstLoad() { |
| 155 | boolean success = false; |
| 156 | try { |
| 157 | success = mFirstLoad.await(15, TimeUnit.SECONDS); |
| 158 | } catch (InterruptedException e) { |
| 159 | } |
| 160 | if (!success) { |
| 161 | Log.w(TAG, "Timeout waiting for first update"); |
| 162 | } |
| 163 | } |
| 164 | |
| 165 | /** |
| 166 | * Load roots from authorities that are in stopped state. Normal |
| 167 | * {@link UpdateTask} passes ignore stopped applications. |
| 168 | */ |
| 169 | private void loadStoppedAuthorities() { |
| 170 | final ContentResolver resolver = mContext.getContentResolver(); |
| 171 | synchronized (mLock) { |
| 172 | for (String authority : mStoppedAuthorities) { |
Steve McKay | 459bc2b | 2015-09-16 15:07:31 -0700 | [diff] [blame] | 173 | if (DEBUG) Log.d(TAG, "Loading stopped authority " + authority); |
Jeff Sharkey | 8b99704 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 174 | mRoots.putAll(authority, loadRootsForAuthority(resolver, authority)); |
| 175 | } |
| 176 | mStoppedAuthorities.clear(); |
| 177 | } |
| 178 | } |
| 179 | |
Tomasz Mikolajewski | 5a1e879 | 2016-01-27 17:36:51 +0900 | [diff] [blame] | 180 | /** |
| 181 | * Load roots from a stopped authority. Normal {@link UpdateTask} passes |
| 182 | * ignore stopped applications. |
| 183 | */ |
| 184 | private void loadStoppedAuthority(String authority) { |
| 185 | final ContentResolver resolver = mContext.getContentResolver(); |
| 186 | synchronized (mLock) { |
Tomasz Mikolajewski | 9daa59f | 2016-02-03 15:18:22 +0900 | [diff] [blame] | 187 | if (!mStoppedAuthorities.contains(authority)) { |
| 188 | return; |
| 189 | } |
Tomasz Mikolajewski | 5a1e879 | 2016-01-27 17:36:51 +0900 | [diff] [blame] | 190 | if (DEBUG) { |
| 191 | Log.d(TAG, "Loading stopped authority " + authority); |
| 192 | } |
| 193 | mRoots.putAll(authority, loadRootsForAuthority(resolver, authority)); |
| 194 | mStoppedAuthorities.remove(authority); |
| 195 | } |
| 196 | } |
| 197 | |
Jeff Sharkey | 8b99704 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 198 | private class UpdateTask extends AsyncTask<Void, Void, Void> { |
| 199 | private final String mFilterPackage; |
| 200 | |
Jeff Sharkey | 85c273d | 2013-10-07 10:16:12 -0700 | [diff] [blame] | 201 | private final Multimap<String, RootInfo> mTaskRoots = ArrayListMultimap.create(); |
Steve McKay | fefcd70 | 2015-08-20 16:19:38 +0000 | [diff] [blame] | 202 | private final HashSet<String> mTaskStoppedAuthorities = new HashSet<>(); |
Jeff Sharkey | 85c273d | 2013-10-07 10:16:12 -0700 | [diff] [blame] | 203 | |
Jeff Sharkey | 8b99704 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 204 | /** |
| 205 | * Update all roots. |
| 206 | */ |
| 207 | public UpdateTask() { |
| 208 | this(null); |
Jeff Sharkey | 7611221 | 2013-08-06 11:26:10 -0700 | [diff] [blame] | 209 | } |
| 210 | |
Jeff Sharkey | 8b99704 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 211 | /** |
| 212 | * Only update roots belonging to given package name. Other roots will |
| 213 | * be copied from cached {@link #mRoots} values. |
| 214 | */ |
| 215 | public UpdateTask(String filterPackage) { |
| 216 | mFilterPackage = filterPackage; |
| 217 | } |
Jeff Sharkey | 7611221 | 2013-08-06 11:26:10 -0700 | [diff] [blame] | 218 | |
Jeff Sharkey | 8b99704 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 219 | @Override |
| 220 | protected Void doInBackground(Void... params) { |
| 221 | final long start = SystemClock.elapsedRealtime(); |
| 222 | |
Jeff Sharkey | ca494a7 | 2014-09-10 11:23:15 -0700 | [diff] [blame] | 223 | if (mFilterPackage != null) { |
| 224 | // Need at least first load, since we're going to be using |
| 225 | // previously cached values for non-matching packages. |
| 226 | waitForFirstLoad(); |
| 227 | } |
| 228 | |
Jeff Sharkey | 85c273d | 2013-10-07 10:16:12 -0700 | [diff] [blame] | 229 | mTaskRoots.put(mRecentsRoot.authority, mRecentsRoot); |
Jeff Sharkey | 4045780 | 2013-09-21 13:57:33 -0700 | [diff] [blame] | 230 | |
Jeff Sharkey | 8b99704 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 231 | final ContentResolver resolver = mContext.getContentResolver(); |
| 232 | final PackageManager pm = mContext.getPackageManager(); |
Jeff Sharkey | 85c273d | 2013-10-07 10:16:12 -0700 | [diff] [blame] | 233 | |
| 234 | // Pick up provider with action string |
| 235 | final Intent intent = new Intent(DocumentsContract.PROVIDER_INTERFACE); |
| 236 | final List<ResolveInfo> providers = pm.queryIntentContentProviders(intent, 0); |
| 237 | for (ResolveInfo info : providers) { |
| 238 | handleDocumentsProvider(info.providerInfo); |
| 239 | } |
| 240 | |
Jeff Sharkey | 8b99704 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 241 | final long delta = SystemClock.elapsedRealtime() - start; |
Steve McKay | 459bc2b | 2015-09-16 15:07:31 -0700 | [diff] [blame] | 242 | if (DEBUG) |
| 243 | Log.d(TAG, "Update found " + mTaskRoots.size() + " roots in " + delta + "ms"); |
Jeff Sharkey | 8b99704 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 244 | synchronized (mLock) { |
Jeff Sharkey | 85c273d | 2013-10-07 10:16:12 -0700 | [diff] [blame] | 245 | mRoots = mTaskRoots; |
| 246 | mStoppedAuthorities = mTaskStoppedAuthorities; |
Jeff Sharkey | 8b99704 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 247 | } |
| 248 | mFirstLoad.countDown(); |
Jeff Sharkey | 5916877 | 2013-10-23 09:59:06 -0700 | [diff] [blame] | 249 | resolver.notifyChange(sNotificationUri, null, false); |
Jeff Sharkey | 8b99704 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 250 | return null; |
| 251 | } |
Jeff Sharkey | 85c273d | 2013-10-07 10:16:12 -0700 | [diff] [blame] | 252 | |
Daichi Hirono | 3067d0d | 2015-12-25 11:08:42 +0900 | [diff] [blame] | 253 | @Override |
| 254 | protected void onPostExecute(Void result) { |
| 255 | if (mCacheUpdateListener != null) { |
| 256 | mCacheUpdateListener.onCacheUpdate(); |
| 257 | } |
| 258 | } |
| 259 | |
Jeff Sharkey | 85c273d | 2013-10-07 10:16:12 -0700 | [diff] [blame] | 260 | private void handleDocumentsProvider(ProviderInfo info) { |
| 261 | // Ignore stopped packages for now; we might query them |
| 262 | // later during UI interaction. |
| 263 | if ((info.applicationInfo.flags & ApplicationInfo.FLAG_STOPPED) != 0) { |
Steve McKay | 459bc2b | 2015-09-16 15:07:31 -0700 | [diff] [blame] | 264 | if (DEBUG) Log.d(TAG, "Ignoring stopped authority " + info.authority); |
Jeff Sharkey | 85c273d | 2013-10-07 10:16:12 -0700 | [diff] [blame] | 265 | mTaskStoppedAuthorities.add(info.authority); |
| 266 | return; |
| 267 | } |
| 268 | |
| 269 | // Try using cached roots if filtering |
| 270 | boolean cacheHit = false; |
| 271 | if (mFilterPackage != null && !mFilterPackage.equals(info.packageName)) { |
| 272 | synchronized (mLock) { |
| 273 | if (mTaskRoots.putAll(info.authority, mRoots.get(info.authority))) { |
Steve McKay | 459bc2b | 2015-09-16 15:07:31 -0700 | [diff] [blame] | 274 | if (DEBUG) Log.d(TAG, "Used cached roots for " + info.authority); |
Jeff Sharkey | 85c273d | 2013-10-07 10:16:12 -0700 | [diff] [blame] | 275 | cacheHit = true; |
| 276 | } |
| 277 | } |
| 278 | } |
| 279 | |
| 280 | // Cache miss, or loading everything |
| 281 | if (!cacheHit) { |
| 282 | mTaskRoots.putAll(info.authority, |
| 283 | loadRootsForAuthority(mContext.getContentResolver(), info.authority)); |
| 284 | } |
| 285 | } |
Jeff Sharkey | 7611221 | 2013-08-06 11:26:10 -0700 | [diff] [blame] | 286 | } |
| 287 | |
Jeff Sharkey | 8b99704 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 288 | /** |
| 289 | * Bring up requested provider and query for all active roots. |
| 290 | */ |
| 291 | private Collection<RootInfo> loadRootsForAuthority(ContentResolver resolver, String authority) { |
Steve McKay | 459bc2b | 2015-09-16 15:07:31 -0700 | [diff] [blame] | 292 | if (DEBUG) Log.d(TAG, "Loading roots for " + authority); |
Jeff Sharkey | 8b99704 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 293 | |
| 294 | synchronized (mObservedAuthorities) { |
| 295 | if (mObservedAuthorities.add(authority)) { |
| 296 | // Watch for any future updates |
| 297 | final Uri rootsUri = DocumentsContract.buildRootsUri(authority); |
| 298 | mContext.getContentResolver().registerContentObserver(rootsUri, true, mObserver); |
| 299 | } |
| 300 | } |
| 301 | |
Steve McKay | fefcd70 | 2015-08-20 16:19:38 +0000 | [diff] [blame] | 302 | final List<RootInfo> roots = new ArrayList<>(); |
Jeff Sharkey | 8b99704 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 303 | final Uri rootsUri = DocumentsContract.buildRootsUri(authority); |
Jeff Sharkey | 3fd1177 | 2013-09-30 14:26:27 -0700 | [diff] [blame] | 304 | |
| 305 | ContentProviderClient client = null; |
Jeff Sharkey | 8b99704 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 306 | Cursor cursor = null; |
| 307 | try { |
Jeff Sharkey | 3fd1177 | 2013-09-30 14:26:27 -0700 | [diff] [blame] | 308 | client = DocumentsApplication.acquireUnstableProviderOrThrow(resolver, authority); |
Jeff Sharkey | 8b99704 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 309 | cursor = client.query(rootsUri, null, null, null, null); |
| 310 | while (cursor.moveToNext()) { |
| 311 | final RootInfo root = RootInfo.fromRootsCursor(authority, cursor); |
| 312 | roots.add(root); |
| 313 | } |
| 314 | } catch (Exception e) { |
| 315 | Log.w(TAG, "Failed to load some roots from " + authority + ": " + e); |
| 316 | } finally { |
| 317 | IoUtils.closeQuietly(cursor); |
Jeff Sharkey | 3fd1177 | 2013-09-30 14:26:27 -0700 | [diff] [blame] | 318 | ContentProviderClient.releaseQuietly(client); |
Jeff Sharkey | 8b99704 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 319 | } |
| 320 | return roots; |
| 321 | } |
| 322 | |
| 323 | /** |
| 324 | * Return the requested {@link RootInfo}, but only loading the roots for the |
| 325 | * requested authority. This is useful when we want to load fast without |
| 326 | * waiting for all the other roots to come back. |
| 327 | */ |
| 328 | public RootInfo getRootOneshot(String authority, String rootId) { |
| 329 | synchronized (mLock) { |
| 330 | RootInfo root = getRootLocked(authority, rootId); |
| 331 | if (root == null) { |
| 332 | mRoots.putAll( |
| 333 | authority, loadRootsForAuthority(mContext.getContentResolver(), authority)); |
| 334 | root = getRootLocked(authority, rootId); |
| 335 | } |
| 336 | return root; |
| 337 | } |
| 338 | } |
| 339 | |
| 340 | public RootInfo getRootBlocking(String authority, String rootId) { |
| 341 | waitForFirstLoad(); |
| 342 | loadStoppedAuthorities(); |
| 343 | synchronized (mLock) { |
| 344 | return getRootLocked(authority, rootId); |
| 345 | } |
| 346 | } |
| 347 | |
| 348 | private RootInfo getRootLocked(String authority, String rootId) { |
| 349 | for (RootInfo root : mRoots.get(authority)) { |
Kenny Root | 16d8598 | 2013-12-13 12:00:26 -0800 | [diff] [blame] | 350 | if (Objects.equals(root.rootId, rootId)) { |
Jeff Sharkey | b6a7f2c | 2013-08-27 18:26:48 -0700 | [diff] [blame] | 351 | return root; |
| 352 | } |
| 353 | } |
| 354 | return null; |
Jeff Sharkey | 7611221 | 2013-08-06 11:26:10 -0700 | [diff] [blame] | 355 | } |
| 356 | |
Jeff Sharkey | 8b99704 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 357 | public boolean isIconUniqueBlocking(RootInfo root) { |
| 358 | waitForFirstLoad(); |
| 359 | loadStoppedAuthorities(); |
| 360 | synchronized (mLock) { |
| 361 | final int rootIcon = root.derivedIcon != 0 ? root.derivedIcon : root.icon; |
| 362 | for (RootInfo test : mRoots.get(root.authority)) { |
Kenny Root | 16d8598 | 2013-12-13 12:00:26 -0800 | [diff] [blame] | 363 | if (Objects.equals(test.rootId, root.rootId)) { |
Jeff Sharkey | a35ac2d | 2013-09-10 12:04:26 -0700 | [diff] [blame] | 364 | continue; |
| 365 | } |
Jeff Sharkey | 9656a53 | 2013-09-13 13:42:19 -0700 | [diff] [blame] | 366 | final int testIcon = test.derivedIcon != 0 ? test.derivedIcon : test.icon; |
| 367 | if (testIcon == rootIcon) { |
Jeff Sharkey | a35ac2d | 2013-09-10 12:04:26 -0700 | [diff] [blame] | 368 | return false; |
| 369 | } |
| 370 | } |
Jeff Sharkey | 8b99704 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 371 | return true; |
Jeff Sharkey | a35ac2d | 2013-09-10 12:04:26 -0700 | [diff] [blame] | 372 | } |
Jeff Sharkey | a35ac2d | 2013-09-10 12:04:26 -0700 | [diff] [blame] | 373 | } |
| 374 | |
Jeff Sharkey | 724deeb | 2013-08-31 15:02:20 -0700 | [diff] [blame] | 375 | public RootInfo getRecentsRoot() { |
Jeff Sharkey | 873daa3 | 2013-08-18 17:38:20 -0700 | [diff] [blame] | 376 | return mRecentsRoot; |
Jeff Sharkey | 2e694f8 | 2013-08-06 16:26:14 -0700 | [diff] [blame] | 377 | } |
| 378 | |
Jeff Sharkey | 724deeb | 2013-08-31 15:02:20 -0700 | [diff] [blame] | 379 | public boolean isRecentsRoot(RootInfo root) { |
Steve McKay | 323dffb | 2016-02-17 18:25:47 -0800 | [diff] [blame] | 380 | return mRecentsRoot.equals(root); |
Jeff Sharkey | 7611221 | 2013-08-06 11:26:10 -0700 | [diff] [blame] | 381 | } |
| 382 | |
Jeff Sharkey | 8b99704 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 383 | public Collection<RootInfo> getRootsBlocking() { |
| 384 | waitForFirstLoad(); |
| 385 | loadStoppedAuthorities(); |
| 386 | synchronized (mLock) { |
| 387 | return mRoots.values(); |
| 388 | } |
Jeff Sharkey | b6a7f2c | 2013-08-27 18:26:48 -0700 | [diff] [blame] | 389 | } |
Jeff Sharkey | 7611221 | 2013-08-06 11:26:10 -0700 | [diff] [blame] | 390 | |
Jeff Sharkey | 8b99704 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 391 | public Collection<RootInfo> getMatchingRootsBlocking(State state) { |
| 392 | waitForFirstLoad(); |
| 393 | loadStoppedAuthorities(); |
| 394 | synchronized (mLock) { |
| 395 | return getMatchingRoots(mRoots.values(), state); |
| 396 | } |
Jeff Sharkey | 06c4187 | 2013-09-06 10:43:45 -0700 | [diff] [blame] | 397 | } |
| 398 | |
Tomasz Mikolajewski | 5a1e879 | 2016-01-27 17:36:51 +0900 | [diff] [blame] | 399 | /** |
| 400 | * Returns a list of roots for the specified authority. If not found, then |
| 401 | * an empty list is returned. |
| 402 | */ |
| 403 | public Collection<RootInfo> getRootsForAuthorityBlocking(String authority) { |
| 404 | waitForFirstLoad(); |
| 405 | loadStoppedAuthority(authority); |
| 406 | synchronized (mLock) { |
| 407 | final Collection<RootInfo> roots = mRoots.get(authority); |
| 408 | return roots != null ? roots : Collections.<RootInfo>emptyList(); |
| 409 | } |
| 410 | } |
| 411 | |
Daichi Hirono | 3067d0d | 2015-12-25 11:08:42 +0900 | [diff] [blame] | 412 | public void setOnCacheUpdateListener(OnCacheUpdateListener cacheUpdateListener) { |
| 413 | mCacheUpdateListener = cacheUpdateListener; |
| 414 | } |
| 415 | |
Jeff Sharkey | 8b99704 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 416 | @VisibleForTesting |
| 417 | static List<RootInfo> getMatchingRoots(Collection<RootInfo> roots, State state) { |
Steve McKay | fefcd70 | 2015-08-20 16:19:38 +0000 | [diff] [blame] | 418 | final List<RootInfo> matching = new ArrayList<>(); |
Jeff Sharkey | 06c4187 | 2013-09-06 10:43:45 -0700 | [diff] [blame] | 419 | for (RootInfo root : roots) { |
Jeff Sharkey | 1c903cc | 2013-09-02 17:19:40 -0700 | [diff] [blame] | 420 | // Exclude read-only devices when creating |
Steve McKay | 323dffb | 2016-02-17 18:25:47 -0800 | [diff] [blame] | 421 | if (state.action == State.ACTION_CREATE && !root.supportsCreate()) continue; |
| 422 | if (state.action == State.ACTION_PICK_COPY_DESTINATION |
| 423 | && !root.supportsCreate()) continue; |
Jeff Sharkey | 6e565ff | 2014-04-05 19:05:24 -0700 | [diff] [blame] | 424 | // Exclude roots that don't support directory picking |
Steve McKay | 323dffb | 2016-02-17 18:25:47 -0800 | [diff] [blame] | 425 | if (state.action == State.ACTION_OPEN_TREE && !root.supportsChildren()) continue; |
Jeff Sharkey | 1c903cc | 2013-09-02 17:19:40 -0700 | [diff] [blame] | 426 | // Exclude advanced devices when not requested |
Steve McKay | 323dffb | 2016-02-17 18:25:47 -0800 | [diff] [blame] | 427 | if (!state.showAdvanced && root.isAdvanced()) continue; |
Jeff Sharkey | 1c903cc | 2013-09-02 17:19:40 -0700 | [diff] [blame] | 428 | // Exclude non-local devices when local only |
Steve McKay | 323dffb | 2016-02-17 18:25:47 -0800 | [diff] [blame] | 429 | if (state.localOnly && !root.isLocalOnly()) continue; |
Steve McKay | 55c00e7 | 2016-02-18 15:32:16 -0800 | [diff] [blame] | 430 | // Exclude downloads roots as it doesn't support directory creation (actually |
| 431 | // we just don't show them). |
| 432 | // TODO: Add flag to check the root supports directory creation. |
| 433 | if (state.directoryCopy && !root.isDownloads()) continue; |
Steve McKay | 459bc2b | 2015-09-16 15:07:31 -0700 | [diff] [blame] | 434 | |
| 435 | // Only show empty roots when creating, or in browse mode. |
Steve McKay | 323dffb | 2016-02-17 18:25:47 -0800 | [diff] [blame] | 436 | if (root.isEmpty() && (state.action == State.ACTION_OPEN |
Steve McKay | 7e8885b | 2015-09-23 16:16:32 -0700 | [diff] [blame] | 437 | || state.action == State.ACTION_GET_CONTENT)) { |
Steve McKay | 459bc2b | 2015-09-16 15:07:31 -0700 | [diff] [blame] | 438 | if (DEBUG) Log.i(TAG, "Skipping empty root: " + root); |
| 439 | continue; |
| 440 | } |
Jeff Sharkey | 1c903cc | 2013-09-02 17:19:40 -0700 | [diff] [blame] | 441 | |
Jeff Sharkey | 62434fc | 2013-09-05 13:55:35 -0700 | [diff] [blame] | 442 | // Only include roots that serve requested content |
Jeff Sharkey | a4d1f22 | 2013-09-07 14:45:03 -0700 | [diff] [blame] | 443 | final boolean overlap = |
| 444 | MimePredicate.mimeMatches(root.derivedMimeTypes, state.acceptMimes) || |
| 445 | MimePredicate.mimeMatches(state.acceptMimes, root.derivedMimeTypes); |
Jeff Sharkey | 06c4187 | 2013-09-06 10:43:45 -0700 | [diff] [blame] | 446 | if (!overlap) { |
Jeff Sharkey | 62434fc | 2013-09-05 13:55:35 -0700 | [diff] [blame] | 447 | continue; |
Jeff Sharkey | 1c903cc | 2013-09-02 17:19:40 -0700 | [diff] [blame] | 448 | } |
| 449 | |
Ben Kwa | 0bcdec3 | 2015-05-29 15:40:31 -0700 | [diff] [blame] | 450 | // Exclude roots from the calling package. |
| 451 | if (state.excludedAuthorities.contains(root.authority)) { |
Steve McKay | 323dffb | 2016-02-17 18:25:47 -0800 | [diff] [blame] | 452 | if (DEBUG) Log.d( |
| 453 | TAG, "Excluding root " + root.authority + " from calling package."); |
Ben Kwa | 0bcdec3 | 2015-05-29 15:40:31 -0700 | [diff] [blame] | 454 | continue; |
| 455 | } |
| 456 | |
Steve McKay | 323dffb | 2016-02-17 18:25:47 -0800 | [diff] [blame] | 457 | if (DEBUG) Log.d( |
| 458 | TAG, "Including root " + root + " in roots list."); |
Jeff Sharkey | 1c903cc | 2013-09-02 17:19:40 -0700 | [diff] [blame] | 459 | matching.add(root); |
| 460 | } |
| 461 | return matching; |
| 462 | } |
Jeff Sharkey | 7611221 | 2013-08-06 11:26:10 -0700 | [diff] [blame] | 463 | } |