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