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 
25 import androidx.annotation.Nullable;
26 import androidx.core.app.ActivityManagerCompat;
27 
28 import java.util.Objects;
29 
30 /**
31  * Implementation of {@link Asset} that wraps another {@link Asset} but keeps an LRU cache of
32  * bitmaps generated by {@link #decodeBitmap(int, int, BitmapReceiver)} to avoid having to decode
33  * the same bitmap multiple times.
34  * The cache key is the wrapped Asset and the target Width and Height requested, so that we only
35  * reuse bitmaps of the same size.
36  */
37 public class BitmapCachingAsset extends Asset {
38 
39     private static class CacheKey {
40         final Asset asset;
41         final int width;
42         final int height;
43 
CacheKey(Asset asset, int width, int height)44         CacheKey(Asset asset, int width, int height) {
45             this.asset = asset;
46             this.width = width;
47             this.height = height;
48         }
49 
50         @Override
hashCode()51         public int hashCode() {
52             return Objects.hash(asset, width, height);
53         }
54 
55         @Override
equals(Object obj)56         public boolean equals(Object obj) {
57             return obj instanceof CacheKey
58                     && ((CacheKey)obj).asset == this.asset
59                     && ((CacheKey)obj).width == this.width
60                     && ((CacheKey)obj).height == this.height;
61         }
62     }
63 
64     private static int cacheSize = 100 * 1024 * 1024; // 100MiB
65     private static LruCache<CacheKey, Bitmap> sCache = new LruCache<CacheKey, Bitmap>(cacheSize) {
66         @Override protected int sizeOf(CacheKey key, Bitmap value) {
67             return value.getByteCount();
68         }
69     };
70 
71     private final boolean mIsLowRam;
72     private final Asset mOriginalAsset;
73 
BitmapCachingAsset(Context context, Asset originalAsset)74     public BitmapCachingAsset(Context context, Asset originalAsset) {
75         mOriginalAsset = originalAsset;
76         mIsLowRam = ActivityManagerCompat.isLowRamDevice(
77                 (ActivityManager) context.getApplicationContext().getSystemService(
78                         Context.ACTIVITY_SERVICE));
79     }
80 
81     @Override
decodeBitmap(int targetWidth, int targetHeight, BitmapReceiver receiver)82     public void decodeBitmap(int targetWidth, int targetHeight, BitmapReceiver receiver) {
83         // Skip the cache in low ram devices
84         if (mIsLowRam) {
85             mOriginalAsset.decodeBitmap(targetWidth, targetHeight, receiver::onBitmapDecoded);
86             return;
87         }
88         CacheKey key = new CacheKey(mOriginalAsset, targetWidth, targetHeight);
89         Bitmap cached = sCache.get(key);
90         if (cached != null) {
91             receiver.onBitmapDecoded(cached);
92         } else {
93             mOriginalAsset.decodeBitmap(targetWidth, targetHeight, bitmap -> {
94                 if (bitmap != null) {
95                     sCache.put(key, bitmap);
96                 }
97                 receiver.onBitmapDecoded(bitmap);
98             });
99         }
100     }
101 
102     @Override
decodeBitmapRegion(Rect rect, int targetWidth, int targetHeight, BitmapReceiver receiver)103     public void decodeBitmapRegion(Rect rect, int targetWidth, int targetHeight,
104             BitmapReceiver receiver) {
105         mOriginalAsset.decodeBitmapRegion(rect, targetWidth, targetHeight, receiver);
106     }
107 
108     @Override
decodeRawDimensions(@ullable Activity activity, DimensionsReceiver receiver)109     public void decodeRawDimensions(@Nullable Activity activity, DimensionsReceiver receiver) {
110         mOriginalAsset.decodeRawDimensions(activity, receiver);
111     }
112 
113     @Override
supportsTiling()114     public boolean supportsTiling() {
115         return mOriginalAsset.supportsTiling();
116     }
117 }
118