| <<<<<<< HEAD (cc6caf Merge "Let launcher to provide its own OverscrollPlugin" int) |
| /* |
| * Copyright (C) 2018 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.android.launcher3.icons.cache; |
| |
| import android.content.ComponentName; |
| import android.content.pm.ApplicationInfo; |
| import android.content.pm.PackageInfo; |
| import android.content.pm.PackageManager; |
| import android.database.Cursor; |
| import android.database.sqlite.SQLiteException; |
| import android.os.SystemClock; |
| import android.os.UserHandle; |
| import android.text.TextUtils; |
| import android.util.ArrayMap; |
| import android.util.Log; |
| import android.util.SparseBooleanArray; |
| |
| import com.android.launcher3.icons.cache.BaseIconCache.IconDB; |
| |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map.Entry; |
| import java.util.Set; |
| import java.util.Stack; |
| |
| /** |
| * Utility class to handle updating the Icon cache |
| */ |
| public class IconCacheUpdateHandler { |
| |
| private static final String TAG = "IconCacheUpdateHandler"; |
| |
| /** |
| * In this mode, all invalid icons are marked as to-be-deleted in {@link #mItemsToDelete}. |
| * This mode is used for the first run. |
| */ |
| private static final boolean MODE_SET_INVALID_ITEMS = true; |
| |
| /** |
| * In this mode, any valid icon is removed from {@link #mItemsToDelete}. This is used for all |
| * subsequent runs, which essentially acts as set-union of all valid items. |
| */ |
| private static final boolean MODE_CLEAR_VALID_ITEMS = false; |
| |
| private static final Object ICON_UPDATE_TOKEN = new Object(); |
| |
| private final HashMap<String, PackageInfo> mPkgInfoMap; |
| private final BaseIconCache mIconCache; |
| |
| private final ArrayMap<UserHandle, Set<String>> mPackagesToIgnore = new ArrayMap<>(); |
| |
| private final SparseBooleanArray mItemsToDelete = new SparseBooleanArray(); |
| private boolean mFilterMode = MODE_SET_INVALID_ITEMS; |
| |
| IconCacheUpdateHandler(BaseIconCache cache) { |
| mIconCache = cache; |
| |
| mPkgInfoMap = new HashMap<>(); |
| |
| // Remove all active icon update tasks. |
| mIconCache.mWorkerHandler.removeCallbacksAndMessages(ICON_UPDATE_TOKEN); |
| |
| createPackageInfoMap(); |
| } |
| |
| /** |
| * Sets a package to ignore for processing |
| */ |
| public void addPackagesToIgnore(UserHandle userHandle, String packageName) { |
| Set<String> packages = mPackagesToIgnore.get(userHandle); |
| if (packages == null) { |
| packages = new HashSet<>(); |
| mPackagesToIgnore.put(userHandle, packages); |
| } |
| packages.add(packageName); |
| } |
| |
| private void createPackageInfoMap() { |
| PackageManager pm = mIconCache.mPackageManager; |
| for (PackageInfo info : |
| pm.getInstalledPackages(PackageManager.MATCH_UNINSTALLED_PACKAGES)) { |
| mPkgInfoMap.put(info.packageName, info); |
| } |
| } |
| |
| /** |
| * Updates the persistent DB, such that only entries corresponding to {@param apps} remain in |
| * the DB and are updated. |
| * @return The set of packages for which icons have updated. |
| */ |
| public <T> void updateIcons(List<T> apps, CachingLogic<T> cachingLogic, |
| OnUpdateCallback onUpdateCallback) { |
| // Filter the list per user |
| HashMap<UserHandle, HashMap<ComponentName, T>> userComponentMap = new HashMap<>(); |
| int count = apps.size(); |
| for (int i = 0; i < count; i++) { |
| T app = apps.get(i); |
| UserHandle userHandle = cachingLogic.getUser(app); |
| HashMap<ComponentName, T> componentMap = userComponentMap.get(userHandle); |
| if (componentMap == null) { |
| componentMap = new HashMap<>(); |
| userComponentMap.put(userHandle, componentMap); |
| } |
| componentMap.put(cachingLogic.getComponent(app), app); |
| } |
| |
| for (Entry<UserHandle, HashMap<ComponentName, T>> entry : userComponentMap.entrySet()) { |
| updateIconsPerUser(entry.getKey(), entry.getValue(), cachingLogic, onUpdateCallback); |
| } |
| |
| // From now on, clear every valid item from the global valid map. |
| mFilterMode = MODE_CLEAR_VALID_ITEMS; |
| } |
| |
| /** |
| * Updates the persistent DB, such that only entries corresponding to {@param apps} remain in |
| * the DB and are updated. |
| * @return The set of packages for which icons have updated. |
| */ |
| @SuppressWarnings("unchecked") |
| private <T> void updateIconsPerUser(UserHandle user, HashMap<ComponentName, T> componentMap, |
| CachingLogic<T> cachingLogic, OnUpdateCallback onUpdateCallback) { |
| Set<String> ignorePackages = mPackagesToIgnore.get(user); |
| if (ignorePackages == null) { |
| ignorePackages = Collections.emptySet(); |
| } |
| long userSerial = mIconCache.getSerialNumberForUser(user); |
| |
| Stack<T> appsToUpdate = new Stack<>(); |
| |
| try (Cursor c = mIconCache.mIconDb.query( |
| new String[]{IconDB.COLUMN_ROWID, IconDB.COLUMN_COMPONENT, |
| IconDB.COLUMN_LAST_UPDATED, IconDB.COLUMN_VERSION, |
| IconDB.COLUMN_SYSTEM_STATE}, |
| IconDB.COLUMN_USER + " = ? ", |
| new String[]{Long.toString(userSerial)})) { |
| |
| final int indexComponent = c.getColumnIndex(IconDB.COLUMN_COMPONENT); |
| final int indexLastUpdate = c.getColumnIndex(IconDB.COLUMN_LAST_UPDATED); |
| final int indexVersion = c.getColumnIndex(IconDB.COLUMN_VERSION); |
| final int rowIndex = c.getColumnIndex(IconDB.COLUMN_ROWID); |
| final int systemStateIndex = c.getColumnIndex(IconDB.COLUMN_SYSTEM_STATE); |
| |
| while (c.moveToNext()) { |
| String cn = c.getString(indexComponent); |
| ComponentName component = ComponentName.unflattenFromString(cn); |
| PackageInfo info = mPkgInfoMap.get(component.getPackageName()); |
| |
| int rowId = c.getInt(rowIndex); |
| if (info == null) { |
| if (!ignorePackages.contains(component.getPackageName())) { |
| |
| if (mFilterMode == MODE_SET_INVALID_ITEMS) { |
| mIconCache.remove(component, user); |
| mItemsToDelete.put(rowId, true); |
| } |
| } |
| continue; |
| } |
| if ((info.applicationInfo.flags & ApplicationInfo.FLAG_IS_DATA_ONLY) != 0) { |
| // Application is not present |
| continue; |
| } |
| |
| long updateTime = c.getLong(indexLastUpdate); |
| int version = c.getInt(indexVersion); |
| T app = componentMap.remove(component); |
| if (version == info.versionCode |
| && updateTime == cachingLogic.getLastUpdatedTime(app, info) |
| && TextUtils.equals(c.getString(systemStateIndex), |
| mIconCache.getIconSystemState(info.packageName))) { |
| |
| if (mFilterMode == MODE_CLEAR_VALID_ITEMS) { |
| mItemsToDelete.put(rowId, false); |
| } |
| continue; |
| } |
| |
| if (app == null) { |
| if (mFilterMode == MODE_SET_INVALID_ITEMS) { |
| mIconCache.remove(component, user); |
| mItemsToDelete.put(rowId, true); |
| } |
| } else { |
| appsToUpdate.add(app); |
| } |
| } |
| } catch (SQLiteException e) { |
| Log.d(TAG, "Error reading icon cache", e); |
| // Continue updating whatever we have read so far |
| } |
| |
| // Insert remaining apps. |
| if (!componentMap.isEmpty() || !appsToUpdate.isEmpty()) { |
| Stack<T> appsToAdd = new Stack<>(); |
| appsToAdd.addAll(componentMap.values()); |
| new SerializedIconUpdateTask(userSerial, user, appsToAdd, appsToUpdate, cachingLogic, |
| onUpdateCallback).scheduleNext(); |
| } |
| } |
| |
| /** |
| * Commits all updates as part of the update handler to disk. Not more calls should be made |
| * to this class after this. |
| */ |
| public void finish() { |
| // Commit all deletes |
| int deleteCount = 0; |
| StringBuilder queryBuilder = new StringBuilder() |
| .append(IconDB.COLUMN_ROWID) |
| .append(" IN ("); |
| |
| int count = mItemsToDelete.size(); |
| for (int i = 0; i < count; i++) { |
| if (mItemsToDelete.valueAt(i)) { |
| if (deleteCount > 0) { |
| queryBuilder.append(", "); |
| } |
| queryBuilder.append(mItemsToDelete.keyAt(i)); |
| deleteCount++; |
| } |
| } |
| queryBuilder.append(')'); |
| |
| if (deleteCount > 0) { |
| mIconCache.mIconDb.delete(queryBuilder.toString(), null); |
| } |
| } |
| |
| /** |
| * A runnable that updates invalid icons and adds missing icons in the DB for the provided |
| * LauncherActivityInfo list. Items are updated/added one at a time, so that the |
| * worker thread doesn't get blocked. |
| */ |
| private class SerializedIconUpdateTask<T> implements Runnable { |
| private final long mUserSerial; |
| private final UserHandle mUserHandle; |
| private final Stack<T> mAppsToAdd; |
| private final Stack<T> mAppsToUpdate; |
| private final CachingLogic<T> mCachingLogic; |
| private final HashSet<String> mUpdatedPackages = new HashSet<>(); |
| private final OnUpdateCallback mOnUpdateCallback; |
| |
| SerializedIconUpdateTask(long userSerial, UserHandle userHandle, |
| Stack<T> appsToAdd, Stack<T> appsToUpdate, CachingLogic<T> cachingLogic, |
| OnUpdateCallback onUpdateCallback) { |
| mUserHandle = userHandle; |
| mUserSerial = userSerial; |
| mAppsToAdd = appsToAdd; |
| mAppsToUpdate = appsToUpdate; |
| mCachingLogic = cachingLogic; |
| mOnUpdateCallback = onUpdateCallback; |
| } |
| |
| @Override |
| public void run() { |
| if (!mAppsToUpdate.isEmpty()) { |
| T app = mAppsToUpdate.pop(); |
| String pkg = mCachingLogic.getComponent(app).getPackageName(); |
| PackageInfo info = mPkgInfoMap.get(pkg); |
| |
| mIconCache.addIconToDBAndMemCache( |
| app, mCachingLogic, info, mUserSerial, true /*replace existing*/); |
| mUpdatedPackages.add(pkg); |
| |
| if (mAppsToUpdate.isEmpty() && !mUpdatedPackages.isEmpty()) { |
| // No more app to update. Notify callback. |
| mOnUpdateCallback.onPackageIconsUpdated(mUpdatedPackages, mUserHandle); |
| } |
| |
| // Let it run one more time. |
| scheduleNext(); |
| } else if (!mAppsToAdd.isEmpty()) { |
| T app = mAppsToAdd.pop(); |
| PackageInfo info = mPkgInfoMap.get(mCachingLogic.getComponent(app).getPackageName()); |
| // We do not check the mPkgInfoMap when generating the mAppsToAdd. Although every |
| // app should have package info, this is not guaranteed by the api |
| if (info != null) { |
| mIconCache.addIconToDBAndMemCache(app, mCachingLogic, info, |
| mUserSerial, false /*replace existing*/); |
| } |
| |
| if (!mAppsToAdd.isEmpty()) { |
| scheduleNext(); |
| } |
| } |
| } |
| |
| public void scheduleNext() { |
| mIconCache.mWorkerHandler.postAtTime(this, ICON_UPDATE_TOKEN, |
| SystemClock.uptimeMillis() + 1); |
| } |
| } |
| |
| public interface OnUpdateCallback { |
| |
| void onPackageIconsUpdated(HashSet<String> updatedPackages, UserHandle user); |
| } |
| } |
| ======= |
| >>>>>>> CHANGE (805b52 Removes iconloaderlib from Launcher3.) |