blob: 61888b80d01be682ba75c632700f97323598da2a [file] [log] [blame]
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.wallpaper.asset;
import android.app.Activity;
import android.app.ActivityManager;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Point;
import android.graphics.Rect;
import android.util.LruCache;
import android.widget.ImageView;
import androidx.annotation.Nullable;
import androidx.core.app.ActivityManagerCompat;
import java.util.Map;
import java.util.Objects;
/**
* Implementation of {@link Asset} that wraps another {@link Asset} but keeps an LRU cache of
* bitmaps generated by {@link #decodeBitmap(int, int, BitmapReceiver)} to avoid having to decode
* the same bitmap multiple times.
* The cache key is the wrapped Asset and the target Width and Height requested, so that we only
* reuse bitmaps of the same size.
*/
public class BitmapCachingAsset extends Asset {
private static class CacheKey {
final Asset mAsset;
/** a (width x height) of (0 x 0) represents the full image */
final int mWidth;
final int mHeight;
final boolean mRtl;
final Rect mRect;
CacheKey(Asset asset, int width, int height) {
this(asset, width, height, false, null);
}
CacheKey(Asset asset, int width, int height, boolean rtl, Rect rect) {
mAsset = asset;
mWidth = width;
mHeight = height;
mRtl = rtl;
mRect = rect;
}
@Override
public int hashCode() {
return Objects.hash(mAsset, mWidth, mHeight);
}
@Override
public boolean equals(Object obj) {
return obj instanceof CacheKey
&& (Objects.equals(this.mAsset, ((CacheKey) obj).mAsset))
&& ((CacheKey) obj).mWidth == this.mWidth
&& ((CacheKey) obj).mHeight == this.mHeight
&& ((CacheKey) obj).mRtl == this.mRtl
&& (Objects.equals(this.mRect, ((CacheKey) obj).mRect));
}
}
private static int cacheSize = 100 * 1024 * 1024; // 100MiB
private static LruCache<CacheKey, Bitmap> sCache = new LruCache<CacheKey, Bitmap>(cacheSize) {
@Override protected int sizeOf(CacheKey key, Bitmap value) {
return value.getByteCount();
}
};
private final boolean mIsLowRam;
private final Asset mOriginalAsset;
public BitmapCachingAsset(Context context, Asset originalAsset) {
mOriginalAsset = originalAsset instanceof BitmapCachingAsset
? ((BitmapCachingAsset) originalAsset).mOriginalAsset : originalAsset;
mIsLowRam = ActivityManagerCompat.isLowRamDevice(
(ActivityManager) context.getApplicationContext().getSystemService(
Context.ACTIVITY_SERVICE));
}
@Override
public void decodeBitmap(int targetWidth, int targetHeight, boolean useHardwareBitmapIfPossible,
BitmapReceiver receiver) {
// Skip the cache in low ram devices
if (mIsLowRam) {
mOriginalAsset.decodeBitmap(targetWidth, targetHeight, useHardwareBitmapIfPossible,
receiver);
return;
}
CacheKey key = new CacheKey(mOriginalAsset, targetWidth, targetHeight);
Bitmap cached = sCache.get(key);
if (cached != null) {
receiver.onBitmapDecoded(cached);
} else {
BitmapReceiver cachingReceiver = bitmap -> {
if (bitmap != null) {
sCache.put(key, bitmap);
}
receiver.onBitmapDecoded(bitmap);
};
if (targetWidth == 0 && targetHeight == 0) {
mOriginalAsset.decodeBitmap(cachingReceiver);
} else {
mOriginalAsset.decodeBitmap(targetWidth, targetHeight, useHardwareBitmapIfPossible,
cachingReceiver);
}
}
}
@Override
public void decodeBitmap(BitmapReceiver receiver) {
decodeBitmap(0, 0, receiver);
}
@Override
public void decodeBitmapRegion(Rect rect, int targetWidth, int targetHeight,
boolean shouldAdjustForRtl, BitmapReceiver receiver) {
// Skip the cache in low ram devices
if (mIsLowRam) {
mOriginalAsset.decodeBitmapRegion(rect, targetWidth, targetHeight, shouldAdjustForRtl,
receiver);
return;
}
CacheKey key = new CacheKey(mOriginalAsset, targetWidth, targetHeight, shouldAdjustForRtl,
rect);
Bitmap cached = sCache.get(key);
if (cached != null) {
receiver.onBitmapDecoded(cached);
} else {
mOriginalAsset.decodeBitmapRegion(rect, targetWidth, targetHeight, shouldAdjustForRtl,
bitmap -> {
if (bitmap != null) {
sCache.put(key, bitmap);
}
receiver.onBitmapDecoded(bitmap);
});
}
}
@Override
public void decodeRawDimensions(@Nullable Activity activity, DimensionsReceiver receiver) {
mOriginalAsset.decodeRawDimensions(activity, receiver);
}
@Override
public boolean supportsTiling() {
return mOriginalAsset.supportsTiling();
}
@Override
public void loadPreviewImage(Activity activity, ImageView imageView, int placeholderColor,
boolean offsetToStart) {
// Honor the original Asset's preview image loading
mOriginalAsset.loadPreviewImage(activity, imageView, placeholderColor, offsetToStart);
}
@Override
public void loadPreviewImage(Activity activity, ImageView imageView, int placeholderColor,
boolean offsetToStart, @Nullable Map<Point, Rect> cropHints) {
// Honor the original Asset's preview image loading
mOriginalAsset.loadPreviewImage(activity, imageView, placeholderColor, offsetToStart,
cropHints);
}
}