1 /* 2 * Copyright (C) 2008 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.launcher3.icons; 18 19 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; 20 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; 21 22 import android.content.ComponentName; 23 import android.content.Context; 24 import android.content.Intent; 25 import android.content.pm.ApplicationInfo; 26 import android.content.pm.LauncherActivityInfo; 27 import android.content.pm.LauncherApps; 28 import android.content.pm.PackageInfo; 29 import android.content.pm.PackageInstaller; 30 import android.content.pm.PackageManager; 31 import android.content.pm.PackageManager.NameNotFoundException; 32 import android.content.pm.ShortcutInfo; 33 import android.graphics.drawable.Drawable; 34 import android.os.Handler; 35 import android.os.Process; 36 import android.os.UserHandle; 37 import android.util.Log; 38 39 import androidx.annotation.NonNull; 40 41 import com.android.launcher3.InvariantDeviceProfile; 42 import com.android.launcher3.LauncherFiles; 43 import com.android.launcher3.Utilities; 44 import com.android.launcher3.config.FeatureFlags; 45 import com.android.launcher3.icons.ComponentWithLabel.ComponentCachingLogic; 46 import com.android.launcher3.icons.cache.BaseIconCache; 47 import com.android.launcher3.icons.cache.CachingLogic; 48 import com.android.launcher3.icons.cache.HandlerRunnable; 49 import com.android.launcher3.model.data.AppInfo; 50 import com.android.launcher3.model.data.ItemInfoWithIcon; 51 import com.android.launcher3.model.data.PackageItemInfo; 52 import com.android.launcher3.model.data.WorkspaceItemInfo; 53 import com.android.launcher3.pm.UserCache; 54 import com.android.launcher3.shortcuts.ShortcutKey; 55 import com.android.launcher3.util.ComponentKey; 56 import com.android.launcher3.util.InstantAppResolver; 57 import com.android.launcher3.util.PackageUserKey; 58 import com.android.launcher3.util.Preconditions; 59 60 import java.util.function.Predicate; 61 import java.util.function.Supplier; 62 63 /** 64 * Cache of application icons. Icons can be made from any thread. 65 */ 66 public class IconCache extends BaseIconCache { 67 68 private static final String TAG = "Launcher.IconCache"; 69 70 private final Predicate<ItemInfoWithIcon> mIsUsingFallbackIconCheck = w -> w.bitmap != null 71 && w.bitmap.isNullOrLowRes() && !isDefaultIcon(w.bitmap, w.user); 72 73 private final CachingLogic<ComponentWithLabel> mComponentWithLabelCachingLogic; 74 private final CachingLogic<LauncherActivityInfo> mLauncherActivityInfoCachingLogic; 75 private final CachingLogic<ShortcutInfo> mShortcutCachingLogic; 76 77 private final LauncherApps mLauncherApps; 78 private final UserCache mUserManager; 79 private final InstantAppResolver mInstantAppResolver; 80 private final IconProvider mIconProvider; 81 82 private int mPendingIconRequestCount = 0; 83 IconCache(Context context, InvariantDeviceProfile idp)84 public IconCache(Context context, InvariantDeviceProfile idp) { 85 this(context, idp, LauncherFiles.APP_ICONS_DB); 86 } 87 IconCache(Context context, InvariantDeviceProfile idp, String dbFileName)88 public IconCache(Context context, InvariantDeviceProfile idp, String dbFileName) { 89 super(context, dbFileName, MODEL_EXECUTOR.getLooper(), 90 idp.fillResIconDpi, idp.iconBitmapSize, true /* inMemoryCache */); 91 mComponentWithLabelCachingLogic = new ComponentCachingLogic(context, false); 92 mLauncherActivityInfoCachingLogic = LauncherActivityCachingLogic.newInstance(context); 93 mShortcutCachingLogic = new ShortcutCachingLogic(); 94 mLauncherApps = mContext.getSystemService(LauncherApps.class); 95 mUserManager = UserCache.INSTANCE.get(mContext); 96 mInstantAppResolver = InstantAppResolver.newInstance(mContext); 97 mIconProvider = new IconProvider(context); 98 } 99 100 @Override getSerialNumberForUser(UserHandle user)101 protected long getSerialNumberForUser(UserHandle user) { 102 return mUserManager.getSerialNumberForUser(user); 103 } 104 105 @Override isInstantApp(ApplicationInfo info)106 protected boolean isInstantApp(ApplicationInfo info) { 107 return mInstantAppResolver.isInstantApp(info); 108 } 109 110 @Override getIconFactory()111 protected BaseIconFactory getIconFactory() { 112 return LauncherIcons.obtain(mContext); 113 } 114 115 /** 116 * Updates the entries related to the given package in memory and persistent DB. 117 */ updateIconsForPkg(String packageName, UserHandle user)118 public synchronized void updateIconsForPkg(String packageName, UserHandle user) { 119 removeIconsForPkg(packageName, user); 120 try { 121 PackageInfo info = mPackageManager.getPackageInfo(packageName, 122 PackageManager.GET_UNINSTALLED_PACKAGES); 123 long userSerial = mUserManager.getSerialNumberForUser(user); 124 for (LauncherActivityInfo app : mLauncherApps.getActivityList(packageName, user)) { 125 addIconToDBAndMemCache(app, mLauncherActivityInfoCachingLogic, info, userSerial, 126 false /*replace existing*/); 127 } 128 } catch (NameNotFoundException e) { 129 Log.d(TAG, "Package not found", e); 130 } 131 } 132 133 /** 134 * Fetches high-res icon for the provided ItemInfo and updates the caller when done. 135 * @return a request ID that can be used to cancel the request. 136 */ updateIconInBackground(final ItemInfoUpdateReceiver caller, final ItemInfoWithIcon info)137 public IconLoadRequest updateIconInBackground(final ItemInfoUpdateReceiver caller, 138 final ItemInfoWithIcon info) { 139 Preconditions.assertUIThread(); 140 if (mPendingIconRequestCount <= 0) { 141 MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_FOREGROUND); 142 } 143 mPendingIconRequestCount ++; 144 145 IconLoadRequest request = new IconLoadRequest(mWorkerHandler, this::onIconRequestEnd) { 146 @Override 147 public void run() { 148 if (info instanceof AppInfo || info instanceof WorkspaceItemInfo) { 149 getTitleAndIcon(info, false); 150 } else if (info instanceof PackageItemInfo) { 151 getTitleAndIconForApp((PackageItemInfo) info, false); 152 } 153 MAIN_EXECUTOR.execute(() -> { 154 caller.reapplyItemInfo(info); 155 onEnd(); 156 }); 157 } 158 }; 159 Utilities.postAsyncCallback(mWorkerHandler, request); 160 return request; 161 } 162 onIconRequestEnd()163 private void onIconRequestEnd() { 164 mPendingIconRequestCount --; 165 if (mPendingIconRequestCount <= 0) { 166 MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); 167 } 168 } 169 170 /** 171 * Updates {@param application} only if a valid entry is found. 172 */ updateTitleAndIcon(AppInfo application)173 public synchronized void updateTitleAndIcon(AppInfo application) { 174 CacheEntry entry = cacheLocked(application.componentName, 175 application.user, () -> null, mLauncherActivityInfoCachingLogic, 176 false, application.usingLowResIcon()); 177 if (entry.bitmap != null && !isDefaultIcon(entry.bitmap, application.user)) { 178 applyCacheEntry(entry, application); 179 } 180 } 181 182 /** 183 * Fill in {@param info} with the icon and label for {@param activityInfo} 184 */ getTitleAndIcon(ItemInfoWithIcon info, LauncherActivityInfo activityInfo, boolean useLowResIcon)185 public synchronized void getTitleAndIcon(ItemInfoWithIcon info, 186 LauncherActivityInfo activityInfo, boolean useLowResIcon) { 187 // If we already have activity info, no need to use package icon 188 getTitleAndIcon(info, () -> activityInfo, false, useLowResIcon); 189 } 190 191 /** 192 * Fill in {@param info} with the icon for {@param si} 193 */ getShortcutIcon(ItemInfoWithIcon info, ShortcutInfo si)194 public void getShortcutIcon(ItemInfoWithIcon info, ShortcutInfo si) { 195 getShortcutIcon(info, si, true, mIsUsingFallbackIconCheck); 196 } 197 198 /** 199 * Fill in {@param info} with an unbadged icon for {@param si} 200 */ getUnbadgedShortcutIcon(ItemInfoWithIcon info, ShortcutInfo si)201 public void getUnbadgedShortcutIcon(ItemInfoWithIcon info, ShortcutInfo si) { 202 getShortcutIcon(info, si, false, mIsUsingFallbackIconCheck); 203 } 204 205 /** 206 * Fill in {@param info} with the icon and label for {@param si}. If the icon is not 207 * available, and fallback check returns true, it keeps the old icon. 208 */ getShortcutIcon(T info, ShortcutInfo si, @NonNull Predicate<T> fallbackIconCheck)209 public <T extends ItemInfoWithIcon> void getShortcutIcon(T info, ShortcutInfo si, 210 @NonNull Predicate<T> fallbackIconCheck) { 211 getShortcutIcon(info, si, true /* use badged */, fallbackIconCheck); 212 } 213 getShortcutIcon(T info, ShortcutInfo si, boolean useBadged, @NonNull Predicate<T> fallbackIconCheck)214 private synchronized <T extends ItemInfoWithIcon> void getShortcutIcon(T info, ShortcutInfo si, 215 boolean useBadged, @NonNull Predicate<T> fallbackIconCheck) { 216 BitmapInfo bitmapInfo; 217 if (FeatureFlags.ENABLE_DEEP_SHORTCUT_ICON_CACHE.get()) { 218 bitmapInfo = cacheLocked(ShortcutKey.fromInfo(si).componentName, si.getUserHandle(), 219 () -> si, mShortcutCachingLogic, false, false).bitmap; 220 } else { 221 // If caching is disabled, load the full icon 222 bitmapInfo = mShortcutCachingLogic.loadIcon(mContext, si); 223 } 224 if (bitmapInfo.isNullOrLowRes()) { 225 bitmapInfo = getDefaultIcon(si.getUserHandle()); 226 } 227 228 if (isDefaultIcon(bitmapInfo, si.getUserHandle()) && fallbackIconCheck.test(info)) { 229 return; 230 } 231 info.bitmap = bitmapInfo; 232 if (useBadged) { 233 BitmapInfo badgeInfo = getShortcutInfoBadge(si); 234 try (LauncherIcons li = LauncherIcons.obtain(mContext)) { 235 info.bitmap = li.badgeBitmap(info.bitmap.icon, badgeInfo); 236 } 237 } 238 } 239 240 /** 241 * Returns the badging info for the shortcut 242 */ getShortcutInfoBadge(ShortcutInfo shortcutInfo)243 public BitmapInfo getShortcutInfoBadge(ShortcutInfo shortcutInfo) { 244 ComponentName cn = shortcutInfo.getActivity(); 245 if (cn != null) { 246 // Get the app info for the source activity. 247 AppInfo appInfo = new AppInfo(); 248 appInfo.user = shortcutInfo.getUserHandle(); 249 appInfo.componentName = cn; 250 appInfo.intent = new Intent(Intent.ACTION_MAIN) 251 .addCategory(Intent.CATEGORY_LAUNCHER) 252 .setComponent(cn); 253 getTitleAndIcon(appInfo, false); 254 return appInfo.bitmap; 255 } else { 256 PackageItemInfo pkgInfo = new PackageItemInfo(shortcutInfo.getPackage()); 257 getTitleAndIconForApp(pkgInfo, false); 258 return pkgInfo.bitmap; 259 } 260 } 261 262 /** 263 * Fill in info with the icon and label for deep shortcut. 264 */ getDeepShortcutTitleAndIcon(ShortcutInfo info)265 public synchronized CacheEntry getDeepShortcutTitleAndIcon(ShortcutInfo info) { 266 return cacheLocked(ShortcutKey.fromInfo(info).componentName, info.getUserHandle(), 267 () -> info, mShortcutCachingLogic, false, false); 268 } 269 270 /** 271 * Fill in {@param info} with the icon and label. If the 272 * corresponding activity is not found, it reverts to the package icon. 273 */ getTitleAndIcon(ItemInfoWithIcon info, boolean useLowResIcon)274 public synchronized void getTitleAndIcon(ItemInfoWithIcon info, boolean useLowResIcon) { 275 // null info means not installed, but if we have a component from the intent then 276 // we should still look in the cache for restored app icons. 277 if (info.getTargetComponent() == null) { 278 info.bitmap = getDefaultIcon(info.user); 279 info.title = ""; 280 info.contentDescription = ""; 281 } else { 282 Intent intent = info.getIntent(); 283 getTitleAndIcon(info, () -> mLauncherApps.resolveActivity(intent, info.user), 284 true, useLowResIcon); 285 } 286 } 287 getTitleNoCache(ComponentWithLabel info)288 public synchronized String getTitleNoCache(ComponentWithLabel info) { 289 CacheEntry entry = cacheLocked(info.getComponent(), info.getUser(), () -> info, 290 mComponentWithLabelCachingLogic, false /* usePackageIcon */, 291 true /* useLowResIcon */); 292 return Utilities.trim(entry.title); 293 } 294 295 /** 296 * Fill in {@param mWorkspaceItemInfo} with the icon and label for {@param info} 297 */ getTitleAndIcon( @onNull ItemInfoWithIcon infoInOut, @NonNull Supplier<LauncherActivityInfo> activityInfoProvider, boolean usePkgIcon, boolean useLowResIcon)298 private synchronized void getTitleAndIcon( 299 @NonNull ItemInfoWithIcon infoInOut, 300 @NonNull Supplier<LauncherActivityInfo> activityInfoProvider, 301 boolean usePkgIcon, boolean useLowResIcon) { 302 CacheEntry entry = cacheLocked(infoInOut.getTargetComponent(), infoInOut.user, 303 activityInfoProvider, mLauncherActivityInfoCachingLogic, usePkgIcon, useLowResIcon); 304 applyCacheEntry(entry, infoInOut); 305 } 306 307 308 /** 309 * Fill in {@param infoInOut} with the corresponding icon and label. 310 */ getTitleAndIconForApp( PackageItemInfo infoInOut, boolean useLowResIcon)311 public synchronized void getTitleAndIconForApp( 312 PackageItemInfo infoInOut, boolean useLowResIcon) { 313 CacheEntry entry = getEntryForPackageLocked( 314 infoInOut.packageName, infoInOut.user, useLowResIcon); 315 applyCacheEntry(entry, infoInOut); 316 } 317 applyCacheEntry(CacheEntry entry, ItemInfoWithIcon info)318 protected void applyCacheEntry(CacheEntry entry, ItemInfoWithIcon info) { 319 info.title = Utilities.trim(entry.title); 320 info.contentDescription = entry.contentDescription; 321 info.bitmap = (entry.bitmap == null) ? getDefaultIcon(info.user) : entry.bitmap; 322 } 323 getFullResIcon(LauncherActivityInfo info)324 public Drawable getFullResIcon(LauncherActivityInfo info) { 325 return mIconProvider.getIcon(info, mIconDpi); 326 } 327 updateSessionCache(PackageUserKey key, PackageInstaller.SessionInfo info)328 public void updateSessionCache(PackageUserKey key, PackageInstaller.SessionInfo info) { 329 cachePackageInstallInfo(key.mPackageName, key.mUser, info.getAppIcon(), info.getAppLabel()); 330 } 331 332 @Override getIconSystemState(String packageName)333 protected String getIconSystemState(String packageName) { 334 return mIconProvider.getSystemStateForPackage(mSystemState, packageName) 335 + ",flags_asi:" + FeatureFlags.APP_SEARCH_IMPROVEMENTS.get(); 336 } 337 338 @Override getEntryFromDB(ComponentKey cacheKey, CacheEntry entry, boolean lowRes)339 protected boolean getEntryFromDB(ComponentKey cacheKey, CacheEntry entry, boolean lowRes) { 340 if (mIconProvider.isClockIcon(cacheKey)) { 341 // For clock icon, we always load the dynamic icon 342 return false; 343 } 344 return super.getEntryFromDB(cacheKey, entry, lowRes); 345 } 346 347 public static abstract class IconLoadRequest extends HandlerRunnable { IconLoadRequest(Handler handler, Runnable endRunnable)348 IconLoadRequest(Handler handler, Runnable endRunnable) { 349 super(handler, endRunnable); 350 } 351 } 352 353 /** 354 * Interface for receiving itemInfo with high-res icon. 355 */ 356 public interface ItemInfoUpdateReceiver { 357 reapplyItemInfo(ItemInfoWithIcon info)358 void reapplyItemInfo(ItemInfoWithIcon info); 359 } 360 } 361