1package com.bumptech.glide.load.engine.cache;
2
3import android.annotation.TargetApi;
4import android.app.ActivityManager;
5import android.content.Context;
6import android.os.Build;
7import android.util.DisplayMetrics;
8import android.util.Log;
9
10/**
11 * A calculator that tries to intelligently determine cache sizes for a given device based on some constants and the
12 * devices screen density, width, and height.
13 */
14public class MemorySizeCalculator {
15    private static final String TAG = "MemorySizeCalculator";
16
17    static final int BYTES_PER_ARGB_8888_PIXEL = 4;
18    static final int MEMORY_CACHE_TARGET_SCREENS = 2;
19    static final int BITMAP_POOL_TARGET_SCREENS = 3;
20
21    static final float MAX_SIZE_MULTIPLIER = 0.4f;
22    static final float LOW_MEMORY_MAX_SIZE_MULTIPLIER = 0.33f;
23    private final int bitmapPoolSize;
24    private final int memoryCacheSize;
25
26    interface ScreenDimensions {
27        int getWidthPixels();
28        int getHeightPixels();
29    }
30
31    public MemorySizeCalculator(Context context) {
32        this((ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE),
33                new DisplayMetricsScreenDimensions(context.getResources().getDisplayMetrics()));
34    }
35
36    MemorySizeCalculator(ActivityManager activityManager, ScreenDimensions screenDimensions) {
37        final int maxSize = getMaxSize(activityManager);
38
39        final int screenSize = screenDimensions.getWidthPixels() * screenDimensions.getHeightPixels()
40                * BYTES_PER_ARGB_8888_PIXEL;
41
42        int targetPoolSize = screenSize * BITMAP_POOL_TARGET_SCREENS;
43        int targetMemoryCacheSize = screenSize * MEMORY_CACHE_TARGET_SCREENS;
44
45        if (targetMemoryCacheSize + targetPoolSize <= maxSize) {
46            memoryCacheSize = targetMemoryCacheSize;
47            bitmapPoolSize = targetPoolSize;
48        } else {
49            int part = Math.round((float) maxSize / (BITMAP_POOL_TARGET_SCREENS + MEMORY_CACHE_TARGET_SCREENS));
50            memoryCacheSize = part * MEMORY_CACHE_TARGET_SCREENS;
51            bitmapPoolSize = part * BITMAP_POOL_TARGET_SCREENS;
52        }
53
54        if (Log.isLoggable(TAG, Log.DEBUG)) {
55            Log.d(TAG, "Calculated memory cache size: " + toMb(memoryCacheSize) + " pool size: " + toMb(bitmapPoolSize)
56                    + " memory class limited? " + (targetMemoryCacheSize + targetPoolSize > maxSize) + " max size: "
57                    + toMb(maxSize) + " memoryClass: " + activityManager.getMemoryClass() + " isLowMemoryDevice: "
58                    + isLowMemoryDevice(activityManager));
59        }
60    }
61
62    /**
63     * Returns the recommended memory cache size for the device it is run on in bytes.
64     */
65    public int getMemoryCacheSize() {
66        return memoryCacheSize;
67    }
68
69    /**
70     * Returns the recommended bitmap pool size for the device it is run on in bytes.
71     */
72    public int getBitmapPoolSize() {
73        return bitmapPoolSize;
74    }
75
76    private static int getMaxSize(ActivityManager activityManager) {
77        final int memoryClassBytes = activityManager.getMemoryClass() * 1024 * 1024;
78        final boolean isLowMemoryDevice = isLowMemoryDevice(activityManager);
79        return Math.round(memoryClassBytes
80                * (isLowMemoryDevice ? LOW_MEMORY_MAX_SIZE_MULTIPLIER : MAX_SIZE_MULTIPLIER));
81    }
82
83    private static int toMb(int bytes) {
84        return bytes / (1024 * 1024);
85    }
86
87    @TargetApi(Build.VERSION_CODES.KITKAT)
88    private static boolean isLowMemoryDevice(ActivityManager activityManager) {
89        final int sdkInt = Build.VERSION.SDK_INT;
90        return sdkInt < Build.VERSION_CODES.HONEYCOMB
91                || (sdkInt >= Build.VERSION_CODES.KITKAT && activityManager.isLowRamDevice());
92    }
93
94    private static class DisplayMetricsScreenDimensions implements ScreenDimensions {
95        private final DisplayMetrics displayMetrics;
96
97        public DisplayMetricsScreenDimensions(DisplayMetrics displayMetrics) {
98            this.displayMetrics = displayMetrics;
99        }
100
101        @Override
102        public int getWidthPixels() {
103            return displayMetrics.widthPixels;
104        }
105
106        @Override
107        public int getHeightPixels() {
108            return displayMetrics.heightPixels;
109        }
110    }
111}
112