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