Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 4cf7314f authored by Kelvin Kwan's avatar Kelvin Kwan
Browse files

Load and cache thumbnail with correct user

Bug: 149557252
Test: manual
Test: atest DocumentsUIGoogleTests:com.android.documentsui.ThumbnailCacheTest
Change-Id: Ic77d5612b7bd039daf1e7dd432acb09f05c88895
parent abba9759
Loading
Loading
Loading
Loading
+7 −1
Original line number Diff line number Diff line
@@ -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;
@@ -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;
@@ -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;
@@ -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.
+102 −29
Original line number Diff line number Diff line
@@ -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;

/**
@@ -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;

    /**
@@ -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) {
@@ -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);
            }
        }

@@ -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);
        }
    }

@@ -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);
            }
        }
    }
@@ -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);
        }
    }
}
+10 −4
Original line number Diff line number Diff line
@@ -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;

@@ -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;
@@ -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;
@@ -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;
@@ -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)) {
+4 −0
Original line number Diff line number Diff line
@@ -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())) {
+1 −1
Original line number Diff line number Diff line
@@ -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