• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
43         /** a (width x height) of (0 x 0) represents the full image */
44         final int mWidth;
45         final int mHeight;
46         final boolean mRtl;
47         final Rect mRect;
48 
CacheKey(Asset asset, int width, int height)49         CacheKey(Asset asset, int width, int height) {
50             this(asset, width, height, false, null);
51         }
52 
CacheKey(Asset asset, int width, int height, boolean rtl, Rect rect)53         CacheKey(Asset asset, int width, int height, boolean rtl, Rect rect) {
54             mAsset = asset;
55             mWidth = width;
56             mHeight = height;
57             mRtl = rtl;
58             mRect = rect;
59         }
60 
61         @Override
hashCode()62         public int hashCode() {
63             return Objects.hash(mAsset, mWidth, mHeight);
64         }
65 
66         @Override
equals(Object obj)67         public boolean equals(Object obj) {
68             return obj instanceof CacheKey
69                     && (Objects.equals(this.mAsset, ((CacheKey) obj).mAsset))
70                     && ((CacheKey) obj).mWidth == this.mWidth
71                     && ((CacheKey) obj).mHeight == this.mHeight
72                     && ((CacheKey) obj).mRtl == this.mRtl
73                     && (Objects.equals(this.mRect, ((CacheKey) obj).mRect));
74         }
75     }
76 
77     private static int cacheSize = 100 * 1024 * 1024; // 100MiB
78     private static LruCache<CacheKey, Bitmap> sCache = new LruCache<CacheKey, Bitmap>(cacheSize) {
79         @Override protected int sizeOf(CacheKey key, Bitmap value) {
80             return value.getByteCount();
81         }
82     };
83 
84     private final boolean mIsLowRam;
85     private final Asset mOriginalAsset;
86 
BitmapCachingAsset(Context context, Asset originalAsset)87     public BitmapCachingAsset(Context context, Asset originalAsset) {
88         mOriginalAsset = originalAsset instanceof BitmapCachingAsset
89                 ? ((BitmapCachingAsset) originalAsset).mOriginalAsset : originalAsset;
90         mIsLowRam = ActivityManagerCompat.isLowRamDevice(
91                 (ActivityManager) context.getApplicationContext().getSystemService(
92                         Context.ACTIVITY_SERVICE));
93     }
94 
95     @Override
decodeBitmap(int targetWidth, int targetHeight, boolean useHardwareBitmapIfPossible, BitmapReceiver receiver)96     public void decodeBitmap(int targetWidth, int targetHeight, boolean useHardwareBitmapIfPossible,
97             BitmapReceiver receiver) {
98         // Skip the cache in low ram devices
99         if (mIsLowRam) {
100             mOriginalAsset.decodeBitmap(targetWidth, targetHeight, useHardwareBitmapIfPossible,
101                     receiver);
102             return;
103         }
104         CacheKey key = new CacheKey(mOriginalAsset, targetWidth, targetHeight);
105         Bitmap cached = sCache.get(key);
106         if (cached != null) {
107             receiver.onBitmapDecoded(cached);
108         } else {
109             BitmapReceiver cachingReceiver = bitmap -> {
110                 if (bitmap != null) {
111                     sCache.put(key, bitmap);
112                 }
113                 receiver.onBitmapDecoded(bitmap);
114             };
115             if (targetWidth == 0 && targetHeight == 0) {
116                 mOriginalAsset.decodeBitmap(cachingReceiver);
117             } else {
118                 mOriginalAsset.decodeBitmap(targetWidth, targetHeight, useHardwareBitmapIfPossible,
119                         cachingReceiver);
120             }
121         }
122     }
123 
124     @Override
decodeBitmap(BitmapReceiver receiver)125     public void decodeBitmap(BitmapReceiver receiver) {
126         decodeBitmap(0, 0, receiver);
127     }
128 
129     @Override
decodeBitmapRegion(Rect rect, int targetWidth, int targetHeight, boolean shouldAdjustForRtl, BitmapReceiver receiver)130     public void decodeBitmapRegion(Rect rect, int targetWidth, int targetHeight,
131             boolean shouldAdjustForRtl, BitmapReceiver receiver) {
132         // Skip the cache in low ram devices
133         if (mIsLowRam) {
134             mOriginalAsset.decodeBitmapRegion(rect, targetWidth, targetHeight, shouldAdjustForRtl,
135                     receiver);
136             return;
137         }
138         CacheKey key = new CacheKey(mOriginalAsset, targetWidth, targetHeight, shouldAdjustForRtl,
139                 rect);
140         Bitmap cached = sCache.get(key);
141         if (cached != null) {
142             receiver.onBitmapDecoded(cached);
143         } else {
144             mOriginalAsset.decodeBitmapRegion(rect, targetWidth, targetHeight, shouldAdjustForRtl,
145                     bitmap -> {
146                         if (bitmap != null) {
147                             sCache.put(key, bitmap);
148                         }
149                         receiver.onBitmapDecoded(bitmap);
150                     });
151         }
152     }
153 
154     @Override
decodeRawDimensions(@ullable Activity activity, DimensionsReceiver receiver)155     public void decodeRawDimensions(@Nullable Activity activity, DimensionsReceiver receiver) {
156         mOriginalAsset.decodeRawDimensions(activity, receiver);
157     }
158 
159     @Override
supportsTiling()160     public boolean supportsTiling() {
161         return mOriginalAsset.supportsTiling();
162     }
163 
164     @Override
loadPreviewImage(Activity activity, ImageView imageView, int placeholderColor, boolean offsetToStart)165     public void loadPreviewImage(Activity activity, ImageView imageView, int placeholderColor,
166             boolean offsetToStart) {
167         // Honor the original Asset's preview image loading
168         mOriginalAsset.loadPreviewImage(activity, imageView, placeholderColor, offsetToStart);
169     }
170 }
171