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