GRID_DB_FILES = Collections.unmodifiableList(Arrays.asList(
LAUNCHER_DB,
+ LAUNCHER_6_BY_8_DB,
+ LAUNCHER_6_BY_6_DB,
LAUNCHER_6_BY_5_DB,
+ LAUNCHER_5_BY_8_DB,
+ LAUNCHER_5_BY_7_DB,
+ LAUNCHER_5_BY_6_DB,
LAUNCHER_4_BY_5_DB,
LAUNCHER_4_BY_4_DB,
LAUNCHER_3_BY_3_DB,
diff --git a/src/com/android/launcher3/OverlayCallbackImpl.java b/src/com/android/launcher3/OverlayCallbackImpl.java
new file mode 100644
index 0000000000..14c53527a1
--- /dev/null
+++ b/src/com/android/launcher3/OverlayCallbackImpl.java
@@ -0,0 +1,185 @@
+/*
+ * 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;
+
+import android.app.Activity;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
+import android.os.Bundle;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherPrefs;
+import com.android.systemui.plugins.shared.LauncherOverlayManager;
+import com.android.systemui.plugins.shared.LauncherOverlayManager.LauncherOverlay;
+
+import com.google.android.libraries.gsa.launcherclient.LauncherClient;
+import com.google.android.libraries.gsa.launcherclient.LauncherClientCallbacks;
+
+import java.io.PrintWriter;
+
+/**
+ * Implements {@link LauncherOverlay} and passes all the corresponding events to {@link
+ * LauncherClient}. {@see setClient}
+ *
+ * Implements {@link LauncherClientCallbacks} and sends all the corresponding callbacks to {@link
+ * Launcher}.
+ */
+public class OverlayCallbackImpl
+ implements LauncherOverlay, LauncherClientCallbacks, LauncherOverlayManager,
+ OnSharedPreferenceChangeListener {
+
+ public static final String KEY_ENABLE_MINUS_ONE = "pref_enable_minus_one";
+
+ private final Launcher mLauncher;
+ private final LauncherClient mClient;
+
+ private LauncherOverlayCallbacks mLauncherOverlayCallbacks;
+ private boolean mWasOverlayAttached = false;
+
+ public OverlayCallbackImpl(Launcher launcher) {
+ SharedPreferences prefs = LauncherPrefs.getPrefs(launcher);
+
+ mLauncher = launcher;
+ mClient = new LauncherClient(mLauncher, this, getClientOptions(prefs));
+ prefs.registerOnSharedPreferenceChangeListener(this);
+ }
+
+ @Override
+ public void onDeviceProvideChanged() {
+ mClient.reattachOverlay();
+ }
+
+ @Override
+ public void onAttachedToWindow() {
+ mClient.onAttachedToWindow();
+ }
+
+ @Override
+ public void onDetachedFromWindow() {
+ mClient.onDetachedFromWindow();
+ }
+
+ @Override
+ public void dump(String prefix, PrintWriter w) {
+ mClient.dump(prefix, w);
+ }
+
+ @Override
+ public void openOverlay() {
+ mClient.showOverlay(true);
+ }
+
+ @Override
+ public void hideOverlay(boolean animate) {
+ mClient.hideOverlay(animate);
+ }
+
+ @Override
+ public void hideOverlay(int duration) {
+ mClient.hideOverlay(duration);
+ }
+
+ @Override
+ public boolean startSearch(byte[] config, Bundle extras) {
+ return false;
+ }
+
+ @Override
+ public void onActivityCreated(Activity activity, Bundle bundle) {
+ // Not called
+ }
+
+ @Override
+ public void onActivityStarted(Activity activity) {
+ mClient.onStart();
+ }
+
+ @Override
+ public void onActivityResumed(Activity activity) {
+ mClient.onResume();
+ }
+
+ @Override
+ public void onActivityPaused(Activity activity) {
+ mClient.onPause();
+ }
+
+ @Override
+ public void onActivityStopped(Activity activity) {
+ mClient.onStop();
+ }
+
+ @Override
+ public void onActivitySaveInstanceState(Activity activity, Bundle bundle) { }
+
+ @Override
+ public void onActivityDestroyed(Activity activity) {
+ mClient.onDestroy();
+ mLauncher.getSharedPrefs().unregisterOnSharedPreferenceChangeListener(this);
+ }
+
+ @Override
+ public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
+ if (KEY_ENABLE_MINUS_ONE.equals(key)) {
+ mClient.setClientOptions(getClientOptions(prefs));
+ }
+ }
+
+ @Override
+ public void onServiceStateChanged(boolean overlayAttached, boolean hotwordActive) {
+ if (overlayAttached != mWasOverlayAttached) {
+ mWasOverlayAttached = overlayAttached;
+ mLauncher.setLauncherOverlay(overlayAttached ? this : null);
+ }
+ }
+
+ @Override
+ public void onOverlayScrollChanged(float progress) {
+ if (mLauncherOverlayCallbacks != null) {
+ mLauncherOverlayCallbacks.onOverlayScrollChanged(progress);
+ }
+ }
+
+ @Override
+ public void onScrollInteractionBegin() {
+ mClient.startMove();
+ }
+
+ @Override
+ public void onScrollInteractionEnd() {
+ mClient.endMove();
+ }
+
+ @Override
+ public void onScrollChange(float progress, boolean rtl) {
+ mClient.updateMove(progress);
+ }
+
+ @Override
+ public void setOverlayCallbacks(LauncherOverlayCallbacks callbacks) {
+ mLauncherOverlayCallbacks = callbacks;
+ }
+
+
+ private LauncherClient.ClientOptions getClientOptions(SharedPreferences prefs) {
+ return new LauncherClient.ClientOptions(
+ prefs.getBoolean(KEY_ENABLE_MINUS_ONE, true),
+ true, /* enableHotword */
+ true /* enablePrewarming */
+ );
+ }
+}
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index d7e84f0306..6c9b18d28c 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -18,9 +18,11 @@
import static com.android.launcher3.icons.BitmapInfo.FLAG_THEMED;
import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_ICON_BADGED;
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT;
import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_TYPE_MAIN;
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
import android.annotation.TargetApi;
import android.app.ActivityManager;
@@ -29,9 +31,12 @@
import android.content.Context;
import android.content.pm.LauncherActivityInfo;
import android.content.pm.LauncherApps;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
import android.content.pm.ShortcutInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
+import android.content.SharedPreferences;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.LightingColorFilter;
@@ -67,10 +72,12 @@
import androidx.annotation.NonNull;
import androidx.core.graphics.ColorUtils;
+import com.android.launcher3.LauncherModel;
import com.android.launcher3.dragndrop.FolderAdaptiveIcon;
import com.android.launcher3.graphics.TintedDrawableSpan;
import com.android.launcher3.icons.ShortcutCachingLogic;
import com.android.launcher3.icons.ThemedIconDrawable;
+import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.ItemInfoWithIcon;
import com.android.launcher3.pm.ShortcutConfigActivityInfo;
@@ -91,6 +98,8 @@
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+
/**
* Various utilities shared amongst the Launcher's classes.
*/
@@ -122,6 +131,11 @@ public final class Utilities {
@ChecksSdkIntAtLeast(api = VERSION_CODES.TIRAMISU, codename = "T")
public static final boolean ATLEAST_T = Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU;
+ public static final boolean ATLEAST_OREO =
+ Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
+
+ private static final long WAIT_BEFORE_RESTART = 250;
+
/**
* Set on a motion event dispatched from the nav bar. See {@link MotionEvent#setEdgeFlags(int)}.
*/
@@ -132,9 +146,14 @@ public final class Utilities {
* add extra logging and not for changing the app behavior.
*/
public static final boolean IS_DEBUG_DEVICE =
- Build.TYPE.toLowerCase(Locale.ROOT).contains("debug") ||
Build.TYPE.toLowerCase(Locale.ROOT).equals("eng");
+ public static final String GSA_PACKAGE = "com.google.android.googlequicksearchbox";
+ public static final String LENS_SHARE_ACTIVITY = "com.google.android.apps.search.lens.LensShareEntryPointActivity";
+ public static final String LENS_ACTIVITY = "com.google.android.apps.lens.MainActivity";
+ public static final String LENS_URI = "google://lens";
+ public static final String KEY_DOCK_SEARCH = "pref_dock_search";
+
/**
* Returns true if theme is dark.
*/
@@ -626,6 +645,17 @@ public static Drawable getBadge(Context context, ItemInfo info, Object obj) {
}
}
+ /**
+ * @param context the context to use for resources
+ * @return true if the user is currently using gesture navigation
+ */
+ public static boolean isUsingGestureNav(Context context) {
+ final Resources res = context.getResources();
+ final int resID = res.getIdentifier(
+ "config_navBarInteractionMode", "integer", "android");
+ return res.getInteger(resID) == 2;
+ }
+
public static float squaredHypot(float x, float y) {
return x * x + y * y;
}
@@ -733,4 +763,36 @@ public static void logMatrix(String label, Matrix matrix) {
matrixValues[Matrix.MTRANS_X], matrixValues[Matrix.MTRANS_Y]
));
}
+
+ public static void restart(final Context context) {
+ MODEL_EXECUTOR.execute(() -> {
+ try {
+ Thread.sleep(WAIT_BEFORE_RESTART);
+ } catch (Exception ignored) {
+ }
+ android.os.Process.killProcess(android.os.Process.myPid());
+ });
+ }
+
+ public static boolean isGSAEnabled(Context context) {
+ try {
+ return context.getPackageManager().getApplicationInfo(GSA_PACKAGE, 0).enabled;
+ } catch (PackageManager.NameNotFoundException e) {
+ return false;
+ }
+ }
+
+ public static boolean isWorkspaceEditAllowed(Context context) {
+ SharedPreferences prefs = LauncherPrefs.getPrefs(context.getApplicationContext());
+ return !prefs.getBoolean(InvariantDeviceProfile.KEY_WORKSPACE_LOCK, false);
+ }
+
+ public static boolean showQSB(Context context) {
+ return isGSAEnabled(context) && isQSBEnabled(context);
+ }
+
+ private static boolean isQSBEnabled(Context context) {
+ SharedPreferences prefs = LauncherPrefs.getPrefs(context.getApplicationContext());
+ return prefs.getBoolean(KEY_DOCK_SEARCH, true);
+ }
}
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 483309d1cf..087633c072 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -2105,8 +2105,7 @@ public void onDrop(final DragObject d, DragOptions options) {
private Runnable getWidgetResizeFrameRunnable(DragOptions options,
LauncherAppWidgetHostView hostView, CellLayout cellLayout) {
AppWidgetProviderInfo pInfo = hostView.getAppWidgetInfo();
- if (pInfo != null && pInfo.resizeMode != AppWidgetProviderInfo.RESIZE_NONE
- && !options.isAccessibleDrag) {
+ if (pInfo != null && !options.isAccessibleDrag) {
return () -> {
if (!isPageInTransition()) {
AppWidgetResizeFrame.showForWidget(hostView, cellLayout);
@@ -3347,8 +3346,12 @@ public boolean isOverlayShown() {
/** Calls {@link #snapToPage(int)} on the {@link #DEFAULT_PAGE}, then requests focus on it. */
public void moveToDefaultScreen() {
int page = DEFAULT_PAGE;
- if (!workspaceInModalState() && getNextPage() != page) {
- snapToPage(page);
+ if (!workspaceInModalState()) {
+ if (getNextPage() != page) {
+ snapToPage(page);
+ } else {
+ mLauncher.getStateManager().goToState(ALL_APPS);
+ }
}
View child = getChildAt(page);
if (child != null) {
diff --git a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
index 2511cada78..0c391b19c5 100644
--- a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
@@ -69,6 +69,7 @@ public ActivityAllAppsContainerView(Context context, AttributeSet attrs, int def
@Override
protected void onFinishInflate() {
super.onFinishInflate();
+ getSearchView().setBackgroundResource(R.drawable.bg_all_apps_searchbox_google_themed);
mSearchUiManager.initializeSearch(this);
}
@@ -122,6 +123,7 @@ private void animateToSearchState(boolean goingToSearch, long durationMs) {
// Fade out the button to pause work apps.
mWorkManager.onActivePageChanged(SEARCH);
}
+ setScrollbarVisibility(!goingToSearch);
mSearchTransitionController.animateToSearchState(goingToSearch, durationMs,
/* onEndRunnable = */ () -> {
mIsSearching = goingToSearch;
@@ -243,6 +245,7 @@ void setupHeader() {
@Override
protected void updateHeaderScroll(int scrolledOffset) {
super.updateHeaderScroll(scrolledOffset);
+ getSearchView().setBackgroundResource(R.drawable.bg_all_apps_searchbox_google_themed);
if (mSearchUiManager.getEditText() == null) {
return;
}
@@ -257,13 +260,6 @@ protected void updateHeaderScroll(int scrolledOffset) {
mSearchUiManager.setBackgroundVisibility(bgVisible, 1 - prog);
}
- @Override
- protected int getHeaderColor(float blendRatio) {
- return ColorUtils.setAlphaComponent(
- super.getHeaderColor(blendRatio),
- (int) (mSearchContainer.getAlpha() * 255));
- }
-
private void layoutBelowSearchContainer(View v, boolean includeTabsMargin) {
if (!(v.getLayoutParams() instanceof RelativeLayout.LayoutParams)) {
return;
diff --git a/src/com/android/launcher3/allapps/BaseAllAppsContainerView.java b/src/com/android/launcher3/allapps/BaseAllAppsContainerView.java
index 00e89bacc5..5eb5bd82f0 100644
--- a/src/com/android/launcher3/allapps/BaseAllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/BaseAllAppsContainerView.java
@@ -148,7 +148,6 @@ public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
private final RectF mTmpRectF = new RectF();
private float[] mBottomSheetCornerRadii;
private ScrimView mScrimView;
- private int mHeaderColor;
private int mBottomSheetBackgroundColor;
private int mTabsProtectionAlpha;
@@ -610,6 +609,13 @@ protected void rebindAdapters(boolean force) {
mAllAppsStore.registerIconContainer(mAH.get(AdapterHolder.SEARCH).mRecyclerView);
}
+ protected void setScrollbarVisibility(boolean visible) {
+ final RecyclerViewFastScroller scrollbar = getScrollBar();
+ if (scrollbar != null) {
+ scrollbar.setVisibility(visible ? VISIBLE : GONE);
+ }
+ }
+
protected void updateSearchResultsVisibility() {
if (isSearching()) {
getSearchRecyclerView().setVisibility(VISIBLE);
@@ -823,48 +829,7 @@ public void drawOnScrim(Canvas canvas) {
canvas.drawPath(mTmpPath, mHeaderPaint);
}
- if (DEBUG_HEADER_PROTECTION) {
- mHeaderPaint.setColor(Color.MAGENTA);
- mHeaderPaint.setAlpha(255);
- } else {
- mHeaderPaint.setColor(mHeaderColor);
- mHeaderPaint.setAlpha((int) (getAlpha() * Color.alpha(mHeaderColor)));
- }
- if (mHeaderPaint.getColor() == mScrimColor || mHeaderPaint.getColor() == 0) {
- return;
- }
- int bottom = getHeaderBottom() + getVisibleContainerView().getPaddingTop();
- FloatingHeaderView headerView = getFloatingHeaderView();
- if (isTablet) {
- // Start adding header protection if search bar or tabs will attach to the top.
- if (!FeatureFlags.ENABLE_FLOATING_SEARCH_BAR.get() || mUsingTabs) {
- View panel = (View) mBottomSheetBackground;
- float translationY = ((View) panel.getParent()).getTranslationY();
- mTmpRectF.set(panel.getLeft(), panel.getTop() + translationY, panel.getRight(),
- bottom);
- mTmpPath.reset();
- mTmpPath.addRoundRect(mTmpRectF, mBottomSheetCornerRadii, Direction.CW);
- canvas.drawPath(mTmpPath, mHeaderPaint);
- }
- } else {
- canvas.drawRect(0, 0, canvas.getWidth(), bottom, mHeaderPaint);
- }
- int tabsHeight = headerView.getPeripheralProtectionHeight();
- if (mTabsProtectionAlpha > 0 && tabsHeight != 0) {
- if (DEBUG_HEADER_PROTECTION) {
- mHeaderPaint.setColor(Color.BLUE);
- mHeaderPaint.setAlpha(255);
- } else {
- mHeaderPaint.setAlpha((int) (getAlpha() * mTabsProtectionAlpha));
- }
- int left = 0;
- int right = canvas.getWidth();
- if (isTablet) {
- left = mBottomSheetBackground.getLeft();
- right = mBottomSheetBackground.getRight();
- }
- canvas.drawRect(left, bottom, right, bottom + tabsHeight, mHeaderPaint);
- }
+ mHeaderPaint.setColor(mHeaderProtectionColor);
}
/**
@@ -878,22 +843,16 @@ public void invalidateHeader() {
protected void updateHeaderScroll(int scrolledOffset) {
float prog = Utilities.boundToRange((float) scrolledOffset / mHeaderThreshold, 0f, 1f);
- int headerColor = getHeaderColor(prog);
int tabsAlpha = mHeader.getPeripheralProtectionHeight() == 0 ? 0
: (int) (Utilities.boundToRange(
(scrolledOffset + mHeader.mSnappedScrolledY) / mHeaderThreshold, 0f, 1f)
* 255);
- if (headerColor != mHeaderColor || mTabsProtectionAlpha != tabsAlpha) {
- mHeaderColor = headerColor;
+ if (mTabsProtectionAlpha != tabsAlpha) {
mTabsProtectionAlpha = tabsAlpha;
invalidateHeader();
}
}
- protected int getHeaderColor(float blendRatio) {
- return ColorUtils.blendARGB(mScrimColor, mHeaderProtectionColor, blendRatio);
- }
-
protected abstract BaseAllAppsAdapter createAdapter(AlphabeticalAppsList mAppsList,
BaseAdapterProvider[] adapterProviders);
diff --git a/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java b/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
index 4427a49dab..f94658c404 100644
--- a/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
+++ b/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
@@ -132,7 +132,7 @@ public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
public boolean onBackKey() {
// Only hide the search field if there is no query
String query = Utilities.trim(mInput.getEditableText().toString());
- if (query.isEmpty()) {
+ if (!query.isEmpty()) {
reset();
return true;
}
diff --git a/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java b/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
index 78c305b2eb..30e2c6337e 100644
--- a/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
+++ b/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
@@ -23,6 +23,7 @@
import static com.android.launcher3.icons.IconNormalizer.ICON_VISIBLE_AREA_FACTOR;
import android.content.Context;
+import android.graphics.drawable.Drawable;
import android.graphics.Rect;
import android.text.Selection;
import android.text.SpannableStringBuilder;
@@ -77,7 +78,6 @@ public AppsSearchContainerLayout(Context context, AttributeSet attrs, int defSty
mSearchQueryBuilder = new SpannableStringBuilder();
Selection.setSelection(mSearchQueryBuilder, 0);
- setHint(prefixTextWithIcon(getContext(), R.drawable.ic_allapps_search, getHint()));
mContentOverlap =
getResources().getDimensionPixelSize(R.dimen.all_apps_search_bar_content_overlap);
@@ -116,6 +116,8 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
+ Drawable gIconThemed = getContext().getDrawable(R.drawable.ic_super_g_themed);
+
// Shift the widget horizontally so that its centered in the parent (b/63428078)
View parent = (View) getParent();
int availableWidth = parent.getWidth() - parent.getPaddingLeft() - parent.getPaddingRight();
@@ -124,6 +126,8 @@ protected void onLayout(boolean changed, int left, int top, int right, int botto
int shift = expectedLeft - left;
setTranslationX(shift);
+ setCompoundDrawablesRelativeWithIntrinsicBounds(gIconThemed, null, null, null);
+
offsetTopAndBottom(mContentOverlap);
}
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index 082f6a1a95..519733b074 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -42,7 +42,7 @@ private FeatureFlags() {
}
public static boolean showFlagTogglerUi(Context context) {
- return Utilities.IS_DEBUG_DEVICE && Utilities.isDevelopersOptionsEnabled(context);
+ return Utilities.isDevelopersOptionsEnabled(context);
}
/**
@@ -54,7 +54,7 @@ public static boolean showFlagTogglerUi(Context context) {
* Enable moving the QSB on the 0th screen of the workspace. This is not a configuration feature
* and should be modified at a project level.
*/
- public static final boolean QSB_ON_FIRST_SCREEN = BuildConfig.QSB_ON_FIRST_SCREEN;
+ public static final boolean QSB_ON_FIRST_SCREEN = false;
/**
* Feature flag to handle define config changes dynamically instead of killing the process.
@@ -184,7 +184,7 @@ public static boolean showFlagTogglerUi(Context context) {
// TODO: b/172467144 Remove ENABLE_LAUNCHER_ACTIVITY_THEME_CROSSFADE feature flag.
public static final BooleanFlag ENABLE_LAUNCHER_ACTIVITY_THEME_CROSSFADE = new DeviceFlag(
- "ENABLE_LAUNCHER_ACTIVITY_THEME_CROSSFADE", false, "Enables a "
+ "ENABLE_LAUNCHER_ACTIVITY_THEME_CROSSFADE", true, "Enables a "
+ "crossfade animation when the system these changes.");
// TODO: b/174174514 Remove ENABLE_APP_PREDICTIONS_WHILE_VISIBLE feature flag.
@@ -343,7 +343,7 @@ public static boolean showFlagTogglerUi(Context context) {
"Use a single page for the workspace");
public static final BooleanFlag ENABLE_TRANSIENT_TASKBAR = getDebugFlag(
- "ENABLE_TRANSIENT_TASKBAR", true, "Enables transient taskbar.");
+ "ENABLE_TRANSIENT_TASKBAR", false, "Enables transient taskbar.");
public static final BooleanFlag SECONDARY_DRAG_N_DROP_TO_PIN = getDebugFlag(
"SECONDARY_DRAG_N_DROP_TO_PIN", false,
@@ -481,8 +481,6 @@ protected StringBuilder appendProps(StringBuilder src) {
}
private static BooleanFlag getDebugFlag(String key, boolean defaultValue, String description) {
- return Utilities.IS_DEBUG_DEVICE
- ? new DebugFlag(key, defaultValue, description)
- : new BooleanFlag(key, defaultValue);
+ return new DebugFlag(key, defaultValue, description);
}
}
diff --git a/src/com/android/launcher3/customization/IconDatabase.java b/src/com/android/launcher3/customization/IconDatabase.java
new file mode 100644
index 0000000000..0673471fff
--- /dev/null
+++ b/src/com/android/launcher3/customization/IconDatabase.java
@@ -0,0 +1,62 @@
+package com.android.launcher3.customization;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+
+import com.android.launcher3.BuildConfig;
+import com.android.launcher3.LauncherPrefs;
+import com.android.launcher3.R;
+import com.android.launcher3.util.ComponentKey;
+
+public class IconDatabase {
+
+ private static final String PREF_FILE_NAME = BuildConfig.APPLICATION_ID + ".ICON_DATABASE";
+ public static final String KEY_ICON_PACK = "pref_icon_pack";
+ public static final String VALUE_DEFAULT = "";
+
+ public static String getGlobal(Context context) {
+ return LauncherPrefs.getPrefs(context).getString(KEY_ICON_PACK, VALUE_DEFAULT);
+ }
+
+ public static String getGlobalLabel(Context context) {
+ final String defaultLabel = context.getString(R.string.icon_pack_default_label);
+ final String pkgName = getGlobal(context);
+ if (VALUE_DEFAULT.equals(pkgName)) {
+ return defaultLabel;
+ }
+
+ final PackageManager pm = context.getPackageManager();
+ try {
+ final ApplicationInfo ai = pm.getApplicationInfo(pkgName, 0);
+ return (String) pm.getApplicationLabel(ai);
+ } catch (PackageManager.NameNotFoundException e) {
+ return defaultLabel;
+ }
+ }
+
+ public static void setGlobal(Context context, String value) {
+ LauncherPrefs.getPrefs(context).edit().putString(KEY_ICON_PACK, value).apply();
+ }
+
+ public static void resetGlobal(Context context) {
+ LauncherPrefs.getPrefs(context).edit().remove(KEY_ICON_PACK).apply();
+ }
+
+ public static String getByComponent(Context context, ComponentKey key) {
+ return getIconPackPrefs(context).getString(key.toString(), getGlobal(context));
+ }
+
+ public static void setForComponent(Context context, ComponentKey key, String value) {
+ getIconPackPrefs(context).edit().putString(key.toString(), value).apply();
+ }
+
+ public static void resetForComponent(Context context, ComponentKey key) {
+ getIconPackPrefs(context).edit().remove(key.toString()).apply();
+ }
+
+ private static SharedPreferences getIconPackPrefs(Context context) {
+ return context.getSharedPreferences(PREF_FILE_NAME, Context.MODE_PRIVATE);
+ }
+}
diff --git a/src/com/android/launcher3/dragndrop/DragController.java b/src/com/android/launcher3/dragndrop/DragController.java
index 5368397c6d..20a5eb6f1b 100644
--- a/src/com/android/launcher3/dragndrop/DragController.java
+++ b/src/com/android/launcher3/dragndrop/DragController.java
@@ -32,6 +32,7 @@
import com.android.launcher3.DragSource;
import com.android.launcher3.DropTarget;
import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.Utilities;
import com.android.launcher3.logging.InstanceId;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
@@ -406,6 +407,11 @@ public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
return false;
}
+ if (!Utilities.isWorkspaceEditAllowed(mActivity.getDragLayer().getContext())) {
+ cancelDrag();
+ return false;
+ }
+
Point dragLayerPos = getClampedDragLayerPos(getX(ev), getY(ev));
mLastTouch.set(dragLayerPos.x, dragLayerPos.y);
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index dd00f07807..d7226a63e3 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -197,7 +197,9 @@ public static FolderIcon inflateIcon(int resId, ActivityContext activity, ViewGr
icon.setClipToPadding(false);
icon.mFolderName = icon.findViewById(R.id.folder_icon_name);
- icon.mFolderName.setText(folderInfo.title);
+ if (icon.mFolderName.shouldShowLabel()) {
+ icon.mFolderName.setText(folderInfo.title);
+ }
icon.mFolderName.setCompoundDrawablePadding(0);
FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) icon.mFolderName.getLayoutParams();
lp.topMargin = grid.iconSizePx + grid.iconDrawablePaddingPx;
diff --git a/src/com/android/launcher3/folder/PreviewItemManager.java b/src/com/android/launcher3/folder/PreviewItemManager.java
index 2e5f2e5d60..e2b47ba685 100644
--- a/src/com/android/launcher3/folder/PreviewItemManager.java
+++ b/src/com/android/launcher3/folder/PreviewItemManager.java
@@ -40,6 +40,7 @@
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.Utilities;
+import com.android.launcher3.graphics.DrawableFactory;
import com.android.launcher3.graphics.PreloadIconDrawable;
import com.android.launcher3.model.data.ItemInfoWithIcon;
import com.android.launcher3.model.data.WorkspaceItemInfo;
@@ -70,6 +71,7 @@ public Float get(PreviewItemManager manager) {
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
@@ -103,6 +105,7 @@ public Float get(PreviewItemManager manager) {
public PreviewItemManager(FolderIcon icon) {
mContext = icon.getContext();
mIcon = icon;
+ mDrawableFactory = DrawableFactory.INSTANCE.get(mContext);
mIconSize = ActivityContext.lookupContext(
mContext).getDeviceProfile().folderChildIconSizePx;
mClipThreshold = Utilities.dpToPx(1f);
@@ -430,7 +433,7 @@ private void updateTransitionParam(final PreviewItemDrawingParams p, WorkspaceIt
private void setDrawable(PreviewItemDrawingParams p, WorkspaceItemInfo item) {
if (item.hasPromiseIconUi() || (item.runtimeStatusFlags
& ItemInfoWithIcon.FLAG_SHOW_DOWNLOAD_PROGRESS_MASK) != 0) {
- PreloadIconDrawable drawable = newPendingIcon(mContext, item);
+ PreloadIconDrawable drawable = mDrawableFactory.newPendingIcon(mContext, item);
drawable.setLevel(item.getProgressLevel());
p.drawable = drawable;
} else {
diff --git a/src/com/android/launcher3/graphics/DrawableFactory.java b/src/com/android/launcher3/graphics/DrawableFactory.java
new file mode 100644
index 0000000000..c9fb243e76
--- /dev/null
+++ b/src/com/android/launcher3/graphics/DrawableFactory.java
@@ -0,0 +1,121 @@
+/*
+ * 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.icons.FastBitmapDrawable;
+import com.android.launcher3.model.data.ItemInfoWithIcon;
+import com.android.launcher3.R;
+import com.android.launcher3.icons.BitmapInfo;
+import com.android.launcher3.icons.PlaceHolderIconDrawable;
+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, 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, context)
+ : new FastBitmapDrawable(info);
+ }
+
+ /**
+ * Returns a FastBitmapDrawable with the icon.
+ */
+ public PreloadIconDrawable newPendingIcon(Context context, ItemInfoWithIcon info) {
+ return new PreloadIconDrawable(info, 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/IconPalette.java b/src/com/android/launcher3/graphics/IconPalette.java
index 778b32a863..bbad36d3e9 100644
--- a/src/com/android/launcher3/graphics/IconPalette.java
+++ b/src/com/android/launcher3/graphics/IconPalette.java
@@ -26,6 +26,8 @@
import com.android.launcher3.R;
import com.android.launcher3.util.Themes;
+import java.lang.IllegalArgumentException;
+
/**
* Contains colors based on the dominant color of an icon.
*/
@@ -107,7 +109,15 @@ private static String contrastChange(int colorOld, int colorNew, int bg) {
* This was copied from com.android.internal.util.NotificationColorUtil.
*/
private static int ensureTextContrast(int color, int bg) {
- return findContrastColor(color, bg, 4.5);
+ int res = color;
+ try {
+ res = findContrastColor(color, bg, 4.5);
+ } catch (IllegalArgumentException e) {
+ // Just returning the same color in this case
+ Log.e(TAG, "ensureTextContrast: Invalid fg/bg color int."
+ + " fg=" + color + " bg=" + bg);
+ }
+ return res;
}
/**
* Finds a suitable color such that there's enough contrast.
diff --git a/src/com/android/launcher3/icons/IconCache.java b/src/com/android/launcher3/icons/IconCache.java
index 0b4a4a53d4..b8b98a8ccc 100644
--- a/src/com/android/launcher3/icons/IconCache.java
+++ b/src/com/android/launcher3/icons/IconCache.java
@@ -51,6 +51,7 @@
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.LauncherFiles;
import com.android.launcher3.Utilities;
+import com.android.launcher3.icons.BaseIconFactory;
import com.android.launcher3.icons.ComponentWithLabel.ComponentCachingLogic;
import com.android.launcher3.icons.cache.BaseIconCache;
import com.android.launcher3.icons.cache.CachingLogic;
@@ -100,7 +101,7 @@ public class IconCache extends BaseIconCache {
private int mPendingIconRequestCount = 0;
public IconCache(Context context, InvariantDeviceProfile idp) {
- this(context, idp, LauncherFiles.APP_ICONS_DB, new IconProvider(context));
+ this(context, idp, LauncherFiles.APP_ICONS_DB, IconProvider.INSTANCE.get(context));
}
public IconCache(Context context, InvariantDeviceProfile idp, String dbFileName,
diff --git a/src/com/android/launcher3/icons/ThirdPartyDrawableFactory.java b/src/com/android/launcher3/icons/ThirdPartyDrawableFactory.java
new file mode 100644
index 0000000000..6d46a7c316
--- /dev/null
+++ b/src/com/android/launcher3/icons/ThirdPartyDrawableFactory.java
@@ -0,0 +1,68 @@
+package com.android.launcher3.icons;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+
+import com.android.launcher3.icons.FastBitmapDrawable;
+import com.android.launcher3.model.data.ItemInfoWithIcon;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.graphics.DrawableFactory;
+import com.android.launcher3.util.ComponentKey;
+
+import com.android.launcher3.icons.calendar.DateChangeReceiver;
+import com.android.launcher3.icons.calendar.DynamicCalendar;
+import com.android.launcher3.icons.clock.CustomClock;
+import com.android.launcher3.icons.clock.DynamicClock;
+import com.android.launcher3.icons.pack.IconPackManager;
+import com.android.launcher3.icons.pack.IconResolver;
+
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
+
+public class ThirdPartyDrawableFactory extends DrawableFactory {
+ private final IconPackManager mManager;
+ private final DynamicClock mDynamicClockDrawer;
+ private final CustomClock mCustomClockDrawer;
+ private final DateChangeReceiver mCalendars;
+
+ public ThirdPartyDrawableFactory(Context context) {
+ mManager = IconPackManager.get(context);
+ if (Utilities.ATLEAST_OREO) {
+ mDynamicClockDrawer = new DynamicClock(context);
+ mCustomClockDrawer = new CustomClock(context);
+ } else {
+ mDynamicClockDrawer = null;
+ mCustomClockDrawer = null;
+ }
+ mCalendars = new DateChangeReceiver(context);
+ }
+
+ @Override
+ public FastBitmapDrawable newIcon(Context context, ItemInfoWithIcon info) {
+ if (info != null && info.getTargetComponent() != null
+ && info.itemType == ITEM_TYPE_APPLICATION) {
+ ComponentKey key = new ComponentKey(info.getTargetComponent(), info.user);
+
+ IconResolver resolver = mManager.resolve(key);
+ mCalendars.setIsDynamic(key, (resolver != null && resolver.isCalendar())
+ || info.getTargetComponent().getPackageName().equals(DynamicCalendar.CALENDAR));
+
+ if (Utilities.ATLEAST_OREO) {
+ if (resolver != null) {
+ if (resolver.isClock()) {
+ Drawable drawable = resolver.getIcon(0, () -> null);
+ if (drawable != null) {
+ FastBitmapDrawable fb = mCustomClockDrawer.drawIcon(
+ info, drawable, resolver.clockData());
+ fb.setIsDisabled(info.isDisabled());
+ return fb;
+ }
+ }
+ } else if (info.getTargetComponent().equals(DynamicClock.DESK_CLOCK)) {
+ return mDynamicClockDrawer.drawIcon(info);
+ }
+ }
+ }
+
+ return super.newIcon(context, info);
+ }
+}
diff --git a/src/com/android/launcher3/icons/ThirdPartyIconProvider.java b/src/com/android/launcher3/icons/ThirdPartyIconProvider.java
new file mode 100644
index 0000000000..3926ab3b6a
--- /dev/null
+++ b/src/com/android/launcher3/icons/ThirdPartyIconProvider.java
@@ -0,0 +1,39 @@
+package com.android.launcher3.icons;
+
+import android.content.Context;
+import android.content.pm.LauncherActivityInfo;
+import android.graphics.drawable.Drawable;
+
+import android.annotation.SuppressLint;
+import com.android.launcher3.util.ComponentKey;
+
+import com.android.launcher3.icons.pack.IconResolver;
+
+import static com.android.launcher3.icons.BaseIconFactory.CONFIG_HINT_NO_WRAP;
+
+@SuppressWarnings("unused")
+public class ThirdPartyIconProvider extends LauncherIconProvider {
+ private final Context mContext;
+
+ public ThirdPartyIconProvider(Context context) {
+ super(context);
+ mContext = context;
+ }
+
+ @SuppressLint("WrongConstant")
+ @Override
+ public Drawable getIcon(LauncherActivityInfo launcherActivityInfo, int iconDpi) {
+ ComponentKey key = new ComponentKey(
+ launcherActivityInfo.getComponentName(), launcherActivityInfo.getUser());
+
+ IconResolver.DefaultDrawableProvider fallback =
+ () -> super.getIcon(launcherActivityInfo, iconDpi);
+ Drawable icon = ThirdPartyIconUtils.getByKey(mContext, key, iconDpi, fallback);
+
+ if (icon == null) {
+ return fallback.get();
+ }
+ icon.setChangingConfigurations(icon.getChangingConfigurations() | CONFIG_HINT_NO_WRAP);
+ return icon;
+ }
+}
diff --git a/src/com/android/launcher3/icons/ThirdPartyIconUtils.java b/src/com/android/launcher3/icons/ThirdPartyIconUtils.java
new file mode 100644
index 0000000000..0368479240
--- /dev/null
+++ b/src/com/android/launcher3/icons/ThirdPartyIconUtils.java
@@ -0,0 +1,42 @@
+package com.android.launcher3.icons;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+
+import com.android.launcher3.Utilities;
+import com.android.launcher3.util.ComponentKey;
+
+import com.android.launcher3.icons.calendar.DynamicCalendar;
+import com.android.launcher3.icons.clock.CustomClock;
+import com.android.launcher3.icons.clock.DynamicClock;
+import com.android.launcher3.icons.pack.IconPackManager;
+import com.android.launcher3.icons.pack.IconResolver;
+
+class ThirdPartyIconUtils {
+ static Drawable getByKey(Context context, ComponentKey key, int iconDpi,
+ IconResolver.DefaultDrawableProvider fallback) {
+ IconResolver resolver = IconPackManager.get(context).resolve(key);
+ Drawable icon = resolver == null
+ ? null
+ : resolver.getIcon(iconDpi, fallback);
+
+ if (Utilities.ATLEAST_OREO) {
+ // Icon pack clocks go first.
+ if (icon != null && resolver.isClock()) {
+ return CustomClock.getClock(context, icon, resolver.clockData());
+ }
+
+ // Google Clock goes second, but only if the icon pack does not override it.
+ if (icon == null && key.componentName.equals(DynamicClock.DESK_CLOCK)) {
+ return DynamicClock.getClock(context, iconDpi);
+ }
+ }
+
+ // Google Calendar is checked last. Only applied if the icon pack does not override it.
+ if (icon == null && key.componentName.getPackageName().equals(DynamicCalendar.CALENDAR)) {
+ return DynamicCalendar.load(context, key.componentName, iconDpi);
+ }
+
+ return icon;
+ }
+}
diff --git a/src/com/android/launcher3/icons/calendar/DateChangeReceiver.java b/src/com/android/launcher3/icons/calendar/DateChangeReceiver.java
new file mode 100644
index 0000000000..4f70640c34
--- /dev/null
+++ b/src/com/android/launcher3/icons/calendar/DateChangeReceiver.java
@@ -0,0 +1,49 @@
+package com.android.launcher3.icons.calendar;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Handler;
+
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+
+import com.android.launcher3.LauncherModel;
+import com.android.launcher3.util.ComponentKey;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import com.android.launcher3.util.AppReloader;
+
+/**
+ * Listens for date change events and uses the IconReloader to reload all loaded calendar icons
+ * when the date has changed.
+ */
+public class DateChangeReceiver extends BroadcastReceiver {
+ private final Set mDynamicCalendars = new HashSet<>();
+
+ public DateChangeReceiver(Context context) {
+ super();
+
+ IntentFilter filter = new IntentFilter(Intent.ACTION_DATE_CHANGED);
+ filter.addAction(Intent.ACTION_TIME_CHANGED);
+ filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
+
+ Handler handler = new Handler(MODEL_EXECUTOR.getLooper());
+ context.registerReceiver(this, filter, null, handler);
+ }
+
+ public void setIsDynamic(ComponentKey key, boolean calendar) {
+ if (calendar) {
+ mDynamicCalendars.add(key);
+ } else {
+ mDynamicCalendars.remove(key);
+ }
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ AppReloader.get(context).reload(mDynamicCalendars);
+ }
+}
diff --git a/src/com/android/launcher3/icons/calendar/DynamicCalendar.java b/src/com/android/launcher3/icons/calendar/DynamicCalendar.java
new file mode 100644
index 0000000000..a8cc84eafc
--- /dev/null
+++ b/src/com/android/launcher3/icons/calendar/DynamicCalendar.java
@@ -0,0 +1,51 @@
+package com.android.launcher3.icons.calendar;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+
+import java.util.Calendar;
+
+public class DynamicCalendar {
+ public static final String CALENDAR = "com.google.android.calendar";
+
+ public static Drawable load(Context context, ComponentName component, int iconDpi) {
+ try {
+ PackageManager pm = context.getPackageManager();
+ Bundle metaData = pm.getActivityInfo(component,
+ PackageManager.GET_META_DATA | PackageManager.GET_UNINSTALLED_PACKAGES).metaData;
+
+ Resources resourcesForApplication = pm.getResourcesForApplication(DynamicCalendar.CALENDAR);
+ int dayResId = DynamicCalendar.getDayResId(metaData, resourcesForApplication);
+ if (dayResId != 0) {
+ return resourcesForApplication.getDrawableForDensity(dayResId, iconDpi);
+ }
+ } catch (PackageManager.NameNotFoundException ignored) {
+ }
+ return null;
+ }
+
+ public static int getDayResId(Bundle bundle, Resources resources) {
+ if (bundle != null) {
+ int dateArrayId = bundle.getInt(CALENDAR + ".dynamic_icons_nexus_round", 0);
+ if (dateArrayId != 0) {
+ try {
+ TypedArray dateIds = resources.obtainTypedArray(dateArrayId);
+ int dateId = dateIds.getResourceId(getDayOfMonth(), 0);
+ dateIds.recycle();
+ return dateId;
+ } catch (Resources.NotFoundException ex) {
+ }
+ }
+ }
+ return 0;
+ }
+
+ public static int getDayOfMonth() {
+ return Calendar.getInstance().get(Calendar.DAY_OF_MONTH) - 1;
+ }
+}
diff --git a/src/com/android/launcher3/icons/clock/AutoUpdateClock.java b/src/com/android/launcher3/icons/clock/AutoUpdateClock.java
new file mode 100644
index 0000000000..9ae5c493ff
--- /dev/null
+++ b/src/com/android/launcher3/icons/clock/AutoUpdateClock.java
@@ -0,0 +1,76 @@
+package com.android.launcher3.icons.clock;
+
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.os.SystemClock;
+
+import com.android.launcher3.icons.FastBitmapDrawable;
+import com.android.launcher3.model.data.ItemInfoWithIcon;
+import com.android.launcher3.Utilities;
+
+import java.util.TimeZone;
+
+class AutoUpdateClock extends FastBitmapDrawable implements Runnable {
+ private ClockLayers mLayers;
+
+ AutoUpdateClock(ItemInfoWithIcon info, ClockLayers layers) {
+ super(info.bitmap);
+ mLayers = layers;
+ }
+
+ private void rescheduleUpdate() {
+ long millisInSecond = 1000L;
+ unscheduleSelf(this);
+ long uptimeMillis = SystemClock.uptimeMillis();
+ scheduleSelf(this, uptimeMillis - uptimeMillis % millisInSecond + millisInSecond);
+ }
+
+ // Used only by Google Clock
+ void updateLayers(ClockLayers layers) {
+ mLayers = layers;
+ if (mLayers != null) {
+ mLayers.mDrawable.setBounds(getBounds());
+ }
+ invalidateSelf();
+ }
+
+ void setTimeZone(TimeZone timeZone) {
+ if (mLayers != null) {
+ mLayers.setTimeZone(timeZone);
+ invalidateSelf();
+ }
+ }
+
+ @Override
+ public void drawInternal(Canvas canvas, Rect rect) {
+ if (mLayers == null || !Utilities.ATLEAST_OREO) {
+ super.drawInternal(canvas, rect);
+ } else {
+ canvas.drawBitmap(mLayers.bitmap, null, rect, mPaint);
+ mLayers.updateAngles();
+ canvas.scale(mLayers.scale, mLayers.scale,
+ rect.exactCenterX() + mLayers.offset,
+ rect.exactCenterY() + mLayers.offset);
+ canvas.clipPath(mLayers.mDrawable.getIconMask());
+ mLayers.mDrawable.getForeground().draw(canvas);
+ rescheduleUpdate();
+ }
+ }
+
+ @Override
+ protected void onBoundsChange(final Rect bounds) {
+ super.onBoundsChange(bounds);
+ if (mLayers != null) {
+ mLayers.mDrawable.setBounds(bounds);
+ }
+ }
+
+ @Override
+ public void run() {
+ if (mLayers.updateAngles()) {
+ invalidateSelf();
+ } else {
+ rescheduleUpdate();
+ }
+ }
+}
diff --git a/src/com/android/launcher3/icons/clock/ClockLayers.java b/src/com/android/launcher3/icons/clock/ClockLayers.java
new file mode 100644
index 0000000000..c6014b0fd3
--- /dev/null
+++ b/src/com/android/launcher3/icons/clock/ClockLayers.java
@@ -0,0 +1,82 @@
+package com.android.launcher3.icons.clock;
+
+import android.annotation.TargetApi;
+import android.graphics.Bitmap;
+import android.graphics.drawable.AdaptiveIconDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.LayerDrawable;
+
+import java.util.Calendar;
+import java.util.TimeZone;
+
+@TargetApi(26)
+class ClockLayers {
+ AdaptiveIconDrawable mDrawable;
+ LayerDrawable mLayerDrawable;
+ private final Calendar mCurrentTime;
+ int mHourIndex;
+ int mMinuteIndex;
+ int mSecondIndex;
+ int mDefaultHour;
+ int mDefaultMinute;
+ int mDefaultSecond;
+ float scale;
+ float offset;
+ Bitmap bitmap;
+
+ ClockLayers() {
+ mCurrentTime = Calendar.getInstance();
+ }
+
+ public final void setDrawable(Drawable drawable) {
+ mDrawable = (AdaptiveIconDrawable) drawable;
+ mLayerDrawable = (LayerDrawable) mDrawable.getForeground();
+ }
+
+ @Override
+ public ClockLayers clone() {
+ ClockLayers ret = null;
+ if (mDrawable == null) {
+ return null;
+ }
+ ClockLayers clone = new ClockLayers();
+ clone.scale = scale;
+ clone.offset = offset;
+ clone.mHourIndex = mHourIndex;
+ clone.mMinuteIndex = mMinuteIndex;
+ clone.mSecondIndex = mSecondIndex;
+ clone.mDefaultHour = mDefaultHour;
+ clone.mDefaultMinute = mDefaultMinute;
+ clone.mDefaultSecond = mDefaultSecond;
+ clone.setDrawable(mDrawable.getConstantState().newDrawable());
+ clone.bitmap = bitmap;
+ if (clone.mLayerDrawable != null) {
+ ret = clone;
+ }
+ return ret;
+ }
+
+ boolean updateAngles() {
+ mCurrentTime.setTimeInMillis(System.currentTimeMillis());
+
+ int hour = (mCurrentTime.get(Calendar.HOUR) + (12 - mDefaultHour)) % 12;
+ int minute = (mCurrentTime.get(Calendar.MINUTE) + (60 - mDefaultMinute)) % 60;
+ int second = (mCurrentTime.get(Calendar.SECOND) + (60 - mDefaultSecond)) % 60;
+
+ boolean hasChanged = false;
+ if (mHourIndex != -1 && mLayerDrawable.getDrawable(mHourIndex).setLevel(hour * 60 + mCurrentTime.get(Calendar.MINUTE))) {
+ hasChanged = true;
+ }
+ if (mMinuteIndex != -1 && mLayerDrawable.getDrawable(mMinuteIndex).setLevel(minute + mCurrentTime.get(Calendar.HOUR) * 60)) {
+ hasChanged = true;
+ }
+ if (mSecondIndex != -1 && mLayerDrawable.getDrawable(mSecondIndex).setLevel(second * 10)) {
+ hasChanged = true;
+ }
+ return hasChanged;
+ }
+
+ void setTimeZone(TimeZone timeZone) {
+ mCurrentTime.setTimeZone(timeZone);
+ }
+}
diff --git a/src/com/android/launcher3/icons/clock/CustomClock.java b/src/com/android/launcher3/icons/clock/CustomClock.java
new file mode 100644
index 0000000000..d6446d7dc2
--- /dev/null
+++ b/src/com/android/launcher3/icons/clock/CustomClock.java
@@ -0,0 +1,131 @@
+package com.android.launcher3.icons.clock;
+
+import android.annotation.TargetApi;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.graphics.drawable.AdaptiveIconDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.LayerDrawable;
+import android.os.Build;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Process;
+
+import com.android.launcher3.icons.FastBitmapDrawable;
+import com.android.launcher3.model.data.ItemInfoWithIcon;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.icons.LauncherIcons;
+import com.android.launcher3.util.Preconditions;
+
+import java.util.Collections;
+import java.util.Set;
+import java.util.TimeZone;
+import java.util.WeakHashMap;
+
+@TargetApi(Build.VERSION_CODES.TIRAMISU)
+public class CustomClock {
+ private final Context mContext;
+ private final Set mUpdaters = Collections.newSetFromMap(new WeakHashMap<>());
+
+ public CustomClock(Context context) {
+ mContext = context;
+
+ mContext.registerReceiver(new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ loadTimeZone(intent.getStringExtra("time-zone"));
+ }
+ }, new IntentFilter(Intent.ACTION_TIMEZONE_CHANGED), null,
+ new Handler(Looper.getMainLooper()));
+ }
+
+ public static Drawable getClock(Context context, Drawable drawable, Metadata metadata) {
+ ClockLayers clone = getClockLayers(context, drawable, metadata, false).clone();
+ if (clone != null) {
+ clone.updateAngles();
+ return clone.mDrawable;
+ }
+ return null;
+ }
+
+ private static ClockLayers getClockLayers(Context context, Drawable drawableForDensity,
+ Metadata metadata, boolean normalizeIcon) {
+ Preconditions.assertWorkerThread();
+ ClockLayers layers = new ClockLayers();
+ layers.setDrawable(drawableForDensity.mutate());
+ layers.mHourIndex = metadata.HOUR_LAYER_INDEX;
+ layers.mMinuteIndex = metadata.MINUTE_LAYER_INDEX;
+ layers.mSecondIndex = metadata.SECOND_LAYER_INDEX;
+ layers.mDefaultHour = metadata.DEFAULT_HOUR;
+ layers.mDefaultMinute = metadata.DEFAULT_MINUTE;
+ layers.mDefaultSecond = metadata.DEFAULT_SECOND;
+ if (normalizeIcon) {
+ LauncherIcons obtain = LauncherIcons.obtain(context);
+ layers.bitmap = obtain.createBadgedIconBitmap(
+ new AdaptiveIconDrawable(
+ layers.mDrawable.getBackground().getConstantState().newDrawable(),
+ null)).icon;
+
+ int iconBitmapSize = LauncherAppState.getInstance(context)
+ .getInvariantDeviceProfile().iconBitmapSize;
+ layers.offset = (int) Math.ceil((double) (0.010416667f * ((float) iconBitmapSize)));
+ obtain.recycle();
+ }
+
+ LayerDrawable layerDrawable = layers.mLayerDrawable;
+ int numberOfLayers = layerDrawable.getNumberOfLayers();
+
+ if (layers.mHourIndex < 0 || layers.mHourIndex >= numberOfLayers) {
+ layers.mHourIndex = -1;
+ }
+ if (layers.mMinuteIndex < 0 || layers.mMinuteIndex >= numberOfLayers) {
+ layers.mMinuteIndex = -1;
+ }
+ if (layers.mSecondIndex < 0 || layers.mSecondIndex >= numberOfLayers) {
+ layers.mSecondIndex = -1;
+ }
+
+ return layers;
+ }
+
+ public FastBitmapDrawable drawIcon(ItemInfoWithIcon info, Drawable drawableForDensity,
+ Metadata metadata) {
+ final AutoUpdateClock updater = new AutoUpdateClock(info,
+ getClockLayers(mContext, drawableForDensity, metadata,true).clone()
+ );
+ mUpdaters.add(updater);
+ return updater;
+ }
+
+ private void loadTimeZone(String timeZoneId) {
+ TimeZone timeZone = timeZoneId == null ?
+ TimeZone.getDefault() :
+ TimeZone.getTimeZone(timeZoneId);
+
+ for (AutoUpdateClock a : mUpdaters) {
+ a.setTimeZone(timeZone);
+ }
+ }
+
+ public static class Metadata {
+ final int HOUR_LAYER_INDEX;
+ final int MINUTE_LAYER_INDEX;
+ final int SECOND_LAYER_INDEX;
+
+ final int DEFAULT_HOUR;
+ final int DEFAULT_MINUTE;
+ final int DEFAULT_SECOND;
+
+ public Metadata(int hourIndex, int minuteIndex, int secondIndex,
+ int defaultHour, int defaultMinute, int defaultSecond) {
+ HOUR_LAYER_INDEX = hourIndex;
+ MINUTE_LAYER_INDEX = minuteIndex;
+ SECOND_LAYER_INDEX = secondIndex;
+ DEFAULT_HOUR = defaultHour;
+ DEFAULT_MINUTE = defaultMinute;
+ DEFAULT_SECOND = defaultSecond;
+ }
+ }
+}
diff --git a/src/com/android/launcher3/icons/clock/DynamicClock.java b/src/com/android/launcher3/icons/clock/DynamicClock.java
new file mode 100644
index 0000000000..607eb3ca71
--- /dev/null
+++ b/src/com/android/launcher3/icons/clock/DynamicClock.java
@@ -0,0 +1,161 @@
+package com.android.launcher3.icons.clock;
+
+import android.annotation.TargetApi;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+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.Handler;
+import android.os.Looper;
+import android.os.Process;
+import android.os.UserHandle;
+
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+
+import com.android.launcher3.model.data.ItemInfoWithIcon;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.icons.BaseIconFactory.IconOptions;
+import com.android.launcher3.icons.LauncherIcons;
+import com.android.launcher3.util.Preconditions;
+
+import java.util.Collections;
+import java.util.Set;
+import java.util.TimeZone;
+import java.util.WeakHashMap;
+
+@TargetApi(26)
+public class DynamicClock extends BroadcastReceiver {
+ public static final ComponentName DESK_CLOCK = new ComponentName(
+ "com.google.android.deskclock",
+ "com.android.deskclock.DeskClock");
+
+ private final Set mUpdaters;
+ private ClockLayers mLayers;
+ private final Context mContext;
+
+ public DynamicClock(Context context) {
+ mUpdaters = Collections.newSetFromMap(new WeakHashMap<>());
+ mLayers = new ClockLayers();
+ mContext = context;
+ final Handler handler = new Handler(MODEL_EXECUTOR.getLooper());
+
+ IntentFilter filter = new IntentFilter();
+ filter.addDataScheme("package");
+ filter.addDataSchemeSpecificPart(DESK_CLOCK.getPackageName(), 0);
+ filter.addAction(Intent.ACTION_PACKAGE_ADDED);
+ filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
+
+ mContext.registerReceiver(this, filter, null, handler);
+ handler.post(this::updateMainThread);
+
+ mContext.registerReceiver(new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ loadTimeZone(intent.getStringExtra("time-zone"));
+ }
+ }, new IntentFilter(Intent.ACTION_TIMEZONE_CHANGED), null, new Handler(Looper.getMainLooper()));
+ }
+
+ public static Drawable getClock(Context context, int iconDpi) {
+ ClockLayers clone = getClockLayers(context, iconDpi, false).clone();
+ if (clone != null) {
+ clone.updateAngles();
+ return clone.mDrawable;
+ }
+ return null;
+ }
+
+ private static ClockLayers getClockLayers(Context context, int iconDpi, boolean normalizeIcon) {
+ Preconditions.assertWorkerThread();
+ ClockLayers layers = new ClockLayers();
+ try {
+ PackageManager packageManager = context.getPackageManager();
+ ApplicationInfo applicationInfo = packageManager.getApplicationInfo("com.google.android.deskclock", PackageManager.GET_META_DATA | PackageManager.GET_UNINSTALLED_PACKAGES);
+ Bundle metaData = applicationInfo.metaData;
+ if (metaData != null) {
+ int levelPerTickIcon = metaData.getInt("com.google.android.apps.nexuslauncher.LEVEL_PER_TICK_ICON_ROUND", 0);
+ if (levelPerTickIcon != 0) {
+ Drawable drawableForDensity = packageManager.getResourcesForApplication(applicationInfo).getDrawableForDensity(levelPerTickIcon, iconDpi);
+ layers.setDrawable(drawableForDensity.mutate());
+ layers.mHourIndex = metaData.getInt("com.google.android.apps.nexuslauncher.HOUR_LAYER_INDEX", -1);
+ layers.mMinuteIndex = metaData.getInt("com.google.android.apps.nexuslauncher.MINUTE_LAYER_INDEX", -1);
+ layers.mSecondIndex = metaData.getInt("com.google.android.apps.nexuslauncher.SECOND_LAYER_INDEX", -1);
+ layers.mDefaultHour = metaData.getInt("com.google.android.apps.nexuslauncher.DEFAULT_HOUR", 0);
+ layers.mDefaultMinute = metaData.getInt("com.google.android.apps.nexuslauncher.DEFAULT_MINUTE", 0);
+ layers.mDefaultSecond = metaData.getInt("com.google.android.apps.nexuslauncher.DEFAULT_SECOND", 0);
+ if (normalizeIcon) {
+ LauncherIcons obtain = LauncherIcons.obtain(context);
+ layers.bitmap = obtain.createBadgedIconBitmap(
+ new AdaptiveIconDrawable(layers.mDrawable.getBackground().getConstantState().newDrawable(), null)).icon;
+ int iconBitmapSize = LauncherAppState.getInstance(context).getInvariantDeviceProfile().iconBitmapSize;
+ layers.offset = (int) Math.ceil((double) (0.010416667f * ((float) iconBitmapSize)));
+ obtain.recycle();
+ }
+
+ LayerDrawable layerDrawable = layers.mLayerDrawable;
+ int numberOfLayers = layerDrawable.getNumberOfLayers();
+
+ if (layers.mHourIndex < 0 || layers.mHourIndex >= numberOfLayers) {
+ layers.mHourIndex = -1;
+ }
+ if (layers.mMinuteIndex < 0 || layers.mMinuteIndex >= numberOfLayers) {
+ layers.mMinuteIndex = -1;
+ }
+ if (layers.mSecondIndex < 0 || layers.mSecondIndex >= numberOfLayers) {
+ layers.mSecondIndex = -1;
+ } else {
+ layerDrawable.setDrawable(layers.mSecondIndex, null);
+ layers.mSecondIndex = -1;
+ }
+ }
+ }
+ } catch (Exception e) {
+ layers.mDrawable = null;
+ }
+ return layers;
+ }
+
+ private void loadTimeZone(String timeZoneId) {
+ TimeZone timeZone = timeZoneId == null ?
+ TimeZone.getDefault() :
+ TimeZone.getTimeZone(timeZoneId);
+
+ for (AutoUpdateClock a : mUpdaters) {
+ a.setTimeZone(timeZone);
+ }
+ }
+
+ private void updateMainThread() {
+ MAIN_EXECUTOR.execute(() -> updateWrapper(getClockLayers(mContext,
+ LauncherAppState.getIDP(mContext).fillResIconDpi,
+ true)));
+ }
+
+ private void updateWrapper(ClockLayers wrapper) {
+ this.mLayers = wrapper;
+ for (AutoUpdateClock updater : mUpdaters) {
+ updater.updateLayers(wrapper.clone());
+ }
+ }
+
+ public AutoUpdateClock drawIcon(ItemInfoWithIcon info) {
+ final AutoUpdateClock updater = new AutoUpdateClock(info, mLayers.clone());
+ mUpdaters.add(updater);
+ return updater;
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ updateMainThread();
+ }
+}
diff --git a/src/com/android/launcher3/icons/pack/GetLaunchableInfoTask.java b/src/com/android/launcher3/icons/pack/GetLaunchableInfoTask.java
new file mode 100644
index 0000000000..6de3de9894
--- /dev/null
+++ b/src/com/android/launcher3/icons/pack/GetLaunchableInfoTask.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2019 The LineageOS 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.pack;
+
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.LauncherActivityInfo;
+import android.content.pm.LauncherApps;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.AsyncTask;
+import android.os.Build;
+import android.os.Process;
+import android.os.UserHandle;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
+final class GetLaunchableInfoTask extends AsyncTask> {
+
+ private final PackageManager pm;
+ private final LauncherApps launcherApps;
+ private final int limit;
+ private final Callback callback;
+
+ GetLaunchableInfoTask(PackageManager pm,
+ LauncherApps launcherApps,
+ int limit,
+ Callback callback) {
+ this.pm = pm;
+ this.launcherApps = launcherApps;
+ this.limit = limit;
+ this.callback = callback;
+ }
+
+ @Override
+ protected List doInBackground(Void... voids) {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
+ // This should never happen
+ return new ArrayList<>();
+ }
+
+ final UserHandle ua = Process.myUserHandle();
+ final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null)
+ .addCategory(Intent.CATEGORY_LAUNCHER);
+ return pm.queryIntentActivities(mainIntent, 0)
+ .parallelStream()
+ .sorted(new ResolveInfo.DisplayNameComparator(pm))
+ .limit(limit)
+ .map((ri) -> {
+ final ActivityInfo ai = ri.activityInfo;
+ final Intent i = new Intent();
+ i.setClassName(ai.applicationInfo.packageName, ai.name);
+ return launcherApps.resolveActivity(i, ua);
+ })
+ .collect(Collectors.toList());
+ }
+
+ @Override
+ protected void onPostExecute(List list) {
+ callback.onLoadCompleted(list);
+ }
+
+ interface Callback {
+ void onLoadCompleted(List result);
+ }
+}
diff --git a/src/com/android/launcher3/icons/pack/IconPack.java b/src/com/android/launcher3/icons/pack/IconPack.java
new file mode 100644
index 0000000000..3e7236b3c0
--- /dev/null
+++ b/src/com/android/launcher3/icons/pack/IconPack.java
@@ -0,0 +1,98 @@
+package com.android.launcher3.icons.pack;
+
+import android.content.ComponentName;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.util.SparseArray;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Class that handles the metadata and data of any icon pack.
+ */
+class IconPack {
+ private final ApplicationInfo mAi;
+ private final CharSequence mPackageLabel;
+ private Data mData;
+ private Resources mRes;
+
+ IconPack(ApplicationInfo ai, CharSequence label) {
+ mAi = ai;
+ mPackageLabel = label;
+ }
+
+ ApplicationInfo getAi() {
+ return mAi;
+ }
+
+ String getPackage() {
+ return mAi.packageName;
+ }
+
+ CharSequence getTitle() {
+ return mPackageLabel;
+ }
+
+ Data getData(PackageManager pm)
+ throws PackageManager.NameNotFoundException, XmlPullParserException, IOException {
+ if (mData == null) {
+ mData = IconPackParser.parsePackage(pm, getResources(pm), getPackage());
+ }
+ return mData;
+ }
+
+ int getDrawableId(PackageManager pm, ComponentName name)
+ throws PackageManager.NameNotFoundException, IOException, XmlPullParserException {
+ Data data = getData(pm);
+ Resources res = getResources(pm);
+ String pkg = getPackage();
+ return res.getIdentifier(data.drawables.get(name), "drawable", pkg);
+ }
+
+ private Resources getResources(PackageManager pm) throws PackageManager.NameNotFoundException {
+ if (mRes == null) {
+ mRes = pm.getResourcesForApplication(getPackage());
+ }
+ return mRes;
+ }
+
+ static class Data {
+ final Map drawables = new HashMap<>();
+ final Map calendarPrefix = new HashMap<>();
+ final SparseArray clockMetadata = new SparseArray<>();
+ final List iconBacks = new ArrayList<>();
+ final List iconMasks = new ArrayList<>();
+ final List iconUpons = new ArrayList<>();
+ float scale = 1f;
+
+ boolean hasMasking() {
+ return !iconBacks.isEmpty() || !iconMasks.isEmpty() || !iconUpons.isEmpty();
+ }
+ }
+
+ static class Clock {
+ final int hourLayerIndex;
+ final int minuteLayerIndex;
+ final int secondLayerIndex;
+ final int defaultHour;
+ final int defaultMinute;
+ final int defaultSecond;
+
+ Clock(int hourLayerIndex, int minuteLayerIndex, int secondLayerIndex,
+ int defaultHour, int defaultMinute, int defaultSecond) {
+ this.hourLayerIndex = hourLayerIndex;
+ this.minuteLayerIndex = minuteLayerIndex;
+ this.secondLayerIndex = secondLayerIndex;
+ this.defaultHour = defaultHour;
+ this.defaultMinute = defaultMinute;
+ this.defaultSecond = defaultSecond;
+ }
+ }
+}
diff --git a/src/com/android/launcher3/icons/pack/IconPackHeaderPreference.java b/src/com/android/launcher3/icons/pack/IconPackHeaderPreference.java
new file mode 100644
index 0000000000..25cf4bfde8
--- /dev/null
+++ b/src/com/android/launcher3/icons/pack/IconPackHeaderPreference.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2020 Shift GmbH
+ *
+ * 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.pack;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.LauncherApps;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.view.View;
+import android.widget.ImageView;
+import android.util.AttributeSet;
+import android.util.Log;
+
+import androidx.core.content.res.TypedArrayUtils;
+import androidx.preference.CheckBoxPreference;
+import androidx.preference.PreferenceViewHolder;
+
+import com.android.launcher3.R;
+import com.android.launcher3.icons.IconProvider;
+
+
+public class IconPackHeaderPreference extends RadioHeaderPreference {
+ private static final String TAG = "IconPackHeaderPreference";
+ private static final int PREVIEW_ICON_NUM = 8;
+ // This value has been selected as an average of usual "device profile-computed" values
+ private static final int PREVIEW_ICON_DPI = 500;
+
+ private final Context context;
+ private ImageView[] icons = null;
+
+ public IconPackHeaderPreference(Context context) {
+ this(context, null);
+ }
+
+ public IconPackHeaderPreference(Context context, AttributeSet attrs) {
+ this(context, attrs, TypedArrayUtils.getAttr(context,
+ androidx.preference.R.attr.preferenceStyle,
+ android.R.attr.preferenceStyle));
+ }
+
+ public IconPackHeaderPreference(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ this.context = context;
+
+ setLayoutResource(R.layout.preference_widget_icons_preview);
+ }
+
+ @Override
+ public void onBindViewHolder(PreferenceViewHolder holder) {
+ final ImageView[] imageViews = {
+ (ImageView) holder.findViewById(R.id.pref_icon_a),
+ (ImageView) holder.findViewById(R.id.pref_icon_b),
+ (ImageView) holder.findViewById(R.id.pref_icon_c),
+ (ImageView) holder.findViewById(R.id.pref_icon_d),
+ (ImageView) holder.findViewById(R.id.pref_icon_e),
+ (ImageView) holder.findViewById(R.id.pref_icon_f),
+ (ImageView) holder.findViewById(R.id.pref_icon_g),
+ (ImageView) holder.findViewById(R.id.pref_icon_h)
+ };
+ this.icons = imageViews;
+ onRadioElementSelected(null);
+ }
+
+ @Override
+ public void onDetached() {
+ this.icons = null;
+ super.onDetached();
+ }
+
+ @Override
+ public void onRadioElementSelected(String key) {
+ if (icons == null || icons.length == 0) {
+ return;
+ }
+
+ final IconProvider iconProvider = IconProvider.INSTANCE.get(context);
+ final PackageManager pm = context.getPackageManager();
+ final LauncherApps launcherApps = context.getSystemService(LauncherApps.class);
+ new GetLaunchableInfoTask(pm, launcherApps, PREVIEW_ICON_NUM, (aiList) -> {
+ for (int i = 0; i < icons.length; i++) {
+ icons[i].setImageDrawable(iconProvider.getIcon(
+ aiList.get(i), PREVIEW_ICON_DPI));
+ }
+ }).execute();
+ }
+}
diff --git a/src/com/android/launcher3/icons/pack/IconPackManager.java b/src/com/android/launcher3/icons/pack/IconPackManager.java
new file mode 100644
index 0000000000..8c54880e52
--- /dev/null
+++ b/src/com/android/launcher3/icons/pack/IconPackManager.java
@@ -0,0 +1,197 @@
+package com.android.launcher3.icons.pack;
+
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.Handler;
+import android.util.Log;
+
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+
+import com.android.launcher3.LauncherModel;
+import com.android.launcher3.util.ComponentKey;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import com.android.launcher3.customization.IconDatabase;
+import com.android.launcher3.util.AppReloader;
+
+public class IconPackManager extends BroadcastReceiver {
+ private static final String TAG = "IconPackManager";
+
+ private static final String[] ICON_INTENT_ACTIONS = new String[] {
+ "com.fede.launcher.THEME_ICONPACK",
+ "com.anddoes.launcher.THEME",
+ "com.novalauncher.THEME",
+ "com.teslacoilsw.launcher.THEME",
+ "com.gau.go.launcherex.theme",
+ "org.adw.launcher.THEMES",
+ "net.oneplus.launcher.icons.ACTION_PICK_ICON",
+ "org.adw.launcher.icons.ACTION_PICK_ICON"
+ };
+
+ private static final Intent[] ICON_INTENTS = new Intent[ICON_INTENT_ACTIONS.length];
+ private static IconPackManager sInstance;
+
+ static {
+ for (int i = 0; i < ICON_INTENT_ACTIONS.length; i++) {
+ ICON_INTENTS[i] = new Intent(ICON_INTENT_ACTIONS[i]);
+ }
+ }
+
+ public static synchronized IconPackManager get(Context context) {
+ if (sInstance == null) {
+ sInstance = new IconPackManager(context);
+ }
+ return sInstance;
+ }
+
+ private final Context mContext;
+ private final Map mProviders = new HashMap<>();
+ private final Handler mHandler = new Handler(MODEL_EXECUTOR.getLooper());
+
+ private IconPackManager(Context context) {
+ mContext = context;
+ reloadProviders();
+
+ IntentFilter filter = new IntentFilter();
+ filter.addDataScheme("package");
+ filter.addAction(Intent.ACTION_PACKAGE_ADDED);
+ filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
+ filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
+ filter.addAction(Intent.ACTION_PACKAGE_FULLY_REMOVED);
+
+ // Called when any app has been installed, enabled, disabled, updated or deleted.
+ context.getApplicationContext().registerReceiver(this, filter, null, mHandler);
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (intent.getData() != null) {
+ String pkg = intent.getData().getEncodedSchemeSpecificPart();
+ if (pkg != null) {
+ Log.d(TAG, "Received intent action " + intent.getAction() + " for " + pkg);
+ AppReloader appReloader = AppReloader.get(mContext);
+
+ // Create a list of apps that are using the changed package icon pack,
+ // either through the global setting or with an override.
+ Set updateKeys = appReloader.withIconPack(pkg);
+
+ // Remove the changed package from the providers to reload the application info.
+ mProviders.remove(pkg);
+
+ // This can reset the global preference, so do this after creating the list.
+ reloadProviders();
+
+ // Ensure all icons are up-to-date after this icon pack change.
+ // Calendar and clock information will automatically be reloaded by this call.
+ appReloader.reload(updateKeys);
+ }
+ }
+ }
+
+ private void reloadProviders() {
+ PackageManager pm = mContext.getPackageManager();
+ Set info = new HashSet<>();
+ for (Intent intent : ICON_INTENTS) {
+ info.addAll(pm.queryIntentActivities(intent, PackageManager.GET_META_DATA));
+ }
+
+ // Remove unavailable packs
+ for (String packageName : mProviders.keySet().toArray(new String[0])) {
+ boolean foundPackageName = false;
+ for (ResolveInfo ri : info) {
+ if (ri.activityInfo.packageName.equals(packageName)) {
+ foundPackageName = true;
+ break;
+ }
+ }
+ if (!foundPackageName) {
+ mProviders.remove(packageName);
+ }
+ }
+
+ // Add new packs
+ for (ResolveInfo ri : info) {
+ String packageName = ri.activityInfo.packageName;
+ if (!mProviders.containsKey(packageName)) {
+ ApplicationInfo ai = ri.activityInfo.applicationInfo;
+ CharSequence label = ai.loadLabel(pm);
+ mProviders.put(packageName, new IconPack(ai, label));
+ }
+ }
+
+ String global = IconDatabase.getGlobal(mContext);
+ if (!global.isEmpty() && !mProviders.containsKey(global)) {
+ // The global icon pack has been removed, so reset it.
+ // This constraint ensures that the global icon pack is always available,
+ // even if the launcher did not receive the uninstall intent.
+ Log.e(TAG, "Resetting global icon pack because provider " + global + " was removed");
+ IconDatabase.resetGlobal(mContext);
+ }
+ }
+
+ public Map getProviderNames() {
+ Map providerTitles = new HashMap<>();
+ for (Map.Entry pack : mProviders.entrySet()) {
+ providerTitles.put(pack.getKey(), pack.getValue().getTitle());
+ }
+ return providerTitles;
+ }
+
+ public boolean packContainsActivity(String packPackage, ComponentName componentName) {
+ try {
+ IconPack pack = mProviders.get(packPackage);
+ IconPack.Data data = pack.getData(mContext.getPackageManager());
+ return data.drawables.containsKey(componentName);
+ } catch (PackageManager.NameNotFoundException | XmlPullParserException | IOException ignored) {
+ return false;
+ }
+ }
+
+ /**
+ * Creates a resolver that can load the icon once.
+ * This resolver should not be stored, as the resolution strategy could change when
+ * the selected icon pack in use is uninstalled or updated.
+ * @param key Component for which an icon should be extracted.
+ * @return Resolver that loads the icon, or null if there is no resolution strategy.
+ */
+ public IconResolver resolve(ComponentKey key) {
+ String packPackage = IconDatabase.getByComponent(mContext, key);
+ if (mProviders.containsKey(packPackage)) {
+ // The icon provider package is available.
+ try {
+ IconPack pack = mProviders.get(packPackage);
+ IconPack.Data data = pack.getData(mContext.getPackageManager());
+ if (data.drawables.containsKey(key.componentName)) {
+ int drawableId = pack.getDrawableId(mContext.getPackageManager(), key.componentName);
+ if (drawableId != 0) {
+ return new IconResolverExternal(mContext.getPackageManager(), pack.getAi(),
+ drawableId,
+ data.calendarPrefix.get(key.componentName),
+ data.clockMetadata.get(drawableId));
+ }
+ }
+ if (data.hasMasking()) {
+ return new IconResolverMasked(mContext, data, pack.getAi(), key.hashCode());
+ }
+ } catch (PackageManager.NameNotFoundException | XmlPullParserException | IOException ignored) {
+ }
+ } else if (!packPackage.isEmpty()) {
+ // The provider is not available, save for next time.
+ IconDatabase.resetForComponent(mContext, key);
+ }
+ return null;
+ }
+}
diff --git a/src/com/android/launcher3/icons/pack/IconPackParser.java b/src/com/android/launcher3/icons/pack/IconPackParser.java
new file mode 100644
index 0000000000..ef08f70697
--- /dev/null
+++ b/src/com/android/launcher3/icons/pack/IconPackParser.java
@@ -0,0 +1,128 @@
+package com.android.launcher3.icons.pack;
+
+import android.content.ComponentName;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.content.res.XmlResourceParser;
+import android.util.Log;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.List;
+
+class IconPackParser {
+ private static final String TAG = "IconPackParser";
+
+ static IconPack.Data parsePackage(PackageManager pm, Resources res, String pkg)
+ throws IOException, XmlPullParserException {
+ IconPack.Data iconPack = new IconPack.Data();
+
+ int resId = res.getIdentifier("appfilter", "xml", pkg);
+ if (resId != 0) {
+ XmlResourceParser parseXml = pm.getXml(pkg, resId, null);
+ while (parseXml.next() != XmlPullParser.END_DOCUMENT) {
+ if (parseXml.getEventType() == XmlPullParser.START_TAG) {
+ switch (parseXml.getName()) {
+ case "item":
+ addItem(parseXml, iconPack);
+ break;
+ case "calendar":
+ addCalendar(parseXml, iconPack);
+ break;
+ case "iconback":
+ addImgsTo(res, pkg, parseXml, iconPack.iconBacks);
+ break;
+ case "iconmask":
+ addImgsTo(res, pkg, parseXml, iconPack.iconMasks);
+ break;
+ case "iconupon":
+ addImgsTo(res, pkg, parseXml, iconPack.iconUpons);
+ break;
+ case "scale":
+ setScale(parseXml, iconPack);
+ break;
+ case "dynamic-clock":
+ addClock(res, pkg, parseXml, iconPack);
+ break;
+ }
+ }
+ }
+ }
+
+ return iconPack;
+ }
+
+ private static void addItem(XmlResourceParser parseXml,
+ IconPack.Data iconPack) {
+ String component = parseXml.getAttributeValue(null, "component");
+ String drawable = parseXml.getAttributeValue(null, "drawable");
+ if (component != null && drawable != null) {
+ ComponentName componentName = parseComponent(component);
+ if (componentName != null) {
+ iconPack.drawables.put(componentName, drawable);
+ }
+ }
+ }
+
+ private static void addCalendar(XmlResourceParser parseXml, IconPack.Data iconPack) {
+ String component = parseXml.getAttributeValue(null, "component");
+ String prefix = parseXml.getAttributeValue(null, "prefix");
+ if (component != null && prefix != null) {
+ ComponentName componentName = parseComponent(component);
+ if (componentName != null) {
+ iconPack.calendarPrefix.put(componentName, prefix);
+ }
+ }
+ }
+
+ private static ComponentName parseComponent(String component) {
+ if (component.startsWith("ComponentInfo{") && component.endsWith("}")) {
+ component = component.substring(14, component.length() - 1);
+ return ComponentName.unflattenFromString(component);
+ }
+ return null;
+ }
+
+ private static void addImgsTo(Resources res, String pkg, XmlResourceParser parseXml,
+ List list) {
+ for (int i = 0; i < parseXml.getAttributeCount(); i++) {
+ if (parseXml.getAttributeName(i).startsWith("img")) {
+ int id = res.getIdentifier(parseXml.getAttributeValue(i), "drawable", pkg);
+ if (id != 0) {
+ list.add(id);
+ }
+ }
+ }
+ }
+
+ private static void setScale(XmlResourceParser parseXml, IconPack.Data iconPack) {
+ String factor = parseXml.getAttributeValue(null, "factor");
+ if (factor != null) {
+ try {
+ // ToDo: Parse reference to dimens
+ iconPack.scale = Float.parseFloat(factor);
+ } catch (NumberFormatException e) {
+ Log.d(TAG, "Invalid scale: " + factor, e);
+ }
+ }
+ }
+
+ private static void addClock(Resources res, String pkg, XmlResourceParser parseXml,
+ IconPack.Data iconPack) {
+ String drawable = parseXml.getAttributeValue(null, "drawable");
+ if (drawable != null) {
+ int drawableId = res.getIdentifier(drawable, "drawable", pkg);
+ if (drawableId != 0) {
+ iconPack.clockMetadata.put(drawableId, new IconPack.Clock(
+ parseXml.getAttributeIntValue(null, "hourLayerIndex", -1),
+ parseXml.getAttributeIntValue(null, "minuteLayerIndex", -1),
+ parseXml.getAttributeIntValue(null, "secondLayerIndex", -1),
+ parseXml.getAttributeIntValue(null, "defaultHour", 0),
+ parseXml.getAttributeIntValue(null, "defaultMinute", 0),
+ parseXml.getAttributeIntValue(null, "defaultSecond", 0)));
+ }
+ }
+ }
+}
diff --git a/src/com/android/launcher3/icons/pack/IconPackSettingsActivity.java b/src/com/android/launcher3/icons/pack/IconPackSettingsActivity.java
new file mode 100644
index 0000000000..484be9a2c3
--- /dev/null
+++ b/src/com/android/launcher3/icons/pack/IconPackSettingsActivity.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2020 Shift GmbH
+ *
+ * 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.pack;
+
+import android.app.ActionBar;
+import android.app.Activity;
+import android.app.Fragment;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.widget.Toast;
+
+import androidx.preference.Preference;
+import androidx.preference.PreferenceFragment;
+import androidx.preference.PreferenceFragment.OnPreferenceStartFragmentCallback;
+import androidx.preference.PreferenceFragment.OnPreferenceStartScreenCallback;
+import androidx.preference.PreferenceScreen;
+
+import com.android.launcher3.R;
+import com.android.launcher3.util.PackageManagerHelper;
+
+import com.android.settingslib.collapsingtoolbar.CollapsingToolbarBaseActivity;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public final class IconPackSettingsActivity extends CollapsingToolbarBaseActivity implements
+ OnPreferenceStartFragmentCallback, OnPreferenceStartScreenCallback {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ final ActionBar actionBar = getActionBar();
+ if (actionBar != null) {
+ actionBar.setDisplayHomeAsUpEnabled(true);
+ }
+
+ if (savedInstanceState == null) {
+ final Fragment f = Fragment.instantiate(this,
+ getString(R.string.icon_pack_settings_class), null);
+ // Display the fragment as the main content.
+ getFragmentManager().beginTransaction()
+ .replace(com.android.settingslib.widget.R.id.content_frame, f)
+ .commit();
+ }
+ }
+
+ private boolean startFragment(String fragment, Bundle args, String key) {
+ if (getFragmentManager().isStateSaved()) {
+ // Sometimes onClick can come after onPause because of being posted on the handler.
+ // Skip starting new fragments in that case.
+ return false;
+ }
+
+ final Fragment f = Fragment.instantiate(this, fragment, args);
+ getFragmentManager()
+ .beginTransaction()
+ .replace(com.android.settingslib.widget.R.id.content_frame, f)
+ .addToBackStack(key)
+ .commit();
+ return true;
+ }
+
+ @Override
+ public boolean onPreferenceStartFragment(PreferenceFragment preferenceFragment,
+ Preference pref) {
+ return startFragment(pref.getFragment(), pref.getExtras(), pref.getKey());
+ }
+
+ @Override
+ public boolean onPreferenceStartScreen(PreferenceFragment caller, PreferenceScreen pref) {
+ Bundle args = new Bundle();
+ args.putString(PreferenceFragment.ARG_PREFERENCE_ROOT, pref.getKey());
+ return startFragment(getString(R.string.icon_pack_settings_class),
+ args, pref.getKey());
+ }
+
+ @Override
+ public boolean onNavigateUp() {
+ onBackPressed();
+ return true;
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ final MenuInflater menuInflater = getMenuInflater();
+ menuInflater.inflate(R.menu.menu_icon_pack, menu);
+ return super.onCreateOptionsMenu(menu);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ int id = item.getItemId();
+ if (id == android.R.id.home) {
+ finish();
+ return true;
+ } else if (id == R.id.menu_icon_pack) {
+ return openMarket();
+ } else {
+ return super.onOptionsItemSelected(item);
+ }
+ }
+
+ private boolean openMarket() {
+ final String query = getString(R.string.icon_pack_title);
+ final Intent intent = PackageManagerHelper.getMarketSearchIntent(this, query);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+ if (intent.resolveActivity(getPackageManager()) == null) {
+ Toast.makeText(this, R.string.icon_pack_no_market, Toast.LENGTH_LONG)
+ .show();
+ return false;
+ }
+
+ startActivity(intent);
+ return true;
+ }
+}
diff --git a/src/com/android/launcher3/icons/pack/IconPackSettingsFragment.java b/src/com/android/launcher3/icons/pack/IconPackSettingsFragment.java
new file mode 100644
index 0000000000..6c80d39cd2
--- /dev/null
+++ b/src/com/android/launcher3/icons/pack/IconPackSettingsFragment.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2020 Shift GmbH
+ *
+ * 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.pack;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.preference.Preference;
+
+import com.android.launcher3.R;
+
+import com.android.launcher3.customization.IconDatabase;
+
+import com.android.settingslib.widget.SelectorWithWidgetPreference;
+
+import java.util.ArrayList;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+
+public final class IconPackSettingsFragment extends RadioSettingsFragment {
+ private static final IntentFilter PKG_UPDATE_INTENT = new IntentFilter();
+
+ private static final String[] ICON_INTENT_ACTIONS = new String[] {
+ "com.fede.launcher.THEME_ICONPACK",
+ "com.anddoes.launcher.THEME",
+ "com.novalauncher.THEME",
+ "com.teslacoilsw.launcher.THEME",
+ "com.gau.go.launcherex.theme",
+ "org.adw.launcher.THEMES",
+ "org.adw.launcher.icons.ACTION_PICK_ICON",
+ "net.oneplus.launcher.icons.ACTION_PICK_ICON",
+ };
+
+ private static final Intent[] ICON_INTENTS = new Intent[ICON_INTENT_ACTIONS.length];
+
+ static {
+ for (int i = 0; i < ICON_INTENT_ACTIONS.length; i++) {
+ ICON_INTENTS[i] = new Intent(ICON_INTENT_ACTIONS[i]);
+ }
+ }
+
+ static {
+ PKG_UPDATE_INTENT.addAction(Intent.ACTION_PACKAGE_INSTALL);
+ PKG_UPDATE_INTENT.addAction(Intent.ACTION_PACKAGE_ADDED);
+ PKG_UPDATE_INTENT.addAction(Intent.ACTION_PACKAGE_CHANGED);
+ PKG_UPDATE_INTENT.addAction(Intent.ACTION_PACKAGE_REMOVED);
+ PKG_UPDATE_INTENT.addDataScheme("package");
+ }
+
+ private BroadcastReceiver broadCastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ reloadPreferences();
+ }
+ };
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ getActivity().registerReceiver(broadCastReceiver, PKG_UPDATE_INTENT);
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ getActivity().unregisterReceiver(broadCastReceiver);
+ }
+
+ @Override
+ protected List getPreferences(Context context) {
+ final String currentIconPack = IconDatabase.getGlobal(context);
+ final List prefsList = new ArrayList<>();
+ final Set iconPacks = getAvailableIconPacks(context);
+
+ for (final IconPackInfo entry : iconPacks) {
+ final boolean isCurrent = currentIconPack.equals(entry.pkgName);
+ final SelectorWithWidgetPreference pref = buildPreference(context,
+ entry.pkgName, entry.label, isCurrent);
+ prefsList.add(pref);
+
+ if (isCurrent) {
+ setSelectedPreference(pref);
+ }
+ }
+
+ return prefsList;
+ }
+
+ @Override
+ public void onSelected(String key) {
+ IconDatabase.setGlobal(getActivity(), key);
+ super.onSelected(key);
+ }
+
+ @Override
+ protected IconPackHeaderPreference getHeader(Context context) {
+ return new IconPackHeaderPreference(context);
+ }
+
+ private Set getAvailableIconPacks(Context context) {
+ final PackageManager pm = context.getPackageManager();
+ final Set availablePacks = new LinkedHashSet<>();
+ final List eligiblePacks = new ArrayList<>();
+ for (Intent intent : ICON_INTENTS) {
+ eligiblePacks.addAll(pm.queryIntentActivities(intent, PackageManager.GET_META_DATA));
+ }
+
+ // Add default
+ final String defaultLabel = context.getString(R.string.icon_pack_default_label);
+ availablePacks.add(new IconPackInfo(IconDatabase.VALUE_DEFAULT, defaultLabel));
+ // Add user-installed packs
+ for (final ResolveInfo r : eligiblePacks) {
+ availablePacks.add(new IconPackInfo(
+ r.activityInfo.packageName, (String) r.loadLabel(pm)));
+ }
+ return availablePacks;
+ }
+
+ private SelectorWithWidgetPreference buildPreference(Context context, String pkgName,
+ String label, boolean isChecked) {
+ final SelectorWithWidgetPreference pref = new SelectorWithWidgetPreference(context);
+ pref.setKey(pkgName);
+ pref.setTitle(label);
+ pref.setPersistent(false);
+ pref.setChecked(isChecked);
+ if (!pkgName.equals(IconDatabase.VALUE_DEFAULT)) {
+ Intent intent = context.getPackageManager().getLaunchIntentForPackage(pkgName);
+ if (intent != null) {
+ pref.setExtraWidgetOnClickListener((v) -> {
+ context.startActivity(intent);
+ });
+ }
+ }
+ return pref;
+ }
+
+ private static class IconPackInfo {
+ final String pkgName;
+ final String label;
+
+ IconPackInfo(String pkgName, String label) {
+ this.pkgName = pkgName;
+ this.label = label;
+ }
+
+ @Override
+ public int hashCode() {
+ return pkgName.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == null) return false;
+ if (!(other instanceof IconPackInfo)) return false;
+ return pkgName.equals(((IconPackInfo) other).pkgName);
+ }
+ }
+}
diff --git a/src/com/android/launcher3/icons/pack/IconResolver.java b/src/com/android/launcher3/icons/pack/IconResolver.java
new file mode 100644
index 0000000000..c6efdc10cc
--- /dev/null
+++ b/src/com/android/launcher3/icons/pack/IconResolver.java
@@ -0,0 +1,25 @@
+package com.android.launcher3.icons.pack;
+
+import android.graphics.drawable.Drawable;
+
+import com.android.launcher3.icons.clock.CustomClock;
+
+public interface IconResolver {
+ boolean isCalendar();
+
+ boolean isClock();
+
+ CustomClock.Metadata clockData();
+
+ /**
+ * Resolves an external icon for a given density.
+ * @param iconDpi Positive integer. If it is non-positive the full scale drawable is returned.
+ * @param fallback Method to load the drawable when resolving using the override fails.
+ * @return Loaded drawable, or fallback drawable when resolving fails.
+ */
+ Drawable getIcon(int iconDpi, DefaultDrawableProvider fallback);
+
+ interface DefaultDrawableProvider {
+ Drawable get();
+ }
+}
diff --git a/src/com/android/launcher3/icons/pack/IconResolverExternal.java b/src/com/android/launcher3/icons/pack/IconResolverExternal.java
new file mode 100644
index 0000000000..df6a5c118f
--- /dev/null
+++ b/src/com/android/launcher3/icons/pack/IconResolverExternal.java
@@ -0,0 +1,85 @@
+package com.android.launcher3.icons.pack;
+
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+
+import java.util.Calendar;
+
+import com.android.launcher3.icons.clock.CustomClock;
+
+public class IconResolverExternal implements IconResolver {
+ private final PackageManager mPm;
+ private final ApplicationInfo mPackInfo;
+ private final int mDrawableId;
+ private final String mCalendarPrefix;
+ private final IconPack.Clock mClockData;
+
+ IconResolverExternal(PackageManager pm, ApplicationInfo packInfo, int drawableId,
+ String calendarPrefix, IconPack.Clock clockData) {
+ mPm = pm;
+ mPackInfo = packInfo;
+ mDrawableId = drawableId;
+ mCalendarPrefix = calendarPrefix;
+ mClockData = clockData;
+ }
+
+ public boolean isCalendar() {
+ return mCalendarPrefix != null;
+ }
+
+ public boolean isClock() {
+ return mClockData != null;
+ }
+
+ public CustomClock.Metadata clockData() {
+ return new CustomClock.Metadata(
+ mClockData.hourLayerIndex,
+ mClockData.minuteLayerIndex,
+ mClockData.secondLayerIndex,
+ mClockData.defaultHour,
+ mClockData.defaultMinute,
+ mClockData.defaultSecond
+ );
+ }
+
+ public Drawable getIcon(int iconDpi, DefaultDrawableProvider fallback) {
+ try {
+ Resources res = mPm.getResourcesForApplication(mPackInfo);
+
+ // First try loading the calendar.
+ if (isCalendar()) {
+ String calendarId = mCalendarPrefix + Calendar.getInstance().get(Calendar.DAY_OF_MONTH);
+ int drawableId = res.getIdentifier(calendarId, "drawable", mPackInfo.packageName);
+ if (drawableId != 0) {
+ Drawable drawable;
+ if (iconDpi > 0) {
+ // Try loading with the right density
+ drawable = res.getDrawableForDensity(drawableId, iconDpi, null);
+ if (drawable != null) {
+ return drawable;
+ }
+ }
+
+ drawable = mPm.getDrawable(mPackInfo.packageName, drawableId, null);
+ if (drawable != null) {
+ return drawable;
+ }
+ }
+ }
+
+ if (iconDpi > 0) {
+ // Fall back to mipmap loading with correct density.
+ Drawable drawable = res.getDrawableForDensity(mDrawableId, iconDpi, null);
+ if (drawable != null) {
+ return drawable;
+ }
+ }
+ } catch (PackageManager.NameNotFoundException | Resources.NotFoundException ignored) {
+ }
+
+ // Finally, try directly returning the drawable.
+ return mPm.getDrawable(mPackInfo.packageName, mDrawableId, mPackInfo);
+ }
+}
diff --git a/src/com/android/launcher3/icons/pack/IconResolverMasked.java b/src/com/android/launcher3/icons/pack/IconResolverMasked.java
new file mode 100644
index 0000000000..bb672a5f4a
--- /dev/null
+++ b/src/com/android/launcher3/icons/pack/IconResolverMasked.java
@@ -0,0 +1,174 @@
+package com.android.launcher3.icons.pack;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+
+import com.android.launcher3.icons.BaseIconFactory;
+import com.android.launcher3.icons.LauncherIcons;
+import static com.android.launcher3.icons.LauncherIcons.CONFIG_HINT_NO_WRAP;
+
+import com.android.launcher3.icons.clock.CustomClock;
+
+public class IconResolverMasked implements IconResolver {
+ private final Context mContext;
+ private final IconPack.Data mData;
+ private final ApplicationInfo mPackInfo;
+ private final int mHashCode;
+
+ private final Canvas mCanvas = new Canvas();
+ private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
+
+ IconResolverMasked(Context context, IconPack.Data data, ApplicationInfo packInfo,
+ int hashCode) {
+ mContext = context;
+ mData = data;
+ mPackInfo = packInfo;
+ mHashCode = hashCode & 0xFFFF;
+ }
+
+ @Override
+ public boolean isCalendar() {
+ return false;
+ }
+
+ @Override
+ public boolean isClock() {
+ return false;
+ }
+
+ @Override
+ public CustomClock.Metadata clockData() {
+ return null;
+ }
+
+ @Override
+ public Drawable getIcon(int iconDpi, DefaultDrawableProvider fallback) {
+ Drawable icon = fallback.get();
+
+ LauncherIcons li = LauncherIcons.obtain(mContext);
+ PackageManager pm = mContext.getPackageManager();
+ try {
+ Resources res = pm.getResourcesForApplication(mPackInfo);
+
+ // Re-render without scaling after creating the bitmap in the right dimensions.
+ Bitmap iconBm = li.createScaledBitmap(icon, BaseIconFactory.MODE_WITH_SHADOW);
+ mCanvas.setBitmap(iconBm);
+ icon.setBounds(0, 0, iconBm.getWidth(), iconBm.getHeight());
+ icon.draw(mCanvas);
+
+ // Scale the bitmap using the icon pack scale.
+ scaleBitmap(iconBm, mData.scale);
+
+ // Cut parts off using the mask image.
+ if (!mData.iconMasks.isEmpty()) {
+ int iconMask = mData.iconMasks.get(mHashCode % mData.iconMasks.size());
+ maskBitmap(iconBm, res.getDrawableForDensity(iconMask, iconDpi, null));
+ }
+
+ // Add icon back after scaling.
+ if (!mData.iconBacks.isEmpty()) {
+ int iconBack = mData.iconBacks.get(mHashCode % mData.iconBacks.size());
+ backBitmap(iconBm, res.getDrawableForDensity(iconBack, iconDpi, null), li);
+ }
+
+ // Render upon image onto icon. We use SRC_ATOP to make sure it stays within bounds.
+ if (!mData.iconUpons.isEmpty()) {
+ int iconUpon = mData.iconUpons.get(mHashCode % mData.iconUpons.size());
+ uponBitmap(iconBm, res.getDrawableForDensity(iconUpon, iconDpi, null));
+ }
+
+ return new BitmapDrawable(mContext.getResources(), iconBm);
+ } catch (PackageManager.NameNotFoundException | Resources.NotFoundException e) {
+ e.printStackTrace();
+ }
+
+ li.recycle();
+ return icon;
+ }
+
+ private void scaleBitmap(Bitmap bitmap, float scale) {
+ if (scale != 1f) {
+ Bitmap iconBmScaled = Bitmap.createScaledBitmap(bitmap,
+ (int)(bitmap.getWidth() * mData.scale),
+ (int)(bitmap.getHeight() * mData.scale),
+ true);
+
+ float move = 0.5f * (1f - mData.scale);
+
+ Matrix matrix = new Matrix();
+ matrix.postTranslate(move * bitmap.getWidth(), move * bitmap.getHeight());
+
+ bitmap.eraseColor(Color.TRANSPARENT);
+ mCanvas.setBitmap(bitmap);
+ mCanvas.drawBitmap(iconBmScaled, matrix, null);
+
+ iconBmScaled.recycle();
+ }
+ }
+
+ private void maskBitmap(Bitmap bitmap, Drawable mask) {
+ if (mask != null) {
+ Bitmap maskBm = Bitmap.createBitmap(
+ bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888);
+
+ // Render the mask image to a bitmap.
+ mCanvas.setBitmap(maskBm);
+ mask.setBounds(0, 0, bitmap.getWidth(), bitmap.getHeight());
+ mask.draw(mCanvas);
+
+ // Then use PorterDuff to remove black mask parts.
+ mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
+ mCanvas.setBitmap(bitmap);
+ mCanvas.drawBitmap(maskBm, 0f, 0f, mPaint);
+
+ maskBm.recycle();
+ }
+ }
+
+ @SuppressLint("WrongConstant")
+ private void backBitmap(Bitmap bitmap, Drawable back, LauncherIcons li) {
+ if (back != null) {
+ Bitmap backBm = Bitmap.createBitmap(
+ bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888);
+
+ mCanvas.setBitmap(backBm);
+ back.setBounds(0, 0, bitmap.getWidth(), bitmap.getHeight());
+ back.draw(mCanvas);
+
+ mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OVER));
+ mCanvas.setBitmap(bitmap);
+ mCanvas.drawBitmap(backBm, 0f, 0f, mPaint);
+
+ backBm.recycle();
+ }
+ }
+
+ private void uponBitmap(Bitmap bitmap, Drawable upon) {
+ if (upon != null) {
+ Bitmap uponBm = Bitmap.createBitmap(
+ bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888);
+
+ mCanvas.setBitmap(uponBm);
+ upon.setBounds(0, 0, bitmap.getWidth(), bitmap.getHeight());
+ upon.draw(mCanvas);
+
+ mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_ATOP));
+ mCanvas.setBitmap(bitmap);
+ mCanvas.drawBitmap(uponBm, 0f, 0f, mPaint);
+
+ uponBm.recycle();
+ }
+ }
+}
diff --git a/src/com/android/launcher3/icons/pack/RadioHeaderPreference.java b/src/com/android/launcher3/icons/pack/RadioHeaderPreference.java
new file mode 100644
index 0000000000..9e7e32eee4
--- /dev/null
+++ b/src/com/android/launcher3/icons/pack/RadioHeaderPreference.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2020 Shift GmbH
+ *
+ * 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.pack;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+import androidx.core.content.res.TypedArrayUtils;
+import androidx.preference.Preference;
+
+import com.android.launcher3.R;
+
+public abstract class RadioHeaderPreference extends Preference {
+
+ public RadioHeaderPreference(Context context) {
+ this(context, null);
+ }
+
+ public RadioHeaderPreference(Context context, AttributeSet attrs) {
+ this(context, attrs, TypedArrayUtils.getAttr(context,
+ androidx.preference.R.attr.preferenceStyle,
+ android.R.attr.preferenceStyle));
+ }
+
+ public RadioHeaderPreference(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ setIconSpaceReserved(false);
+ }
+
+ public abstract void onRadioElementSelected(String key);
+}
diff --git a/src/com/android/launcher3/icons/pack/RadioSettingsFragment.java b/src/com/android/launcher3/icons/pack/RadioSettingsFragment.java
new file mode 100644
index 0000000000..34e5073130
--- /dev/null
+++ b/src/com/android/launcher3/icons/pack/RadioSettingsFragment.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2020 Shift GmbH
+ *
+ * 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.pack;
+
+import android.content.Context;
+import android.os.Bundle;
+
+import androidx.preference.Preference;
+import androidx.preference.PreferenceFragment;
+import androidx.preference.PreferenceManager;
+import androidx.preference.PreferenceScreen;
+
+import com.android.settingslib.widget.SelectorWithWidgetPreference;
+
+import java.util.List;
+
+public abstract class RadioSettingsFragment extends PreferenceFragment implements
+ Preference.OnPreferenceClickListener {
+ private SelectorWithWidgetPreference selectedPreference = null;
+ private RadioHeaderPreference headerPref = null;
+
+ protected abstract List getPreferences(Context context);
+
+ @Override
+ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
+ final PreferenceManager prefManager = getPreferenceManager();
+ final Context context = prefManager.getContext();
+ final PreferenceScreen screen = prefManager.createPreferenceScreen(context);
+
+ headerPref = getHeader(context);
+ if (headerPref != null) {
+ screen.addPreference(headerPref);
+ }
+
+ loadPreferences(context, screen, null);
+ setPreferenceScreen(screen);
+ }
+
+ @Override
+ public boolean onPreferenceClick(Preference preference) {
+ if (preference instanceof SelectorWithWidgetPreference) {
+ onSelected(preference.getKey());
+
+ if (selectedPreference != null) {
+ selectedPreference.setChecked(false);
+ }
+ selectedPreference = (SelectorWithWidgetPreference) preference;
+ selectedPreference.setChecked(true);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void onDestroyView() {
+ selectedPreference = null;
+ headerPref = null;
+ super.onDestroyView();
+ }
+
+ protected RadioHeaderPreference getHeader(Context context) {
+ return null;
+ }
+
+ protected final void setSelectedPreference(SelectorWithWidgetPreference preference) {
+ selectedPreference = preference;
+ }
+
+ protected void onSelected(String key) {
+ if (headerPref != null) {
+ headerPref.onRadioElementSelected(key);
+ }
+ }
+
+ protected void reloadPreferences() {
+ final PreferenceScreen screen = getPreferenceScreen();
+ if (screen == null) {
+ return;
+ }
+
+ // Save current key for later
+ final String currentKey = selectedPreference == null ?
+ null : selectedPreference.getKey();
+ selectedPreference = null;
+
+ // Reload header contents
+ if (headerPref != null) {
+ headerPref.onRadioElementSelected(currentKey);
+ }
+
+ // Remove radio preferences (backwards so we don't mess up indices)
+ final int numPreferences = screen.getPreferenceCount();
+ for (int i = numPreferences - 1; i >= 0; i--) {
+ final Preference p = screen.getPreference(i);
+ if (p instanceof SelectorWithWidgetPreference) {
+ screen.removePreference(p);
+ }
+ }
+
+ // Add radio preferences
+ final PreferenceManager prefManager = getPreferenceManager();
+ final Context context = prefManager.getContext();
+ loadPreferences(context, screen, currentKey);
+ }
+
+ private void loadPreferences(Context context, PreferenceScreen screen,
+ String currentKey) {
+ boolean hasSetNewCurrent = false;
+
+ final List prefs = getPreferences(context);
+ for (final SelectorWithWidgetPreference p : prefs) {
+ if (currentKey != null && currentKey.equals(p.getKey())) {
+ p.setChecked(true);
+ selectedPreference = p;
+ hasSetNewCurrent = true;
+ }
+ p.setOnPreferenceClickListener(this);
+ screen.addPreference(p);
+ }
+
+ if (!hasSetNewCurrent && currentKey != null && !prefs.isEmpty()) {
+ // Old "current" preference was removed, fallback to
+ // the first candidate
+ selectedPreference = prefs.get(0);
+ selectedPreference.setChecked(true);
+ onPreferenceClick(selectedPreference);
+ }
+ }
+}
diff --git a/src/com/android/launcher3/keyboard/ViewGroupFocusHelper.java b/src/com/android/launcher3/keyboard/ViewGroupFocusHelper.java
index fde220cbf6..b6fcdf4143 100644
--- a/src/com/android/launcher3/keyboard/ViewGroupFocusHelper.java
+++ b/src/com/android/launcher3/keyboard/ViewGroupFocusHelper.java
@@ -54,7 +54,7 @@ private void computeLocationRelativeToContainer(View child, Rect outRect) {
outRect.left += child.getX();
outRect.top += child.getY();
- if (parent != mContainer) {
+ if (parent != null && parent != mContainer) {
if (parent instanceof PagedView) {
PagedView page = (PagedView) parent;
outRect.left -= page.getScrollForPage(page.indexOfChild(child));
diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java
index 3d9d81ff4e..d2020687f0 100644
--- a/src/com/android/launcher3/model/PackageUpdatedTask.java
+++ b/src/com/android/launcher3/model/PackageUpdatedTask.java
@@ -35,6 +35,7 @@
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.LauncherSettings.Favorites;
+import com.android.launcher3.Utilities;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.icons.BitmapInfo;
import com.android.launcher3.icons.IconCache;
@@ -111,11 +112,15 @@ public void execute(@NonNull final LauncherAppState app, @NonNull final BgDataMo
: ItemInfoMatcher.ofPackages(packageSet, mUser);
final HashSet removedComponents = new HashSet<>();
final HashMap> activitiesLists = new HashMap<>();
+ boolean needsRestart = false;
switch (mOp) {
case OP_ADD: {
for (int i = 0; i < N; i++) {
if (DEBUG) Log.d(TAG, "mAllAppsList.addPackage " + packages[i]);
+ if (isTargetPackage(packages[i])) {
+ needsRestart = true;
+ }
iconCache.updateIconsForPkg(packages[i], mUser);
if (FeatureFlags.PROMISE_APPS_IN_ALL_APPS.get()) {
appsList.removePackage(packages[i], mUser);
@@ -151,6 +156,9 @@ public void execute(@NonNull final LauncherAppState app, @NonNull final BgDataMo
for (int i = 0; i < N; i++) {
FileLog.d(TAG, "Removing app icon" + packages[i]);
iconCache.removeIconsForPkg(packages[i], mUser);
+ if (isTargetPackage(packages[i])) {
+ needsRestart = true;
+ }
}
// Fall through
}
@@ -167,6 +175,11 @@ public void execute(@NonNull final LauncherAppState app, @NonNull final BgDataMo
WorkspaceItemInfo.FLAG_DISABLED_SUSPENDED, mOp == OP_SUSPEND);
if (DEBUG) Log.d(TAG, "mAllAppsList.(un)suspend " + N);
appsList.updateDisabledFlags(matcher, flagOp);
+ for (int i = 0; i < N; i++) {
+ if (isTargetPackage(packages[i])) {
+ needsRestart = true;
+ }
+ }
break;
case OP_USER_AVAILABILITY_CHANGE: {
UserManagerState ums = new UserManagerState();
@@ -373,6 +386,10 @@ public void execute(@NonNull final LauncherAppState app, @NonNull final BgDataMo
}
bindUpdatedWidgets(dataModel);
}
+
+ if (needsRestart) {
+ Utilities.restart(context);
+ }
}
/**
@@ -395,4 +412,8 @@ private boolean updateWorkspaceItemIntent(Context context,
}
return false;
}
+
+ private boolean isTargetPackage(String packageName) {
+ return packageName.equals(Utilities.GSA_PACKAGE);
+ }
}
diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
index 4da588e839..ad7e299905 100644
--- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java
+++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
@@ -47,6 +47,7 @@
import com.android.launcher3.DropTarget.DragObject;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
import com.android.launcher3.accessibility.ShortcutMenuAccessibilityDelegate;
import com.android.launcher3.dot.DotInfo;
@@ -592,6 +593,8 @@ public boolean onLongClick(View v) {
if (!ItemLongClickListener.canStartDrag(mLauncher)) return false;
// Return early if not the correct view
if (!(v.getParent() instanceof DeepShortcutView)) return false;
+ // Return early if workspace edit is disabled
+ if (!Utilities.isWorkspaceEditAllowed(mLauncher.getApplicationContext())) return false;
// Long clicked on a shortcut.
DeepShortcutView sv = (DeepShortcutView) v.getParent();
diff --git a/src/com/android/launcher3/popup/SystemShortcut.java b/src/com/android/launcher3/popup/SystemShortcut.java
index 0e25984c1b..47dbf99251 100644
--- a/src/com/android/launcher3/popup/SystemShortcut.java
+++ b/src/com/android/launcher3/popup/SystemShortcut.java
@@ -133,6 +133,7 @@ public Widgets(Launcher target, ItemInfo itemInfo, View originalView) {
@Override
public void onClick(View view) {
+ if (!Utilities.isWorkspaceEditAllowed(mTarget.getApplicationContext())) return;
AbstractFloatingView.closeAllOpenViews(mTarget);
WidgetsBottomSheet widgetsBottomSheet =
(WidgetsBottomSheet) mTarget.getLayoutInflater().inflate(
@@ -243,6 +244,30 @@ public void onClick(View view) {
}
}
+ public static final Factory UNINSTALL = (activity, itemInfo, originalView) -> {
+ final String packageName = itemInfo.getTargetPackage();
+ if (packageName == null) return null;
+ return PackageManagerHelper.isSystemApp(activity, packageName)
+ ? null : new UnInstall(activity, itemInfo, originalView);
+ };
+
+ public static class UnInstall extends SystemShortcut {
+
+ public UnInstall(BaseDraggingActivity target, ItemInfo itemInfo, View originalView) {
+ super(R.drawable.ic_uninstall_no_shadow, R.string.uninstall_drop_target_label,
+ target, itemInfo, originalView);
+ }
+
+ @Override
+ public void onClick(View view) {
+ String packageName = mItemInfo.getTargetComponent().getPackageName();
+ Intent intent = new PackageManagerHelper(
+ view.getContext()).getUninstallIntent(packageName);
+ mTarget.startActivitySafely(view, intent, mItemInfo);
+ AbstractFloatingView.closeAllOpenViews(mTarget);
+ }
+ }
+
public static void dismissTaskMenuView(T activity) {
AbstractFloatingView.closeOpenViews(activity, true,
AbstractFloatingView.TYPE_ALL & ~AbstractFloatingView.TYPE_REBIND_SAFE);
diff --git a/src/com/android/launcher3/qsb/AssistantIconView.java b/src/com/android/launcher3/qsb/AssistantIconView.java
new file mode 100644
index 0000000000..0821164b8e
--- /dev/null
+++ b/src/com/android/launcher3/qsb/AssistantIconView.java
@@ -0,0 +1,31 @@
+package com.android.launcher3.qsb;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.content.Intent;
+import android.util.AttributeSet;
+import android.widget.ImageView;
+import com.android.launcher3.R;
+import com.android.launcher3.qsb.QsbContainerView;
+
+public class AssistantIconView extends ImageView {
+
+ public AssistantIconView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ setScaleType(ScaleType.CENTER);
+ setOnClickListener(view -> {
+ Intent intent = new Intent(Intent.ACTION_VOICE_COMMAND).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK).setPackage(QsbContainerView.getSearchWidgetPackageName(context));
+ context.startActivity(intent);
+ });
+ }
+
+ public AssistantIconView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ setScaleType(ScaleType.CENTER);
+ setOnClickListener(view -> {
+ Intent intent = new Intent(Intent.ACTION_VOICE_COMMAND).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK).setPackage(QsbContainerView.getSearchWidgetPackageName(context));
+ context.startActivity(intent);
+ });
+ }
+
+}
diff --git a/src/com/android/launcher3/qsb/QsbContainerView.java b/src/com/android/launcher3/qsb/QsbContainerView.java
index f2952041f7..d8d0574a42 100644
--- a/src/com/android/launcher3/qsb/QsbContainerView.java
+++ b/src/com/android/launcher3/qsb/QsbContainerView.java
@@ -45,6 +45,7 @@
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.graphics.FragmentWithPreview;
import com.android.launcher3.widget.util.WidgetSizes;
@@ -75,6 +76,9 @@ public static String getSearchWidgetPackageName(@NonNull Context context) {
if (componentName != null) {
providerPkg = searchManager.getGlobalSearchActivity().getPackageName();
}
+ if (providerPkg == null && Utilities.isGSAEnabled(context)) {
+ providerPkg = Utilities.GSA_PACKAGE;
+ }
}
return providerPkg;
}
diff --git a/src/com/android/launcher3/qsb/QsbLayout.java b/src/com/android/launcher3/qsb/QsbLayout.java
new file mode 100644
index 0000000000..07790f98ee
--- /dev/null
+++ b/src/com/android/launcher3/qsb/QsbLayout.java
@@ -0,0 +1,118 @@
+package com.android.launcher3.qsb;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.SystemClock;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import androidx.core.view.ViewCompat;
+import com.android.launcher3.BaseActivity;
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.LauncherPrefs;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.qsb.QsbContainerView;
+import com.android.launcher3.util.Themes;
+import com.android.launcher3.views.ActivityContext;
+
+public class QsbLayout extends FrameLayout implements
+ SharedPreferences.OnSharedPreferenceChangeListener {
+
+ ImageView mAssistantIcon;
+ ImageView mGoogleIcon;
+ ImageView mLensIcon;
+ Context mContext;
+
+ public QsbLayout(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mContext = context;
+ }
+
+ public QsbLayout(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ mContext = context;
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mAssistantIcon = findViewById(R.id.mic_icon);
+ mGoogleIcon = findViewById(R.id.g_icon);
+ mLensIcon = findViewById(R.id.lens_icon);
+ setIcons();
+
+ LauncherPrefs.getPrefs(mContext).registerOnSharedPreferenceChangeListener(this);
+
+ String searchPackage = QsbContainerView.getSearchWidgetPackageName(mContext);
+ setOnClickListener(view -> {
+ mContext.startActivity(new Intent("android.search.action.GLOBAL_SEARCH").addFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
+ Intent.FLAG_ACTIVITY_CLEAR_TASK).setPackage(searchPackage));
+ });
+
+ if (Utilities.isGSAEnabled(mContext)) {
+ enableLensIcon();
+ }
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ int requestedWidth = MeasureSpec.getSize(widthMeasureSpec);
+ int height = MeasureSpec.getSize(heightMeasureSpec);
+
+ DeviceProfile dp = ActivityContext.lookupContext(mContext).getDeviceProfile();
+ int cellWidth = DeviceProfile.calculateCellWidth(requestedWidth, dp.cellLayoutBorderSpacePx.x, dp.numShownHotseatIcons);
+ int iconSize = (int)(Math.round((dp.iconSizePx * 0.92f)));
+ int width = requestedWidth + (cellWidth - iconSize);
+ setMeasuredDimension(width, height);
+
+ for (int i = 0; i < getChildCount(); i++) {
+ final View child = getChildAt(i);
+ if (child != null) {
+ measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
+ }
+ }
+ }
+
+ @Override
+ public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
+ if (key.equals(Themes.KEY_THEMED_ICONS)) {
+ setIcons();
+ }
+ }
+
+ private void setIcons() {
+ if (Themes.isThemedIconEnabled(mContext)) {
+ mAssistantIcon.setImageResource(R.drawable.ic_mic_themed);
+ mGoogleIcon.setImageResource(R.drawable.ic_super_g_themed);
+ mLensIcon.setImageResource(R.drawable.ic_lens_themed);
+ } else {
+ mAssistantIcon.setImageResource(R.drawable.ic_mic_color);
+ mGoogleIcon.setImageResource(R.drawable.ic_super_g_color);
+ mLensIcon.setImageResource(R.drawable.ic_lens_color);
+ }
+ }
+
+ private void enableLensIcon() {
+ mLensIcon.setVisibility(View.VISIBLE);
+ mLensIcon.setOnClickListener(view -> {
+ Intent lensIntent = new Intent();
+ Bundle bundle = new Bundle();
+ bundle.putString("caller_package", Utilities.GSA_PACKAGE);
+ bundle.putLong("start_activity_time_nanos", SystemClock.elapsedRealtimeNanos());
+ lensIntent.setComponent(new ComponentName(Utilities.GSA_PACKAGE, Utilities.LENS_ACTIVITY))
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ .setPackage(Utilities.GSA_PACKAGE)
+ .setData(Uri.parse(Utilities.LENS_URI))
+ .putExtra("lens_activity_params", bundle);
+ mContext.startActivity(lensIntent);
+ });
+ }
+
+}
diff --git a/src/com/android/launcher3/settings/SettingsActivity.java b/src/com/android/launcher3/settings/SettingsActivity.java
index 7ab3013da1..fdd8f11fab 100644
--- a/src/com/android/launcher3/settings/SettingsActivity.java
+++ b/src/com/android/launcher3/settings/SettingsActivity.java
@@ -21,8 +21,15 @@
import static com.android.launcher3.config.FeatureFlags.IS_STUDIO_BUILD;
import static com.android.launcher3.states.RotationHelper.ALLOW_ROTATION_PREFERENCE_KEY;
+import static com.android.launcher3.OverlayCallbackImpl.KEY_ENABLE_MINUS_ONE;
+
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.content.Context;
import android.content.Intent;
+import android.content.pm.PackageManager;
import android.content.SharedPreferences;
+import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.MenuItem;
@@ -43,17 +50,22 @@
import androidx.preference.PreferenceScreen;
import androidx.recyclerview.widget.RecyclerView;
+import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.customization.IconDatabase;
import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.icons.pack.IconPackSettingsActivity;
import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherFiles;
import com.android.launcher3.LauncherPrefs;
-import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.model.WidgetsModel;
+import com.android.launcher3.R;
import com.android.launcher3.states.RotationHelper;
import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
import com.android.launcher3.util.DisplayController;
+import com.android.launcher3.Utilities;
+
+import com.android.settingslib.collapsingtoolbar.CollapsingToolbarBaseActivity;
import java.util.Collections;
import java.util.List;
@@ -61,14 +73,13 @@
/**
* Settings activity for Launcher. Currently implements the following setting: Allow rotation
*/
-public class SettingsActivity extends FragmentActivity
+public class SettingsActivity extends CollapsingToolbarBaseActivity
implements OnPreferenceStartFragmentCallback, OnPreferenceStartScreenCallback,
SharedPreferences.OnSharedPreferenceChangeListener{
/** List of fragments that can be hosted by this activity. */
- private static final List VALID_PREFERENCE_FRAGMENTS =
- !Utilities.IS_DEBUG_DEVICE ? Collections.emptyList()
- : Collections.singletonList(DeveloperOptionsFragment.class.getName());
+ private static final List VALID_PREFERENCE_FRAGMENTS = Collections.singletonList(
+ DeveloperOptionsFragment.class.getName());
private static final String DEVELOPER_OPTIONS_KEY = "pref_developer_options";
private static final String FLAGS_PREFERENCE_KEY = "flag_toggler";
@@ -89,13 +100,9 @@ public class SettingsActivity extends FragmentActivity
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.settings_activity);
- setActionBar(findViewById(R.id.action_bar));
WindowCompat.setDecorFitsSystemWindows(getWindow(), false);
Intent intent = getIntent();
- if (intent.hasExtra(EXTRA_FRAGMENT) || intent.hasExtra(EXTRA_FRAGMENT_ARGS)) {
- getActionBar().setDisplayHomeAsUpEnabled(true);
- }
if (savedInstanceState == null) {
Bundle args = intent.getBundleExtra(EXTRA_FRAGMENT_ARGS);
@@ -113,7 +120,7 @@ protected void onCreate(Bundle savedInstanceState) {
getPreferenceFragment());
f.setArguments(args);
// Display the fragment as the main content.
- fm.beginTransaction().replace(R.id.content_frame, f).commit();
+ fm.beginTransaction().replace(com.android.settingslib.widget.R.id.content_frame, f).commit();
}
LauncherPrefs.getPrefs(getApplicationContext())
.registerOnSharedPreferenceChangeListener(this);
@@ -141,7 +148,15 @@ private String getPreferenceFragment() {
}
@Override
- public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { }
+ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
+ switch (key) {
+ case Utilities.KEY_DOCK_SEARCH:
+ LauncherAppState.getInstanceNoCreate().setNeedsRestart();
+ break;
+ default:
+ break;
+ }
+ }
private boolean startPreference(String fragment, Bundle args, String key) {
if (Utilities.ATLEAST_P && getSupportFragmentManager().isStateSaved()) {
@@ -184,15 +199,19 @@ public boolean onOptionsItemSelected(MenuItem item) {
return super.onOptionsItemSelected(item);
}
+
/**
* This fragment shows the launcher preferences.
*/
- public static class LauncherSettingsFragment extends PreferenceFragmentCompat {
+ public static class LauncherSettingsFragment extends PreferenceFragmentCompat implements
+ SharedPreferences.OnSharedPreferenceChangeListener {
private String mHighLightKey;
private boolean mPreferenceHighlighted = false;
private Preference mDeveloperOptionPref;
+ private Preference mShowGoogleAppPref;
+
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
final Bundle args = getArguments();
@@ -208,6 +227,20 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
getPreferenceManager().setSharedPreferencesName(LauncherFiles.SHARED_PREFERENCES_KEY);
setPreferencesFromResource(R.xml.launcher_preferences, rootKey);
+ updatePreferences();
+
+ LauncherPrefs.getPrefs(getContext())
+ .registerOnSharedPreferenceChangeListener(this);
+ }
+
+ @Override
+ public void onDestroyView () {
+ LauncherPrefs.getPrefs(getContext())
+ .unregisterOnSharedPreferenceChangeListener(this);
+ super.onDestroyView();
+ }
+
+ private void updatePreferences() {
PreferenceScreen screen = getPreferenceScreen();
for (int i = screen.getPreferenceCount() - 1; i >= 0; i--) {
Preference preference = screen.getPreference(i);
@@ -254,6 +287,15 @@ public void onSaveInstanceState(Bundle outState) {
outState.putBoolean(SAVE_HIGHLIGHTED_KEY, mPreferenceHighlighted);
}
+ @Override
+ public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
+ switch (key) {
+ case IconDatabase.KEY_ICON_PACK:
+ updatePreferences();
+ break;
+ }
+ }
+
protected String getParentKeyForPref(String key) {
return null;
}
@@ -285,11 +327,28 @@ protected boolean initPreference(Preference preference) {
case DEVELOPER_OPTIONS_KEY:
mDeveloperOptionPref = preference;
return updateDeveloperOption();
+
+ case KEY_ENABLE_MINUS_ONE:
+ mShowGoogleAppPref = preference;
+ updateIsGoogleAppEnabled();
+ return true;
+ case IconDatabase.KEY_ICON_PACK:
+ setupIconPackPreference(preference);
+ return true;
}
return true;
}
+ private void setupIconPackPreference(Preference preference) {
+ final String pkgLabel = IconDatabase.getGlobalLabel(getActivity());
+ preference.setSummary(pkgLabel);
+ preference.setOnPreferenceClickListener(p -> {
+ startActivity(new Intent(getActivity(), IconPackSettingsActivity.class));
+ return true;
+ });
+ }
+
/**
* Show if plugins are enabled or flag UI is enabled.
* @return True if we should show the preference option.
@@ -308,6 +367,12 @@ private boolean updateDeveloperOption() {
return showPreference;
}
+ private void updateIsGoogleAppEnabled() {
+ if (mShowGoogleAppPref != null) {
+ mShowGoogleAppPref.setEnabled(Utilities.isGSAEnabled(getContext()));
+ }
+ }
+
@Override
public void onResume() {
super.onResume();
@@ -323,6 +388,13 @@ public void onResume() {
requestAccessibilityFocus(getListView());
}
}
+ updateIsGoogleAppEnabled();
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ LauncherAppState.getInstanceNoCreate().checkIfRestartNeeded();
}
private PreferenceHighlighter createHighlighter() {
@@ -352,4 +424,8 @@ private void requestAccessibilityFocus(@NonNull final RecyclerView rv) {
});
}
}
+
+ public interface OnResumePreferenceCallback {
+ void onResume();
+ }
}
diff --git a/src/com/android/launcher3/settings/preference/IconPackPrefSetter.java b/src/com/android/launcher3/settings/preference/IconPackPrefSetter.java
new file mode 100644
index 0000000000..04eb869c0a
--- /dev/null
+++ b/src/com/android/launcher3/settings/preference/IconPackPrefSetter.java
@@ -0,0 +1,70 @@
+package com.android.launcher3.settings.preference;
+
+import android.content.ComponentName;
+import android.content.Context;
+
+import androidx.preference.ListPreference;
+
+import com.android.launcher3.R;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+
+import com.android.launcher3.icons.pack.IconPackManager;
+import com.android.launcher3.customization.IconDatabase;
+
+public class IconPackPrefSetter implements ReloadingListPreference.OnReloadListener {
+ private final Context mContext;
+ private final ComponentName mFilter;
+
+ public IconPackPrefSetter(Context context) {
+ this(context, null);
+ }
+
+ public IconPackPrefSetter(Context context, ComponentName filter) {
+ mContext = context;
+ mFilter = filter;
+ }
+
+ @Override
+ public Runnable listUpdater(ReloadingListPreference pref) {
+ IconPackManager ipm = IconPackManager.get(mContext);
+ Map packList = ipm.getProviderNames();
+ String globalPack = IconDatabase.getGlobal(mContext);
+
+ if (mFilter != null) {
+ // Filter for packs with icon for this app, or the global pack.
+ for (String pkg : new HashSet<>(packList.keySet())) {
+ if (!ipm.packContainsActivity(pkg, mFilter)) {
+ packList.remove(pkg);
+ }
+ }
+ }
+
+ CharSequence[] keys = new String[packList.size() + 1];
+ CharSequence[] values = new String[keys.length];
+ int i = 0;
+
+ // First value, system default, or the current icon pack if that has no icon yet.
+ keys[i] = mContext.getResources().getString(R.string.icon_pack_default_label);
+ values[i++] = packList.containsKey(globalPack) ? "" : globalPack;
+
+ // List of available icon packs
+ List> packs = new ArrayList<>(packList.entrySet());
+ Collections.sort(packs, (o1, o2) ->
+ normalizeTitle(o1.getValue()).compareTo(normalizeTitle(o2.getValue())));
+ for (Map.Entry entry : packs) {
+ keys[i] = entry.getValue();
+ values[i++] = entry.getKey();
+ }
+
+ return () -> pref.setEntriesWithValues(keys, values);
+ }
+
+ private String normalizeTitle(CharSequence title) {
+ return title.toString().toLowerCase();
+ }
+}
diff --git a/src/com/android/launcher3/settings/preference/ReloadingListPreference.java b/src/com/android/launcher3/settings/preference/ReloadingListPreference.java
new file mode 100644
index 0000000000..59eceabc69
--- /dev/null
+++ b/src/com/android/launcher3/settings/preference/ReloadingListPreference.java
@@ -0,0 +1,76 @@
+package com.android.launcher3.settings.preference;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+import androidx.preference.ListPreference;
+
+import java.util.function.Function;
+
+import com.android.launcher3.settings.SettingsActivity;
+
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.launcher3.util.Executors.THREAD_POOL_EXECUTOR;
+
+@SuppressWarnings("unused")
+public class ReloadingListPreference extends ListPreference
+ implements SettingsActivity.OnResumePreferenceCallback {
+ public interface OnReloadListener {
+ Runnable listUpdater(ReloadingListPreference pref);
+ }
+
+ private OnReloadListener mOnReloadListener;
+
+ public ReloadingListPreference(Context context) {
+ super(context);
+ }
+
+ public ReloadingListPreference(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public ReloadingListPreference(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ public ReloadingListPreference(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ @Override
+ protected void onClick() {
+ // Run the entries updater on the main thread immediately.
+ // Should be fast as the data was cached from the async load before.
+ // If it wasn't, we need to block to ensure the data has been loaded.
+ loadEntries(false);
+ super.onClick();
+ }
+
+ public void setOnReloadListener(Function supplier) {
+ mOnReloadListener = supplier.apply(getContext());
+ loadEntries(true);
+ }
+
+ @Override
+ public void onResume() {
+ loadEntries(true);
+ }
+
+ private void loadEntries(boolean async) {
+ if (mOnReloadListener != null) {
+ if (async) {
+ THREAD_POOL_EXECUTOR.execute(
+ () -> MAIN_EXECUTOR.execute(mOnReloadListener.listUpdater(this)));
+ } else {
+ mOnReloadListener.listUpdater(this).run();
+ }
+ }
+ }
+
+ void setEntriesWithValues(CharSequence[] entries, CharSequence[] entryValues) {
+ setEntries(entries);
+ setEntryValues(entryValues);
+ setSummary("%s");
+ }
+}
diff --git a/src/com/android/launcher3/touch/WorkspaceTouchListener.java b/src/com/android/launcher3/touch/WorkspaceTouchListener.java
index 96ae4a32fd..f427ef8bb7 100644
--- a/src/com/android/launcher3/touch/WorkspaceTouchListener.java
+++ b/src/com/android/launcher3/touch/WorkspaceTouchListener.java
@@ -26,8 +26,10 @@
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_CLOSE_TAP_OUTSIDE;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_WORKSPACE_LONGPRESS;
+import android.content.Context;
import android.graphics.PointF;
import android.graphics.Rect;
+import android.os.PowerManager;
import android.view.GestureDetector;
import android.view.HapticFeedbackConstants;
import android.view.MotionEvent;
@@ -39,7 +41,9 @@
import com.android.launcher3.CellLayout;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.Workspace;
+import com.android.launcher3.Utilities;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.logger.LauncherAtom;
import com.android.launcher3.testing.TestLogging;
@@ -63,6 +67,8 @@ public class WorkspaceTouchListener extends GestureDetector.SimpleOnGestureListe
private static final int STATE_PENDING_PARENT_INFORM = 2;
private static final int STATE_COMPLETED = 3;
+ private static final String SLEEP_GESTURE = "pref_sleep_gesture";
+
private final Rect mTempRect = new Rect();
private final Launcher mLauncher;
private final Workspace> mWorkspace;
@@ -71,15 +77,21 @@ public class WorkspaceTouchListener extends GestureDetector.SimpleOnGestureListe
private int mLongPressState = STATE_CANCELLED;
+ private final PowerManager mPm;
+
private final GestureDetector mGestureDetector;
+ private final Context mContext;
+
public WorkspaceTouchListener(Launcher launcher, Workspace> workspace) {
mLauncher = launcher;
mWorkspace = workspace;
+ mContext = workspace.getContext();
// Use twice the touch slop as we are looking for long press which is more
// likely to cause movement.
mTouchSlop = 2 * ViewConfiguration.get(launcher).getScaledTouchSlop();
- mGestureDetector = new GestureDetector(workspace.getContext(), this);
+ mPm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+ mGestureDetector = new GestureDetector(mContext, this);
}
@Override
@@ -210,4 +222,11 @@ private void maybeShowMenu() {
}
}
}
+
+ @Override
+ public boolean onDoubleTap(MotionEvent event) {
+ if (LauncherPrefs.getPrefs(mContext).getBoolean(SLEEP_GESTURE, true))
+ mPm.goToSleep(event.getEventTime());
+ return true;
+ }
}
diff --git a/src/com/android/launcher3/util/AppReloader.java b/src/com/android/launcher3/util/AppReloader.java
new file mode 100644
index 0000000000..65f1273500
--- /dev/null
+++ b/src/com/android/launcher3/util/AppReloader.java
@@ -0,0 +1,81 @@
+package com.android.launcher3.util;
+
+import android.content.Context;
+import android.content.pm.LauncherActivityInfo;
+import android.content.pm.ShortcutInfo;
+import android.os.UserHandle;
+
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherModel;
+import android.content.pm.LauncherApps;
+import android.os.UserManager;
+import com.android.launcher3.util.ComponentKey;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import com.android.launcher3.customization.IconDatabase;
+
+public class AppReloader {
+ private static AppReloader sInstance;
+
+ public static synchronized AppReloader get(Context context) {
+ if (sInstance == null) {
+ sInstance = new AppReloader(context);
+ }
+ return sInstance;
+ }
+
+ private final Context mContext;
+ private final LauncherModel mModel;
+ private final UserManager mUsers;
+ private final LauncherApps mApps;
+
+ private AppReloader(Context context) {
+ mContext = context;
+ mModel = LauncherAppState.getInstance(context).getModel();
+ mUsers = context.getSystemService(UserManager.class);
+ mApps = context.getSystemService(LauncherApps.class);
+ }
+
+ public Set withIconPack(String iconPack) {
+ Set reloadKeys = new HashSet<>();
+ for (UserHandle user : mUsers.getUserProfiles()) {
+ for (LauncherActivityInfo info : mApps.getActivityList(null, user)) {
+ ComponentKey key = new ComponentKey(info.getComponentName(), info.getUser());
+ if (IconDatabase.getByComponent(mContext, key).equals(iconPack)) {
+ reloadKeys.add(key);
+ }
+ }
+ }
+ return reloadKeys;
+ }
+
+ public void reload() {
+ for (UserHandle user : mUsers.getUserProfiles()) {
+ Set pkgsSet = new HashSet<>();
+ for (LauncherActivityInfo info : mApps.getActivityList(null, user)) {
+ pkgsSet.add(info.getComponentName().getPackageName());
+ }
+ for (String pkg : pkgsSet) {
+ reload(user, pkg);
+ }
+ }
+ }
+
+ public void reload(ComponentKey key) {
+ reload(key.user, key.componentName.getPackageName());
+ }
+
+ public void reload(Collection keys) {
+ for (ComponentKey key : keys) {
+ reload(key);
+ }
+ }
+
+ private void reload(UserHandle user, String pkg) {
+ mModel.onPackageChanged(pkg, user);
+ }
+}
diff --git a/src/com/android/launcher3/util/PackageManagerHelper.java b/src/com/android/launcher3/util/PackageManagerHelper.java
index 140440eee2..86d90ff01c 100644
--- a/src/com/android/launcher3/util/PackageManagerHelper.java
+++ b/src/com/android/launcher3/util/PackageManagerHelper.java
@@ -194,6 +194,12 @@ public Intent getMarketIntent(String packageName) {
.authority(mContext.getPackageName()).build());
}
+ public Intent getUninstallIntent(String packageName) {
+ return new Intent(Intent.ACTION_UNINSTALL_PACKAGE)
+ .setData(Uri.parse("package:" + packageName))
+ .putExtra(Intent.EXTRA_RETURN_RESULT, true);
+ }
+
/**
* Creates a new market search intent.
*/
@@ -262,22 +268,34 @@ public static IntentFilter getPackageFilter(String pkg, String... actions) {
return packageFilter;
}
- public static boolean isSystemApp(@NonNull final Context context,
- @NonNull final Intent intent) {
+ public static boolean isSystemApp(@NonNull final Context context, String pkgName) {
+ return isSystemApp(context, null, pkgName);
+ }
+
+ public static boolean isSystemApp(@NonNull final Context context, Intent intent) {
+ return isSystemApp(context, intent, null);
+ }
+
+ public static boolean isSystemApp(@NonNull final Context context, Intent intent, String pkgName) {
PackageManager pm = context.getPackageManager();
- ComponentName cn = intent.getComponent();
String packageName = null;
- if (cn == null) {
- ResolveInfo info = pm.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY);
- if ((info != null) && (info.activityInfo != null)) {
- packageName = info.activityInfo.packageName;
+ // If the intent is not null, let's get the package name from the intent.
+ if (intent != null) {
+ ComponentName cn = intent.getComponent();
+ if (cn == null) {
+ ResolveInfo info = pm.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY);
+ if ((info != null) && (info.activityInfo != null)) {
+ packageName = info.activityInfo.packageName;
+ }
+ } else {
+ packageName = cn.getPackageName();
}
- } else {
- packageName = cn.getPackageName();
}
- if (packageName == null) {
- packageName = intent.getPackage();
+ // Otherwise we have the package name passed from the method.
+ else {
+ packageName = pkgName;
}
+ // Check if the provided package is a system app.
if (packageName != null) {
try {
PackageInfo info = pm.getPackageInfo(packageName, 0);
diff --git a/src/com/android/launcher3/views/OptionsPopupView.java b/src/com/android/launcher3/views/OptionsPopupView.java
index 5b57e22d81..5dad7917a1 100644
--- a/src/com/android/launcher3/views/OptionsPopupView.java
+++ b/src/com/android/launcher3/views/OptionsPopupView.java
@@ -183,7 +183,7 @@ public static ArrayList getOptions(Launcher launcher) {
resDrawable,
IGNORE,
OptionsPopupView::startWallpaperPicker));
- if (!WidgetsModel.GO_DISABLE_WIDGETS) {
+ if (!WidgetsModel.GO_DISABLE_WIDGETS && Utilities.isWorkspaceEditAllowed(launcher)) {
options.add(new OptionItem(launcher,
R.string.widget_button_text,
R.drawable.ic_widget,
diff --git a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
index bc3889fd26..e8773a993e 100644
--- a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
@@ -119,6 +119,7 @@ public void setColorResources(@Nullable SparseIntArray colors) {
@Override
public boolean onLongClick(View view) {
+ if (!Utilities.isWorkspaceEditAllowed(mLauncher.getApplicationContext())) return true;
if (mIsScrollable) {
DragLayer dragLayer = mLauncher.getDragLayer();
dragLayer.requestDisallowInterceptTouchEvent(false);
diff --git a/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfo.java b/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfo.java
index bba1016f34..00b32b02e2 100644
--- a/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfo.java
+++ b/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfo.java
@@ -123,7 +123,7 @@ public void initSpans(Context context, InvariantDeviceProfile idp) {
minSpanX = Math.max(minSpanX,
getSpanX(localPadding, minResizeWidth, dp.cellLayoutBorderSpacePx.x,
cellSize.x));
- minSpanY = Math.max(minSpanY,
+ minSpanY = Math.min(minSpanY,
getSpanY(localPadding, minResizeHeight, dp.cellLayoutBorderSpacePx.y,
cellSize.y));
diff --git a/src/com/android/launcher3/widget/LocalWallpaperColorsExtractor.java b/src/com/android/launcher3/widget/LocalWallpaperColorsExtractor.java
new file mode 100644
index 0000000000..c36e31d1fc
--- /dev/null
+++ b/src/com/android/launcher3/widget/LocalWallpaperColorsExtractor.java
@@ -0,0 +1,211 @@
+package com.android.launcher3.widget;
+
+import android.app.WallpaperColors;
+import android.app.WallpaperManager;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.provider.Settings;
+import android.util.SparseIntArray;
+import android.view.View;
+import android.widget.RemoteViews;
+
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.views.ActivityContext;
+import com.android.launcher3.widget.LocalColorExtractor;
+import com.android.systemui.monet.ColorScheme;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+public class LocalWallpaperColorsExtractor extends LocalColorExtractor implements
+ WallpaperManager.LocalWallpaperColorConsumer {
+ private static final String KEY_COLOR_SOURCE = "android.theme.customization.color_source";
+
+ private final WallpaperManager wallpaperManager;
+ private Listener listener;
+ private Context mContext;
+
+ private boolean applyOverlay = true;
+
+ // For calculating and returning bounds
+ private final RectF tempRectF = new RectF();
+
+ public static final int[] accent = {
+ android.R.color.system_accent1_10,
+ android.R.color.system_accent1_50,
+ android.R.color.system_accent1_100,
+ android.R.color.system_accent1_200,
+ android.R.color.system_accent1_300,
+ android.R.color.system_accent1_400,
+ android.R.color.system_accent1_500,
+ android.R.color.system_accent1_600,
+ android.R.color.system_accent1_700,
+ android.R.color.system_accent1_800,
+ android.R.color.system_accent1_900,
+ android.R.color.system_accent1_1000,
+ android.R.color.system_accent2_10,
+ android.R.color.system_accent2_50,
+ android.R.color.system_accent2_100,
+ android.R.color.system_accent2_200,
+ android.R.color.system_accent2_300,
+ android.R.color.system_accent2_400,
+ android.R.color.system_accent2_500,
+ android.R.color.system_accent2_600,
+ android.R.color.system_accent2_700,
+ android.R.color.system_accent2_800,
+ android.R.color.system_accent2_900,
+ android.R.color.system_accent2_1000,
+ android.R.color.system_accent3_10,
+ android.R.color.system_accent3_50,
+ android.R.color.system_accent3_100,
+ android.R.color.system_accent3_200,
+ android.R.color.system_accent3_300,
+ android.R.color.system_accent3_400,
+ android.R.color.system_accent3_500,
+ android.R.color.system_accent3_600,
+ android.R.color.system_accent3_700,
+ android.R.color.system_accent3_800,
+ android.R.color.system_accent3_900,
+ android.R.color.system_accent3_1000
+ };
+
+ public static final int[] neutral = {
+ android.R.color.system_neutral1_10,
+ android.R.color.system_neutral1_50,
+ android.R.color.system_neutral1_100,
+ android.R.color.system_neutral1_200,
+ android.R.color.system_neutral1_300,
+ android.R.color.system_neutral1_400,
+ android.R.color.system_neutral1_500,
+ android.R.color.system_neutral1_600,
+ android.R.color.system_neutral1_700,
+ android.R.color.system_neutral1_800,
+ android.R.color.system_neutral1_900,
+ android.R.color.system_neutral1_1000,
+ android.R.color.system_neutral2_10,
+ android.R.color.system_neutral2_50,
+ android.R.color.system_neutral2_100,
+ android.R.color.system_neutral2_200,
+ android.R.color.system_neutral2_300,
+ android.R.color.system_neutral2_400,
+ android.R.color.system_neutral2_500,
+ android.R.color.system_neutral2_600,
+ android.R.color.system_neutral2_700,
+ android.R.color.system_neutral2_800,
+ android.R.color.system_neutral2_900,
+ android.R.color.system_neutral2_1000
+ };
+
+ public LocalWallpaperColorsExtractor(Context context) {
+ mContext = context;
+ wallpaperManager = (WallpaperManager) context.getSystemService(Context.WALLPAPER_SERVICE);
+
+ try {
+ String json = Settings.Secure.getString(context.getContentResolver(),
+ Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES);
+ if (json != null && !json.isEmpty()) {
+ JSONObject packages = new JSONObject(json);
+ applyOverlay = !"preset".equals(packages.getString(KEY_COLOR_SOURCE));
+ }
+ } catch (JSONException e) {
+ // Ignore: enabled by default
+ }
+ }
+
+ private static void addColorsToArray(List list, int[] resArray, SparseIntArray array) {
+ for (int i = 0; i < resArray.length; i++) {
+ array.put(resArray[i], -16777216 | list.get(i));
+ }
+ }
+
+ @Override
+ public void setListener(Listener listener) {
+ this.listener = listener;
+ }
+
+ @Override
+ public void setWorkspaceLocation(Rect pos, View child, int screenId) {
+ ActivityContext activityContext = (ActivityContext) ActivityContext.lookupContext(child.getContext());
+ if (!(activityContext instanceof Launcher)) {
+ tempRectF.setEmpty();
+ return;
+ }
+ Launcher launcher = (Launcher) activityContext;
+ Resources res = launcher.getResources();
+ DeviceProfile dp = launcher.getDeviceProfile().inv.getDeviceProfile(launcher);
+ float screenWidth = dp.widthPx;
+ float screenHeight = dp.heightPx;
+ int numScreens = launcher.getWorkspace().getNumPagesForWallpaperParallax();
+ float relativeScreenWidth = 1f / numScreens;
+
+ int[] dragLayerBounds = new int[2];
+ launcher.getDragLayer().getLocationOnScreen(dragLayerBounds);
+ // Translate from drag layer coordinates to screen coordinates.
+ int screenLeft = pos.left + dragLayerBounds[0];
+ int screenTop = pos.top + dragLayerBounds[1];
+ int screenRight = pos.right + dragLayerBounds[0];
+ int screenBottom = pos.bottom + dragLayerBounds[1];
+ tempRectF.left = (screenLeft / screenWidth + screenId) * relativeScreenWidth;
+ tempRectF.right = (screenRight / screenWidth + screenId) * relativeScreenWidth;
+ tempRectF.top = screenTop / screenHeight;
+ tempRectF.bottom = screenBottom / screenHeight;
+
+ if (tempRectF.left < 0
+ || tempRectF.right > 1
+ || tempRectF.top < 0
+ || tempRectF.bottom > 1) {
+ tempRectF.setEmpty();
+ }
+
+ if (wallpaperManager != null && !tempRectF.isEmpty()) {
+ wallpaperManager.removeOnColorsChangedListener(this);
+ wallpaperManager.addOnColorsChangedListener(this, new ArrayList(List.of(tempRectF)));
+ }
+ }
+
+ @Override
+ public SparseIntArray generateColorsOverride(WallpaperColors colors) {
+ if (!applyOverlay) {
+ return null;
+ }
+
+ SparseIntArray colorRes = new SparseIntArray(5 * 13);
+ boolean nightMode = (mContext.getResources().getConfiguration().uiMode
+ & Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES;
+
+ ColorScheme colorScheme = new ColorScheme(ColorScheme.getSeedColor(colors), nightMode);
+
+ addColorsToArray(colorScheme.getAllAccentColors(), accent, colorRes);
+ addColorsToArray(colorScheme.getAllNeutralColors(), neutral, colorRes);
+
+ return colorRes;
+ }
+
+ @Override
+ public void applyColorsOverride(Context base, WallpaperColors colors) {
+ if (!applyOverlay) {
+ return;
+ }
+
+ RemoteViews.ColorResources res =
+ RemoteViews.ColorResources.create(base, generateColorsOverride(colors));
+ if (res != null) {
+ res.apply(base);
+ }
+ }
+
+ @Override
+ public void onColorsChanged(RectF area, WallpaperColors colors) {
+ if (listener != null) {
+ listener.onColorsChanged(generateColorsOverride(colors));
+ }
+ }
+}
diff --git a/src/com/android/launcher3/widget/PendingAppWidgetHostView.java b/src/com/android/launcher3/widget/PendingAppWidgetHostView.java
index 130ee3a70c..a40cf6643b 100644
--- a/src/com/android/launcher3/widget/PendingAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/PendingAppWidgetHostView.java
@@ -41,6 +41,7 @@
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.R;
import com.android.launcher3.icons.FastBitmapDrawable;
+import com.android.launcher3.graphics.DrawableFactory;
import com.android.launcher3.icons.IconCache;
import com.android.launcher3.icons.IconCache.ItemInfoUpdateReceiver;
import com.android.launcher3.model.data.ItemInfoWithIcon;
@@ -143,6 +144,7 @@ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
@Override
public void reapplyItemInfo(ItemInfoWithIcon info) {
+ DrawableFactory drawableFactory = DrawableFactory.INSTANCE.get(getContext());
if (mCenterDrawable != null) {
mCenterDrawable.setCallback(null);
mCenterDrawable = null;
@@ -155,7 +157,7 @@ public void reapplyItemInfo(ItemInfoWithIcon info) {
// 3) App icon in the center with a setup icon on the top left corner.
if (mDisabledForSafeMode) {
if (widgetCategoryIcon == null) {
- FastBitmapDrawable disabledIcon = info.newIcon(getContext());
+ FastBitmapDrawable disabledIcon = drawableFactory.newIcon(getContext(), info);
disabledIcon.setIsDisabled(true);
mCenterDrawable = disabledIcon;
} else {
@@ -165,13 +167,13 @@ public void reapplyItemInfo(ItemInfoWithIcon info) {
mSettingIconDrawable = null;
} else if (isReadyForClickSetup()) {
mCenterDrawable = widgetCategoryIcon == null
- ? info.newIcon(getContext())
+ ? drawableFactory.newIcon(getContext(), info)
: widgetCategoryIcon;
mSettingIconDrawable = getResources().getDrawable(R.drawable.ic_setting).mutate();
updateSettingColor(info.bitmap.color);
} else {
mCenterDrawable = widgetCategoryIcon == null
- ? newPendingIcon(getContext(), info)
+ ? drawableFactory.newPendingIcon(getContext(), info)
: widgetCategoryIcon;
mSettingIconDrawable = null;
applyState();