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