1 /* 2 * Copyright (C) 2018 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 package com.android.launcher3.icons.cache; 17 18 import static com.android.launcher3.icons.BaseIconFactory.getFullResDefaultActivityIcon; 19 import static com.android.launcher3.icons.BitmapInfo.LOW_RES_ICON; 20 import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound; 21 22 import android.content.ComponentName; 23 import android.content.ContentValues; 24 import android.content.Context; 25 import android.content.pm.ActivityInfo; 26 import android.content.pm.ApplicationInfo; 27 import android.content.pm.PackageInfo; 28 import android.content.pm.PackageManager; 29 import android.content.pm.PackageManager.NameNotFoundException; 30 import android.content.res.Resources; 31 import android.database.Cursor; 32 import android.database.sqlite.SQLiteDatabase; 33 import android.database.sqlite.SQLiteException; 34 import android.graphics.Bitmap; 35 import android.graphics.BitmapFactory; 36 import android.graphics.drawable.Drawable; 37 import android.os.Build; 38 import android.os.Handler; 39 import android.os.LocaleList; 40 import android.os.Looper; 41 import android.os.Process; 42 import android.os.UserHandle; 43 import android.text.TextUtils; 44 import android.util.Log; 45 46 import androidx.annotation.NonNull; 47 import androidx.annotation.Nullable; 48 import androidx.annotation.VisibleForTesting; 49 50 import com.android.launcher3.icons.BaseIconFactory; 51 import com.android.launcher3.icons.BitmapInfo; 52 import com.android.launcher3.icons.BitmapRenderer; 53 import com.android.launcher3.icons.GraphicsUtils; 54 import com.android.launcher3.util.ComponentKey; 55 import com.android.launcher3.util.SQLiteCacheHelper; 56 57 import java.util.AbstractMap; 58 import java.util.Collections; 59 import java.util.HashMap; 60 import java.util.HashSet; 61 import java.util.Map; 62 import java.util.Set; 63 import java.util.function.Supplier; 64 65 public abstract class BaseIconCache { 66 67 private static final String TAG = "BaseIconCache"; 68 private static final boolean DEBUG = false; 69 70 private static final int INITIAL_ICON_CACHE_CAPACITY = 50; 71 72 // Empty class name is used for storing package default entry. 73 public static final String EMPTY_CLASS_NAME = "."; 74 75 public static class CacheEntry { 76 77 @NonNull 78 public BitmapInfo bitmap = BitmapInfo.LOW_RES_INFO; 79 public CharSequence title = ""; 80 public CharSequence contentDescription = ""; 81 } 82 83 private final HashMap<UserHandle, BitmapInfo> mDefaultIcons = new HashMap<>(); 84 85 protected final Context mContext; 86 protected final PackageManager mPackageManager; 87 88 private final Map<ComponentKey, CacheEntry> mCache; 89 protected final Handler mWorkerHandler; 90 91 protected int mIconDpi; 92 protected IconDB mIconDb; 93 protected LocaleList mLocaleList = LocaleList.getEmptyLocaleList(); 94 protected String mSystemState = ""; 95 96 private final String mDbFileName; 97 private final BitmapFactory.Options mDecodeOptions; 98 private final Looper mBgLooper; 99 BaseIconCache(Context context, String dbFileName, Looper bgLooper, int iconDpi, int iconPixelSize, boolean inMemoryCache)100 public BaseIconCache(Context context, String dbFileName, Looper bgLooper, 101 int iconDpi, int iconPixelSize, boolean inMemoryCache) { 102 mContext = context; 103 mDbFileName = dbFileName; 104 mPackageManager = context.getPackageManager(); 105 mBgLooper = bgLooper; 106 mWorkerHandler = new Handler(mBgLooper); 107 108 if (inMemoryCache) { 109 mCache = new HashMap<>(INITIAL_ICON_CACHE_CAPACITY); 110 } else { 111 // Use a dummy cache 112 mCache = new AbstractMap<ComponentKey, CacheEntry>() { 113 @Override 114 public Set<Entry<ComponentKey, CacheEntry>> entrySet() { 115 return Collections.emptySet(); 116 } 117 118 @Override 119 public CacheEntry put(ComponentKey key, CacheEntry value) { 120 return value; 121 } 122 }; 123 } 124 125 if (BitmapRenderer.USE_HARDWARE_BITMAP && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 126 mDecodeOptions = new BitmapFactory.Options(); 127 mDecodeOptions.inPreferredConfig = Bitmap.Config.HARDWARE; 128 } else { 129 mDecodeOptions = null; 130 } 131 132 updateSystemState(); 133 mIconDpi = iconDpi; 134 mIconDb = new IconDB(context, dbFileName, iconPixelSize); 135 } 136 137 /** 138 * Returns the persistable serial number for {@param user}. Subclass should implement proper 139 * caching strategy to avoid making binder call every time. 140 */ getSerialNumberForUser(UserHandle user)141 protected abstract long getSerialNumberForUser(UserHandle user); 142 143 /** 144 * Return true if the given app is an instant app and should be badged appropriately. 145 */ isInstantApp(ApplicationInfo info)146 protected abstract boolean isInstantApp(ApplicationInfo info); 147 148 /** 149 * Opens and returns an icon factory. The factory is recycled by the caller. 150 */ getIconFactory()151 protected abstract BaseIconFactory getIconFactory(); 152 updateIconParams(int iconDpi, int iconPixelSize)153 public void updateIconParams(int iconDpi, int iconPixelSize) { 154 mWorkerHandler.post(() -> updateIconParamsBg(iconDpi, iconPixelSize)); 155 } 156 updateIconParamsBg(int iconDpi, int iconPixelSize)157 private synchronized void updateIconParamsBg(int iconDpi, int iconPixelSize) { 158 mIconDpi = iconDpi; 159 mDefaultIcons.clear(); 160 mIconDb.clear(); 161 mIconDb.close(); 162 mIconDb = new IconDB(mContext, mDbFileName, iconPixelSize); 163 mCache.clear(); 164 } 165 getFullResIcon(Resources resources, int iconId)166 private Drawable getFullResIcon(Resources resources, int iconId) { 167 if (resources != null && iconId != 0) { 168 try { 169 return resources.getDrawableForDensity(iconId, mIconDpi); 170 } catch (Resources.NotFoundException e) { } 171 } 172 return getFullResDefaultActivityIcon(mIconDpi); 173 } 174 getFullResIcon(String packageName, int iconId)175 public Drawable getFullResIcon(String packageName, int iconId) { 176 try { 177 return getFullResIcon(mPackageManager.getResourcesForApplication(packageName), iconId); 178 } catch (PackageManager.NameNotFoundException e) { } 179 return getFullResDefaultActivityIcon(mIconDpi); 180 } 181 getFullResIcon(ActivityInfo info)182 public Drawable getFullResIcon(ActivityInfo info) { 183 try { 184 return getFullResIcon(mPackageManager.getResourcesForApplication(info.applicationInfo), 185 info.getIconResource()); 186 } catch (PackageManager.NameNotFoundException e) { } 187 return getFullResDefaultActivityIcon(mIconDpi); 188 } 189 makeDefaultIcon(UserHandle user)190 private BitmapInfo makeDefaultIcon(UserHandle user) { 191 try (BaseIconFactory li = getIconFactory()) { 192 return li.makeDefaultIcon(user); 193 } 194 } 195 196 /** 197 * Remove any records for the supplied ComponentName. 198 */ remove(ComponentName componentName, UserHandle user)199 public synchronized void remove(ComponentName componentName, UserHandle user) { 200 mCache.remove(new ComponentKey(componentName, user)); 201 } 202 203 /** 204 * Remove any records for the supplied package name from memory. 205 */ removeFromMemCacheLocked(String packageName, UserHandle user)206 private void removeFromMemCacheLocked(String packageName, UserHandle user) { 207 HashSet<ComponentKey> forDeletion = new HashSet<>(); 208 for (ComponentKey key: mCache.keySet()) { 209 if (key.componentName.getPackageName().equals(packageName) 210 && key.user.equals(user)) { 211 forDeletion.add(key); 212 } 213 } 214 for (ComponentKey condemned: forDeletion) { 215 mCache.remove(condemned); 216 } 217 } 218 219 /** 220 * Removes the entries related to the given package in memory and persistent DB. 221 */ removeIconsForPkg(String packageName, UserHandle user)222 public synchronized void removeIconsForPkg(String packageName, UserHandle user) { 223 removeFromMemCacheLocked(packageName, user); 224 long userSerial = getSerialNumberForUser(user); 225 mIconDb.delete( 226 IconDB.COLUMN_COMPONENT + " LIKE ? AND " + IconDB.COLUMN_USER + " = ?", 227 new String[]{packageName + "/%", Long.toString(userSerial)}); 228 } 229 getUpdateHandler()230 public IconCacheUpdateHandler getUpdateHandler() { 231 updateSystemState(); 232 return new IconCacheUpdateHandler(this); 233 } 234 235 /** 236 * Refreshes the system state definition used to check the validity of the cache. It 237 * incorporates all the properties that can affect the cache like the list of enabled locale 238 * and system-version. 239 */ updateSystemState()240 private void updateSystemState() { 241 mLocaleList = mContext.getResources().getConfiguration().getLocales(); 242 mSystemState = mLocaleList.toLanguageTags() + "," + Build.VERSION.SDK_INT; 243 } 244 getIconSystemState(String packageName)245 protected String getIconSystemState(String packageName) { 246 return mSystemState; 247 } 248 249 /** 250 * Adds an entry into the DB and the in-memory cache. 251 * @param replaceExisting if true, it will recreate the bitmap even if it already exists in 252 * the memory. This is useful then the previous bitmap was created using 253 * old data. 254 */ 255 @VisibleForTesting addIconToDBAndMemCache(T object, CachingLogic<T> cachingLogic, PackageInfo info, long userSerial, boolean replaceExisting)256 public synchronized <T> void addIconToDBAndMemCache(T object, CachingLogic<T> cachingLogic, 257 PackageInfo info, long userSerial, boolean replaceExisting) { 258 UserHandle user = cachingLogic.getUser(object); 259 ComponentName componentName = cachingLogic.getComponent(object); 260 261 final ComponentKey key = new ComponentKey(componentName, user); 262 CacheEntry entry = null; 263 if (!replaceExisting) { 264 entry = mCache.get(key); 265 // We can't reuse the entry if the high-res icon is not present. 266 if (entry == null || entry.bitmap.isNullOrLowRes()) { 267 entry = null; 268 } 269 } 270 if (entry == null) { 271 entry = new CacheEntry(); 272 entry.bitmap = cachingLogic.loadIcon(mContext, object); 273 } 274 // Icon can't be loaded from cachingLogic, which implies alternative icon was loaded 275 // (e.g. fallback icon, default icon). So we drop here since there's no point in caching 276 // an empty entry. 277 if (entry.bitmap.isNullOrLowRes()) return; 278 entry.title = cachingLogic.getLabel(object); 279 entry.contentDescription = mPackageManager.getUserBadgedLabel(entry.title, user); 280 if (cachingLogic.addToMemCache()) mCache.put(key, entry); 281 282 ContentValues values = newContentValues(entry.bitmap, entry.title.toString(), 283 componentName.getPackageName(), cachingLogic.getKeywords(object, mLocaleList)); 284 addIconToDB(values, componentName, info, userSerial, 285 cachingLogic.getLastUpdatedTime(object, info)); 286 } 287 288 /** 289 * Updates {@param values} to contain versioning information and adds it to the DB. 290 * @param values {@link ContentValues} containing icon & title 291 */ addIconToDB(ContentValues values, ComponentName key, PackageInfo info, long userSerial, long lastUpdateTime)292 private void addIconToDB(ContentValues values, ComponentName key, 293 PackageInfo info, long userSerial, long lastUpdateTime) { 294 values.put(IconDB.COLUMN_COMPONENT, key.flattenToString()); 295 values.put(IconDB.COLUMN_USER, userSerial); 296 values.put(IconDB.COLUMN_LAST_UPDATED, lastUpdateTime); 297 values.put(IconDB.COLUMN_VERSION, info.versionCode); 298 mIconDb.insertOrReplace(values); 299 } 300 getDefaultIcon(UserHandle user)301 public synchronized BitmapInfo getDefaultIcon(UserHandle user) { 302 if (!mDefaultIcons.containsKey(user)) { 303 mDefaultIcons.put(user, makeDefaultIcon(user)); 304 } 305 return mDefaultIcons.get(user); 306 } 307 isDefaultIcon(BitmapInfo icon, UserHandle user)308 public boolean isDefaultIcon(BitmapInfo icon, UserHandle user) { 309 return getDefaultIcon(user).icon == icon.icon; 310 } 311 312 /** 313 * Retrieves the entry from the cache. If the entry is not present, it creates a new entry. 314 * This method is not thread safe, it must be called from a synchronized method. 315 */ cacheLocked( @onNull ComponentName componentName, @NonNull UserHandle user, @NonNull Supplier<T> infoProvider, @NonNull CachingLogic<T> cachingLogic, boolean usePackageIcon, boolean useLowResIcon)316 protected <T> CacheEntry cacheLocked( 317 @NonNull ComponentName componentName, @NonNull UserHandle user, 318 @NonNull Supplier<T> infoProvider, @NonNull CachingLogic<T> cachingLogic, 319 boolean usePackageIcon, boolean useLowResIcon) { 320 assertWorkerThread(); 321 ComponentKey cacheKey = new ComponentKey(componentName, user); 322 CacheEntry entry = mCache.get(cacheKey); 323 if (entry == null || (entry.bitmap.isLowRes() && !useLowResIcon)) { 324 entry = new CacheEntry(); 325 if (cachingLogic.addToMemCache()) { 326 mCache.put(cacheKey, entry); 327 } 328 329 // Check the DB first. 330 T object = null; 331 boolean providerFetchedOnce = false; 332 333 if (!getEntryFromDB(cacheKey, entry, useLowResIcon)) { 334 object = infoProvider.get(); 335 providerFetchedOnce = true; 336 337 if (object != null) { 338 entry.bitmap = cachingLogic.loadIcon(mContext, object); 339 } else { 340 if (usePackageIcon) { 341 CacheEntry packageEntry = getEntryForPackageLocked( 342 componentName.getPackageName(), user, false); 343 if (packageEntry != null) { 344 if (DEBUG) Log.d(TAG, "using package default icon for " + 345 componentName.toShortString()); 346 entry.bitmap = packageEntry.bitmap; 347 entry.title = packageEntry.title; 348 entry.contentDescription = packageEntry.contentDescription; 349 } 350 } 351 if (entry.bitmap == null) { 352 if (DEBUG) Log.d(TAG, "using default icon for " + 353 componentName.toShortString()); 354 entry.bitmap = getDefaultIcon(user); 355 } 356 } 357 } 358 359 if (TextUtils.isEmpty(entry.title)) { 360 if (object == null && !providerFetchedOnce) { 361 object = infoProvider.get(); 362 providerFetchedOnce = true; 363 } 364 if (object != null) { 365 entry.title = cachingLogic.getLabel(object); 366 entry.contentDescription = mPackageManager.getUserBadgedLabel( 367 cachingLogic.getDescription(object, entry.title), user); 368 } 369 } 370 } 371 return entry; 372 } 373 clear()374 public synchronized void clear() { 375 assertWorkerThread(); 376 mIconDb.clear(); 377 } 378 379 /** 380 * Adds a default package entry in the cache. This entry is not persisted and will be removed 381 * when the cache is flushed. 382 */ cachePackageInstallInfo(String packageName, UserHandle user, Bitmap icon, CharSequence title)383 protected synchronized void cachePackageInstallInfo(String packageName, UserHandle user, 384 Bitmap icon, CharSequence title) { 385 removeFromMemCacheLocked(packageName, user); 386 387 ComponentKey cacheKey = getPackageKey(packageName, user); 388 CacheEntry entry = mCache.get(cacheKey); 389 390 // For icon caching, do not go through DB. Just update the in-memory entry. 391 if (entry == null) { 392 entry = new CacheEntry(); 393 } 394 if (!TextUtils.isEmpty(title)) { 395 entry.title = title; 396 } 397 if (icon != null) { 398 BaseIconFactory li = getIconFactory(); 399 entry.bitmap = li.createIconBitmap(icon); 400 li.close(); 401 } 402 if (!TextUtils.isEmpty(title) && entry.bitmap.icon != null) { 403 mCache.put(cacheKey, entry); 404 } 405 } 406 getPackageKey(String packageName, UserHandle user)407 private static ComponentKey getPackageKey(String packageName, UserHandle user) { 408 ComponentName cn = new ComponentName(packageName, packageName + EMPTY_CLASS_NAME); 409 return new ComponentKey(cn, user); 410 } 411 412 /** 413 * Gets an entry for the package, which can be used as a fallback entry for various components. 414 * This method is not thread safe, it must be called from a synchronized method. 415 */ getEntryForPackageLocked(String packageName, UserHandle user, boolean useLowResIcon)416 protected CacheEntry getEntryForPackageLocked(String packageName, UserHandle user, 417 boolean useLowResIcon) { 418 assertWorkerThread(); 419 ComponentKey cacheKey = getPackageKey(packageName, user); 420 CacheEntry entry = mCache.get(cacheKey); 421 422 if (entry == null || (entry.bitmap.isLowRes() && !useLowResIcon)) { 423 entry = new CacheEntry(); 424 boolean entryUpdated = true; 425 426 // Check the DB first. 427 if (!getEntryFromDB(cacheKey, entry, useLowResIcon)) { 428 try { 429 int flags = Process.myUserHandle().equals(user) ? 0 : 430 PackageManager.GET_UNINSTALLED_PACKAGES; 431 PackageInfo info = mPackageManager.getPackageInfo(packageName, flags); 432 ApplicationInfo appInfo = info.applicationInfo; 433 if (appInfo == null) { 434 throw new NameNotFoundException("ApplicationInfo is null"); 435 } 436 437 BaseIconFactory li = getIconFactory(); 438 // Load the full res icon for the application, but if useLowResIcon is set, then 439 // only keep the low resolution icon instead of the larger full-sized icon 440 BitmapInfo iconInfo = li.createBadgedIconBitmap( 441 appInfo.loadIcon(mPackageManager), user, appInfo.targetSdkVersion, 442 isInstantApp(appInfo)); 443 li.close(); 444 445 entry.title = appInfo.loadLabel(mPackageManager); 446 entry.contentDescription = mPackageManager.getUserBadgedLabel(entry.title, user); 447 entry.bitmap = BitmapInfo.of( 448 useLowResIcon ? LOW_RES_ICON : iconInfo.icon, iconInfo.color); 449 450 // Add the icon in the DB here, since these do not get written during 451 // package updates. 452 ContentValues values = newContentValues( 453 iconInfo, entry.title.toString(), packageName, null); 454 addIconToDB(values, cacheKey.componentName, info, getSerialNumberForUser(user), 455 info.lastUpdateTime); 456 457 } catch (NameNotFoundException e) { 458 if (DEBUG) Log.d(TAG, "Application not installed " + packageName); 459 entryUpdated = false; 460 } 461 } 462 463 // Only add a filled-out entry to the cache 464 if (entryUpdated) { 465 mCache.put(cacheKey, entry); 466 } 467 } 468 return entry; 469 } 470 getEntryFromDB(ComponentKey cacheKey, CacheEntry entry, boolean lowRes)471 protected boolean getEntryFromDB(ComponentKey cacheKey, CacheEntry entry, boolean lowRes) { 472 Cursor c = null; 473 try { 474 c = mIconDb.query( 475 lowRes ? IconDB.COLUMNS_LOW_RES : IconDB.COLUMNS_HIGH_RES, 476 IconDB.COLUMN_COMPONENT + " = ? AND " + IconDB.COLUMN_USER + " = ?", 477 new String[]{ 478 cacheKey.componentName.flattenToString(), 479 Long.toString(getSerialNumberForUser(cacheKey.user))}); 480 if (c.moveToNext()) { 481 // Set the alpha to be 255, so that we never have a wrong color 482 entry.bitmap = BitmapInfo.of(LOW_RES_ICON, setColorAlphaBound(c.getInt(0), 255)); 483 entry.title = c.getString(1); 484 if (entry.title == null) { 485 entry.title = ""; 486 entry.contentDescription = ""; 487 } else { 488 entry.contentDescription = mPackageManager.getUserBadgedLabel( 489 entry.title, cacheKey.user); 490 } 491 492 if (!lowRes) { 493 byte[] data = c.getBlob(2); 494 try { 495 entry.bitmap = BitmapInfo.of( 496 BitmapFactory.decodeByteArray(data, 0, data.length, mDecodeOptions), 497 entry.bitmap.color); 498 } catch (Exception e) { } 499 } 500 return true; 501 } 502 } catch (SQLiteException e) { 503 Log.d(TAG, "Error reading icon cache", e); 504 } finally { 505 if (c != null) { 506 c.close(); 507 } 508 } 509 return false; 510 } 511 512 /** 513 * Returns a cursor for an arbitrary query to the cache db 514 */ queryCacheDb(String[] columns, String selection, String[] selectionArgs)515 public synchronized Cursor queryCacheDb(String[] columns, String selection, 516 String[] selectionArgs) { 517 return mIconDb.query(columns, selection, selectionArgs); 518 } 519 520 /** 521 * Cache class to store the actual entries on disk 522 */ 523 public static final class IconDB extends SQLiteCacheHelper { 524 private static final int RELEASE_VERSION = 27; 525 526 public static final String TABLE_NAME = "icons"; 527 public static final String COLUMN_ROWID = "rowid"; 528 public static final String COLUMN_COMPONENT = "componentName"; 529 public static final String COLUMN_USER = "profileId"; 530 public static final String COLUMN_LAST_UPDATED = "lastUpdated"; 531 public static final String COLUMN_VERSION = "version"; 532 public static final String COLUMN_ICON = "icon"; 533 public static final String COLUMN_ICON_COLOR = "icon_color"; 534 public static final String COLUMN_LABEL = "label"; 535 public static final String COLUMN_SYSTEM_STATE = "system_state"; 536 public static final String COLUMN_KEYWORDS = "keywords"; 537 538 public static final String[] COLUMNS_HIGH_RES = new String[] { 539 IconDB.COLUMN_ICON_COLOR, IconDB.COLUMN_LABEL, IconDB.COLUMN_ICON }; 540 public static final String[] COLUMNS_LOW_RES = new String[] { 541 IconDB.COLUMN_ICON_COLOR, IconDB.COLUMN_LABEL }; 542 IconDB(Context context, String dbFileName, int iconPixelSize)543 public IconDB(Context context, String dbFileName, int iconPixelSize) { 544 super(context, dbFileName, (RELEASE_VERSION << 16) + iconPixelSize, TABLE_NAME); 545 } 546 547 @Override onCreateTable(SQLiteDatabase db)548 protected void onCreateTable(SQLiteDatabase db) { 549 db.execSQL("CREATE TABLE IF NOT EXISTS " + TABLE_NAME + " (" 550 + COLUMN_COMPONENT + " TEXT NOT NULL, " 551 + COLUMN_USER + " INTEGER NOT NULL, " 552 + COLUMN_LAST_UPDATED + " INTEGER NOT NULL DEFAULT 0, " 553 + COLUMN_VERSION + " INTEGER NOT NULL DEFAULT 0, " 554 + COLUMN_ICON + " BLOB, " 555 + COLUMN_ICON_COLOR + " INTEGER NOT NULL DEFAULT 0, " 556 + COLUMN_LABEL + " TEXT, " 557 + COLUMN_SYSTEM_STATE + " TEXT, " 558 + COLUMN_KEYWORDS + " TEXT, " 559 + "PRIMARY KEY (" + COLUMN_COMPONENT + ", " + COLUMN_USER + ") " 560 + ");"); 561 } 562 } 563 newContentValues(BitmapInfo bitmapInfo, String label, String packageName, @Nullable String keywords)564 private ContentValues newContentValues(BitmapInfo bitmapInfo, String label, 565 String packageName, @Nullable String keywords) { 566 ContentValues values = new ContentValues(); 567 values.put(IconDB.COLUMN_ICON, 568 bitmapInfo.isLowRes() ? null : GraphicsUtils.flattenBitmap(bitmapInfo.icon)); 569 values.put(IconDB.COLUMN_ICON_COLOR, bitmapInfo.color); 570 571 values.put(IconDB.COLUMN_LABEL, label); 572 values.put(IconDB.COLUMN_SYSTEM_STATE, getIconSystemState(packageName)); 573 values.put(IconDB.COLUMN_KEYWORDS, keywords); 574 return values; 575 } 576 assertWorkerThread()577 private void assertWorkerThread() { 578 if (Looper.myLooper() != mBgLooper) { 579 throw new IllegalStateException("Cache accessed on wrong thread " + Looper.myLooper()); 580 } 581 } 582 } 583