1 /* 2 * Copyright (C) 2019 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 package com.android.wallpaper.asset; 17 18 import android.app.Activity; 19 import android.app.ActivityManager; 20 import android.content.Context; 21 import android.graphics.Bitmap; 22 import android.graphics.Rect; 23 import android.util.LruCache; 24 import android.widget.ImageView; 25 26 import androidx.annotation.Nullable; 27 import androidx.core.app.ActivityManagerCompat; 28 29 import java.util.Objects; 30 31 /** 32 * Implementation of {@link Asset} that wraps another {@link Asset} but keeps an LRU cache of 33 * bitmaps generated by {@link #decodeBitmap(int, int, BitmapReceiver)} to avoid having to decode 34 * the same bitmap multiple times. 35 * The cache key is the wrapped Asset and the target Width and Height requested, so that we only 36 * reuse bitmaps of the same size. 37 */ 38 public class BitmapCachingAsset extends Asset { 39 40 private static class CacheKey { 41 final Asset mAsset; 42 final int mWidth; 43 final int mHeight; 44 final boolean mRtl; 45 final Rect mRect; 46 CacheKey(Asset asset, int width, int height)47 CacheKey(Asset asset, int width, int height) { 48 this(asset, width, height, false, null); 49 } 50 CacheKey(Asset asset, int width, int height, boolean rtl, Rect rect)51 CacheKey(Asset asset, int width, int height, boolean rtl, Rect rect) { 52 mAsset = asset; 53 mWidth = width; 54 mHeight = height; 55 mRtl = rtl; 56 mRect = rect; 57 } 58 59 @Override hashCode()60 public int hashCode() { 61 return Objects.hash(mAsset, mWidth, mHeight); 62 } 63 64 @Override equals(Object obj)65 public boolean equals(Object obj) { 66 return obj instanceof CacheKey 67 && (Objects.equals(this.mAsset, ((CacheKey) obj).mAsset)) 68 && ((CacheKey) obj).mWidth == this.mWidth 69 && ((CacheKey) obj).mHeight == this.mHeight 70 && ((CacheKey) obj).mRtl == this.mRtl 71 && (Objects.equals(this.mRect, ((CacheKey) obj).mRect)); 72 } 73 } 74 75 private static int cacheSize = 100 * 1024 * 1024; // 100MiB 76 private static LruCache<CacheKey, Bitmap> sCache = new LruCache<CacheKey, Bitmap>(cacheSize) { 77 @Override protected int sizeOf(CacheKey key, Bitmap value) { 78 return value.getByteCount(); 79 } 80 }; 81 82 private final boolean mIsLowRam; 83 private final Asset mOriginalAsset; 84 BitmapCachingAsset(Context context, Asset originalAsset)85 public BitmapCachingAsset(Context context, Asset originalAsset) { 86 mOriginalAsset = originalAsset instanceof BitmapCachingAsset 87 ? ((BitmapCachingAsset) originalAsset).mOriginalAsset : originalAsset; 88 mIsLowRam = ActivityManagerCompat.isLowRamDevice( 89 (ActivityManager) context.getApplicationContext().getSystemService( 90 Context.ACTIVITY_SERVICE)); 91 } 92 93 @Override decodeBitmap(int targetWidth, int targetHeight, BitmapReceiver receiver)94 public void decodeBitmap(int targetWidth, int targetHeight, BitmapReceiver receiver) { 95 // Skip the cache in low ram devices 96 if (mIsLowRam) { 97 mOriginalAsset.decodeBitmap(targetWidth, targetHeight, receiver::onBitmapDecoded); 98 return; 99 } 100 CacheKey key = new CacheKey(mOriginalAsset, targetWidth, targetHeight); 101 Bitmap cached = sCache.get(key); 102 if (cached != null) { 103 receiver.onBitmapDecoded(cached); 104 } else { 105 mOriginalAsset.decodeBitmap(targetWidth, targetHeight, bitmap -> { 106 if (bitmap != null) { 107 sCache.put(key, bitmap); 108 } 109 receiver.onBitmapDecoded(bitmap); 110 }); 111 } 112 } 113 114 @Override decodeBitmapRegion(Rect rect, int targetWidth, int targetHeight, boolean shouldAdjustForRtl, BitmapReceiver receiver)115 public void decodeBitmapRegion(Rect rect, int targetWidth, int targetHeight, 116 boolean shouldAdjustForRtl, BitmapReceiver receiver) { 117 // Skip the cache in low ram devices 118 if (mIsLowRam) { 119 mOriginalAsset.decodeBitmapRegion(rect, targetWidth, targetHeight, shouldAdjustForRtl, 120 receiver); 121 return; 122 } 123 CacheKey key = new CacheKey(mOriginalAsset, targetWidth, targetHeight, shouldAdjustForRtl, 124 rect); 125 Bitmap cached = sCache.get(key); 126 if (cached != null) { 127 receiver.onBitmapDecoded(cached); 128 } else { 129 mOriginalAsset.decodeBitmapRegion(rect, targetWidth, targetHeight, shouldAdjustForRtl, 130 bitmap -> { 131 if (bitmap != null) { 132 sCache.put(key, bitmap); 133 } 134 receiver.onBitmapDecoded(bitmap); 135 }); 136 } 137 } 138 139 @Override decodeRawDimensions(@ullable Activity activity, DimensionsReceiver receiver)140 public void decodeRawDimensions(@Nullable Activity activity, DimensionsReceiver receiver) { 141 mOriginalAsset.decodeRawDimensions(activity, receiver); 142 } 143 144 @Override supportsTiling()145 public boolean supportsTiling() { 146 return mOriginalAsset.supportsTiling(); 147 } 148 149 @Override loadPreviewImage(Activity activity, ImageView imageView, int placeholderColor)150 public void loadPreviewImage(Activity activity, ImageView imageView, int placeholderColor) { 151 // Honor the original Asset's preview image loading 152 mOriginalAsset.loadPreviewImage(activity, imageView, placeholderColor); 153 } 154 } 155