Jeff Sharkey | 6651669 | 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 | 83df8c0 | 2015-09-16 15:07:31 -0700 | [diff] [blame] | 19 | import static com.android.documentsui.Shared.DEBUG; |
Jeff Sharkey | 6651669 | 2013-08-06 11:26:10 -0700 | [diff] [blame] | 20 | |
Jeff Sharkey | 8731408 | 2016-03-11 17:25:11 -0700 | [diff] [blame] | 21 | import android.content.BroadcastReceiver.PendingResult; |
Jeff Sharkey | aeb16e2 | 2013-08-27 18:26:48 -0700 | [diff] [blame] | 22 | import android.content.ContentProviderClient; |
| 23 | import android.content.ContentResolver; |
Jeff Sharkey | 6651669 | 2013-08-06 11:26:10 -0700 | [diff] [blame] | 24 | import android.content.Context; |
Jeff Sharkey | 85f5f81 | 2013-10-07 10:16:12 -0700 | [diff] [blame] | 25 | import android.content.Intent; |
Jeff Sharkey | a9ce049 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 26 | import android.content.pm.ApplicationInfo; |
Jeff Sharkey | 6651669 | 2013-08-06 11:26:10 -0700 | [diff] [blame] | 27 | import android.content.pm.PackageManager; |
| 28 | import android.content.pm.ProviderInfo; |
Jeff Sharkey | 85f5f81 | 2013-10-07 10:16:12 -0700 | [diff] [blame] | 29 | import android.content.pm.ResolveInfo; |
Jeff Sharkey | a9ce049 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 30 | import android.database.ContentObserver; |
Jeff Sharkey | ae9b51b | 2013-08-31 15:02:20 -0700 | [diff] [blame] | 31 | import android.database.Cursor; |
Jeff Sharkey | 6651669 | 2013-08-06 11:26:10 -0700 | [diff] [blame] | 32 | import android.net.Uri; |
Jeff Sharkey | a9ce049 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 33 | import android.os.AsyncTask; |
Jeff Sharkey | 8731408 | 2016-03-11 17:25:11 -0700 | [diff] [blame] | 34 | import android.os.Bundle; |
Jeff Sharkey | a9ce049 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 35 | import android.os.Handler; |
| 36 | import android.os.SystemClock; |
Jeff Sharkey | 6651669 | 2013-08-06 11:26:10 -0700 | [diff] [blame] | 37 | import android.provider.DocumentsContract; |
Jeff Sharkey | ae9b51b | 2013-08-31 15:02:20 -0700 | [diff] [blame] | 38 | import android.provider.DocumentsContract.Root; |
Steve McKay | 7a3b88c | 2015-09-23 17:21:40 -0700 | [diff] [blame] | 39 | import android.support.annotation.VisibleForTesting; |
Jeff Sharkey | 6651669 | 2013-08-06 11:26:10 -0700 | [diff] [blame] | 40 | import android.util.Log; |
Jeff Sharkey | 6651669 | 2013-08-06 11:26:10 -0700 | [diff] [blame] | 41 | |
Jeff Sharkey | ae9b51b | 2013-08-31 15:02:20 -0700 | [diff] [blame] | 42 | import com.android.documentsui.model.RootInfo; |
Jeff Sharkey | 6651669 | 2013-08-06 11:26:10 -0700 | [diff] [blame] | 43 | import com.android.internal.annotations.GuardedBy; |
Steve McKay | 58efce3 | 2015-08-20 16:19:38 +0000 | [diff] [blame] | 44 | |
Jeff Sharkey | 8731408 | 2016-03-11 17:25:11 -0700 | [diff] [blame] | 45 | import libcore.io.IoUtils; |
| 46 | |
Jeff Sharkey | a9ce049 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 47 | import com.google.common.collect.ArrayListMultimap; |
| 48 | import com.google.common.collect.Multimap; |
Jeff Sharkey | 6651669 | 2013-08-06 11:26:10 -0700 | [diff] [blame] | 49 | |
Steve McKay | 58efce3 | 2015-08-20 16:19:38 +0000 | [diff] [blame] | 50 | import java.util.ArrayList; |
Jeff Sharkey | a9ce049 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 51 | import java.util.Collection; |
Tomasz Mikolajewski | a6120da | 2016-01-27 17:36:51 +0900 | [diff] [blame] | 52 | import java.util.Collections; |
Jeff Sharkey | a9ce049 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 53 | import java.util.HashSet; |
Jeff Sharkey | 6651669 | 2013-08-06 11:26:10 -0700 | [diff] [blame] | 54 | import java.util.List; |
Kenny Root | e6585b3 | 2013-12-13 12:00:26 -0800 | [diff] [blame] | 55 | import java.util.Objects; |
Jeff Sharkey | a9ce049 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 56 | import java.util.concurrent.CountDownLatch; |
| 57 | import java.util.concurrent.TimeUnit; |
Jeff Sharkey | 6651669 | 2013-08-06 11:26:10 -0700 | [diff] [blame] | 58 | |
| 59 | /** |
| 60 | * Cache of known storage backends and their roots. |
| 61 | */ |
| 62 | public class RootsCache { |
Jeff Sharkey | 46de7b5 | 2013-10-23 09:59:06 -0700 | [diff] [blame] | 63 | public static final Uri sNotificationUri = Uri.parse( |
| 64 | "content://com.android.documentsui.roots/"); |
Jeff Sharkey | 6651669 | 2013-08-06 11:26:10 -0700 | [diff] [blame] | 65 | |
Steve McKay | ea9ec29 | 2016-03-01 15:11:56 -0800 | [diff] [blame] | 66 | private static final String TAG = "RootsCache"; |
| 67 | |
Jeff Sharkey | 8731408 | 2016-03-11 17:25:11 -0700 | [diff] [blame] | 68 | private static final boolean ENABLE_SYSTEM_CACHE = true; |
| 69 | |
Jeff Sharkey | 4eb407a | 2013-08-18 17:38:20 -0700 | [diff] [blame] | 70 | private final Context mContext; |
Jeff Sharkey | a9ce049 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 71 | private final ContentObserver mObserver; |
Daichi Hirono | 60e9a07 | 2015-12-25 11:08:42 +0900 | [diff] [blame] | 72 | private OnCacheUpdateListener mCacheUpdateListener; |
Jeff Sharkey | 6651669 | 2013-08-06 11:26:10 -0700 | [diff] [blame] | 73 | |
Steve McKay | 4a1ca86 | 2016-02-17 18:25:47 -0800 | [diff] [blame] | 74 | private final RootInfo mRecentsRoot; |
Jeff Sharkey | 6651669 | 2013-08-06 11:26:10 -0700 | [diff] [blame] | 75 | |
Jeff Sharkey | a9ce049 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 76 | private final Object mLock = new Object(); |
| 77 | private final CountDownLatch mFirstLoad = new CountDownLatch(1); |
| 78 | |
| 79 | @GuardedBy("mLock") |
Jeff Sharkey | 8731408 | 2016-03-11 17:25:11 -0700 | [diff] [blame] | 80 | private boolean mFirstLoadDone; |
| 81 | @GuardedBy("mLock") |
| 82 | private PendingResult mBootCompletedResult; |
| 83 | |
| 84 | @GuardedBy("mLock") |
Jeff Sharkey | a9ce049 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 85 | private Multimap<String, RootInfo> mRoots = ArrayListMultimap.create(); |
| 86 | @GuardedBy("mLock") |
Steve McKay | 58efce3 | 2015-08-20 16:19:38 +0000 | [diff] [blame] | 87 | private HashSet<String> mStoppedAuthorities = new HashSet<>(); |
Jeff Sharkey | a9ce049 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 88 | |
| 89 | @GuardedBy("mObservedAuthorities") |
Steve McKay | 58efce3 | 2015-08-20 16:19:38 +0000 | [diff] [blame] | 90 | private final HashSet<String> mObservedAuthorities = new HashSet<>(); |
Jeff Sharkey | 4eb407a | 2013-08-18 17:38:20 -0700 | [diff] [blame] | 91 | |
| 92 | public RootsCache(Context context) { |
| 93 | mContext = context; |
Jeff Sharkey | a9ce049 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 94 | mObserver = new RootsChangedObserver(); |
Steve McKay | 4a1ca86 | 2016-02-17 18:25:47 -0800 | [diff] [blame] | 95 | |
| 96 | // Create a new anonymous "Recents" RootInfo. It's a faker. |
| 97 | mRecentsRoot = new RootInfo() {{ |
Steve McKay | 008e948 | 2016-02-18 15:32:16 -0800 | [diff] [blame] | 98 | // Special root for recents |
| 99 | derivedIcon = R.drawable.ic_root_recent; |
| 100 | derivedType = RootInfo.TYPE_RECENTS; |
Steve McKay | 327c313 | 2016-02-26 15:30:19 -0800 | [diff] [blame] | 101 | flags = Root.FLAG_LOCAL_ONLY | Root.FLAG_SUPPORTS_IS_CHILD |
| 102 | | Root.FLAG_SUPPORTS_CREATE; |
Steve McKay | 008e948 | 2016-02-18 15:32:16 -0800 | [diff] [blame] | 103 | title = mContext.getString(R.string.root_recent); |
| 104 | availableBytes = -1; |
| 105 | }}; |
Jeff Sharkey | a9ce049 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 106 | } |
| 107 | |
| 108 | private class RootsChangedObserver extends ContentObserver { |
| 109 | public RootsChangedObserver() { |
| 110 | super(new Handler()); |
| 111 | } |
| 112 | |
| 113 | @Override |
| 114 | public void onChange(boolean selfChange, Uri uri) { |
Steve McKay | 83df8c0 | 2015-09-16 15:07:31 -0700 | [diff] [blame] | 115 | if (DEBUG) Log.d(TAG, "Updating roots due to change at " + uri); |
Jeff Sharkey | a9ce049 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 116 | updateAuthorityAsync(uri.getAuthority()); |
| 117 | } |
Jeff Sharkey | 4eb407a | 2013-08-18 17:38:20 -0700 | [diff] [blame] | 118 | } |
Jeff Sharkey | 6651669 | 2013-08-06 11:26:10 -0700 | [diff] [blame] | 119 | |
Daichi Hirono | 60e9a07 | 2015-12-25 11:08:42 +0900 | [diff] [blame] | 120 | static interface OnCacheUpdateListener { |
| 121 | void onCacheUpdate(); |
| 122 | } |
| 123 | |
Jeff Sharkey | 6651669 | 2013-08-06 11:26:10 -0700 | [diff] [blame] | 124 | /** |
| 125 | * Gather roots from all known storage providers. |
| 126 | */ |
Jeff Sharkey | a9ce049 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 127 | public void updateAsync() { |
Steve McKay | 355d82b | 2016-03-01 12:57:44 -0800 | [diff] [blame] | 128 | |
| 129 | // NOTE: This method is called when the UI language changes. |
Jeff Sharkey | 8731408 | 2016-03-11 17:25:11 -0700 | [diff] [blame] | 130 | // For that reason we update our RecentsRoot to reflect |
Steve McKay | 355d82b | 2016-03-01 12:57:44 -0800 | [diff] [blame] | 131 | // the current language. |
| 132 | mRecentsRoot.title = mContext.getString(R.string.root_recent); |
| 133 | |
| 134 | // Nothing else about the root should ever change. |
Steve McKay | a1f7680 | 2016-02-25 13:34:03 -0800 | [diff] [blame] | 135 | assert(mRecentsRoot.authority == null); |
| 136 | assert(mRecentsRoot.rootId == null); |
| 137 | assert(mRecentsRoot.derivedIcon == R.drawable.ic_root_recent); |
| 138 | assert(mRecentsRoot.derivedType == RootInfo.TYPE_RECENTS); |
| 139 | assert(mRecentsRoot.flags == (Root.FLAG_LOCAL_ONLY |
| 140 | | Root.FLAG_SUPPORTS_IS_CHILD |
| 141 | | Root.FLAG_SUPPORTS_CREATE)); |
Steve McKay | a1f7680 | 2016-02-25 13:34:03 -0800 | [diff] [blame] | 142 | assert(mRecentsRoot.availableBytes == -1); |
| 143 | |
Jeff Sharkey | a9ce049 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 144 | new UpdateTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); |
| 145 | } |
Jeff Sharkey | aeb16e2 | 2013-08-27 18:26:48 -0700 | [diff] [blame] | 146 | |
Jeff Sharkey | a9ce049 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 147 | /** |
| 148 | * Gather roots from storage providers belonging to given package name. |
| 149 | */ |
| 150 | public void updatePackageAsync(String packageName) { |
Jeff Sharkey | a9ce049 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 151 | new UpdateTask(packageName).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); |
| 152 | } |
| 153 | |
| 154 | /** |
| 155 | * Gather roots from storage providers belonging to given authority. |
| 156 | */ |
| 157 | public void updateAuthorityAsync(String authority) { |
| 158 | final ProviderInfo info = mContext.getPackageManager().resolveContentProvider(authority, 0); |
| 159 | if (info != null) { |
| 160 | updatePackageAsync(info.packageName); |
| 161 | } |
| 162 | } |
| 163 | |
Jeff Sharkey | 8731408 | 2016-03-11 17:25:11 -0700 | [diff] [blame] | 164 | public void setBootCompletedResult(PendingResult result) { |
| 165 | synchronized (mLock) { |
| 166 | // Quickly check if we've already finished loading, otherwise hang |
| 167 | // out until first pass is finished. |
| 168 | if (mFirstLoadDone) { |
| 169 | result.finish(); |
| 170 | } else { |
| 171 | mBootCompletedResult = result; |
| 172 | } |
| 173 | } |
| 174 | } |
| 175 | |
| 176 | /** |
| 177 | * Block until the first {@link UpdateTask} pass has finished. |
| 178 | * |
| 179 | * @return {@code true} if cached roots is ready to roll, otherwise |
| 180 | * {@code false} if we timed out while waiting. |
| 181 | */ |
| 182 | private boolean waitForFirstLoad() { |
Jeff Sharkey | a9ce049 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 183 | boolean success = false; |
| 184 | try { |
| 185 | success = mFirstLoad.await(15, TimeUnit.SECONDS); |
| 186 | } catch (InterruptedException e) { |
| 187 | } |
| 188 | if (!success) { |
| 189 | Log.w(TAG, "Timeout waiting for first update"); |
| 190 | } |
Jeff Sharkey | 8731408 | 2016-03-11 17:25:11 -0700 | [diff] [blame] | 191 | return success; |
Jeff Sharkey | a9ce049 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 192 | } |
| 193 | |
| 194 | /** |
| 195 | * Load roots from authorities that are in stopped state. Normal |
| 196 | * {@link UpdateTask} passes ignore stopped applications. |
| 197 | */ |
| 198 | private void loadStoppedAuthorities() { |
| 199 | final ContentResolver resolver = mContext.getContentResolver(); |
| 200 | synchronized (mLock) { |
| 201 | for (String authority : mStoppedAuthorities) { |
Steve McKay | 83df8c0 | 2015-09-16 15:07:31 -0700 | [diff] [blame] | 202 | if (DEBUG) Log.d(TAG, "Loading stopped authority " + authority); |
Jeff Sharkey | a9ce049 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 203 | mRoots.putAll(authority, loadRootsForAuthority(resolver, authority)); |
| 204 | } |
| 205 | mStoppedAuthorities.clear(); |
| 206 | } |
| 207 | } |
| 208 | |
Tomasz Mikolajewski | a6120da | 2016-01-27 17:36:51 +0900 | [diff] [blame] | 209 | /** |
| 210 | * Load roots from a stopped authority. Normal {@link UpdateTask} passes |
| 211 | * ignore stopped applications. |
| 212 | */ |
| 213 | private void loadStoppedAuthority(String authority) { |
| 214 | final ContentResolver resolver = mContext.getContentResolver(); |
| 215 | synchronized (mLock) { |
Tomasz Mikolajewski | df676dc | 2016-02-03 15:18:22 +0900 | [diff] [blame] | 216 | if (!mStoppedAuthorities.contains(authority)) { |
| 217 | return; |
| 218 | } |
Tomasz Mikolajewski | a6120da | 2016-01-27 17:36:51 +0900 | [diff] [blame] | 219 | if (DEBUG) { |
| 220 | Log.d(TAG, "Loading stopped authority " + authority); |
| 221 | } |
| 222 | mRoots.putAll(authority, loadRootsForAuthority(resolver, authority)); |
| 223 | mStoppedAuthorities.remove(authority); |
| 224 | } |
| 225 | } |
| 226 | |
Jeff Sharkey | a9ce049 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 227 | private class UpdateTask extends AsyncTask<Void, Void, Void> { |
| 228 | private final String mFilterPackage; |
| 229 | |
Jeff Sharkey | 85f5f81 | 2013-10-07 10:16:12 -0700 | [diff] [blame] | 230 | private final Multimap<String, RootInfo> mTaskRoots = ArrayListMultimap.create(); |
Steve McKay | 58efce3 | 2015-08-20 16:19:38 +0000 | [diff] [blame] | 231 | private final HashSet<String> mTaskStoppedAuthorities = new HashSet<>(); |
Jeff Sharkey | 85f5f81 | 2013-10-07 10:16:12 -0700 | [diff] [blame] | 232 | |
Jeff Sharkey | a9ce049 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 233 | /** |
| 234 | * Update all roots. |
| 235 | */ |
| 236 | public UpdateTask() { |
| 237 | this(null); |
Jeff Sharkey | 6651669 | 2013-08-06 11:26:10 -0700 | [diff] [blame] | 238 | } |
| 239 | |
Jeff Sharkey | a9ce049 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 240 | /** |
| 241 | * Only update roots belonging to given package name. Other roots will |
| 242 | * be copied from cached {@link #mRoots} values. |
| 243 | */ |
| 244 | public UpdateTask(String filterPackage) { |
| 245 | mFilterPackage = filterPackage; |
| 246 | } |
Jeff Sharkey | 6651669 | 2013-08-06 11:26:10 -0700 | [diff] [blame] | 247 | |
Jeff Sharkey | a9ce049 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 248 | @Override |
| 249 | protected Void doInBackground(Void... params) { |
| 250 | final long start = SystemClock.elapsedRealtime(); |
| 251 | |
Jeff Sharkey | c9d7150 | 2014-09-10 11:23:15 -0700 | [diff] [blame] | 252 | if (mFilterPackage != null) { |
Jeff Sharkey | 8731408 | 2016-03-11 17:25:11 -0700 | [diff] [blame] | 253 | // We must have previously cached values to fill in non-matching |
| 254 | // packages, so wait around for successful first load. |
| 255 | if (!waitForFirstLoad()) { |
| 256 | return null; |
| 257 | } |
Jeff Sharkey | c9d7150 | 2014-09-10 11:23:15 -0700 | [diff] [blame] | 258 | } |
| 259 | |
Jeff Sharkey | 85f5f81 | 2013-10-07 10:16:12 -0700 | [diff] [blame] | 260 | mTaskRoots.put(mRecentsRoot.authority, mRecentsRoot); |
Jeff Sharkey | 5545f56 | 2013-09-21 13:57:33 -0700 | [diff] [blame] | 261 | |
Jeff Sharkey | a9ce049 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 262 | final ContentResolver resolver = mContext.getContentResolver(); |
| 263 | final PackageManager pm = mContext.getPackageManager(); |
Jeff Sharkey | 85f5f81 | 2013-10-07 10:16:12 -0700 | [diff] [blame] | 264 | |
| 265 | // Pick up provider with action string |
| 266 | final Intent intent = new Intent(DocumentsContract.PROVIDER_INTERFACE); |
| 267 | final List<ResolveInfo> providers = pm.queryIntentContentProviders(intent, 0); |
| 268 | for (ResolveInfo info : providers) { |
| 269 | handleDocumentsProvider(info.providerInfo); |
| 270 | } |
| 271 | |
Jeff Sharkey | a9ce049 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 272 | final long delta = SystemClock.elapsedRealtime() - start; |
Steve McKay | 83df8c0 | 2015-09-16 15:07:31 -0700 | [diff] [blame] | 273 | if (DEBUG) |
| 274 | Log.d(TAG, "Update found " + mTaskRoots.size() + " roots in " + delta + "ms"); |
Jeff Sharkey | a9ce049 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 275 | synchronized (mLock) { |
Jeff Sharkey | 8731408 | 2016-03-11 17:25:11 -0700 | [diff] [blame] | 276 | mFirstLoadDone = true; |
| 277 | if (mBootCompletedResult != null) { |
| 278 | mBootCompletedResult.finish(); |
| 279 | mBootCompletedResult = null; |
| 280 | } |
Jeff Sharkey | 85f5f81 | 2013-10-07 10:16:12 -0700 | [diff] [blame] | 281 | mRoots = mTaskRoots; |
| 282 | mStoppedAuthorities = mTaskStoppedAuthorities; |
Jeff Sharkey | a9ce049 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 283 | } |
| 284 | mFirstLoad.countDown(); |
Jeff Sharkey | 46de7b5 | 2013-10-23 09:59:06 -0700 | [diff] [blame] | 285 | resolver.notifyChange(sNotificationUri, null, false); |
Jeff Sharkey | a9ce049 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 286 | return null; |
| 287 | } |
Jeff Sharkey | 85f5f81 | 2013-10-07 10:16:12 -0700 | [diff] [blame] | 288 | |
Daichi Hirono | 60e9a07 | 2015-12-25 11:08:42 +0900 | [diff] [blame] | 289 | @Override |
| 290 | protected void onPostExecute(Void result) { |
| 291 | if (mCacheUpdateListener != null) { |
| 292 | mCacheUpdateListener.onCacheUpdate(); |
| 293 | } |
| 294 | } |
| 295 | |
Jeff Sharkey | 85f5f81 | 2013-10-07 10:16:12 -0700 | [diff] [blame] | 296 | private void handleDocumentsProvider(ProviderInfo info) { |
| 297 | // Ignore stopped packages for now; we might query them |
| 298 | // later during UI interaction. |
| 299 | if ((info.applicationInfo.flags & ApplicationInfo.FLAG_STOPPED) != 0) { |
Steve McKay | 83df8c0 | 2015-09-16 15:07:31 -0700 | [diff] [blame] | 300 | if (DEBUG) Log.d(TAG, "Ignoring stopped authority " + info.authority); |
Jeff Sharkey | 85f5f81 | 2013-10-07 10:16:12 -0700 | [diff] [blame] | 301 | mTaskStoppedAuthorities.add(info.authority); |
| 302 | return; |
| 303 | } |
| 304 | |
| 305 | // Try using cached roots if filtering |
| 306 | boolean cacheHit = false; |
| 307 | if (mFilterPackage != null && !mFilterPackage.equals(info.packageName)) { |
| 308 | synchronized (mLock) { |
| 309 | if (mTaskRoots.putAll(info.authority, mRoots.get(info.authority))) { |
Steve McKay | 83df8c0 | 2015-09-16 15:07:31 -0700 | [diff] [blame] | 310 | if (DEBUG) Log.d(TAG, "Used cached roots for " + info.authority); |
Jeff Sharkey | 85f5f81 | 2013-10-07 10:16:12 -0700 | [diff] [blame] | 311 | cacheHit = true; |
| 312 | } |
| 313 | } |
| 314 | } |
| 315 | |
| 316 | // Cache miss, or loading everything |
| 317 | if (!cacheHit) { |
| 318 | mTaskRoots.putAll(info.authority, |
| 319 | loadRootsForAuthority(mContext.getContentResolver(), info.authority)); |
| 320 | } |
| 321 | } |
Jeff Sharkey | 6651669 | 2013-08-06 11:26:10 -0700 | [diff] [blame] | 322 | } |
| 323 | |
Jeff Sharkey | a9ce049 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 324 | /** |
| 325 | * Bring up requested provider and query for all active roots. |
| 326 | */ |
| 327 | private Collection<RootInfo> loadRootsForAuthority(ContentResolver resolver, String authority) { |
Steve McKay | 83df8c0 | 2015-09-16 15:07:31 -0700 | [diff] [blame] | 328 | if (DEBUG) Log.d(TAG, "Loading roots for " + authority); |
Jeff Sharkey | a9ce049 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 329 | |
| 330 | synchronized (mObservedAuthorities) { |
| 331 | if (mObservedAuthorities.add(authority)) { |
| 332 | // Watch for any future updates |
| 333 | final Uri rootsUri = DocumentsContract.buildRootsUri(authority); |
| 334 | mContext.getContentResolver().registerContentObserver(rootsUri, true, mObserver); |
| 335 | } |
| 336 | } |
| 337 | |
Jeff Sharkey | a9ce049 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 338 | final Uri rootsUri = DocumentsContract.buildRootsUri(authority); |
Jeff Sharkey | 8731408 | 2016-03-11 17:25:11 -0700 | [diff] [blame] | 339 | if (ENABLE_SYSTEM_CACHE) { |
| 340 | // Look for roots data that we might have cached for ourselves in the |
| 341 | // long-lived system process. |
| 342 | final Bundle systemCache = resolver.getCache(rootsUri); |
| 343 | if (systemCache != null) { |
| 344 | if (DEBUG) Log.d(TAG, "System cache hit for " + authority); |
| 345 | return systemCache.getParcelableArrayList(TAG); |
| 346 | } |
| 347 | } |
Jeff Sharkey | 7aa7601 | 2013-09-30 14:26:27 -0700 | [diff] [blame] | 348 | |
Jeff Sharkey | 8731408 | 2016-03-11 17:25:11 -0700 | [diff] [blame] | 349 | final ArrayList<RootInfo> roots = new ArrayList<>(); |
Jeff Sharkey | 7aa7601 | 2013-09-30 14:26:27 -0700 | [diff] [blame] | 350 | ContentProviderClient client = null; |
Jeff Sharkey | a9ce049 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 351 | Cursor cursor = null; |
| 352 | try { |
Jeff Sharkey | 7aa7601 | 2013-09-30 14:26:27 -0700 | [diff] [blame] | 353 | client = DocumentsApplication.acquireUnstableProviderOrThrow(resolver, authority); |
Jeff Sharkey | a9ce049 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 354 | cursor = client.query(rootsUri, null, null, null, null); |
| 355 | while (cursor.moveToNext()) { |
| 356 | final RootInfo root = RootInfo.fromRootsCursor(authority, cursor); |
| 357 | roots.add(root); |
| 358 | } |
| 359 | } catch (Exception e) { |
| 360 | Log.w(TAG, "Failed to load some roots from " + authority + ": " + e); |
| 361 | } finally { |
| 362 | IoUtils.closeQuietly(cursor); |
Jeff Sharkey | 7aa7601 | 2013-09-30 14:26:27 -0700 | [diff] [blame] | 363 | ContentProviderClient.releaseQuietly(client); |
Jeff Sharkey | a9ce049 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 364 | } |
Jeff Sharkey | 8731408 | 2016-03-11 17:25:11 -0700 | [diff] [blame] | 365 | |
| 366 | if (ENABLE_SYSTEM_CACHE) { |
| 367 | // Cache these freshly parsed roots over in the long-lived system |
| 368 | // process, in case our process goes away. The system takes care of |
| 369 | // invalidating the cache if the package or Uri changes. |
| 370 | final Bundle systemCache = new Bundle(); |
| 371 | systemCache.putParcelableArrayList(TAG, roots); |
| 372 | resolver.putCache(rootsUri, systemCache); |
| 373 | } |
| 374 | |
Jeff Sharkey | a9ce049 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 375 | return roots; |
| 376 | } |
| 377 | |
| 378 | /** |
| 379 | * Return the requested {@link RootInfo}, but only loading the roots for the |
| 380 | * requested authority. This is useful when we want to load fast without |
| 381 | * waiting for all the other roots to come back. |
| 382 | */ |
| 383 | public RootInfo getRootOneshot(String authority, String rootId) { |
| 384 | synchronized (mLock) { |
| 385 | RootInfo root = getRootLocked(authority, rootId); |
| 386 | if (root == null) { |
| 387 | mRoots.putAll( |
| 388 | authority, loadRootsForAuthority(mContext.getContentResolver(), authority)); |
| 389 | root = getRootLocked(authority, rootId); |
| 390 | } |
| 391 | return root; |
| 392 | } |
| 393 | } |
| 394 | |
| 395 | public RootInfo getRootBlocking(String authority, String rootId) { |
| 396 | waitForFirstLoad(); |
| 397 | loadStoppedAuthorities(); |
| 398 | synchronized (mLock) { |
| 399 | return getRootLocked(authority, rootId); |
| 400 | } |
| 401 | } |
| 402 | |
| 403 | private RootInfo getRootLocked(String authority, String rootId) { |
| 404 | for (RootInfo root : mRoots.get(authority)) { |
Kenny Root | e6585b3 | 2013-12-13 12:00:26 -0800 | [diff] [blame] | 405 | if (Objects.equals(root.rootId, rootId)) { |
Jeff Sharkey | aeb16e2 | 2013-08-27 18:26:48 -0700 | [diff] [blame] | 406 | return root; |
| 407 | } |
| 408 | } |
| 409 | return null; |
Jeff Sharkey | 6651669 | 2013-08-06 11:26:10 -0700 | [diff] [blame] | 410 | } |
| 411 | |
Jeff Sharkey | a9ce049 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 412 | public boolean isIconUniqueBlocking(RootInfo root) { |
| 413 | waitForFirstLoad(); |
| 414 | loadStoppedAuthorities(); |
| 415 | synchronized (mLock) { |
| 416 | final int rootIcon = root.derivedIcon != 0 ? root.derivedIcon : root.icon; |
| 417 | for (RootInfo test : mRoots.get(root.authority)) { |
Kenny Root | e6585b3 | 2013-12-13 12:00:26 -0800 | [diff] [blame] | 418 | if (Objects.equals(test.rootId, root.rootId)) { |
Jeff Sharkey | 4ec9739 | 2013-09-10 12:04:26 -0700 | [diff] [blame] | 419 | continue; |
| 420 | } |
Jeff Sharkey | f6db154 | 2013-09-13 13:42:19 -0700 | [diff] [blame] | 421 | final int testIcon = test.derivedIcon != 0 ? test.derivedIcon : test.icon; |
| 422 | if (testIcon == rootIcon) { |
Jeff Sharkey | 4ec9739 | 2013-09-10 12:04:26 -0700 | [diff] [blame] | 423 | return false; |
| 424 | } |
| 425 | } |
Jeff Sharkey | a9ce049 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 426 | return true; |
Jeff Sharkey | 4ec9739 | 2013-09-10 12:04:26 -0700 | [diff] [blame] | 427 | } |
Jeff Sharkey | 4ec9739 | 2013-09-10 12:04:26 -0700 | [diff] [blame] | 428 | } |
| 429 | |
Jeff Sharkey | ae9b51b | 2013-08-31 15:02:20 -0700 | [diff] [blame] | 430 | public RootInfo getRecentsRoot() { |
Jeff Sharkey | 4eb407a | 2013-08-18 17:38:20 -0700 | [diff] [blame] | 431 | return mRecentsRoot; |
Jeff Sharkey | b156f4b | 2013-08-06 16:26:14 -0700 | [diff] [blame] | 432 | } |
| 433 | |
Jeff Sharkey | ae9b51b | 2013-08-31 15:02:20 -0700 | [diff] [blame] | 434 | public boolean isRecentsRoot(RootInfo root) { |
Steve McKay | 4a1ca86 | 2016-02-17 18:25:47 -0800 | [diff] [blame] | 435 | return mRecentsRoot.equals(root); |
Jeff Sharkey | 6651669 | 2013-08-06 11:26:10 -0700 | [diff] [blame] | 436 | } |
| 437 | |
Jeff Sharkey | a9ce049 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 438 | public Collection<RootInfo> getRootsBlocking() { |
| 439 | waitForFirstLoad(); |
| 440 | loadStoppedAuthorities(); |
| 441 | synchronized (mLock) { |
| 442 | return mRoots.values(); |
| 443 | } |
Jeff Sharkey | aeb16e2 | 2013-08-27 18:26:48 -0700 | [diff] [blame] | 444 | } |
Jeff Sharkey | 6651669 | 2013-08-06 11:26:10 -0700 | [diff] [blame] | 445 | |
Jeff Sharkey | a9ce049 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 446 | public Collection<RootInfo> getMatchingRootsBlocking(State state) { |
| 447 | waitForFirstLoad(); |
| 448 | loadStoppedAuthorities(); |
| 449 | synchronized (mLock) { |
| 450 | return getMatchingRoots(mRoots.values(), state); |
| 451 | } |
Jeff Sharkey | 6d97d3c | 2013-09-06 10:43:45 -0700 | [diff] [blame] | 452 | } |
| 453 | |
Tomasz Mikolajewski | a6120da | 2016-01-27 17:36:51 +0900 | [diff] [blame] | 454 | /** |
| 455 | * Returns a list of roots for the specified authority. If not found, then |
| 456 | * an empty list is returned. |
| 457 | */ |
| 458 | public Collection<RootInfo> getRootsForAuthorityBlocking(String authority) { |
| 459 | waitForFirstLoad(); |
| 460 | loadStoppedAuthority(authority); |
| 461 | synchronized (mLock) { |
| 462 | final Collection<RootInfo> roots = mRoots.get(authority); |
| 463 | return roots != null ? roots : Collections.<RootInfo>emptyList(); |
| 464 | } |
| 465 | } |
| 466 | |
Daichi Hirono | 60e9a07 | 2015-12-25 11:08:42 +0900 | [diff] [blame] | 467 | public void setOnCacheUpdateListener(OnCacheUpdateListener cacheUpdateListener) { |
| 468 | mCacheUpdateListener = cacheUpdateListener; |
| 469 | } |
| 470 | |
Jeff Sharkey | a9ce049 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 471 | @VisibleForTesting |
| 472 | static List<RootInfo> getMatchingRoots(Collection<RootInfo> roots, State state) { |
Steve McKay | 58efce3 | 2015-08-20 16:19:38 +0000 | [diff] [blame] | 473 | final List<RootInfo> matching = new ArrayList<>(); |
Jeff Sharkey | 6d97d3c | 2013-09-06 10:43:45 -0700 | [diff] [blame] | 474 | for (RootInfo root : roots) { |
Steve McKay | ea9ec29 | 2016-03-01 15:11:56 -0800 | [diff] [blame] | 475 | |
| 476 | if (DEBUG) Log.d(TAG, "Evaluating " + root); |
| 477 | |
| 478 | if (state.action == State.ACTION_CREATE && !root.supportsCreate()) { |
| 479 | if (DEBUG) Log.d(TAG, "Excluding read-only root because: ACTION_CREATE."); |
| 480 | continue; |
| 481 | } |
| 482 | |
Steve McKay | 4a1ca86 | 2016-02-17 18:25:47 -0800 | [diff] [blame] | 483 | if (state.action == State.ACTION_PICK_COPY_DESTINATION |
Steve McKay | ea9ec29 | 2016-03-01 15:11:56 -0800 | [diff] [blame] | 484 | && !root.supportsCreate()) { |
| 485 | if (DEBUG) Log.d( |
| 486 | TAG, "Excluding read-only root because: ACTION_PICK_COPY_DESTINATION."); |
| 487 | continue; |
| 488 | } |
| 489 | |
| 490 | if (state.action == State.ACTION_OPEN_TREE && !root.supportsChildren()) { |
| 491 | if (DEBUG) Log.d( |
| 492 | TAG, "Excluding root !supportsChildren because: ACTION_OPEN_TREE."); |
| 493 | continue; |
| 494 | } |
| 495 | |
Steve McKay | ea9ec29 | 2016-03-01 15:11:56 -0800 | [diff] [blame] | 496 | if (state.localOnly && !root.isLocalOnly()) { |
| 497 | if (DEBUG) Log.d(TAG, "Excluding root because: unwanted non-local device."); |
| 498 | continue; |
| 499 | } |
| 500 | |
Steve McKay | ea9ec29 | 2016-03-01 15:11:56 -0800 | [diff] [blame] | 501 | if (state.directoryCopy && root.isDownloads()) { |
| 502 | if (DEBUG) Log.d( |
| 503 | TAG, "Excluding downloads root because: unsupported directory copy."); |
| 504 | continue; |
| 505 | } |
Steve McKay | 83df8c0 | 2015-09-16 15:07:31 -0700 | [diff] [blame] | 506 | |
Steve McKay | ea9ec29 | 2016-03-01 15:11:56 -0800 | [diff] [blame] | 507 | if (state.action == State.ACTION_OPEN && root.isEmpty()) { |
| 508 | if (DEBUG) Log.d(TAG, "Excluding empty root because: ACTION_OPEN."); |
| 509 | continue; |
| 510 | } |
| 511 | |
Steve McKay | ea9ec29 | 2016-03-01 15:11:56 -0800 | [diff] [blame] | 512 | if (state.action == State.ACTION_GET_CONTENT && root.isEmpty()) { |
| 513 | if (DEBUG) Log.d(TAG, "Excluding empty root because: ACTION_GET_CONTENT."); |
Steve McKay | 83df8c0 | 2015-09-16 15:07:31 -0700 | [diff] [blame] | 514 | continue; |
| 515 | } |
Jeff Sharkey | 348ad68 | 2013-09-02 17:19:40 -0700 | [diff] [blame] | 516 | |
Jeff Sharkey | d182bb6 | 2013-09-07 14:45:03 -0700 | [diff] [blame] | 517 | final boolean overlap = |
| 518 | MimePredicate.mimeMatches(root.derivedMimeTypes, state.acceptMimes) || |
| 519 | MimePredicate.mimeMatches(state.acceptMimes, root.derivedMimeTypes); |
Jeff Sharkey | 6d97d3c | 2013-09-06 10:43:45 -0700 | [diff] [blame] | 520 | if (!overlap) { |
Steve McKay | ea9ec29 | 2016-03-01 15:11:56 -0800 | [diff] [blame] | 521 | if (DEBUG) Log.d( |
| 522 | TAG, "Excluding root because: unsupported content types > " |
| 523 | + state.acceptMimes); |
Jeff Sharkey | 923396b | 2013-09-05 13:55:35 -0700 | [diff] [blame] | 524 | continue; |
Jeff Sharkey | 348ad68 | 2013-09-02 17:19:40 -0700 | [diff] [blame] | 525 | } |
| 526 | |
Ben Kwa | 7779740 | 2015-05-29 15:40:31 -0700 | [diff] [blame] | 527 | if (state.excludedAuthorities.contains(root.authority)) { |
Steve McKay | 7c43958 | 2016-03-01 15:41:47 -0800 | [diff] [blame] | 528 | if (DEBUG) Log.d(TAG, "Excluding root because: owned by calling package."); |
Ben Kwa | 7779740 | 2015-05-29 15:40:31 -0700 | [diff] [blame] | 529 | continue; |
| 530 | } |
| 531 | |
Steve McKay | ea9ec29 | 2016-03-01 15:11:56 -0800 | [diff] [blame] | 532 | if (DEBUG) Log.d(TAG, "Including " + root); |
Jeff Sharkey | 348ad68 | 2013-09-02 17:19:40 -0700 | [diff] [blame] | 533 | matching.add(root); |
| 534 | } |
| 535 | return matching; |
| 536 | } |
Jeff Sharkey | 6651669 | 2013-08-06 11:26:10 -0700 | [diff] [blame] | 537 | } |