1/*
2 * Copyright (C) 2016 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 */
16package com.android.car.apps.common;
17
18import android.graphics.Bitmap;
19import android.util.Log;
20import android.util.SparseArray;
21
22import java.lang.ref.SoftReference;
23import java.lang.reflect.InvocationTargetException;
24import java.lang.reflect.Method;
25import java.util.ArrayList;
26
27
28/**
29 * This class saves recycle bitmap as SoftReference,  which is too vulnerable to
30 * be garbage collected due to other part of application is re-allocating lots of
31 * memory,  we will lose all SoftReference in a GC run.  We should maintain
32 * certain amount of recycled bitmaps in memory, we may also need remove bitmap from LRUCache
33 * if we are not able to get a recycled bitmap here.
34 *
35 * @hide
36 */
37public class RecycleBitmapPool {
38
39    private static final String TAG = "RecycleBitmapPool";
40    private static final boolean DEBUG = false;
41    // allow reuse bitmap with larger bytes, set to 0 to disable different byte size
42    private static final int LARGER_BITMAP_ALLOWED_REUSE = 0;
43    private static final boolean LARGER_BITMAP_ALLOWED = LARGER_BITMAP_ALLOWED_REUSE > 0;
44
45    private static Method sGetAllocationByteCount;
46
47    static {
48        try {
49            // KLP or later
50            sGetAllocationByteCount = Bitmap.class.getMethod("getAllocationByteCount");
51        } catch (NoSuchMethodException e) {
52        }
53    }
54
55    private final SparseArray<ArrayList<SoftReference<Bitmap>>> mRecycled8888 =
56            new SparseArray<ArrayList<SoftReference<Bitmap>>>();
57
58    public RecycleBitmapPool() {
59    }
60
61    public static int getSize(Bitmap bitmap) {
62        if (sGetAllocationByteCount != null) {
63            try {
64                return (Integer) sGetAllocationByteCount.invoke(bitmap);
65            } catch (IllegalArgumentException e) {
66                Log.e(TAG, "getAllocationByteCount() failed", e);
67            } catch (IllegalAccessException e) {
68                Log.e(TAG, "getAllocationByteCount() failed", e);
69            } catch (InvocationTargetException e) {
70                Log.e(TAG, "getAllocationByteCount() failed", e);
71            }
72            sGetAllocationByteCount = null;
73        }
74        return bitmap.getByteCount();
75    }
76
77    private static int getSize(int width, int height) {
78        if (width >= 2048 || height >= 2048) {
79            return 0;
80        }
81        return width * height * 4;
82    }
83
84    public void addRecycledBitmap(Bitmap bitmap) {
85        if (bitmap.isRecycled()) {
86            return;
87        }
88        Bitmap.Config config = bitmap.getConfig();
89        if (config != Bitmap.Config.ARGB_8888) {
90            return;
91        }
92        int key = getSize(bitmap);
93        if (key == 0) {
94            return;
95        }
96        synchronized (mRecycled8888) {
97            ArrayList<SoftReference<Bitmap>> list = mRecycled8888.get(key);
98            if (list == null) {
99                list = new ArrayList<SoftReference<Bitmap>>();
100                mRecycled8888.put(key, list);
101            }
102            list.add(new SoftReference<Bitmap>(bitmap));
103            if (DEBUG) {
104                Log.d(TAG, list.size() + " add bitmap " + bitmap.getWidth() + " "
105                        + bitmap.getHeight());
106            }
107        }
108    }
109
110    public Bitmap getRecycledBitmap(int width, int height) {
111        int key = getSize(width, height);
112        if (key == 0) {
113            return null;
114        }
115        synchronized (mRecycled8888) {
116            // for the new version with getAllocationByteCount(), we allow larger size
117            // to be reused for the bitmap,  otherwise we just looks for same size
118            Bitmap bitmap = getRecycledBitmap(mRecycled8888.get(key));
119            if (sGetAllocationByteCount == null || bitmap != null) {
120                return bitmap;
121            }
122            if (LARGER_BITMAP_ALLOWED) {
123                for (int i = 0, c = mRecycled8888.size(); i < c; i++) {
124                    int k = mRecycled8888.keyAt(i);
125                    if (k > key && k <= key * LARGER_BITMAP_ALLOWED_REUSE) {
126                        bitmap = getRecycledBitmap(mRecycled8888.valueAt(i));
127                        if (bitmap != null) {
128                            return bitmap;
129                        }
130                    }
131                }
132            }
133        }
134        if (DEBUG) {
135            Log.d(TAG, "not avaialbe for " + width + "," + height);
136        }
137        return null;
138    }
139
140    private static Bitmap getRecycledBitmap(ArrayList<SoftReference<Bitmap>> list) {
141        if (list != null && !list.isEmpty()) {
142            while (!list.isEmpty()) {
143                SoftReference<Bitmap> ref = list.remove(list.size() - 1);
144                Bitmap bitmap = ref.get();
145                if (bitmap != null && !bitmap.isRecycled()) {
146                    if (DEBUG) {
147                        Log.d(TAG, "reuse " + bitmap.getWidth() + " " + bitmap.getHeight());
148                    }
149                    return bitmap;
150                } else {
151                    if (DEBUG) {
152                        Log.d(TAG, " we lost SoftReference to bitmap");
153                    }
154                }
155            }
156        }
157        return null;
158    }
159}
160