From 14168431bd521f1e3ff46c0f46cc02c43a149d31 Mon Sep 17 00:00:00 2001 From: Sunny Goyal Date: Thu, 24 Oct 2019 15:59:49 -0700 Subject: [PATCH] Adding support for dynamic calendar and clock icons Change-Id: Icdba34340a27a4f6dff7310d0bf9fd29aef1330c --- .../quickstep/views/IconRecentsView.java | 56 ++- .../launcher3/icons/BaseIconFactory.java | 11 +- .../android/launcher3/icons/BitmapInfo.java | 28 +- .../launcher3/icons/cache/BaseIconCache.java | 2 +- .../appprediction/ComponentKeyMapper.java | 6 +- .../PredictionUiStateManager.java | 2 +- .../android/quickstep/views/RecentsView.java | 20 +- .../com/android/quickstep/RecentsModel.java | 37 +- .../com/android/quickstep/TaskIconCache.java | 18 +- .../android/quickstep/TaskThumbnailCache.java | 23 +- .../quickstep/util/TaskKeyLruCache.java | 124 +++++++ res/values/config.xml | 29 +- src/com/android/launcher3/BubbleTextView.java | 9 +- .../android/launcher3/FastBitmapDrawable.java | 37 +- src/com/android/launcher3/IconProvider.java | 29 -- .../android/launcher3/LauncherAppState.java | 8 + src/com/android/launcher3/LauncherModel.java | 21 +- src/com/android/launcher3/Utilities.java | 10 +- .../launcher3/WidgetPreviewLoader.java | 53 ++- .../android/launcher3/dragndrop/DragView.java | 3 +- .../launcher3/folder/PreviewItemManager.java | 9 +- .../launcher3/graphics/DrawableFactory.java | 120 ------- .../graphics/PlaceHolderIconDrawable.java | 15 +- .../graphics/PreloadIconDrawable.java | 15 +- .../launcher3/icons/ClockDrawableWrapper.java | 328 ++++++++++++++++++ .../android/launcher3/icons/IconCache.java | 19 +- .../android/launcher3/icons/IconProvider.java | 251 ++++++++++++++ .../icons/LauncherActivityCachingLogic.java | 6 +- .../launcher3/icons/LauncherIcons.java | 15 +- .../launcher3/views/FloatingIconView.java | 4 +- .../widget/PendingAppWidgetHostView.java | 12 +- .../android/launcher3/widget/WidgetCell.java | 7 +- 32 files changed, 1021 insertions(+), 306 deletions(-) create mode 100644 quickstep/src/com/android/quickstep/util/TaskKeyLruCache.java delete mode 100644 src/com/android/launcher3/IconProvider.java delete mode 100644 src/com/android/launcher3/graphics/DrawableFactory.java create mode 100644 src/com/android/launcher3/icons/ClockDrawableWrapper.java create mode 100644 src/com/android/launcher3/icons/IconProvider.java diff --git a/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java b/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java index 87b4d4e2e5..e380698d38 100644 --- a/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java +++ b/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java @@ -40,6 +40,7 @@ import android.graphics.Matrix; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.drawable.Drawable; +import android.os.UserHandle; import android.util.ArraySet; import android.util.AttributeSet; import android.util.FloatProperty; @@ -66,6 +67,7 @@ import com.android.launcher3.R; import com.android.launcher3.util.Themes; import com.android.quickstep.ContentFillItemAnimator; import com.android.quickstep.RecentsModel; +import com.android.quickstep.RecentsModel.TaskVisualsChangeListener; import com.android.quickstep.RecentsToActivityHelper; import com.android.quickstep.TaskActionController; import com.android.quickstep.TaskAdapter; @@ -74,6 +76,7 @@ import com.android.quickstep.TaskListLoader; import com.android.quickstep.TaskSwipeCallback; import com.android.quickstep.util.MultiValueUpdateListener; import com.android.systemui.shared.recents.model.Task; +import com.android.systemui.shared.recents.model.ThumbnailData; import com.android.systemui.shared.system.RemoteAnimationTargetCompat; import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat; import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams; @@ -87,7 +90,8 @@ import java.util.Optional; * Root view for the icon recents view. Acts as the main interface to the rest of the Launcher code * base. */ -public final class IconRecentsView extends FrameLayout implements Insettable { +public final class IconRecentsView extends FrameLayout + implements Insettable, TaskVisualsChangeListener { public static final FloatProperty CONTENT_ALPHA = new FloatProperty("contentAlpha") { @@ -159,22 +163,6 @@ public final class IconRecentsView extends FrameLayout implements Insettable { private AnimatorSet mLayoutAnimation; private final ArraySet mLayingOutViews = new ArraySet<>(); private Rect mInsets; - private final RecentsModel.TaskThumbnailChangeListener listener = (taskId, thumbnailData) -> { - ArrayList itemViews = getTaskViews(); - for (int i = 0, size = itemViews.size(); i < size; i++) { - TaskItemView taskView = itemViews.get(i); - TaskHolder taskHolder = (TaskHolder) mTaskRecyclerView.getChildViewHolder(taskView); - Optional optTask = taskHolder.getTask(); - if (optTask.filter(task -> task.key.id == taskId).isPresent()) { - Task task = optTask.get(); - // Update thumbnail on the task. - task.thumbnail = thumbnailData; - taskView.setThumbnail(thumbnailData); - return task; - } - } - return null; - }; public IconRecentsView(Context context, AttributeSet attrs) { super(context, attrs); @@ -189,9 +177,29 @@ public final class IconRecentsView extends FrameLayout implements Insettable { mActivity.getStatsLogManager()); mTaskAdapter.setActionController(mTaskActionController); mTaskLayoutManager = new LinearLayoutManager(mContext, VERTICAL, true /* reverseLayout */); - RecentsModel.INSTANCE.get(context).addThumbnailChangeListener(listener); } + @Override + public Task onTaskThumbnailChanged(int taskId, ThumbnailData thumbnailData) { + ArrayList itemViews = getTaskViews(); + for (int i = 0, size = itemViews.size(); i < size; i++) { + TaskItemView taskView = itemViews.get(i); + TaskHolder taskHolder = (TaskHolder) mTaskRecyclerView.getChildViewHolder(taskView); + Optional optTask = taskHolder.getTask(); + if (optTask.filter(task -> task.key.id == taskId).isPresent()) { + Task task = optTask.get(); + // Update thumbnail on the task. + task.thumbnail = thumbnailData; + taskView.setThumbnail(thumbnailData); + return task; + } + } + return null; + } + + @Override + public void onTaskIconChanged(String pkg, UserHandle user) { } + @Override protected void onFinishInflate() { super.onFinishInflate(); @@ -274,6 +282,18 @@ public final class IconRecentsView extends FrameLayout implements Insettable { } } + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + RecentsModel.INSTANCE.get(getContext()).addThumbnailChangeListener(this); + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + RecentsModel.INSTANCE.get(getContext()).removeThumbnailChangeListener(this); + } + @Override public void setEnabled(boolean enabled) { super.setEnabled(enabled); diff --git a/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java b/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java index f491ed73f4..535c5d8296 100644 --- a/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java +++ b/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java @@ -116,7 +116,7 @@ public class BaseIconFactory implements AutoCloseable { icon = createIconBitmap(new BitmapDrawable(mContext.getResources(), icon), 1f); } - return BitmapInfo.fromBitmap(icon, mDisableColorExtractor ? null : mColorExtractor); + return BitmapInfo.of(icon, extractColor(icon)); } public BitmapInfo createBadgedIconBitmap(Drawable icon, UserHandle user, @@ -186,7 +186,10 @@ public class BaseIconFactory implements AutoCloseable { } else { result = bitmap; } - return BitmapInfo.fromBitmap(result, mDisableColorExtractor ? null : mColorExtractor); + int color = extractColor(result); + return icon instanceof BitmapInfo.Extender + ? ((BitmapInfo.Extender) icon).getExtendedInfo(result, color, this) + : BitmapInfo.of(result, color); } public Bitmap createScaledBitmapWithoutShadow(Drawable icon, boolean shrinkNonAdaptiveIcons) { @@ -337,6 +340,10 @@ public class BaseIconFactory implements AutoCloseable { iconDpi); } + private int extractColor(Bitmap bitmap) { + return mDisableColorExtractor ? 0 : mColorExtractor.findDominantColorByHue(bitmap); + } + /** * Returns the correct badge size given an icon size */ diff --git a/iconloaderlib/src/com/android/launcher3/icons/BitmapInfo.java b/iconloaderlib/src/com/android/launcher3/icons/BitmapInfo.java index d2b9c5e60b..d33f9b1112 100644 --- a/iconloaderlib/src/com/android/launcher3/icons/BitmapInfo.java +++ b/iconloaderlib/src/com/android/launcher3/icons/BitmapInfo.java @@ -19,12 +19,11 @@ import android.graphics.Bitmap; import android.graphics.Bitmap.Config; import androidx.annotation.NonNull; -import androidx.annotation.Nullable; public class BitmapInfo { public static final Bitmap LOW_RES_ICON = Bitmap.createBitmap(1, 1, Config.ALPHA_8); - public static final BitmapInfo LOW_RES_INFO = fromBitmap(LOW_RES_ICON, null); + public static final BitmapInfo LOW_RES_INFO = fromBitmap(LOW_RES_ICON); public final Bitmap icon; public final int color; @@ -49,14 +48,25 @@ public class BitmapInfo { return of(bitmap, 0); } - public static BitmapInfo fromBitmap(@NonNull Bitmap bitmap, - @Nullable ColorExtractor dominantColorExtractor) { - return of(bitmap, dominantColorExtractor != null - ? dominantColorExtractor.findDominantColorByHue(bitmap) - : 0); - } - public static BitmapInfo of(@NonNull Bitmap bitmap, int color) { return new BitmapInfo(bitmap, color); } + + /** + * Interface to be implemented by drawables to provide a custom BitmapInfo + */ + public interface Extender { + + /** + * Called for creating a custom BitmapInfo + */ + default BitmapInfo getExtendedInfo(Bitmap bitmap, int color, BaseIconFactory iconFactory) { + return BitmapInfo.of(bitmap, color); + } + + /** + * Notifies the drawable that it will be drawn directly in the UI, without any preprocessing + */ + default void prepareToDrawOnUi() { } + } } diff --git a/iconloaderlib/src/com/android/launcher3/icons/cache/BaseIconCache.java b/iconloaderlib/src/com/android/launcher3/icons/cache/BaseIconCache.java index 8bae94cf3d..6f63d88722 100644 --- a/iconloaderlib/src/com/android/launcher3/icons/cache/BaseIconCache.java +++ b/iconloaderlib/src/com/android/launcher3/icons/cache/BaseIconCache.java @@ -464,7 +464,7 @@ public abstract class BaseIconCache { return entry; } - private boolean getEntryFromDB(ComponentKey cacheKey, CacheEntry entry, boolean lowRes) { + protected boolean getEntryFromDB(ComponentKey cacheKey, CacheEntry entry, boolean lowRes) { Cursor c = null; try { c = mIconDb.query( diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/ComponentKeyMapper.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/ComponentKeyMapper.java index b9f4147f85..0712285aec 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/ComponentKeyMapper.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/ComponentKeyMapper.java @@ -18,8 +18,6 @@ package com.android.launcher3.appprediction; import static com.android.quickstep.InstantAppResolverImpl.COMPONENT_CLASS_MARKER; -import android.content.Context; - import com.android.launcher3.AppInfo; import com.android.launcher3.ItemInfoWithIcon; import com.android.launcher3.allapps.AllAppsStore; @@ -29,11 +27,9 @@ import com.android.launcher3.util.ComponentKey; public class ComponentKeyMapper { protected final ComponentKey componentKey; - private final Context mContext; private final DynamicItemCache mCache; - public ComponentKeyMapper(Context context, ComponentKey key, DynamicItemCache cache) { - mContext = context; + public ComponentKeyMapper(ComponentKey key, DynamicItemCache cache) { componentKey = key; mCache = cache; } diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java index 1a59770a02..afe988b570 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java @@ -239,7 +239,7 @@ public class PredictionUiStateManager implements StateListener, ItemInfoUpdateRe key = new ComponentKey(new ComponentName(appTarget.getPackageName(), appTarget.getClassName()), appTarget.getUser()); } - state.apps.add(new ComponentKeyMapper(mContext, key, mDynamicItemCache)); + state.apps.add(new ComponentKeyMapper(key, mDynamicItemCache)); } } updateDependencies(state); diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java index c634825ea6..c875ba0d75 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java @@ -60,6 +60,7 @@ import android.graphics.Typeface; import android.graphics.drawable.Drawable; import android.os.Build; import android.os.Handler; +import android.os.UserHandle; import android.text.Layout; import android.text.StaticLayout; import android.text.TextPaint; @@ -105,7 +106,7 @@ import com.android.launcher3.util.ViewPool; import com.android.quickstep.RecentsAnimationController; import com.android.quickstep.RecentsAnimationTargets; import com.android.quickstep.RecentsModel; -import com.android.quickstep.RecentsModel.TaskThumbnailChangeListener; +import com.android.quickstep.RecentsModel.TaskVisualsChangeListener; import com.android.quickstep.TaskThumbnailCache; import com.android.quickstep.TaskUtils; import com.android.quickstep.ViewUtils; @@ -127,7 +128,7 @@ import java.util.function.Consumer; @TargetApi(Build.VERSION_CODES.P) public abstract class RecentsView extends PagedView implements Insettable, TaskThumbnailCache.HighResLoadingState.HighResLoadingStateChangedCallback, - InvariantDeviceProfile.OnIDPChangeListener, TaskThumbnailChangeListener { + InvariantDeviceProfile.OnIDPChangeListener, TaskVisualsChangeListener { private static final String TAG = RecentsView.class.getSimpleName(); @@ -383,6 +384,21 @@ public abstract class RecentsView extends PagedView impl return null; } + @Override + public void onTaskIconChanged(String pkg, UserHandle user) { + for (int i = 0; i < getTaskViewCount(); i++) { + TaskView tv = getTaskViewAt(i); + Task task = tv.getTask(); + if (task != null && task.key != null && pkg.equals(task.key.getPackageName()) + && task.key.userId == user.getIdentifier()) { + task.icon = null; + if (tv.getIconView().getDrawable() != null) { + tv.onTaskListVisibilityChanged(true /* visible */); + } + } + } + } + /** * Update the thumbnail of the task. * @param refreshNow Refresh immediately if it's true. diff --git a/quickstep/src/com/android/quickstep/RecentsModel.java b/quickstep/src/com/android/quickstep/RecentsModel.java index f2484239b7..517501ad9c 100644 --- a/quickstep/src/com/android/quickstep/RecentsModel.java +++ b/quickstep/src/com/android/quickstep/RecentsModel.java @@ -28,7 +28,9 @@ import android.content.Context; import android.os.Build; import android.os.Looper; import android.os.Process; +import android.os.UserHandle; +import com.android.launcher3.icons.IconProvider; import com.android.launcher3.util.MainThreadInitializedObject; import com.android.systemui.shared.recents.model.Task; import com.android.systemui.shared.recents.model.ThumbnailData; @@ -50,7 +52,7 @@ public class RecentsModel extends TaskStackChangeListener { public static final MainThreadInitializedObject INSTANCE = new MainThreadInitializedObject<>(RecentsModel::new); - private final List mThumbnailChangeListeners = new ArrayList<>(); + private final List mThumbnailChangeListeners = new ArrayList<>(); private final Context mContext; private final RecentTasksList mTaskList; @@ -65,7 +67,10 @@ public class RecentsModel extends TaskStackChangeListener { new KeyguardManagerCompat(context), ActivityManagerWrapper.getInstance()); mIconCache = new TaskIconCache(context, looper); mThumbnailCache = new TaskThumbnailCache(context, looper); + ActivityManagerWrapper.getInstance().registerTaskStackListener(this); + IconProvider.registerIconChangeListener(context, + this::onPackageIconChanged, MAIN_EXECUTOR.getHandler()); } public TaskIconCache getIconCache() { @@ -178,16 +183,40 @@ public class RecentsModel extends TaskStackChangeListener { } } - public void addThumbnailChangeListener(TaskThumbnailChangeListener listener) { + private void onPackageIconChanged(String pkg, UserHandle user) { + mIconCache.invalidateCacheEntries(pkg, user); + for (int i = mThumbnailChangeListeners.size() - 1; i >= 0; i--) { + mThumbnailChangeListeners.get(i).onTaskIconChanged(pkg, user); + } + } + + /** + * Adds a listener for visuals changes + */ + public void addThumbnailChangeListener(TaskVisualsChangeListener listener) { mThumbnailChangeListeners.add(listener); } - public void removeThumbnailChangeListener(TaskThumbnailChangeListener listener) { + /** + * Removes a previously added listener + */ + public void removeThumbnailChangeListener(TaskVisualsChangeListener listener) { mThumbnailChangeListeners.remove(listener); } - public interface TaskThumbnailChangeListener { + /** + * Listener for receiving various task properties changes + */ + public interface TaskVisualsChangeListener { + /** + * Called whn the task thumbnail changes + */ Task onTaskThumbnailChanged(int taskId, ThumbnailData thumbnailData); + + /** + * Called when the icon for a task changes + */ + void onTaskIconChanged(String pkg, UserHandle user); } } diff --git a/quickstep/src/com/android/quickstep/TaskIconCache.java b/quickstep/src/com/android/quickstep/TaskIconCache.java index 873f29c861..e590aea4f6 100644 --- a/quickstep/src/com/android/quickstep/TaskIconCache.java +++ b/quickstep/src/com/android/quickstep/TaskIconCache.java @@ -15,6 +15,7 @@ */ package com.android.quickstep; +import static com.android.launcher3.FastBitmapDrawable.newIcon; import static com.android.launcher3.uioverrides.QuickstepLauncher.GO_LOW_RAM_RECENTS_ENABLED; import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; @@ -37,14 +38,14 @@ import androidx.annotation.WorkerThread; import com.android.launcher3.FastBitmapDrawable; import com.android.launcher3.R; import com.android.launcher3.Utilities; -import com.android.launcher3.graphics.DrawableFactory; import com.android.launcher3.icons.BitmapInfo; +import com.android.launcher3.icons.IconProvider; import com.android.launcher3.icons.LauncherIcons; import com.android.launcher3.icons.cache.HandlerRunnable; import com.android.launcher3.util.Preconditions; +import com.android.quickstep.util.TaskKeyLruCache; import com.android.systemui.shared.recents.model.Task; import com.android.systemui.shared.recents.model.Task.TaskKey; -import com.android.systemui.shared.recents.model.TaskKeyLruCache; import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.PackageManagerWrapper; @@ -61,6 +62,7 @@ public class TaskIconCache { private final Context mContext; private final TaskKeyLruCache mIconCache; private final SparseArray mDefaultIcons = new SparseArray<>(); + private final IconProvider mIconProvider; public TaskIconCache(Context context, Looper backgroundLooper) { mContext = context; @@ -70,6 +72,7 @@ public class TaskIconCache { Resources res = context.getResources(); int cacheSize = res.getInteger(R.integer.recentsIconCacheSize); mIconCache = new TaskKeyLruCache<>(cacheSize); + mIconProvider = new IconProvider(context); } /** @@ -115,6 +118,12 @@ public class TaskIconCache { mIconCache.remove(taskKey); } + void invalidateCacheEntries(String pkg, UserHandle handle) { + Utilities.postAsyncCallback(mBackgroundHandler, + () -> mIconCache.removeAll(key -> + pkg.equals(key.getPackageName()) && handle.getIdentifier() == key.userId)); + } + @WorkerThread private TaskCacheEntry getCacheEntry(Task task) { TaskCacheEntry entry = mIconCache.getAndInvalidateIfModified(task.key); @@ -143,12 +152,11 @@ public class TaskIconCache { key.getComponent(), key.userId); if (activityInfo != null) { BitmapInfo bitmapInfo = getBitmapInfo( - activityInfo.loadUnbadgedIcon(mContext.getPackageManager()), + mIconProvider.getIcon(activityInfo, UserHandle.of(key.userId)), key.userId, desc.getPrimaryColor(), activityInfo.applicationInfo.isInstantApp()); - entry.icon = DrawableFactory.INSTANCE.get(mContext).newIcon( - mContext, bitmapInfo, activityInfo); + entry.icon = newIcon(mContext, bitmapInfo); } else { entry.icon = getDefaultIcon(key.userId); } diff --git a/quickstep/src/com/android/quickstep/TaskThumbnailCache.java b/quickstep/src/com/android/quickstep/TaskThumbnailCache.java index 3b50c2623b..e47df6c673 100644 --- a/quickstep/src/com/android/quickstep/TaskThumbnailCache.java +++ b/quickstep/src/com/android/quickstep/TaskThumbnailCache.java @@ -27,9 +27,9 @@ import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.icons.cache.HandlerRunnable; import com.android.launcher3.util.Preconditions; +import com.android.quickstep.util.TaskKeyLruCache; import com.android.systemui.shared.recents.model.Task; import com.android.systemui.shared.recents.model.Task.TaskKey; -import com.android.systemui.shared.recents.model.TaskKeyLruCache; import com.android.systemui.shared.recents.model.ThumbnailData; import com.android.systemui.shared.system.ActivityManagerWrapper; @@ -41,7 +41,7 @@ public class TaskThumbnailCache { private final Handler mBackgroundHandler; private final int mCacheSize; - private final ThumbnailCache mCache; + private final TaskKeyLruCache mCache; private final HighResLoadingState mHighResLoadingState; public static class HighResLoadingState { @@ -100,7 +100,7 @@ public class TaskThumbnailCache { Resources res = context.getResources(); mCacheSize = res.getInteger(R.integer.recentsThumbnailCacheSize); - mCache = new ThumbnailCache(mCacheSize); + mCache = new TaskKeyLruCache<>(mCacheSize); } /** @@ -223,21 +223,4 @@ public class TaskThumbnailCache { this.reducedResolution = reducedResolution; } } - - private static class ThumbnailCache extends TaskKeyLruCache { - - public ThumbnailCache(int cacheSize) { - super(cacheSize); - } - - /** - * Updates the cache entry if it is already present in the cache - */ - public void updateIfAlreadyInCache(int taskId, ThumbnailData thumbnailData) { - ThumbnailData oldData = getCacheEntry(taskId); - if (oldData != null) { - putCacheEntry(taskId, thumbnailData); - } - } - } } diff --git a/quickstep/src/com/android/quickstep/util/TaskKeyLruCache.java b/quickstep/src/com/android/quickstep/util/TaskKeyLruCache.java new file mode 100644 index 0000000000..d87feec069 --- /dev/null +++ b/quickstep/src/com/android/quickstep/util/TaskKeyLruCache.java @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2019 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.quickstep.util; + +import android.util.Log; + +import com.android.systemui.shared.recents.model.Task.TaskKey; + +import java.util.LinkedHashMap; +import java.util.function.Predicate; + +/** + * A simple LRU cache for task key entries + * @param The type of the value + */ +public class TaskKeyLruCache { + + private final MyLinkedHashMap mMap; + + public TaskKeyLruCache(int maxSize) { + mMap = new MyLinkedHashMap<>(maxSize); + } + + /** + * Removes all entries from the cache + */ + public synchronized void evictAll() { + mMap.clear(); + } + + /** + * Removes a particular entry from the cache + */ + public synchronized void remove(TaskKey key) { + mMap.remove(key.id); + } + + /** + * Removes all entries matching keyCheck + */ + public synchronized void removeAll(Predicate keyCheck) { + mMap.entrySet().removeIf(e -> keyCheck.test(e.getValue().mKey)); + } + + /** + * Gets the entry if it is still valid + */ + public synchronized V getAndInvalidateIfModified(TaskKey key) { + Entry entry = mMap.get(key.id); + + if (entry != null && entry.mKey.windowingMode == key.windowingMode + && entry.mKey.lastActiveTime == key.lastActiveTime) { + return entry.mValue; + } else { + remove(key); + return null; + } + } + + /** + * Adds an entry to the cache, optionally evicting the last accessed entry + */ + public final synchronized void put(TaskKey key, V value) { + if (key != null && value != null) { + mMap.put(key.id, new Entry<>(key, value)); + } else { + Log.e("TaskKeyCache", "Unexpected null key or value: " + key + ", " + value); + } + } + + /** + * Updates the cache entry if it is already present in the cache + */ + public synchronized void updateIfAlreadyInCache(int taskId, V data) { + Entry entry = mMap.get(taskId); + if (entry != null) { + entry.mValue = data; + } + } + + private static class Entry { + + final TaskKey mKey; + V mValue; + + Entry(TaskKey key, V value) { + mKey = key; + mValue = value; + } + + @Override + public int hashCode() { + return mKey.id; + } + } + + private static class MyLinkedHashMap extends LinkedHashMap> { + + private final int mMaxSize; + + MyLinkedHashMap(int maxSize) { + super(0, 0.75f, true /* accessOrder */); + mMaxSize = maxSize; + } + + @Override + protected boolean removeEldestEntry(Entry> eldest) { + return size() > mMaxSize; + } + } +} diff --git a/res/values/config.xml b/res/values/config.xml index 10671c5f3a..2a1f6f7864 100644 --- a/res/values/config.xml +++ b/res/values/config.xml @@ -1,5 +1,5 @@ - + false false false @@ -21,10 +21,10 @@ com.android.launcher3.settings.SettingsActivity$LauncherSettingsFragment - + - + 90 @@ -34,7 +34,7 @@ - + 900 @@ -57,13 +57,11 @@ 200 - + true - - @@ -73,12 +71,6 @@ - - - - - - @@ -96,7 +88,12 @@ 40 300 - + + + + + + @@ -111,10 +108,10 @@ - + - + diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java index e8aa83f70f..ff5f33d526 100644 --- a/src/com/android/launcher3/BubbleTextView.java +++ b/src/com/android/launcher3/BubbleTextView.java @@ -16,6 +16,8 @@ package com.android.launcher3; +import static com.android.launcher3.FastBitmapDrawable.newIcon; +import static com.android.launcher3.graphics.PreloadIconDrawable.newPendingIcon; import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound; import android.animation.Animator; @@ -45,7 +47,6 @@ import com.android.launcher3.Launcher.OnResumeCallback; import com.android.launcher3.accessibility.LauncherAccessibilityDelegate; import com.android.launcher3.dot.DotInfo; import com.android.launcher3.folder.FolderIcon; -import com.android.launcher3.graphics.DrawableFactory; import com.android.launcher3.graphics.IconPalette; import com.android.launcher3.graphics.IconShape; import com.android.launcher3.graphics.PreloadIconDrawable; @@ -287,8 +288,7 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, } private void applyIconAndLabel(ItemInfoWithIcon info) { - FastBitmapDrawable iconDrawable = DrawableFactory.INSTANCE.get(getContext()) - .newIcon(getContext(), info); + FastBitmapDrawable iconDrawable = newIcon(getContext(), info); mDotParams.color = IconPalette.getMutedColor(info.bitmap.color, 0.54f); setIcon(iconDrawable); @@ -567,8 +567,7 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, preloadDrawable = (PreloadIconDrawable) mIcon; preloadDrawable.setLevel(progressLevel); } else { - preloadDrawable = DrawableFactory.INSTANCE.get(getContext()) - .newPendingIcon(getContext(), info); + preloadDrawable = newPendingIcon(getContext(), info); preloadDrawable.setLevel(progressLevel); setIcon(preloadDrawable); } diff --git a/src/com/android/launcher3/FastBitmapDrawable.java b/src/com/android/launcher3/FastBitmapDrawable.java index 2e57f71412..50916840d9 100644 --- a/src/com/android/launcher3/FastBitmapDrawable.java +++ b/src/com/android/launcher3/FastBitmapDrawable.java @@ -20,6 +20,7 @@ import static com.android.launcher3.anim.Interpolators.ACCEL; import static com.android.launcher3.anim.Interpolators.DEACCEL; import android.animation.ObjectAnimator; +import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; @@ -35,6 +36,7 @@ import android.graphics.drawable.Drawable; import android.util.Property; import android.util.SparseArray; +import com.android.launcher3.graphics.PlaceHolderIconDrawable; import com.android.launcher3.icons.BitmapInfo; public class FastBitmapDrawable extends Drawable { @@ -361,7 +363,7 @@ public class FastBitmapDrawable extends Drawable { } @Override - public Drawable newDrawable() { + public FastBitmapDrawable newDrawable() { return new FastBitmapDrawable(mBitmap, mIconColor, mIsDisabled); } @@ -370,4 +372,37 @@ public class FastBitmapDrawable extends Drawable { return 0; } } + + /** + * Interface to be implemented by custom {@link BitmapInfo} to handle drawable construction + */ + public interface Factory { + + /** + * Called to create a new drawable + */ + FastBitmapDrawable newDrawable(); + } + + /** + * Returns a FastBitmapDrawable with the icon. + */ + public static FastBitmapDrawable newIcon(Context context, ItemInfoWithIcon info) { + FastBitmapDrawable drawable = newIcon(context, info.bitmap); + drawable.setIsDisabled(info.isDisabled()); + return drawable; + } + + /** + * Creates a drawable for the provided BitmapInfo + */ + public static FastBitmapDrawable newIcon(Context context, BitmapInfo info) { + if (info instanceof Factory) { + return ((Factory) info).newDrawable(); + } else if (info.isLowRes()) { + return new PlaceHolderIconDrawable(info, context); + } else { + return new FastBitmapDrawable(info); + } + } } diff --git a/src/com/android/launcher3/IconProvider.java b/src/com/android/launcher3/IconProvider.java deleted file mode 100644 index 0f006f7c19..0000000000 --- a/src/com/android/launcher3/IconProvider.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.android.launcher3; - -import static com.android.launcher3.util.MainThreadInitializedObject.forOverride; - -import android.content.pm.LauncherActivityInfo; -import android.graphics.drawable.Drawable; - -import com.android.launcher3.util.MainThreadInitializedObject; -import com.android.launcher3.util.ResourceBasedOverride; - -public class IconProvider implements ResourceBasedOverride { - - public static MainThreadInitializedObject INSTANCE = - forOverride(IconProvider.class, R.string.icon_provider_class); - - public IconProvider() { } - - public String getSystemStateForPackage(String systemState, String packageName) { - return systemState; - } - - /** - * @param flattenDrawable true if the caller does not care about the specification of the - * original icon as long as the flattened version looks the same. - */ - public Drawable getIcon(LauncherActivityInfo info, int iconDpi, boolean flattenDrawable) { - return info.getIcon(iconDpi); - } -} diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java index c717d1a4a3..79f48215f9 100644 --- a/src/com/android/launcher3/LauncherAppState.java +++ b/src/com/android/launcher3/LauncherAppState.java @@ -30,12 +30,14 @@ import android.util.Log; import com.android.launcher3.compat.UserManagerCompat; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.icons.IconCache; +import com.android.launcher3.icons.IconProvider; import com.android.launcher3.icons.LauncherIcons; import com.android.launcher3.notification.NotificationListener; import com.android.launcher3.pm.InstallSessionTracker; import com.android.launcher3.pm.PackageInstallerCompat; import com.android.launcher3.util.MainThreadInitializedObject; import com.android.launcher3.util.Preconditions; +import com.android.launcher3.util.SafeCloseable; import com.android.launcher3.util.SecureSettingsObserver; import com.android.launcher3.util.SimpleBroadcastReceiver; import com.android.launcher3.widget.custom.CustomWidgetManager; @@ -57,6 +59,7 @@ public class LauncherAppState { private final InstallSessionTracker mInstallSessionTracker; private final SimpleBroadcastReceiver mModelChangeReceiver; + private final SafeCloseable mCalendarChangeTracker; public static LauncherAppState getInstance(final Context context) { return INSTANCE.get(context); @@ -92,6 +95,10 @@ public class LauncherAppState { if (FeatureFlags.IS_DOGFOOD_BUILD) { mModelChangeReceiver.register(mContext, ACTION_FORCE_ROLOAD); } + + mCalendarChangeTracker = IconProvider.registerIconChangeListener(mContext, + mModel::onAppIconChanged, MODEL_EXECUTOR.getHandler()); + // TODO: remove listener on terminate FeatureFlags.APP_SEARCH_IMPROVEMENTS.addChangeListener(context, mModel::forceReload); CustomWidgetManager.INSTANCE.get(mContext) @@ -143,6 +150,7 @@ public class LauncherAppState { mContext.unregisterReceiver(mModelChangeReceiver); mContext.getSystemService(LauncherApps.class).unregisterCallback(mModel); mInstallSessionTracker.unregister(); + mCalendarChangeTracker.close(); CustomWidgetManager.INSTANCE.get(mContext).setWidgetRefreshCallback(null); if (mNotificationDotsObserver != null) { diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java index 93304a177a..1e25c0cc68 100644 --- a/src/com/android/launcher3/LauncherModel.java +++ b/src/com/android/launcher3/LauncherModel.java @@ -21,6 +21,7 @@ import static com.android.launcher3.config.FeatureFlags.IS_DOGFOOD_BUILD; import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; +import android.content.Context; import android.content.Intent; import android.content.pm.LauncherApps; import android.content.pm.PackageInstaller; @@ -31,6 +32,7 @@ import android.util.Log; import android.util.Pair; import androidx.annotation.Nullable; +import androidx.annotation.WorkerThread; import com.android.launcher3.compat.UserManagerCompat; import com.android.launcher3.config.FeatureFlags; @@ -53,7 +55,6 @@ import com.android.launcher3.model.UserLockStateChangedTask; import com.android.launcher3.pm.InstallSessionTracker; import com.android.launcher3.pm.PackageInstallInfo; import com.android.launcher3.shortcuts.DeepShortcutManager; -import com.android.launcher3.testing.TestProtocol; import com.android.launcher3.util.IntSparseArrayMap; import com.android.launcher3.util.ItemInfoMatcher; import com.android.launcher3.util.PackageUserKey; @@ -210,9 +211,21 @@ public class LauncherModel extends LauncherApps.Callback implements InstallSessi enqueueModelUpdateTask(new ShortcutsChangedTask(packageName, shortcuts, user, true)); } - public void updatePinnedShortcuts(String packageName, List shortcuts, - UserHandle user) { - enqueueModelUpdateTask(new ShortcutsChangedTask(packageName, shortcuts, user, false)); + /** + * Called when the icon for an app changes, outside of package event + */ + @WorkerThread + public void onAppIconChanged(String packageName, UserHandle user) { + // Update the icon for the calendar package + Context context = mApp.getContext(); + onPackageChanged(packageName, user); + + List pinnedShortcuts = DeepShortcutManager.getInstance(context) + .queryForPinnedShortcuts(packageName, user); + if (!pinnedShortcuts.isEmpty()) { + enqueueModelUpdateTask(new ShortcutsChangedTask(packageName, pinnedShortcuts, user, + false)); + } } public void onBroadcastIntent(Intent intent) { diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java index aa6e61cc77..20328459c1 100644 --- a/src/com/android/launcher3/Utilities.java +++ b/src/com/android/launcher3/Utilities.java @@ -63,6 +63,7 @@ import android.view.animation.Interpolator; import com.android.launcher3.dragndrop.FolderAdaptiveIcon; import com.android.launcher3.graphics.RotationMode; import com.android.launcher3.graphics.TintedDrawableSpan; +import com.android.launcher3.icons.IconProvider; import com.android.launcher3.icons.LauncherIcons; import com.android.launcher3.pm.ShortcutConfigActivityInfo; import com.android.launcher3.shortcuts.DeepShortcutManager; @@ -518,19 +519,20 @@ public final class Utilities { } /** - * Returns the full drawable for {@param info}. + * Returns the full drawable for info without any flattening or pre-processing. + * * @param outObj this is set to the internal data associated with {@param info}, * eg {@link LauncherActivityInfo} or {@link ShortcutInfo}. */ public static Drawable getFullDrawable(Launcher launcher, ItemInfo info, int width, int height, - boolean flattenDrawable, Object[] outObj) { + Object[] outObj) { LauncherAppState appState = LauncherAppState.getInstance(launcher); if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) { LauncherActivityInfo activityInfo = launcher.getSystemService(LauncherApps.class) .resolveActivity(info.getIntent(), info.user); outObj[0] = activityInfo; - return (activityInfo != null) ? appState.getIconCache() - .getFullResIcon(activityInfo, flattenDrawable) : null; + return activityInfo == null ? null : new IconProvider(launcher).getIconForUI( + activityInfo, launcher.getDeviceProfile().inv.fillResIconDpi); } else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) { if (info instanceof PendingAddShortcutInfo) { ShortcutConfigActivityInfo activityInfo = diff --git a/src/com/android/launcher3/WidgetPreviewLoader.java b/src/com/android/launcher3/WidgetPreviewLoader.java index c5e74effa9..37b58d306d 100644 --- a/src/com/android/launcher3/WidgetPreviewLoader.java +++ b/src/com/android/launcher3/WidgetPreviewLoader.java @@ -23,16 +23,19 @@ import android.graphics.PorterDuff; import android.graphics.PorterDuffXfermode; import android.graphics.Rect; import android.graphics.RectF; +import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.os.AsyncTask; import android.os.CancellationSignal; import android.os.Process; import android.os.UserHandle; +import android.util.ArrayMap; import android.util.Log; import android.util.LongSparseArray; import android.util.Pair; import androidx.annotation.Nullable; +import androidx.annotation.UiThread; import com.android.launcher3.compat.AppWidgetManagerCompat; import com.android.launcher3.compat.UserManagerCompat; @@ -78,6 +81,9 @@ public class WidgetPreviewLoader { private final UserManagerCompat mUserManager; private final CacheDb mDb; + private final UserHandle mMyUser = Process.myUserHandle(); + private final ArrayMap mUserBadges = new ArrayMap<>(); + public WidgetPreviewLoader(Context context, IconCache iconCache) { mContext = context; mIconCache = iconCache; @@ -85,6 +91,51 @@ public class WidgetPreviewLoader { mDb = new CacheDb(context); } + /** + * Returns a drawable that can be used as a badge for the user or null. + */ + @UiThread + public Drawable getBadgeForUser(UserHandle user, int badgeSize) { + if (mMyUser.equals(user)) { + return null; + } + + Bitmap badgeBitmap = getUserBadge(user, badgeSize); + FastBitmapDrawable d = new FastBitmapDrawable(badgeBitmap); + d.setFilterBitmap(true); + d.setBounds(0, 0, badgeBitmap.getWidth(), badgeBitmap.getHeight()); + return d; + } + + private Bitmap getUserBadge(UserHandle user, int badgeSize) { + synchronized (mUserBadges) { + Bitmap badgeBitmap = mUserBadges.get(user); + if (badgeBitmap != null) { + return badgeBitmap; + } + + final Resources res = mContext.getResources(); + badgeBitmap = Bitmap.createBitmap(badgeSize, badgeSize, Bitmap.Config.ARGB_8888); + + Drawable drawable = mContext.getPackageManager().getUserBadgedDrawableForDensity( + new BitmapDrawable(res, badgeBitmap), user, + new Rect(0, 0, badgeSize, badgeSize), + 0); + if (drawable instanceof BitmapDrawable) { + badgeBitmap = ((BitmapDrawable) drawable).getBitmap(); + } else { + badgeBitmap.eraseColor(Color.TRANSPARENT); + Canvas c = new Canvas(badgeBitmap); + drawable.setBounds(0, 0, badgeSize, badgeSize); + drawable.draw(c); + c.setBitmap(null); + } + + mUserBadges.put(user, badgeBitmap); + return badgeBitmap; + } + } + /** * Generates the widget preview on {@link AsyncTask#THREAD_POOL_EXECUTOR}. Must be * called on UI thread @@ -106,8 +157,8 @@ public class WidgetPreviewLoader { public void refresh() { mDb.clear(); - } + /** * The DB holds the generated previews for various components. Previews can also have different * sizes (landscape vs portrait). diff --git a/src/com/android/launcher3/dragndrop/DragView.java b/src/com/android/launcher3/dragndrop/DragView.java index f66d07e3d8..145885a458 100644 --- a/src/com/android/launcher3/dragndrop/DragView.java +++ b/src/com/android/launcher3/dragndrop/DragView.java @@ -216,8 +216,7 @@ public class DragView extends View implements LauncherStateManager.StateListener Object[] outObj = new Object[1]; int w = mBitmap.getWidth(); int h = mBitmap.getHeight(); - Drawable dr = Utilities.getFullDrawable(mLauncher, info, w, h, - false /* flattenDrawable */, outObj); + Drawable dr = Utilities.getFullDrawable(mLauncher, info, w, h, outObj); if (dr instanceof AdaptiveIconDrawable) { int blurMargin = (int) mLauncher.getResources() diff --git a/src/com/android/launcher3/folder/PreviewItemManager.java b/src/com/android/launcher3/folder/PreviewItemManager.java index 2d817e6d2d..5b3a05ef10 100644 --- a/src/com/android/launcher3/folder/PreviewItemManager.java +++ b/src/com/android/launcher3/folder/PreviewItemManager.java @@ -16,10 +16,12 @@ package com.android.launcher3.folder; +import static com.android.launcher3.FastBitmapDrawable.newIcon; import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.ENTER_INDEX; import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.EXIT_INDEX; import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW; import static com.android.launcher3.folder.FolderIcon.DROP_IN_ANIMATION_DURATION; +import static com.android.launcher3.graphics.PreloadIconDrawable.newPendingIcon; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; @@ -38,7 +40,6 @@ import androidx.annotation.NonNull; import com.android.launcher3.Launcher; import com.android.launcher3.Utilities; import com.android.launcher3.WorkspaceItemInfo; -import com.android.launcher3.graphics.DrawableFactory; import com.android.launcher3.graphics.PreloadIconDrawable; import java.util.ArrayList; @@ -66,7 +67,6 @@ public class PreviewItemManager { private final Context mContext; private final FolderIcon mIcon; - private final DrawableFactory mDrawableFactory; private final int mIconSize; // These variables are all associated with the drawing of the preview; they are stored @@ -94,7 +94,6 @@ public class PreviewItemManager { public PreviewItemManager(FolderIcon icon) { mContext = icon.getContext(); mIcon = icon; - mDrawableFactory = DrawableFactory.INSTANCE.get(mContext); mIconSize = Launcher.getLauncher(mContext).getDeviceProfile().folderChildIconSizePx; } @@ -395,11 +394,11 @@ public class PreviewItemManager { private void setDrawable(PreviewItemDrawingParams p, WorkspaceItemInfo item) { if (item.hasPromiseIconUi()) { - PreloadIconDrawable drawable = mDrawableFactory.newPendingIcon(mContext, item); + PreloadIconDrawable drawable = newPendingIcon(mContext, item); drawable.setLevel(item.getInstallProgress()); p.drawable = drawable; } else { - p.drawable = mDrawableFactory.newIcon(mContext, item); + p.drawable = newIcon(mContext, item); } p.drawable.setBounds(0, 0, mIconSize, mIconSize); p.item = item; diff --git a/src/com/android/launcher3/graphics/DrawableFactory.java b/src/com/android/launcher3/graphics/DrawableFactory.java deleted file mode 100644 index 13dbab589c..0000000000 --- a/src/com/android/launcher3/graphics/DrawableFactory.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright (C) 2016 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.graphics; - -import static com.android.launcher3.graphics.IconShape.getShapePath; -import static com.android.launcher3.util.MainThreadInitializedObject.forOverride; - -import android.content.Context; -import android.content.pm.ActivityInfo; -import android.content.res.Resources; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Rect; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.Drawable; -import android.os.Process; -import android.os.UserHandle; -import android.util.ArrayMap; - -import androidx.annotation.UiThread; - -import com.android.launcher3.FastBitmapDrawable; -import com.android.launcher3.ItemInfoWithIcon; -import com.android.launcher3.R; -import com.android.launcher3.icons.BitmapInfo; -import com.android.launcher3.util.MainThreadInitializedObject; -import com.android.launcher3.util.ResourceBasedOverride; - -/** - * Factory for creating new drawables. - */ -public class DrawableFactory implements ResourceBasedOverride { - - public static final MainThreadInitializedObject INSTANCE = - forOverride(DrawableFactory.class, R.string.drawable_factory_class); - - protected final UserHandle mMyUser = Process.myUserHandle(); - protected final ArrayMap mUserBadges = new ArrayMap<>(); - - /** - * Returns a FastBitmapDrawable with the icon. - */ - public FastBitmapDrawable newIcon(Context context, ItemInfoWithIcon info) { - FastBitmapDrawable drawable = info.usingLowResIcon() - ? new PlaceHolderIconDrawable(info.bitmap, getShapePath(), context) - : new FastBitmapDrawable(info.bitmap); - drawable.setIsDisabled(info.isDisabled()); - return drawable; - } - - public FastBitmapDrawable newIcon(Context context, BitmapInfo info, ActivityInfo target) { - return info.isLowRes() - ? new PlaceHolderIconDrawable(info, getShapePath(), context) - : new FastBitmapDrawable(info); - } - - /** - * Returns a FastBitmapDrawable with the icon. - */ - public PreloadIconDrawable newPendingIcon(Context context, ItemInfoWithIcon info) { - return new PreloadIconDrawable(info, getShapePath(), context); - } - - /** - * Returns a drawable that can be used as a badge for the user or null. - */ - @UiThread - public Drawable getBadgeForUser(UserHandle user, Context context, int badgeSize) { - if (mMyUser.equals(user)) { - return null; - } - - Bitmap badgeBitmap = getUserBadge(user, context, badgeSize); - FastBitmapDrawable d = new FastBitmapDrawable(badgeBitmap); - d.setFilterBitmap(true); - d.setBounds(0, 0, badgeBitmap.getWidth(), badgeBitmap.getHeight()); - return d; - } - - protected synchronized Bitmap getUserBadge(UserHandle user, Context context, int badgeSize) { - Bitmap badgeBitmap = mUserBadges.get(user); - if (badgeBitmap != null) { - return badgeBitmap; - } - - final Resources res = context.getApplicationContext().getResources(); - badgeBitmap = Bitmap.createBitmap(badgeSize, badgeSize, Bitmap.Config.ARGB_8888); - - Drawable drawable = context.getPackageManager().getUserBadgedDrawableForDensity( - new BitmapDrawable(res, badgeBitmap), user, new Rect(0, 0, badgeSize, badgeSize), - 0); - if (drawable instanceof BitmapDrawable) { - badgeBitmap = ((BitmapDrawable) drawable).getBitmap(); - } else { - badgeBitmap.eraseColor(Color.TRANSPARENT); - Canvas c = new Canvas(badgeBitmap); - drawable.setBounds(0, 0, badgeSize, badgeSize); - drawable.draw(c); - c.setBitmap(null); - } - - mUserBadges.put(user, badgeBitmap); - return badgeBitmap; - } -} diff --git a/src/com/android/launcher3/graphics/PlaceHolderIconDrawable.java b/src/com/android/launcher3/graphics/PlaceHolderIconDrawable.java index c6f807f63a..d347e8fdeb 100644 --- a/src/com/android/launcher3/graphics/PlaceHolderIconDrawable.java +++ b/src/com/android/launcher3/graphics/PlaceHolderIconDrawable.java @@ -17,8 +17,9 @@ package com.android.launcher3.graphics; import static androidx.core.graphics.ColorUtils.compositeColors; +import static com.android.launcher3.graphics.IconShape.getShapePath; + import android.content.Context; -import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Path; import android.graphics.Rect; @@ -36,16 +37,12 @@ public class PlaceHolderIconDrawable extends FastBitmapDrawable { // Path in [0, 100] bounds. private final Path mProgressPath; - public PlaceHolderIconDrawable(BitmapInfo info, Path progressPath, Context context) { - this(info.icon, info.color, progressPath, context); - } + public PlaceHolderIconDrawable(BitmapInfo info, Context context) { + super(info); - protected PlaceHolderIconDrawable(Bitmap b, int iconColor, Path progressPath, Context context) { - super(b, iconColor); - - mProgressPath = progressPath; + mProgressPath = getShapePath(); mPaint.setColor(compositeColors( - Themes.getAttrColor(context, R.attr.loadingIconColor), iconColor)); + Themes.getAttrColor(context, R.attr.loadingIconColor), info.color)); } @Override diff --git a/src/com/android/launcher3/graphics/PreloadIconDrawable.java b/src/com/android/launcher3/graphics/PreloadIconDrawable.java index acdf9420fc..b0e1db1019 100644 --- a/src/com/android/launcher3/graphics/PreloadIconDrawable.java +++ b/src/com/android/launcher3/graphics/PreloadIconDrawable.java @@ -18,6 +18,7 @@ package com.android.launcher3.graphics; import static com.android.launcher3.graphics.IconShape.DEFAULT_PATH_SIZE; +import static com.android.launcher3.graphics.IconShape.getShapePath; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; @@ -101,13 +102,10 @@ public class PreloadIconDrawable extends FastBitmapDrawable { private ObjectAnimator mCurrentAnim; - /** - * @param progressPath fixed path in the bounds [0, 0, 100, 100] representing a progress bar. - */ - public PreloadIconDrawable(ItemInfoWithIcon info, Path progressPath, Context context) { + public PreloadIconDrawable(ItemInfoWithIcon info, Context context) { super(info.bitmap); mItem = info; - mProgressPath = progressPath; + mProgressPath = getShapePath(); mScaledTrackPath = new Path(); mScaledProgressPath = new Path(); @@ -289,4 +287,11 @@ public class PreloadIconDrawable extends FastBitmapDrawable { } invalidateSelf(); } + + /** + * Returns a FastBitmapDrawable with the icon. + */ + public static PreloadIconDrawable newPendingIcon(Context context, ItemInfoWithIcon info) { + return new PreloadIconDrawable(info, context); + } } diff --git a/src/com/android/launcher3/icons/ClockDrawableWrapper.java b/src/com/android/launcher3/icons/ClockDrawableWrapper.java new file mode 100644 index 0000000000..b7dd092ab4 --- /dev/null +++ b/src/com/android/launcher3/icons/ClockDrawableWrapper.java @@ -0,0 +1,328 @@ +/* + * Copyright (C) 2019 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; + +import android.annotation.TargetApi; +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Rect; +import android.graphics.drawable.AdaptiveIconDrawable; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.LayerDrawable; +import android.os.Build; +import android.os.Bundle; +import android.os.Process; +import android.os.SystemClock; +import android.util.Log; + +import com.android.launcher3.FastBitmapDrawable; + +import java.util.Calendar; +import java.util.concurrent.TimeUnit; + +/** + * Wrapper over {@link AdaptiveIconDrawable} to intercept icon flattening logic for dynamic + * clock icons + */ +@TargetApi(Build.VERSION_CODES.O) +public class ClockDrawableWrapper extends AdaptiveIconDrawable implements BitmapInfo.Extender { + + private static final String TAG = "ClockDrawableWrapper"; + + private static final boolean DISABLE_SECONDS = true; + + // Time after which the clock icon should check for an update. The actual invalidate + // will only happen in case of any change. + public static final long TICK_MS = DISABLE_SECONDS ? TimeUnit.MINUTES.toMillis(1) : 200L; + + private static final String LAUNCHER_PACKAGE = "com.android.launcher3"; + private static final String ROUND_ICON_METADATA_KEY = LAUNCHER_PACKAGE + + ".LEVEL_PER_TICK_ICON_ROUND"; + private static final String HOUR_INDEX_METADATA_KEY = LAUNCHER_PACKAGE + ".HOUR_LAYER_INDEX"; + private static final String MINUTE_INDEX_METADATA_KEY = LAUNCHER_PACKAGE + + ".MINUTE_LAYER_INDEX"; + private static final String SECOND_INDEX_METADATA_KEY = LAUNCHER_PACKAGE + + ".SECOND_LAYER_INDEX"; + private static final String DEFAULT_HOUR_METADATA_KEY = LAUNCHER_PACKAGE + + ".DEFAULT_HOUR"; + private static final String DEFAULT_MINUTE_METADATA_KEY = LAUNCHER_PACKAGE + + ".DEFAULT_MINUTE"; + private static final String DEFAULT_SECOND_METADATA_KEY = LAUNCHER_PACKAGE + + ".DEFAULT_SECOND"; + + /* Number of levels to jump per second for the second hand */ + private static final int LEVELS_PER_SECOND = 10; + + public static final int INVALID_VALUE = -1; + + private final AnimationInfo mAnimationInfo = new AnimationInfo(); + private int mTargetSdkVersion; + + public ClockDrawableWrapper(AdaptiveIconDrawable base) { + super(base.getBackground(), base.getForeground()); + } + + /** + * Loads and returns the wrapper from the provided package, or returns null + * if it is unable to load. + */ + public static ClockDrawableWrapper forPackage(Context context, String pkg, int iconDpi) { + try { + PackageManager pm = context.getPackageManager(); + ApplicationInfo appInfo = pm.getApplicationInfo(pkg, + PackageManager.MATCH_UNINSTALLED_PACKAGES | PackageManager.GET_META_DATA); + final Bundle metadata = appInfo.metaData; + if (metadata == null) { + return null; + } + int drawableId = metadata.getInt(ROUND_ICON_METADATA_KEY, 0); + if (drawableId == 0) { + return null; + } + + Drawable drawable = pm.getResourcesForApplication(appInfo).getDrawableForDensity( + drawableId, iconDpi).mutate(); + if (!(drawable instanceof AdaptiveIconDrawable)) { + return null; + } + + ClockDrawableWrapper wrapper = + new ClockDrawableWrapper((AdaptiveIconDrawable) drawable); + wrapper.mTargetSdkVersion = appInfo.targetSdkVersion; + AnimationInfo info = wrapper.mAnimationInfo; + + info.baseDrawableState = drawable.getConstantState(); + + info.hourLayerIndex = metadata.getInt(HOUR_INDEX_METADATA_KEY, INVALID_VALUE); + info.minuteLayerIndex = metadata.getInt(MINUTE_INDEX_METADATA_KEY, INVALID_VALUE); + info.secondLayerIndex = metadata.getInt(SECOND_INDEX_METADATA_KEY, INVALID_VALUE); + + info.defaultHour = metadata.getInt(DEFAULT_HOUR_METADATA_KEY, 0); + info.defaultMinute = metadata.getInt(DEFAULT_MINUTE_METADATA_KEY, 0); + info.defaultSecond = metadata.getInt(DEFAULT_SECOND_METADATA_KEY, 0); + + LayerDrawable foreground = (LayerDrawable) wrapper.getForeground(); + int layerCount = foreground.getNumberOfLayers(); + if (info.hourLayerIndex < 0 || info.hourLayerIndex >= layerCount) { + info.hourLayerIndex = INVALID_VALUE; + } + if (info.minuteLayerIndex < 0 || info.minuteLayerIndex >= layerCount) { + info.minuteLayerIndex = INVALID_VALUE; + } + if (info.secondLayerIndex < 0 || info.secondLayerIndex >= layerCount) { + info.secondLayerIndex = INVALID_VALUE; + } else if (DISABLE_SECONDS) { + foreground.setDrawable(info.secondLayerIndex, null); + info.secondLayerIndex = INVALID_VALUE; + } + return wrapper; + } catch (Exception e) { + Log.d(TAG, "Unable to load clock drawable info", e); + } + return null; + } + + @Override + public BitmapInfo getExtendedInfo(Bitmap bitmap, int color, BaseIconFactory iconFactory) { + iconFactory.disableColorExtraction(); + float [] scale = new float[1]; + AdaptiveIconDrawable background = new AdaptiveIconDrawable( + getBackground().getConstantState().newDrawable(), null); + BitmapInfo bitmapInfo = iconFactory.createBadgedIconBitmap(background, + Process.myUserHandle(), mTargetSdkVersion, false, scale); + + return new ClockBitmapInfo(bitmap, color, scale[0], mAnimationInfo, bitmapInfo.icon); + } + + @Override + public void prepareToDrawOnUi() { + mAnimationInfo.applyTime(Calendar.getInstance(), (LayerDrawable) getForeground()); + } + + private static class AnimationInfo { + + public ConstantState baseDrawableState; + + public int hourLayerIndex; + public int minuteLayerIndex; + public int secondLayerIndex; + public int defaultHour; + public int defaultMinute; + public int defaultSecond; + + boolean applyTime(Calendar time, LayerDrawable foregroundDrawable) { + time.setTimeInMillis(System.currentTimeMillis()); + + // We need to rotate by the difference from the default time if one is specified. + int convertedHour = (time.get(Calendar.HOUR) + (12 - defaultHour)) % 12; + int convertedMinute = (time.get(Calendar.MINUTE) + (60 - defaultMinute)) % 60; + int convertedSecond = (time.get(Calendar.SECOND) + (60 - defaultSecond)) % 60; + + boolean invalidate = false; + if (hourLayerIndex != INVALID_VALUE) { + final Drawable hour = foregroundDrawable.getDrawable(hourLayerIndex); + if (hour.setLevel(convertedHour * 60 + time.get(Calendar.MINUTE))) { + invalidate = true; + } + } + + if (minuteLayerIndex != INVALID_VALUE) { + final Drawable minute = foregroundDrawable.getDrawable(minuteLayerIndex); + if (minute.setLevel(time.get(Calendar.HOUR) * 60 + convertedMinute)) { + invalidate = true; + } + } + + if (secondLayerIndex != INVALID_VALUE) { + final Drawable second = foregroundDrawable.getDrawable(secondLayerIndex); + if (second.setLevel(convertedSecond * LEVELS_PER_SECOND)) { + invalidate = true; + } + } + + return invalidate; + } + } + + private static class ClockBitmapInfo extends BitmapInfo implements FastBitmapDrawable.Factory { + + public final float scale; + public final int offset; + public final AnimationInfo animInfo; + public final Bitmap mFlattenedBackground; + + ClockBitmapInfo(Bitmap icon, int color, float scale, AnimationInfo animInfo, + Bitmap background) { + super(icon, color); + this.scale = scale; + this.animInfo = animInfo; + this.offset = (int) Math.ceil(ShadowGenerator.BLUR_FACTOR * icon.getWidth()); + this.mFlattenedBackground = background; + } + + @Override + public FastBitmapDrawable newDrawable() { + return new ClockIconDrawable(this); + } + } + + private static class ClockIconDrawable extends FastBitmapDrawable implements Runnable { + + private final Calendar mTime = Calendar.getInstance(); + + private final ClockBitmapInfo mInfo; + + private final AdaptiveIconDrawable mFullDrawable; + private final LayerDrawable mForeground; + + ClockIconDrawable(ClockBitmapInfo clockInfo) { + super(clockInfo); + + mInfo = clockInfo; + + mFullDrawable = (AdaptiveIconDrawable) mInfo.animInfo.baseDrawableState.newDrawable(); + mForeground = (LayerDrawable) mFullDrawable.getForeground(); + } + + @Override + protected void onBoundsChange(Rect bounds) { + super.onBoundsChange(bounds); + mFullDrawable.setBounds(bounds); + } + + @Override + public void drawInternal(Canvas canvas, Rect bounds) { + if (mInfo == null) { + super.drawInternal(canvas, bounds); + return; + } + // draw the background that is already flattened to a bitmap + canvas.drawBitmap(mInfo.mFlattenedBackground, null, bounds, mPaint); + + // prepare and draw the foreground + mInfo.animInfo.applyTime(mTime, mForeground); + + canvas.scale(mInfo.scale, mInfo.scale, + bounds.exactCenterX() + mInfo.offset, bounds.exactCenterY() + mInfo.offset); + canvas.clipPath(mFullDrawable.getIconMask()); + mForeground.draw(canvas); + + reschedule(); + } + + @Override + protected void updateFilter() { + super.updateFilter(); + mFullDrawable.setColorFilter(mPaint.getColorFilter()); + } + + @Override + public void run() { + if (mInfo.animInfo.applyTime(mTime, mForeground)) { + invalidateSelf(); + } else { + reschedule(); + } + } + + @Override + public boolean setVisible(boolean visible, boolean restart) { + boolean result = super.setVisible(visible, restart); + if (visible) { + reschedule(); + } else { + unscheduleSelf(this); + } + return result; + } + + private void reschedule() { + if (!isVisible()) { + return; + } + + unscheduleSelf(this); + final long upTime = SystemClock.uptimeMillis(); + final long step = TICK_MS; /* tick every 200 ms */ + scheduleSelf(this, upTime - ((upTime % step)) + step); + } + + @Override + public ConstantState getConstantState() { + return new ClockConstantState(mInfo, isDisabled()); + } + + private static class ClockConstantState extends MyConstantState { + + private final ClockBitmapInfo mInfo; + + ClockConstantState(ClockBitmapInfo info, boolean isDisabled) { + super(info.icon, info.color, isDisabled); + mInfo = info; + } + + @Override + public FastBitmapDrawable newDrawable() { + ClockIconDrawable drawable = new ClockIconDrawable(mInfo); + drawable.setIsDisabled(mIsDisabled); + return drawable; + } + } + } +} diff --git a/src/com/android/launcher3/icons/IconCache.java b/src/com/android/launcher3/icons/IconCache.java index 13dc08f458..f7faca6899 100644 --- a/src/com/android/launcher3/icons/IconCache.java +++ b/src/com/android/launcher3/icons/IconCache.java @@ -37,7 +37,6 @@ import android.util.Log; import androidx.annotation.NonNull; import com.android.launcher3.AppInfo; -import com.android.launcher3.IconProvider; import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.ItemInfoWithIcon; import com.android.launcher3.LauncherFiles; @@ -50,6 +49,7 @@ import com.android.launcher3.icons.cache.BaseIconCache; import com.android.launcher3.icons.cache.CachingLogic; import com.android.launcher3.icons.cache.HandlerRunnable; import com.android.launcher3.model.PackageItemInfo; +import com.android.launcher3.util.ComponentKey; import com.android.launcher3.util.InstantAppResolver; import com.android.launcher3.util.PackageUserKey; import com.android.launcher3.util.Preconditions; @@ -81,7 +81,7 @@ public class IconCache extends BaseIconCache { mLauncherApps = mContext.getSystemService(LauncherApps.class); mUserManager = UserManagerCompat.getInstance(mContext); mInstantAppResolver = InstantAppResolver.newInstance(mContext); - mIconProvider = IconProvider.INSTANCE.get(context); + mIconProvider = new IconProvider(context); } @Override @@ -230,11 +230,7 @@ public class IconCache extends BaseIconCache { } public Drawable getFullResIcon(LauncherActivityInfo info) { - return getFullResIcon(info, true); - } - - public Drawable getFullResIcon(LauncherActivityInfo info, boolean flattenDrawable) { - return mIconProvider.getIcon(info, mIconDpi, flattenDrawable); + return mIconProvider.getIcon(info, mIconDpi); } public void updateSessionCache(PackageUserKey key, PackageInstaller.SessionInfo info) { @@ -247,6 +243,15 @@ public class IconCache extends BaseIconCache { + ",flags_asi:" + FeatureFlags.APP_SEARCH_IMPROVEMENTS.get(); } + @Override + protected boolean getEntryFromDB(ComponentKey cacheKey, CacheEntry entry, boolean lowRes) { + if (mIconProvider.isClockIcon(cacheKey)) { + // For clock icon, we always load the dynamic icon + return false; + } + return super.getEntryFromDB(cacheKey, entry, lowRes); + } + public static abstract class IconLoadRequest extends HandlerRunnable { IconLoadRequest(Handler handler, Runnable endRunnable) { super(handler, endRunnable); diff --git a/src/com/android/launcher3/icons/IconProvider.java b/src/com/android/launcher3/icons/IconProvider.java new file mode 100644 index 0000000000..26b7eae379 --- /dev/null +++ b/src/com/android/launcher3/icons/IconProvider.java @@ -0,0 +1,251 @@ +/* + * Copyright (C) 2019 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; + +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.ActivityInfo; +import android.content.pm.LauncherActivityInfo; +import android.content.pm.PackageManager; +import android.content.res.Resources; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.os.Handler; +import android.os.Process; +import android.os.UserHandle; +import android.text.TextUtils; +import android.util.Log; + +import com.android.launcher3.R; +import com.android.launcher3.compat.UserManagerCompat; +import com.android.launcher3.icons.BitmapInfo.Extender; +import com.android.launcher3.util.ComponentKey; +import com.android.launcher3.util.SafeCloseable; + +import java.util.Calendar; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; + +/** + * Class to handle icon loading from different packages + */ +public class IconProvider { + + private static final String TAG = "IconProvider"; + private static final boolean DEBUG = false; + + private static final String ICON_METADATA_KEY_PREFIX = ".dynamic_icons"; + + private static final String SYSTEM_STATE_SEPARATOR = " "; + + // Default value returned if there are problems getting resources. + private static final int NO_ID = 0; + + private static final BiFunction LAI_LOADER = + LauncherActivityInfo::getIcon; + + private static final BiFunction AI_LOADER = + ActivityInfo::loadUnbadgedIcon; + + + private final Context mContext; + private final ComponentName mCalendar; + private final ComponentName mClock; + + public IconProvider(Context context) { + mContext = context; + mCalendar = parseComponentOrNull(context, R.string.calendar_component_name); + mClock = parseComponentOrNull(context, R.string.clock_component_name); + } + + /** + * Adds any modification to the provided systemState for dynamic icons. This system state + * is used by caches to check for icon invalidation. + */ + public String getSystemStateForPackage(String systemState, String packageName) { + if (mCalendar != null && mCalendar.getPackageName().equals(packageName)) { + return systemState + SYSTEM_STATE_SEPARATOR + getDay(); + } else { + return systemState; + } + } + + /** + * Loads the icon for the provided LauncherActivityInfo such that it can be drawn directly + * on the UI + */ + public Drawable getIconForUI(LauncherActivityInfo info, int iconDpi) { + Drawable icon = getIcon(info, iconDpi); + if (icon instanceof BitmapInfo.Extender) { + ((Extender) icon).prepareToDrawOnUi(); + } + return icon; + } + + /** + * Loads the icon for the provided LauncherActivityInfo + */ + public Drawable getIcon(LauncherActivityInfo info, int iconDpi) { + return getIcon(info.getApplicationInfo().packageName, info.getUser(), + info, iconDpi, LAI_LOADER); + } + + /** + * Loads the icon for the provided activity info + */ + public Drawable getIcon(ActivityInfo info, UserHandle user) { + return getIcon(info.applicationInfo.packageName, user, info, mContext.getPackageManager(), + AI_LOADER); + } + + private Drawable getIcon(String packageName, UserHandle user, T obj, P param, + BiFunction loader) { + Drawable icon = null; + if (mCalendar != null && mCalendar.getPackageName().equals(packageName)) { + icon = loadCalendarDrawable(0); + } else if (mClock != null + && mClock.getPackageName().equals(packageName) + && Process.myUserHandle().equals(user)) { + icon = loadClockDrawable(0); + } + return icon == null ? loader.apply(obj, param) : icon; + } + + private Drawable loadCalendarDrawable(int iconDpi) { + PackageManager pm = mContext.getPackageManager(); + try { + final Bundle metadata = pm.getActivityInfo( + mCalendar, + PackageManager.GET_UNINSTALLED_PACKAGES | PackageManager.GET_META_DATA) + .metaData; + final Resources resources = pm.getResourcesForApplication(mCalendar.getPackageName()); + final int id = getDynamicIconId(metadata, resources); + if (id != NO_ID) { + if (DEBUG) Log.d(TAG, "Got icon #" + id); + return resources.getDrawableForDensity(id, iconDpi, null /* theme */); + } + } catch (PackageManager.NameNotFoundException e) { + if (DEBUG) { + Log.d(TAG, "Could not get activityinfo or resources for package: " + + mCalendar.getPackageName()); + } + } + return null; + } + + private Drawable loadClockDrawable(int iconDpi) { + return ClockDrawableWrapper.forPackage(mContext, mClock.getPackageName(), iconDpi); + } + + protected boolean isClockIcon(ComponentKey key) { + return mClock != null && mClock.equals(key.componentName) + && Process.myUserHandle().equals(key.user); + } + + /** + * @param metadata metadata of the default activity of Calendar + * @param resources from the Calendar package + * @return the resource id for today's Calendar icon; 0 if resources cannot be found. + */ + private int getDynamicIconId(Bundle metadata, Resources resources) { + if (metadata == null) { + return NO_ID; + } + String key = mCalendar.getPackageName() + ICON_METADATA_KEY_PREFIX; + final int arrayId = metadata.getInt(key, NO_ID); + if (arrayId == NO_ID) { + return NO_ID; + } + try { + return resources.obtainTypedArray(arrayId).getResourceId(getDay(), NO_ID); + } catch (Resources.NotFoundException e) { + if (DEBUG) { + Log.d(TAG, "package defines '" + key + "' but corresponding array not found"); + } + return NO_ID; + } + } + + /** + * @return Today's day of the month, zero-indexed. + */ + private int getDay() { + return Calendar.getInstance().get(Calendar.DAY_OF_MONTH) - 1; + } + + + /** + * Registers a callback to listen for calendar icon changes. + * The callback receives the packageName for the calendar icon + */ + public static SafeCloseable registerIconChangeListener(Context context, + BiConsumer callback, Handler handler) { + ComponentName calendar = parseComponentOrNull(context, R.string.calendar_component_name); + ComponentName clock = parseComponentOrNull(context, R.string.clock_component_name); + + if (calendar == null && clock == null) { + return () -> { }; + } + + BroadcastReceiver receiver = new DateTimeChangeReceiver(callback); + final IntentFilter filter = new IntentFilter(Intent.ACTION_TIMEZONE_CHANGED); + if (calendar != null) { + filter.addAction(Intent.ACTION_TIME_CHANGED); + filter.addAction(Intent.ACTION_DATE_CHANGED); + } + context.registerReceiver(receiver, filter, null, handler); + + return () -> context.unregisterReceiver(receiver); + } + + private static class DateTimeChangeReceiver extends BroadcastReceiver { + + private final BiConsumer mCallback; + + DateTimeChangeReceiver(BiConsumer callback) { + mCallback = callback; + } + + @Override + public void onReceive(Context context, Intent intent) { + if (Intent.ACTION_TIMEZONE_CHANGED.equals(intent.getAction())) { + ComponentName clock = parseComponentOrNull(context, R.string.clock_component_name); + if (clock != null) { + mCallback.accept(clock.getPackageName(), Process.myUserHandle()); + } + } + + ComponentName calendar = + parseComponentOrNull(context, R.string.calendar_component_name); + if (calendar != null) { + for (UserHandle user : UserManagerCompat.getInstance(context).getUserProfiles()) { + mCallback.accept(calendar.getPackageName(), user); + } + } + + } + } + + private static ComponentName parseComponentOrNull(Context context, int resId) { + String cn = context.getString(resId); + return TextUtils.isEmpty(cn) ? null : ComponentName.unflattenFromString(cn); + + } +} diff --git a/src/com/android/launcher3/icons/LauncherActivityCachingLogic.java b/src/com/android/launcher3/icons/LauncherActivityCachingLogic.java index a29d4feb9d..93de35a88a 100644 --- a/src/com/android/launcher3/icons/LauncherActivityCachingLogic.java +++ b/src/com/android/launcher3/icons/LauncherActivityCachingLogic.java @@ -20,7 +20,6 @@ import android.content.Context; import android.content.pm.LauncherActivityInfo; import android.os.UserHandle; -import com.android.launcher3.IconProvider; import com.android.launcher3.R; import com.android.launcher3.icons.cache.CachingLogic; import com.android.launcher3.util.ResourceBasedOverride; @@ -57,9 +56,8 @@ public class LauncherActivityCachingLogic @Override public BitmapInfo loadIcon(Context context, LauncherActivityInfo object) { try (LauncherIcons li = LauncherIcons.obtain(context)) { - return li.createBadgedIconBitmap( - IconProvider.INSTANCE.get(context) - .getIcon(object, li.mFillResIconDpi, true /* flattenDrawable */), + return li.createBadgedIconBitmap(new IconProvider(context) + .getIcon(object, li.mFillResIconDpi), object.getUser(), object.getApplicationInfo().targetSdkVersion); } } diff --git a/src/com/android/launcher3/icons/LauncherIcons.java b/src/com/android/launcher3/icons/LauncherIcons.java index 1c34dc455b..3c4041a1df 100644 --- a/src/com/android/launcher3/icons/LauncherIcons.java +++ b/src/com/android/launcher3/icons/LauncherIcons.java @@ -31,7 +31,6 @@ import com.android.launcher3.FastBitmapDrawable; import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.ItemInfoWithIcon; import com.android.launcher3.LauncherAppState; -import com.android.launcher3.R; import com.android.launcher3.graphics.IconShape; import com.android.launcher3.model.PackageItemInfo; import com.android.launcher3.shortcuts.DeepShortcutManager; @@ -45,8 +44,6 @@ import java.util.function.Supplier; */ public class LauncherIcons extends BaseIconFactory implements AutoCloseable { - private static final String EXTRA_BADGEPKG = "badge_package"; - private static final Object sPoolSync = new Object(); private static LauncherIcons sPool; private static int sPoolId = 0; @@ -160,7 +157,7 @@ public class LauncherIcons extends BaseIconFactory implements AutoCloseable { public ItemInfoWithIcon getShortcutInfoBadge(ShortcutInfo shortcutInfo, IconCache cache) { ComponentName cn = shortcutInfo.getActivity(); - String badgePkg = getBadgePackage(shortcutInfo); + String badgePkg = shortcutInfo.getPackage(); boolean hasBadgePkgSet = !badgePkg.equals(shortcutInfo.getPackage()); if (cn != null && !hasBadgePkgSet) { // Get the app info for the source activity. @@ -178,14 +175,4 @@ public class LauncherIcons extends BaseIconFactory implements AutoCloseable { return pkgInfo; } } - - private String getBadgePackage(ShortcutInfo si) { - String whitelistedPkg = mContext.getString(R.string.shortcutinfo_badgepkg_whitelist); - if (whitelistedPkg.equals(si.getPackage()) - && si.getExtras() != null - && si.getExtras().containsKey(EXTRA_BADGEPKG)) { - return si.getExtras().getString(EXTRA_BADGEPKG); - } - return si.getPackage(); - } } diff --git a/src/com/android/launcher3/views/FloatingIconView.java b/src/com/android/launcher3/views/FloatingIconView.java index 45c0d90971..c63d745e0d 100644 --- a/src/com/android/launcher3/views/FloatingIconView.java +++ b/src/com/android/launcher3/views/FloatingIconView.java @@ -415,7 +415,7 @@ public class FloatingIconView extends View implements int width = isFolderIcon ? originalView.getWidth() : (int) pos.width(); int height = isFolderIcon ? originalView.getHeight() : (int) pos.height(); if (supportsAdaptiveIcons) { - drawable = getFullDrawable(l, info, width, height, false, sTmpObjArray); + drawable = getFullDrawable(l, info, width, height, sTmpObjArray); if (drawable instanceof AdaptiveIconDrawable) { badge = getBadge(l, info, sTmpObjArray[0]); } else { @@ -428,7 +428,7 @@ public class FloatingIconView extends View implements // Similar to DragView, we simply use the BubbleTextView icon here. drawable = btvIcon; } else { - drawable = getFullDrawable(l, info, width, height, false, sTmpObjArray); + drawable = getFullDrawable(l, info, width, height, sTmpObjArray); } } } diff --git a/src/com/android/launcher3/widget/PendingAppWidgetHostView.java b/src/com/android/launcher3/widget/PendingAppWidgetHostView.java index 3ece5f4c09..6038873c42 100644 --- a/src/com/android/launcher3/widget/PendingAppWidgetHostView.java +++ b/src/com/android/launcher3/widget/PendingAppWidgetHostView.java @@ -16,6 +16,9 @@ package com.android.launcher3.widget; +import static com.android.launcher3.FastBitmapDrawable.newIcon; +import static com.android.launcher3.graphics.PreloadIconDrawable.newPendingIcon; + import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; @@ -36,7 +39,6 @@ import com.android.launcher3.FastBitmapDrawable; import com.android.launcher3.ItemInfoWithIcon; import com.android.launcher3.LauncherAppWidgetInfo; import com.android.launcher3.R; -import com.android.launcher3.graphics.DrawableFactory; import com.android.launcher3.icons.IconCache; import com.android.launcher3.icons.IconCache.ItemInfoUpdateReceiver; import com.android.launcher3.model.PackageItemInfo; @@ -133,19 +135,17 @@ public class PendingAppWidgetHostView extends LauncherAppWidgetHostView // 1) App icon in the center // 2) Preload icon in the center // 3) Setup icon in the center and app icon in the top right corner. - DrawableFactory drawableFactory = DrawableFactory.INSTANCE.get(getContext()); if (mDisabledForSafeMode) { - FastBitmapDrawable disabledIcon = drawableFactory.newIcon(getContext(), info); + FastBitmapDrawable disabledIcon = newIcon(getContext(), info); disabledIcon.setIsDisabled(true); mCenterDrawable = disabledIcon; mSettingIconDrawable = null; } else if (isReadyForClickSetup()) { - mCenterDrawable = drawableFactory.newIcon(getContext(), info); + mCenterDrawable = newIcon(getContext(), info); mSettingIconDrawable = getResources().getDrawable(R.drawable.ic_setting).mutate(); updateSettingColor(info.bitmap.color); } else { - mCenterDrawable = DrawableFactory.INSTANCE.get(getContext()) - .newPendingIcon(getContext(), info); + mCenterDrawable = newPendingIcon(getContext(), info); mSettingIconDrawable = null; applyState(); } diff --git a/src/com/android/launcher3/widget/WidgetCell.java b/src/com/android/launcher3/widget/WidgetCell.java index 6944879beb..f713b339bd 100644 --- a/src/com/android/launcher3/widget/WidgetCell.java +++ b/src/com/android/launcher3/widget/WidgetCell.java @@ -36,7 +36,6 @@ import com.android.launcher3.R; import com.android.launcher3.SimpleOnStylusPressListener; import com.android.launcher3.StylusEventHelper; import com.android.launcher3.WidgetPreviewLoader; -import com.android.launcher3.graphics.DrawableFactory; import com.android.launcher3.icons.BaseIconFactory; import com.android.launcher3.model.WidgetItem; @@ -182,10 +181,8 @@ public class WidgetCell extends LinearLayout implements OnLayoutChangeListener { return; } if (bitmap != null) { - mWidgetImage.setBitmap(bitmap, - DrawableFactory.INSTANCE.get(getContext()).getBadgeForUser(mItem.user, - getContext(), BaseIconFactory.getBadgeSizeForIconSize( - mDeviceProfile.allAppsIconSizePx))); + mWidgetImage.setBitmap(bitmap, mWidgetPreviewLoader.getBadgeForUser(mItem.user, + BaseIconFactory.getBadgeSizeForIconSize(mDeviceProfile.allAppsIconSizePx))); if (mAnimatePreview) { mWidgetImage.setAlpha(0f); ViewPropertyAnimator anim = mWidgetImage.animate();