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.Process; 35 import android.os.UserHandle; 36 import android.util.Log; 37 38 import androidx.annotation.NonNull; 39 40 import com.android.launcher3.InvariantDeviceProfile; 41 import com.android.launcher3.LauncherFiles; 42 import com.android.launcher3.R; 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.InstantAppResolver; 56 import com.android.launcher3.util.PackageUserKey; 57 import com.android.launcher3.util.Preconditions; 58 59 import java.util.function.Predicate; 60 import java.util.function.Supplier; 61 62 /** 63 * Cache of application icons. Icons can be made from any thread. 64 */ 65 public class IconCache extends BaseIconCache { 66 67 private static final String TAG = "Launcher.IconCache"; 68 69 private final Predicate<ItemInfoWithIcon> mIsUsingFallbackOrNonDefaultIconCheck = w -> 70 w.bitmap != null && (w.bitmap.isNullOrLowRes() || !isDefaultIcon(w.bitmap, w.user)); 71 72 private final CachingLogic<ComponentWithLabel> mComponentWithLabelCachingLogic; 73 private final CachingLogic<LauncherActivityInfo> mLauncherActivityInfoCachingLogic; 74 private final CachingLogic<ShortcutInfo> mShortcutCachingLogic; 75 76 private final LauncherApps mLauncherApps; 77 private final UserCache mUserManager; 78 private final InstantAppResolver mInstantAppResolver; 79 private final IconProvider mIconProvider; 80 81 private int mPendingIconRequestCount = 0; 82 IconCache(Context context, InvariantDeviceProfile idp)83 public IconCache(Context context, InvariantDeviceProfile idp) { 84 this(context, idp, LauncherFiles.APP_ICONS_DB, new IconProvider(context)); 85 } 86 IconCache(Context context, InvariantDeviceProfile idp, String dbFileName, IconProvider iconProvider)87 public IconCache(Context context, InvariantDeviceProfile idp, String dbFileName, 88 IconProvider iconProvider) { 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 = iconProvider; 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 public 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 * Closes the cache DB. This will clear any in-memory cache. 135 */ close()136 public void close() { 137 mIconDb.close(); 138 } 139 140 /** 141 * Fetches high-res icon for the provided ItemInfo and updates the caller when done. 142 * 143 * @return a request ID that can be used to cancel the request. 144 */ updateIconInBackground(final ItemInfoUpdateReceiver caller, final ItemInfoWithIcon info)145 public HandlerRunnable updateIconInBackground(final ItemInfoUpdateReceiver caller, 146 final ItemInfoWithIcon info) { 147 Preconditions.assertUIThread(); 148 if (mPendingIconRequestCount <= 0) { 149 MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_FOREGROUND); 150 } 151 mPendingIconRequestCount++; 152 153 HandlerRunnable<ItemInfoWithIcon> request = new HandlerRunnable<>(mWorkerHandler, 154 () -> { 155 if (info instanceof AppInfo || info instanceof WorkspaceItemInfo) { 156 getTitleAndIcon(info, false); 157 } else if (info instanceof PackageItemInfo) { 158 getTitleAndIconForApp((PackageItemInfo) info, false); 159 } 160 return info; 161 }, 162 MAIN_EXECUTOR, 163 caller::reapplyItemInfo, 164 this::onIconRequestEnd); 165 Utilities.postAsyncCallback(mWorkerHandler, request); 166 return request; 167 } 168 onIconRequestEnd()169 private void onIconRequestEnd() { 170 mPendingIconRequestCount--; 171 if (mPendingIconRequestCount <= 0) { 172 MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); 173 } 174 } 175 176 /** 177 * Updates {@param application} only if a valid entry is found. 178 */ updateTitleAndIcon(AppInfo application)179 public synchronized void updateTitleAndIcon(AppInfo application) { 180 CacheEntry entry = cacheLocked(application.componentName, 181 application.user, () -> null, mLauncherActivityInfoCachingLogic, 182 false, application.usingLowResIcon()); 183 if (entry.bitmap != null && !isDefaultIcon(entry.bitmap, application.user)) { 184 applyCacheEntry(entry, application); 185 } 186 } 187 188 /** 189 * Fill in {@param info} with the icon and label for {@param activityInfo} 190 */ getTitleAndIcon(ItemInfoWithIcon info, LauncherActivityInfo activityInfo, boolean useLowResIcon)191 public synchronized void getTitleAndIcon(ItemInfoWithIcon info, 192 LauncherActivityInfo activityInfo, boolean useLowResIcon) { 193 // If we already have activity info, no need to use package icon 194 getTitleAndIcon(info, () -> activityInfo, false, useLowResIcon); 195 } 196 197 /** 198 * Fill in {@param info} with the icon for {@param si} 199 */ getShortcutIcon(ItemInfoWithIcon info, ShortcutInfo si)200 public void getShortcutIcon(ItemInfoWithIcon info, ShortcutInfo si) { 201 getShortcutIcon(info, si, true, mIsUsingFallbackOrNonDefaultIconCheck); 202 } 203 204 /** 205 * Fill in {@param info} with an unbadged icon for {@param si} 206 */ getUnbadgedShortcutIcon(ItemInfoWithIcon info, ShortcutInfo si)207 public void getUnbadgedShortcutIcon(ItemInfoWithIcon info, ShortcutInfo si) { 208 getShortcutIcon(info, si, false, mIsUsingFallbackOrNonDefaultIconCheck); 209 } 210 211 /** 212 * Fill in {@param info} with the icon and label for {@param si}. If the icon is not 213 * available, and fallback check returns true, it keeps the old icon. 214 */ getShortcutIcon(T info, ShortcutInfo si, @NonNull Predicate<T> fallbackIconCheck)215 public <T extends ItemInfoWithIcon> void getShortcutIcon(T info, ShortcutInfo si, 216 @NonNull Predicate<T> fallbackIconCheck) { 217 getShortcutIcon(info, si, true /* use badged */, fallbackIconCheck); 218 } 219 getShortcutIcon(T info, ShortcutInfo si, boolean useBadged, @NonNull Predicate<T> fallbackIconCheck)220 private synchronized <T extends ItemInfoWithIcon> void getShortcutIcon(T info, ShortcutInfo si, 221 boolean useBadged, @NonNull Predicate<T> fallbackIconCheck) { 222 BitmapInfo bitmapInfo; 223 if (FeatureFlags.ENABLE_DEEP_SHORTCUT_ICON_CACHE.get()) { 224 bitmapInfo = cacheLocked(ShortcutKey.fromInfo(si).componentName, si.getUserHandle(), 225 () -> si, mShortcutCachingLogic, false, false).bitmap; 226 } else { 227 // If caching is disabled, load the full icon 228 bitmapInfo = mShortcutCachingLogic.loadIcon(mContext, si); 229 } 230 if (bitmapInfo.isNullOrLowRes()) { 231 bitmapInfo = getDefaultIcon(si.getUserHandle()); 232 } 233 234 if (isDefaultIcon(bitmapInfo, si.getUserHandle()) && fallbackIconCheck.test(info)) { 235 return; 236 } 237 info.bitmap = bitmapInfo; 238 if (useBadged) { 239 BitmapInfo badgeInfo = getShortcutInfoBadge(si); 240 try (LauncherIcons li = LauncherIcons.obtain(mContext)) { 241 info.bitmap = li.badgeBitmap(info.bitmap.icon, badgeInfo); 242 } 243 } 244 } 245 246 /** 247 * Returns the badging info for the shortcut 248 */ getShortcutInfoBadge(ShortcutInfo shortcutInfo)249 public BitmapInfo getShortcutInfoBadge(ShortcutInfo shortcutInfo) { 250 ComponentName cn = shortcutInfo.getActivity(); 251 if (cn != null) { 252 // Get the app info for the source activity. 253 AppInfo appInfo = new AppInfo(); 254 appInfo.user = shortcutInfo.getUserHandle(); 255 appInfo.componentName = cn; 256 appInfo.intent = new Intent(Intent.ACTION_MAIN) 257 .addCategory(Intent.CATEGORY_LAUNCHER) 258 .setComponent(cn); 259 getTitleAndIcon(appInfo, false); 260 return appInfo.bitmap; 261 } else { 262 PackageItemInfo pkgInfo = new PackageItemInfo(shortcutInfo.getPackage()); 263 getTitleAndIconForApp(pkgInfo, false); 264 return pkgInfo.bitmap; 265 } 266 } 267 268 /** 269 * Fill in {@param info} with the icon and label. If the 270 * corresponding activity is not found, it reverts to the package icon. 271 */ getTitleAndIcon(ItemInfoWithIcon info, boolean useLowResIcon)272 public synchronized void getTitleAndIcon(ItemInfoWithIcon info, boolean useLowResIcon) { 273 // null info means not installed, but if we have a component from the intent then 274 // we should still look in the cache for restored app icons. 275 if (info.getTargetComponent() == null) { 276 info.bitmap = getDefaultIcon(info.user); 277 info.title = ""; 278 info.contentDescription = ""; 279 } else { 280 Intent intent = info.getIntent(); 281 getTitleAndIcon(info, () -> mLauncherApps.resolveActivity(intent, info.user), 282 true, useLowResIcon); 283 } 284 } 285 getTitleNoCache(ComponentWithLabel info)286 public synchronized String getTitleNoCache(ComponentWithLabel info) { 287 CacheEntry entry = cacheLocked(info.getComponent(), info.getUser(), () -> info, 288 mComponentWithLabelCachingLogic, false /* usePackageIcon */, 289 true /* useLowResIcon */); 290 return Utilities.trim(entry.title); 291 } 292 293 /** 294 * Fill in {@param mWorkspaceItemInfo} with the icon and label for {@param info} 295 */ getTitleAndIcon( @onNull ItemInfoWithIcon infoInOut, @NonNull Supplier<LauncherActivityInfo> activityInfoProvider, boolean usePkgIcon, boolean useLowResIcon)296 public synchronized void getTitleAndIcon( 297 @NonNull ItemInfoWithIcon infoInOut, 298 @NonNull Supplier<LauncherActivityInfo> activityInfoProvider, 299 boolean usePkgIcon, boolean useLowResIcon) { 300 CacheEntry entry = cacheLocked(infoInOut.getTargetComponent(), infoInOut.user, 301 activityInfoProvider, mLauncherActivityInfoCachingLogic, usePkgIcon, 302 useLowResIcon); 303 applyCacheEntry(entry, infoInOut); 304 } 305 306 307 /** 308 * Fill in {@param infoInOut} with the corresponding icon and label. 309 */ getTitleAndIconForApp( PackageItemInfo infoInOut, boolean useLowResIcon)310 public synchronized void getTitleAndIconForApp( 311 PackageItemInfo infoInOut, boolean useLowResIcon) { 312 CacheEntry entry = getEntryForPackageLocked( 313 infoInOut.packageName, infoInOut.user, useLowResIcon); 314 applyCacheEntry(entry, infoInOut); 315 if (infoInOut.category == PackageItemInfo.CONVERSATIONS) { 316 infoInOut.title = mContext.getString(R.string.widget_category_conversations); 317 infoInOut.contentDescription = mPackageManager.getUserBadgedLabel( 318 infoInOut.title, infoInOut.user); 319 } 320 } 321 applyCacheEntry(CacheEntry entry, ItemInfoWithIcon info)322 protected void applyCacheEntry(CacheEntry entry, ItemInfoWithIcon info) { 323 info.title = Utilities.trim(entry.title); 324 info.contentDescription = entry.contentDescription; 325 info.bitmap = (entry.bitmap == null) ? getDefaultIcon(info.user) : entry.bitmap; 326 } 327 getFullResIcon(LauncherActivityInfo info)328 public Drawable getFullResIcon(LauncherActivityInfo info) { 329 return mIconProvider.getIcon(info, mIconDpi); 330 } 331 updateSessionCache(PackageUserKey key, PackageInstaller.SessionInfo info)332 public void updateSessionCache(PackageUserKey key, PackageInstaller.SessionInfo info) { 333 cachePackageInstallInfo(key.mPackageName, key.mUser, info.getAppIcon(), 334 info.getAppLabel()); 335 } 336 337 @Override getIconSystemState(String packageName)338 protected String getIconSystemState(String packageName) { 339 return mIconProvider.getSystemStateForPackage(mSystemState, packageName); 340 } 341 342 /** 343 * Interface for receiving itemInfo with high-res icon. 344 */ 345 public interface ItemInfoUpdateReceiver { 346 reapplyItemInfo(ItemInfoWithIcon info)347 void reapplyItemInfo(ItemInfoWithIcon info); 348 } 349 } 350