Loading src/com/android/documentsui/Model.java +7 −1 Original line number Diff line number Diff line Loading @@ -20,7 +20,6 @@ import static com.android.documentsui.base.DocumentInfo.getCursorString; import static com.android.documentsui.base.SharedMinimal.DEBUG; import static com.android.documentsui.base.SharedMinimal.VERBOSE; import androidx.annotation.IntDef; import android.app.AuthenticationRequiredException; import android.database.Cursor; import android.net.Uri; Loading @@ -29,6 +28,7 @@ import android.provider.DocumentsContract; import android.provider.DocumentsContract.Document; import android.util.Log; import androidx.annotation.IntDef; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import androidx.recyclerview.selection.Selection; Loading @@ -37,6 +37,7 @@ import com.android.documentsui.base.DocumentFilters; import com.android.documentsui.base.DocumentInfo; import com.android.documentsui.base.EventListener; import com.android.documentsui.base.Features; import com.android.documentsui.base.UserId; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; Loading Loading @@ -258,6 +259,11 @@ public class Model { return DocumentInfo.getUri(cursor); } public UserId getItemUserId(String modelId) { final Cursor cursor = getItem(modelId); return DocumentInfo.getUserId(cursor); } /** * @return An ordered array of model IDs representing the documents in the model. It is sorted * according to the current sort order, which was set by the last model update. Loading src/com/android/documentsui/ThumbnailCache.java +102 −29 Original line number Diff line number Diff line Loading @@ -16,23 +16,26 @@ package com.android.documentsui; import androidx.annotation.IntDef; import androidx.annotation.Nullable; import androidx.core.util.Pools; import static androidx.core.util.Preconditions.checkNotNull; import android.content.ComponentCallbacks2; import android.graphics.Bitmap; import android.graphics.Point; import android.net.Uri; import android.util.LruCache; import android.util.Pair; import androidx.annotation.IntDef; import androidx.annotation.Nullable; import androidx.core.util.Pools; import com.android.documentsui.base.Shared; import com.android.documentsui.base.UserId; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Comparator; import java.util.HashMap; import java.util.Objects; import java.util.TreeMap; /** Loading @@ -44,10 +47,10 @@ public class ThumbnailCache { private static final SizeComparator SIZE_COMPARATOR = new SizeComparator(); /** * A 2-dimensional index into {@link #mCache} entries. Pair<Uri, Point> is the key to * A 2-dimensional index into {@link #mCache} entries. {@link CacheKey} is the key to * {@link #mCache}. TreeMap is used to search the closest size to a given size and a given uri. */ private final HashMap<Uri, TreeMap<Point, Pair<Uri, Point>>> mSizeIndex; private final HashMap<SizeIndexKey, TreeMap<Point, CacheKey>> mSizeIndex; private final Cache mCache; /** Loading @@ -67,16 +70,16 @@ public class ThumbnailCache { * @param size the desired size of the thumbnail * @return the thumbnail result */ public Result getThumbnail(Uri uri, Point size) { TreeMap<Point, Pair<Uri, Point>> sizeMap; sizeMap = mSizeIndex.get(uri); public Result getThumbnail(Uri uri, UserId userId, Point size) { TreeMap<Point, CacheKey> sizeMap; sizeMap = mSizeIndex.get(new SizeIndexKey(uri, userId)); if (sizeMap == null || sizeMap.isEmpty()) { // There is not any thumbnail for this uri. return Result.obtainMiss(); } // Look for thumbnail of the same size. Pair<Uri, Point> cacheKey = sizeMap.get(size); CacheKey cacheKey = sizeMap.get(size); if (cacheKey != null) { Entry entry = mCache.get(cacheKey); if (entry != null) { Loading Loading @@ -121,15 +124,16 @@ public class ThumbnailCache { * @param thumbnail the thumbnail to put in cache * @param lastModified last modified value of the thumbnail to track its validity */ public void putThumbnail(Uri uri, Point size, Bitmap thumbnail, long lastModified) { Pair<Uri, Point> cacheKey = Pair.create(uri, size); public void putThumbnail(Uri uri, UserId userId, Point size, Bitmap thumbnail, long lastModified) { CacheKey cacheKey = new CacheKey(uri, userId, size); TreeMap<Point, Pair<Uri, Point>> sizeMap; TreeMap<Point, CacheKey> sizeMap; synchronized (mSizeIndex) { sizeMap = mSizeIndex.get(uri); sizeMap = mSizeIndex.get(new SizeIndexKey(uri, userId)); if (sizeMap == null) { sizeMap = new TreeMap<>(SIZE_COMPARATOR); mSizeIndex.put(uri, sizeMap); mSizeIndex.put(new SizeIndexKey(uri, userId), sizeMap); } } Loading @@ -141,34 +145,34 @@ public class ThumbnailCache { } /** * Removes all thumbnail cache associated to the given uri. * Removes all thumbnail cache associated to the given uri and user. * @param uri the uri which thumbnail cache to remove */ public void removeUri(Uri uri) { TreeMap<Point, Pair<Uri, Point>> sizeMap; public void removeUri(Uri uri, UserId userId) { TreeMap<Point, CacheKey> sizeMap; synchronized (mSizeIndex) { sizeMap = mSizeIndex.get(uri); sizeMap = mSizeIndex.get(new SizeIndexKey(uri, userId)); } if (sizeMap != null) { // Create an array to hold all values to avoid ConcurrentModificationException because // removeKey() will be called by LruCache but we can't modify the map while we're // iterating over the collection of values. for (Pair<Uri, Point> index : sizeMap.values().toArray(new Pair[0])) { for (CacheKey index : sizeMap.values().toArray(new CacheKey[0])) { mCache.remove(index); } } } private void removeKey(Uri uri, Point size) { TreeMap<Point, Pair<Uri, Point>> sizeMap; private void removeKey(CacheKey cacheKey) { TreeMap<Point, CacheKey> sizeMap; synchronized (mSizeIndex) { sizeMap = mSizeIndex.get(uri); sizeMap = mSizeIndex.get(new SizeIndexKey(cacheKey.uri, cacheKey.userId)); } assert (sizeMap != null); synchronized (sizeMap) { sizeMap.remove(size); sizeMap.remove(cacheKey.point); } } Loading Loading @@ -291,22 +295,22 @@ public class ThumbnailCache { } } private final class Cache extends LruCache<Pair<Uri, Point>, Entry> { private final class Cache extends LruCache<CacheKey, Entry> { private Cache(int maxSizeBytes) { super(maxSizeBytes); } @Override protected int sizeOf(Pair<Uri, Point> key, Entry value) { protected int sizeOf(CacheKey key, Entry value) { return value.mThumbnail.getByteCount(); } @Override protected void entryRemoved( boolean evicted, Pair<Uri, Point> key, Entry oldValue, Entry newValue) { boolean evicted, CacheKey key, Entry oldValue, Entry newValue) { if (newValue == null) { removeKey(key.first, key.second); removeKey(key); } } } Loading @@ -318,4 +322,73 @@ public class ThumbnailCache { return size0.x - size1.x; } } private static class SizeIndexKey { final Uri uri; final UserId userId; SizeIndexKey(Uri uri, UserId userId) { this.uri = checkNotNull(uri); this.userId = checkNotNull(userId); } @Override public boolean equals(Object o) { if (o == null) { return false; } if (this == o) { return true; } if (o instanceof SizeIndexKey) { SizeIndexKey other = (SizeIndexKey) o; return Objects.equals(uri, other.uri) && Objects.equals(userId, other.userId); } return false; } @Override public int hashCode() { return Objects.hash(uri, userId); } } private static class CacheKey { final Uri uri; final UserId userId; final Point point; CacheKey(Uri uri, UserId userId, Point point) { this.uri = checkNotNull(uri); this.userId = checkNotNull(userId); this.point = checkNotNull(point); } @Override public boolean equals(Object o) { if (o == null) { return false; } if (this == o) { return true; } if (o instanceof CacheKey) { CacheKey other = (CacheKey) o; return Objects.equals(uri, other.uri) && Objects.equals(userId, other.userId) && Objects.equals(point, other.point); } return false; } @Override public int hashCode() { return Objects.hash(uri, userId, point); } } } src/com/android/documentsui/ThumbnailLoader.java +10 −4 Original line number Diff line number Diff line Loading @@ -33,7 +33,10 @@ import android.provider.DocumentsContract; import android.util.Log; import android.view.View; import android.widget.ImageView; import com.android.documentsui.ProviderExecutor.Preemptable; import com.android.documentsui.base.UserId; import java.util.function.BiConsumer; import java.util.function.Consumer; Loading @@ -59,6 +62,7 @@ public final class ThumbnailLoader extends AsyncTask<Uri, Void, Bitmap> implemen private final ImageView mIconThumb; private final Point mThumbSize; private final Uri mUri; private final UserId mUserId; private final long mLastModified; private final Consumer<Bitmap> mCallback; private final boolean mAddToCache; Loading @@ -66,15 +70,17 @@ public final class ThumbnailLoader extends AsyncTask<Uri, Void, Bitmap> implemen /** * @param uri - to a thumbnail. * @param userId - user of the uri. * @param iconThumb - ImageView to display the thumbnail. * @param thumbSize - size of the thumbnail. * @param lastModified - used for updating thumbnail caches. * @param addToCache - flag that determines if the loader saves the thumbnail to the cache. */ public ThumbnailLoader(Uri uri, ImageView iconThumb, Point thumbSize, long lastModified, Consumer<Bitmap> callback, boolean addToCache) { public ThumbnailLoader(Uri uri, UserId userId, ImageView iconThumb, Point thumbSize, long lastModified, Consumer<Bitmap> callback, boolean addToCache) { mUri = uri; mUserId = userId; mIconThumb = iconThumb; mThumbSize = thumbSize; mLastModified = lastModified; Loading @@ -100,7 +106,7 @@ public final class ThumbnailLoader extends AsyncTask<Uri, Void, Bitmap> implemen } final Context context = mIconThumb.getContext(); final ContentResolver resolver = context.getContentResolver(); final ContentResolver resolver = mUserId.getContentResolver(context); ContentProviderClient client = null; Bitmap result = null; Loading @@ -111,7 +117,7 @@ public final class ThumbnailLoader extends AsyncTask<Uri, Void, Bitmap> implemen mUri, mThumbSize, mSignal); if (result != null && mAddToCache) { final ThumbnailCache cache = DocumentsApplication.getThumbnailCache(context); cache.putThumbnail(mUri, mThumbSize, result, mLastModified); cache.putThumbnail(mUri, mUserId, mThumbSize, result, mLastModified); } } catch (Exception e) { if (!(e instanceof OperationCanceledException)) { Loading src/com/android/documentsui/base/DocumentInfo.java +4 −0 Original line number Diff line number Diff line Loading @@ -418,6 +418,10 @@ public class DocumentInfo implements Durable, Parcelable { getCursorString(cursor, Document.COLUMN_DOCUMENT_ID)); } public static UserId getUserId(Cursor cursor) { return UserId.of(getCursorInt(cursor, RootCursorWrapper.COLUMN_USER_ID)); } public static void addMimeTypes(ContentResolver resolver, Uri uri, Set<String> mimeTypes) { assert(uri != null); if ("content".equals(uri.getScheme())) { Loading src/com/android/documentsui/dirlist/DirectoryFragment.java +1 −1 Original line number Diff line number Diff line Loading @@ -1161,7 +1161,7 @@ public class DirectoryFragment extends Fragment implements SwipeRefreshLayout.On String[] ids = mModel.getModelIds(); int numOfEvicts = Math.min(ids.length, CACHE_EVICT_LIMIT); for (int i = 0; i < numOfEvicts; ++i) { cache.removeUri(mModel.getItemUri(ids[i])); cache.removeUri(mModel.getItemUri(ids[i]), mModel.getItemUserId(ids[i])); } final DocumentInfo doc = mActivity.getCurrentDirectory(); Loading Loading
src/com/android/documentsui/Model.java +7 −1 Original line number Diff line number Diff line Loading @@ -20,7 +20,6 @@ import static com.android.documentsui.base.DocumentInfo.getCursorString; import static com.android.documentsui.base.SharedMinimal.DEBUG; import static com.android.documentsui.base.SharedMinimal.VERBOSE; import androidx.annotation.IntDef; import android.app.AuthenticationRequiredException; import android.database.Cursor; import android.net.Uri; Loading @@ -29,6 +28,7 @@ import android.provider.DocumentsContract; import android.provider.DocumentsContract.Document; import android.util.Log; import androidx.annotation.IntDef; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import androidx.recyclerview.selection.Selection; Loading @@ -37,6 +37,7 @@ import com.android.documentsui.base.DocumentFilters; import com.android.documentsui.base.DocumentInfo; import com.android.documentsui.base.EventListener; import com.android.documentsui.base.Features; import com.android.documentsui.base.UserId; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; Loading Loading @@ -258,6 +259,11 @@ public class Model { return DocumentInfo.getUri(cursor); } public UserId getItemUserId(String modelId) { final Cursor cursor = getItem(modelId); return DocumentInfo.getUserId(cursor); } /** * @return An ordered array of model IDs representing the documents in the model. It is sorted * according to the current sort order, which was set by the last model update. Loading
src/com/android/documentsui/ThumbnailCache.java +102 −29 Original line number Diff line number Diff line Loading @@ -16,23 +16,26 @@ package com.android.documentsui; import androidx.annotation.IntDef; import androidx.annotation.Nullable; import androidx.core.util.Pools; import static androidx.core.util.Preconditions.checkNotNull; import android.content.ComponentCallbacks2; import android.graphics.Bitmap; import android.graphics.Point; import android.net.Uri; import android.util.LruCache; import android.util.Pair; import androidx.annotation.IntDef; import androidx.annotation.Nullable; import androidx.core.util.Pools; import com.android.documentsui.base.Shared; import com.android.documentsui.base.UserId; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Comparator; import java.util.HashMap; import java.util.Objects; import java.util.TreeMap; /** Loading @@ -44,10 +47,10 @@ public class ThumbnailCache { private static final SizeComparator SIZE_COMPARATOR = new SizeComparator(); /** * A 2-dimensional index into {@link #mCache} entries. Pair<Uri, Point> is the key to * A 2-dimensional index into {@link #mCache} entries. {@link CacheKey} is the key to * {@link #mCache}. TreeMap is used to search the closest size to a given size and a given uri. */ private final HashMap<Uri, TreeMap<Point, Pair<Uri, Point>>> mSizeIndex; private final HashMap<SizeIndexKey, TreeMap<Point, CacheKey>> mSizeIndex; private final Cache mCache; /** Loading @@ -67,16 +70,16 @@ public class ThumbnailCache { * @param size the desired size of the thumbnail * @return the thumbnail result */ public Result getThumbnail(Uri uri, Point size) { TreeMap<Point, Pair<Uri, Point>> sizeMap; sizeMap = mSizeIndex.get(uri); public Result getThumbnail(Uri uri, UserId userId, Point size) { TreeMap<Point, CacheKey> sizeMap; sizeMap = mSizeIndex.get(new SizeIndexKey(uri, userId)); if (sizeMap == null || sizeMap.isEmpty()) { // There is not any thumbnail for this uri. return Result.obtainMiss(); } // Look for thumbnail of the same size. Pair<Uri, Point> cacheKey = sizeMap.get(size); CacheKey cacheKey = sizeMap.get(size); if (cacheKey != null) { Entry entry = mCache.get(cacheKey); if (entry != null) { Loading Loading @@ -121,15 +124,16 @@ public class ThumbnailCache { * @param thumbnail the thumbnail to put in cache * @param lastModified last modified value of the thumbnail to track its validity */ public void putThumbnail(Uri uri, Point size, Bitmap thumbnail, long lastModified) { Pair<Uri, Point> cacheKey = Pair.create(uri, size); public void putThumbnail(Uri uri, UserId userId, Point size, Bitmap thumbnail, long lastModified) { CacheKey cacheKey = new CacheKey(uri, userId, size); TreeMap<Point, Pair<Uri, Point>> sizeMap; TreeMap<Point, CacheKey> sizeMap; synchronized (mSizeIndex) { sizeMap = mSizeIndex.get(uri); sizeMap = mSizeIndex.get(new SizeIndexKey(uri, userId)); if (sizeMap == null) { sizeMap = new TreeMap<>(SIZE_COMPARATOR); mSizeIndex.put(uri, sizeMap); mSizeIndex.put(new SizeIndexKey(uri, userId), sizeMap); } } Loading @@ -141,34 +145,34 @@ public class ThumbnailCache { } /** * Removes all thumbnail cache associated to the given uri. * Removes all thumbnail cache associated to the given uri and user. * @param uri the uri which thumbnail cache to remove */ public void removeUri(Uri uri) { TreeMap<Point, Pair<Uri, Point>> sizeMap; public void removeUri(Uri uri, UserId userId) { TreeMap<Point, CacheKey> sizeMap; synchronized (mSizeIndex) { sizeMap = mSizeIndex.get(uri); sizeMap = mSizeIndex.get(new SizeIndexKey(uri, userId)); } if (sizeMap != null) { // Create an array to hold all values to avoid ConcurrentModificationException because // removeKey() will be called by LruCache but we can't modify the map while we're // iterating over the collection of values. for (Pair<Uri, Point> index : sizeMap.values().toArray(new Pair[0])) { for (CacheKey index : sizeMap.values().toArray(new CacheKey[0])) { mCache.remove(index); } } } private void removeKey(Uri uri, Point size) { TreeMap<Point, Pair<Uri, Point>> sizeMap; private void removeKey(CacheKey cacheKey) { TreeMap<Point, CacheKey> sizeMap; synchronized (mSizeIndex) { sizeMap = mSizeIndex.get(uri); sizeMap = mSizeIndex.get(new SizeIndexKey(cacheKey.uri, cacheKey.userId)); } assert (sizeMap != null); synchronized (sizeMap) { sizeMap.remove(size); sizeMap.remove(cacheKey.point); } } Loading Loading @@ -291,22 +295,22 @@ public class ThumbnailCache { } } private final class Cache extends LruCache<Pair<Uri, Point>, Entry> { private final class Cache extends LruCache<CacheKey, Entry> { private Cache(int maxSizeBytes) { super(maxSizeBytes); } @Override protected int sizeOf(Pair<Uri, Point> key, Entry value) { protected int sizeOf(CacheKey key, Entry value) { return value.mThumbnail.getByteCount(); } @Override protected void entryRemoved( boolean evicted, Pair<Uri, Point> key, Entry oldValue, Entry newValue) { boolean evicted, CacheKey key, Entry oldValue, Entry newValue) { if (newValue == null) { removeKey(key.first, key.second); removeKey(key); } } } Loading @@ -318,4 +322,73 @@ public class ThumbnailCache { return size0.x - size1.x; } } private static class SizeIndexKey { final Uri uri; final UserId userId; SizeIndexKey(Uri uri, UserId userId) { this.uri = checkNotNull(uri); this.userId = checkNotNull(userId); } @Override public boolean equals(Object o) { if (o == null) { return false; } if (this == o) { return true; } if (o instanceof SizeIndexKey) { SizeIndexKey other = (SizeIndexKey) o; return Objects.equals(uri, other.uri) && Objects.equals(userId, other.userId); } return false; } @Override public int hashCode() { return Objects.hash(uri, userId); } } private static class CacheKey { final Uri uri; final UserId userId; final Point point; CacheKey(Uri uri, UserId userId, Point point) { this.uri = checkNotNull(uri); this.userId = checkNotNull(userId); this.point = checkNotNull(point); } @Override public boolean equals(Object o) { if (o == null) { return false; } if (this == o) { return true; } if (o instanceof CacheKey) { CacheKey other = (CacheKey) o; return Objects.equals(uri, other.uri) && Objects.equals(userId, other.userId) && Objects.equals(point, other.point); } return false; } @Override public int hashCode() { return Objects.hash(uri, userId, point); } } }
src/com/android/documentsui/ThumbnailLoader.java +10 −4 Original line number Diff line number Diff line Loading @@ -33,7 +33,10 @@ import android.provider.DocumentsContract; import android.util.Log; import android.view.View; import android.widget.ImageView; import com.android.documentsui.ProviderExecutor.Preemptable; import com.android.documentsui.base.UserId; import java.util.function.BiConsumer; import java.util.function.Consumer; Loading @@ -59,6 +62,7 @@ public final class ThumbnailLoader extends AsyncTask<Uri, Void, Bitmap> implemen private final ImageView mIconThumb; private final Point mThumbSize; private final Uri mUri; private final UserId mUserId; private final long mLastModified; private final Consumer<Bitmap> mCallback; private final boolean mAddToCache; Loading @@ -66,15 +70,17 @@ public final class ThumbnailLoader extends AsyncTask<Uri, Void, Bitmap> implemen /** * @param uri - to a thumbnail. * @param userId - user of the uri. * @param iconThumb - ImageView to display the thumbnail. * @param thumbSize - size of the thumbnail. * @param lastModified - used for updating thumbnail caches. * @param addToCache - flag that determines if the loader saves the thumbnail to the cache. */ public ThumbnailLoader(Uri uri, ImageView iconThumb, Point thumbSize, long lastModified, Consumer<Bitmap> callback, boolean addToCache) { public ThumbnailLoader(Uri uri, UserId userId, ImageView iconThumb, Point thumbSize, long lastModified, Consumer<Bitmap> callback, boolean addToCache) { mUri = uri; mUserId = userId; mIconThumb = iconThumb; mThumbSize = thumbSize; mLastModified = lastModified; Loading @@ -100,7 +106,7 @@ public final class ThumbnailLoader extends AsyncTask<Uri, Void, Bitmap> implemen } final Context context = mIconThumb.getContext(); final ContentResolver resolver = context.getContentResolver(); final ContentResolver resolver = mUserId.getContentResolver(context); ContentProviderClient client = null; Bitmap result = null; Loading @@ -111,7 +117,7 @@ public final class ThumbnailLoader extends AsyncTask<Uri, Void, Bitmap> implemen mUri, mThumbSize, mSignal); if (result != null && mAddToCache) { final ThumbnailCache cache = DocumentsApplication.getThumbnailCache(context); cache.putThumbnail(mUri, mThumbSize, result, mLastModified); cache.putThumbnail(mUri, mUserId, mThumbSize, result, mLastModified); } } catch (Exception e) { if (!(e instanceof OperationCanceledException)) { Loading
src/com/android/documentsui/base/DocumentInfo.java +4 −0 Original line number Diff line number Diff line Loading @@ -418,6 +418,10 @@ public class DocumentInfo implements Durable, Parcelable { getCursorString(cursor, Document.COLUMN_DOCUMENT_ID)); } public static UserId getUserId(Cursor cursor) { return UserId.of(getCursorInt(cursor, RootCursorWrapper.COLUMN_USER_ID)); } public static void addMimeTypes(ContentResolver resolver, Uri uri, Set<String> mimeTypes) { assert(uri != null); if ("content".equals(uri.getScheme())) { Loading
src/com/android/documentsui/dirlist/DirectoryFragment.java +1 −1 Original line number Diff line number Diff line Loading @@ -1161,7 +1161,7 @@ public class DirectoryFragment extends Fragment implements SwipeRefreshLayout.On String[] ids = mModel.getModelIds(); int numOfEvicts = Math.min(ids.length, CACHE_EVICT_LIMIT); for (int i = 0; i < numOfEvicts; ++i) { cache.removeUri(mModel.getItemUri(ids[i])); cache.removeUri(mModel.getItemUri(ids[i]), mModel.getItemUserId(ids[i])); } final DocumentInfo doc = mActivity.getCurrentDirectory(); Loading