• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 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 
17 package com.android.settingslib.applications;
18 
19 import android.graphics.drawable.BitmapDrawable;
20 import android.graphics.drawable.Drawable;
21 import android.os.UserHandle;
22 import android.util.Log;
23 import android.util.LruCache;
24 
25 import androidx.annotation.NonNull;
26 import androidx.annotation.VisibleForTesting;
27 
28 /**
29  * Cache app icon for management.
30  */
31 public class AppIconCacheManager {
32     private static final String TAG = "AppIconCacheManager";
33     private static final float CACHE_RATIO = 0.1f;
34     @VisibleForTesting
35     static final int MAX_CACHE_SIZE_IN_KB = getMaxCacheInKb();
36     private static final String DELIMITER = ":";
37     private static volatile AppIconCacheManager sAppIconCacheManager;
38     private final LruCache<String, Drawable> mDrawableCache;
39 
AppIconCacheManager()40     private AppIconCacheManager() {
41         mDrawableCache = new LruCache<String, Drawable>(MAX_CACHE_SIZE_IN_KB) {
42             @Override
43             protected int sizeOf(String key, Drawable drawable) {
44                 if (drawable instanceof BitmapDrawable) {
45                     return ((BitmapDrawable) drawable).getBitmap().getByteCount() / 1024;
46                 }
47                 // Rough estimate each pixel will use 4 bytes by default.
48                 return drawable.getIntrinsicHeight() * drawable.getIntrinsicWidth() * 4 / 1024;
49             }
50         };
51     }
52 
53     /**
54      * Get an {@link AppIconCacheManager} instance.
55      */
getInstance()56     public static @NonNull AppIconCacheManager getInstance() {
57         AppIconCacheManager result = sAppIconCacheManager;
58         if (result == null) {
59             synchronized (AppIconCacheManager.class) {
60                 result = sAppIconCacheManager;
61                 if (result == null) {
62                     result = new AppIconCacheManager();
63                     sAppIconCacheManager = result;
64                 }
65             }
66         }
67         return result;
68     }
69 
70     /**
71      * Put app icon to cache
72      *
73      * @param packageName of icon
74      * @param uid         of packageName
75      * @param drawable    app icon
76      */
put(String packageName, int uid, Drawable drawable)77     public void put(String packageName, int uid, Drawable drawable) {
78         final String key = getKey(packageName, uid);
79         if (key == null || drawable == null || drawable.getIntrinsicHeight() < 0
80                 || drawable.getIntrinsicWidth() < 0) {
81             Log.w(TAG, "Invalid key or drawable.");
82             return;
83         }
84         mDrawableCache.put(key, drawable);
85     }
86 
87     /**
88      * Get app icon from cache.
89      *
90      * @param packageName of icon
91      * @param uid         of packageName
92      * @return app icon
93      */
get(String packageName, int uid)94     public Drawable get(String packageName, int uid) {
95         final String key = getKey(packageName, uid);
96         if (key == null) {
97             Log.w(TAG, "Invalid key with package or uid.");
98             return null;
99         }
100         final Drawable cachedDrawable = mDrawableCache.get(key);
101         return cachedDrawable != null ? cachedDrawable.mutate() : null;
102     }
103 
104     /**
105      * Release cache.
106      */
release()107     public static void release() {
108         if (sAppIconCacheManager != null) {
109             sAppIconCacheManager.mDrawableCache.evictAll();
110         }
111     }
112 
getKey(String packageName, int uid)113     private static String getKey(String packageName, int uid) {
114         if (packageName == null || uid < 0) {
115             return null;
116         }
117         return packageName + DELIMITER + UserHandle.getUserId(uid);
118     }
119 
getMaxCacheInKb()120     private static int getMaxCacheInKb() {
121         return Math.round(CACHE_RATIO * Runtime.getRuntime().maxMemory() / 1024);
122     }
123 
124     /**
125      * Clears as much memory as possible.
126      *
127      * @see android.content.ComponentCallbacks2#onTrimMemory(int)
128      */
trimMemory(int level)129     public static void trimMemory(int level) {
130         if (level >= android.content.ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) {
131             // Time to clear everything
132             if (sAppIconCacheManager != null) {
133                 sAppIconCacheManager.mDrawableCache.trimToSize(0);
134             }
135         } else if (level >= android.content.ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN
136                 || level == android.content.ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL) {
137             // Tough time but still affordable, clear half of the cache
138             if (sAppIconCacheManager != null) {
139                 final int maxSize = sAppIconCacheManager.mDrawableCache.maxSize();
140                 sAppIconCacheManager.mDrawableCache.trimToSize(maxSize / 2);
141             }
142         }
143     }
144 }
145