/* * 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.Rect; import android.util.LruCache; import androidx.annotation.Nullable; import androidx.core.app.ActivityManagerCompat; 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 asset; final int width; final int height; CacheKey(Asset asset, int width, int height) { this.asset = asset; this.width = width; this.height = height; } @Override public int hashCode() { return Objects.hash(asset, width, height); } @Override public boolean equals(Object obj) { return obj instanceof CacheKey && ((CacheKey)obj).asset == this.asset && ((CacheKey)obj).width == this.width && ((CacheKey)obj).height == this.height; } } private static int cacheSize = 100 * 1024 * 1024; // 100MiB private static LruCache sCache = new LruCache(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; mIsLowRam = ActivityManagerCompat.isLowRamDevice( (ActivityManager) context.getApplicationContext().getSystemService( Context.ACTIVITY_SERVICE)); } @Override public void decodeBitmap(int targetWidth, int targetHeight, BitmapReceiver receiver) { // Skip the cache in low ram devices if (mIsLowRam) { mOriginalAsset.decodeBitmap(targetWidth, targetHeight, receiver::onBitmapDecoded); return; } CacheKey key = new CacheKey(mOriginalAsset, targetWidth, targetHeight); Bitmap cached = sCache.get(key); if (cached != null) { receiver.onBitmapDecoded(cached); } else { mOriginalAsset.decodeBitmap(targetWidth, targetHeight, bitmap -> { if (bitmap != null) { sCache.put(key, bitmap); } receiver.onBitmapDecoded(bitmap); }); } } @Override public void decodeBitmapRegion(Rect rect, int targetWidth, int targetHeight, BitmapReceiver receiver) { mOriginalAsset.decodeBitmapRegion(rect, targetWidth, targetHeight, receiver); } @Override public void decodeRawDimensions(@Nullable Activity activity, DimensionsReceiver receiver) { mOriginalAsset.decodeRawDimensions(activity, receiver); } @Override public boolean supportsTiling() { return mOriginalAsset.supportsTiling(); } }