cache) {
DotRenderer renderer = cache.get(size);
@@ -1345,8 +1365,14 @@ public class DeviceProfile {
}
if ((Flags.enableTwolineToggle()
&& LauncherPrefs.ENABLE_TWOLINE_ALLAPPS_TOGGLE.get(context))) {
- // Add extra textHeight to the existing allAppsCellHeight.
- allAppsCellHeightPx += Utilities.calculateTextHeight(allAppsIconTextSizePx);
+ if (!isEnglishLanguage(context)) {
+ // Set toggle preference value to false if not english here as it's possible the
+ // preference is stale after language change.
+ LauncherPrefs.get(context).put(LauncherPrefs.ENABLE_TWOLINE_ALLAPPS_TOGGLE, false);
+ } else {
+ // Add extra textHeight to the existing allAppsCellHeight.
+ allAppsCellHeightPx += Utilities.calculateTextHeight(allAppsIconTextSizePx);
+ }
}
updateHotseatSizes(iconSizePx);
@@ -1510,6 +1536,11 @@ public class DeviceProfile {
}
}
+ /** Whether All Apps should be presented on a bottom sheet. */
+ public boolean shouldShowAllAppsOnSheet() {
+ return isTablet || Flags.allAppsSheetForHandheld();
+ }
+
private void setupAllAppsStyle(Context context) {
TypedArray allAppsStyle = context.obtainStyledAttributes(
inv.allAppsStyle != INVALID_RESOURCE_HANDLE ? inv.allAppsStyle
@@ -1804,7 +1835,8 @@ public class DeviceProfile {
workspacePageIndicatorHeight - mWorkspacePageIndicatorOverlapWorkspace;
}
int paddingTop = workspaceTopPadding + (mIsScalableGrid ? 0 : edgeMarginPx);
- int paddingSide = desiredWorkspaceHorizontalMarginPx;
+ // On isFixedLandscapeMode on phones we already have padding because of the camera hole
+ int paddingSide = inv.isFixedLandscape ? 0 : desiredWorkspaceHorizontalMarginPx;
padding.set(paddingSide, paddingTop, paddingSide, paddingBottom);
}
@@ -1830,19 +1862,16 @@ public class DeviceProfile {
* Returns the new border space that should be used between hotseat icons after adjusting it to
* the bubble bar.
*
+ * Does not check for visible bubbles persistence, so caller should call
+ * {@link #shouldAdjustHotseatOrQsbForBubbleBar} first.
+ *
*
If there's no adjustment needed, this method returns {@code 0}.
+ * @see #shouldAdjustHotseatOrQsbForBubbleBar(Context, boolean)
*/
public float getHotseatAdjustedBorderSpaceForBubbleBar(Context context) {
- // only need to adjust when QSB is on top of the hotseat.
- if (isQsbInline) {
+ if (shouldAlignBubbleBarWithQSB() || !shouldAdjustHotseatOrQsbForBubbleBar(context)) {
return 0;
}
-
- // no need to adjust if there's enough space for the bubble bar to the right of the hotseat.
- if (getHotseatLayoutPadding(context).right > mBubbleBarSpaceThresholdPx) {
- return 0;
- }
-
// The adjustment is shrinking the hotseat's width by 1 icon on either side.
int iconsWidth =
iconSizePx * numShownHotseatIcons + hotseatBorderSpace * (numShownHotseatIcons - 1);
@@ -1851,6 +1880,43 @@ public class DeviceProfile {
return (float) (newWidth - iconSizePx * numShownHotseatIcons) / (numShownHotseatIcons - 1);
}
+ /**
+ * Returns the hotseat icon translation X for the cellX index.
+ *
+ *
Does not check for visible bubbles persistence, so caller should call
+ * {@link #shouldAdjustHotseatOrQsbForBubbleBar} first.
+ *
+ *
If there's no adjustment needed, this method returns {@code 0}.
+ * @see #shouldAdjustHotseatOrQsbForBubbleBar(Context, boolean)
+ */
+ public float getHotseatAdjustedTranslation(Context context, int cellX) {
+ float borderSpace = getHotseatAdjustedBorderSpaceForBubbleBar(context);
+ if (borderSpace == 0) return borderSpace;
+ float borderSpaceDelta = borderSpace - hotseatBorderSpace;
+ return iconSizePx + cellX * borderSpaceDelta;
+ }
+
+ /** Returns whether hotseat or QSB should be adjusted for the bubble bar. */
+ public boolean shouldAdjustHotseatOrQsbForBubbleBar(Context context, boolean hasBubbles) {
+ return hasBubbles && shouldAdjustHotseatOrQsbForBubbleBar(context);
+ }
+
+ /** Returns whether hotseat should be adjusted for the bubble bar. */
+ public boolean shouldAdjustHotseatForBubbleBar(Context context, boolean hasBubbles) {
+ return shouldAlignBubbleBarWithHotseat()
+ && shouldAdjustHotseatOrQsbForBubbleBar(context, hasBubbles);
+ }
+
+ /** Returns whether hotseat or QSB should be adjusted for the bubble bar. */
+ public boolean shouldAdjustHotseatOrQsbForBubbleBar(Context context) {
+ // only need to adjust if QSB is on top of the hotseat and there's not enough space for the
+ // bubble bar to either side of the hotseat.
+ if (isQsbInline) return false;
+ Rect hotseatPadding = getHotseatLayoutPadding(context);
+ int hotseatMinHorizontalPadding = Math.min(hotseatPadding.left, hotseatPadding.right);
+ return hotseatMinHorizontalPadding <= mBubbleBarSpaceThresholdPx;
+ }
+
/**
* Returns the padding for hotseat view
*/
@@ -1870,7 +1936,7 @@ public class DeviceProfile {
hotseatBarPadding.set(mHotseatBarWorkspaceSpacePx, paddingTop,
mInsets.right + mHotseatBarEdgePaddingPx, paddingBottom);
}
- } else if (isTaskbarPresent) {
+ } else if (isTaskbarPresent || inv.isFixedLandscape) {
// Center the QSB vertically with hotseat
int hotseatBarBottomPadding = getHotseatBarBottomPadding();
int hotseatBarTopPadding =
@@ -1889,6 +1955,11 @@ public class DeviceProfile {
}
startSpacing += getAdditionalQsbSpace();
+ if (inv.isFixedLandscape) {
+ endSpacing += mInsets.right;
+ startSpacing += mInsets.left;
+ }
+
hotseatBarPadding.top = hotseatBarTopPadding;
hotseatBarPadding.bottom = hotseatBarBottomPadding;
boolean isRtl = Utilities.isRtl(context.getResources());
@@ -1997,6 +2068,32 @@ public class DeviceProfile {
}
}
+ /**
+ * Returns the number of pixels the hotseat icons or QSB vertical center is translated from the
+ * bottom of the screen.
+ */
+ public int getBubbleBarVerticalCenterForHome() {
+ if (shouldAlignBubbleBarWithHotseat()) {
+ return hotseatBarSizePx
+ - (isQsbInline ? 0 : hotseatQsbVisualHeight)
+ - hotseatQsbSpace
+ - (hotseatCellHeightPx / 2)
+ + ((hotseatCellHeightPx - iconSizePx) / 2);
+ } else {
+ return hotseatBarSizePx - (hotseatQsbVisualHeight / 2);
+ }
+ }
+
+ /** Returns whether bubble bar should be aligned with the hotseat. */
+ public boolean shouldAlignBubbleBarWithQSB() {
+ return !shouldAlignBubbleBarWithHotseat();
+ }
+
+ /** Returns whether bubble bar should be aligned with the hotseat. */
+ public boolean shouldAlignBubbleBarWithHotseat() {
+ return isQsbInline || isGestureMode;
+ }
+
/**
* Returns the number of pixels the taskbar is translated from the bottom of the screen.
*/
@@ -2214,6 +2311,10 @@ public class DeviceProfile {
mHotseatBarEdgePaddingPx));
writer.println(prefix + pxToDpStr("mHotseatBarWorkspaceSpacePx",
mHotseatBarWorkspaceSpacePx));
+ writer.println(prefix
+ + pxToDpStr("inlineNavButtonsEndSpacingPx", inlineNavButtonsEndSpacingPx));
+ writer.println(prefix
+ + pxToDpStr("navButtonsLayoutWidthPx", navButtonsLayoutWidthPx));
writer.println(prefix + pxToDpStr("hotseatBarEndOffset", hotseatBarEndOffset));
writer.println(prefix + pxToDpStr("hotseatQsbSpace", hotseatQsbSpace));
writer.println(prefix + pxToDpStr("hotseatQsbHeight", hotseatQsbHeight));
@@ -2327,6 +2428,29 @@ public class DeviceProfile {
return c.createConfigurationContext(config);
}
+ /**
+ * Returns whether Taskbar and Hotseat should adjust horizontally on bubble bar location update.
+ */
+ public boolean shouldAdjustHotseatOnNavBarLocationUpdate(Context context) {
+ return enableBubbleBar()
+ && enableBubbleBarInPersistentTaskBar()
+ && !DisplayController.getNavigationMode(context).hasGestures;
+ }
+
+ /** Returns hotseat translation X for the bubble bar position. */
+ public int getHotseatTranslationXForNavBar(Context context, boolean isBubblesOnLeft) {
+ if (shouldAdjustHotseatOnNavBarLocationUpdate(context)) {
+ boolean isRtl = Utilities.isRtl(context.getResources());
+ if (isBubblesOnLeft) {
+ return isRtl ? -navButtonsLayoutWidthPx : 0;
+ } else {
+ return isRtl ? 0 : navButtonsLayoutWidthPx;
+ }
+ } else {
+ return 0;
+ }
+ }
+
/**
* Callback when a component changes the DeviceProfile associated with it, as a result of
* configuration change
@@ -2445,7 +2569,8 @@ public class DeviceProfile {
throw new IllegalArgumentException("Window bounds not set");
}
if (mTransposeLayoutWithOrientation == null) {
- mTransposeLayoutWithOrientation = !mInfo.isTablet(mWindowBounds);
+ mTransposeLayoutWithOrientation =
+ !(mInfo.isTablet(mWindowBounds) || mInv.isFixedLandscape);
}
if (mIsGestureMode == null) {
mIsGestureMode = mInfo.getNavigationMode().hasGestures;
diff --git a/src/com/android/launcher3/DropTargetHandler.kt b/src/com/android/launcher3/DropTargetHandler.kt
index f1029b1f2b..4d3fe526a9 100644
--- a/src/com/android/launcher3/DropTargetHandler.kt
+++ b/src/com/android/launcher3/DropTargetHandler.kt
@@ -65,6 +65,7 @@ class DropTargetHandler(launcher: Launcher) {
}
fun onDeleteComplete(item: ItemInfo) {
+ removeItemAndStripEmptyScreens(null /* view */, item)
var pageItem: ItemInfo = item
if (item.container <= 0) {
val v = mLauncher.workspace.getHomescreenIconByItemId(item.container)
@@ -90,11 +91,7 @@ class DropTargetHandler(launcher: Launcher) {
}
fun onAccessibilityDelete(view: View?, item: ItemInfo, announcement: CharSequence) {
- // Remove the item from launcher and the db, we can ignore the containerInfo in this call
- // because we already remove the drag view from the folder (if the drag originated from
- // a folder) in Folder.beginDrag()
- mLauncher.removeItem(view, item, true /* deleteFromDb */, "removed by accessibility drop")
- mLauncher.workspace.stripEmptyScreens()
+ removeItemAndStripEmptyScreens(view, item)
mLauncher.dragLayer.announceForAccessibility(announcement)
}
@@ -105,4 +102,12 @@ class DropTargetHandler(launcher: Launcher) {
fun onClick(buttonDropTarget: ButtonDropTarget) {
mLauncher.accessibilityDelegate.handleAccessibleDrop(buttonDropTarget, null, null)
}
+
+ private fun removeItemAndStripEmptyScreens(view: View?, item: ItemInfo) {
+ // Remove the item from launcher and the db, we can ignore the containerInfo in this call
+ // because we already remove the drag view from the folder (if the drag originated from
+ // a folder) in Folder.beginDrag()
+ mLauncher.removeItem(view, item, true /* deleteFromDb */, "removed by accessibility drop")
+ mLauncher.workspace.stripEmptyScreens()
+ }
}
diff --git a/src/com/android/launcher3/Hotseat.java b/src/com/android/launcher3/Hotseat.java
index 024dde477f..b20d8a5152 100644
--- a/src/com/android/launcher3/Hotseat.java
+++ b/src/com/android/launcher3/Hotseat.java
@@ -34,8 +34,11 @@ import android.view.ViewGroup;
import android.widget.FrameLayout;
import androidx.annotation.IntDef;
+import androidx.annotation.Nullable;
+import com.android.launcher3.celllayout.CellLayoutLayoutParams;
import com.android.launcher3.util.HorizontalInsettableView;
+import com.android.launcher3.util.MultiPropertyFactory;
import com.android.launcher3.util.MultiPropertyFactory.MultiProperty;
import com.android.launcher3.util.MultiTranslateDelegate;
import com.android.launcher3.util.MultiValueAlpha;
@@ -61,6 +64,14 @@ public class Hotseat extends CellLayout implements Insettable {
public @interface HotseatQsbAlphaId {
}
+ public static final int ICONS_TRANSLATION_X_NAV_BAR_ALIGNMENT = 0;
+ public static final int ICONS_TRANSLATION_X_CHANNELS_COUNT = 1;
+
+ @Retention(RetentionPolicy.RUNTIME)
+ @IntDef({ICONS_TRANSLATION_X_NAV_BAR_ALIGNMENT})
+ public @interface IconsTranslationX {
+ }
+
// Ratio of empty space, qsb should take up to appear visually centered.
public static final float QSB_CENTER_FACTOR = .325f;
private static final int BUBBLE_BAR_ADJUSTMENT_ANIMATION_DURATION_MS = 250;
@@ -72,6 +83,10 @@ public class Hotseat extends CellLayout implements Insettable {
private final MultiValueAlpha mIconsAlphaChannels;
private final MultiValueAlpha mQsbAlphaChannels;
+ private @Nullable MultiProperty mQsbTranslationX;
+
+ private final MultiPropertyFactory mIconsTranslationXFactory;
+
private final View mQsb;
public Hotseat(Context context) {
@@ -88,9 +103,26 @@ public class Hotseat extends CellLayout implements Insettable {
addView(mQsb);
mIconsAlphaChannels = new MultiValueAlpha(getShortcutsAndWidgets(),
ALPHA_CHANNEL_CHANNELS_COUNT);
+ if (mQsb instanceof Reorderable qsbReorderable) {
+ mQsbTranslationX = qsbReorderable.getTranslateDelegate()
+ .getTranslationX(MultiTranslateDelegate.INDEX_NAV_BAR_ANIM);
+ }
+ mIconsTranslationXFactory = new MultiPropertyFactory<>(getShortcutsAndWidgets(),
+ VIEW_TRANSLATE_X, ICONS_TRANSLATION_X_CHANNELS_COUNT, Float::sum);
mQsbAlphaChannels = new MultiValueAlpha(mQsb, ALPHA_CHANNEL_CHANNELS_COUNT);
}
+ /** Provides translation X for hotseat icons for the channel. */
+ public MultiProperty getIconsTranslationX(@IconsTranslationX int channelId) {
+ return mIconsTranslationXFactory.get(channelId);
+ }
+
+ /** Provides translation X for hotseat Qsb. */
+ @Nullable
+ public MultiProperty getQsbTranslationX() {
+ return mQsbTranslationX;
+ }
+
/**
* Returns orientation specific cell X given invariant order in the hotseat
*/
@@ -118,12 +150,9 @@ public class Hotseat extends CellLayout implements Insettable {
DeviceProfile dp = mActivity.getDeviceProfile();
if (bubbleBarEnabled) {
- float adjustedBorderSpace = dp.getHotseatAdjustedBorderSpaceForBubbleBar(getContext());
- if (hasBubbles && Float.compare(adjustedBorderSpace, 0f) != 0) {
- getShortcutsAndWidgets().setTranslationProvider(cellX -> {
- float borderSpaceDelta = adjustedBorderSpace - dp.hotseatBorderSpace;
- return dp.iconSizePx + cellX * borderSpaceDelta;
- });
+ if (dp.shouldAdjustHotseatForBubbleBar(getContext(), hasBubbles)) {
+ getShortcutsAndWidgets().setTranslationProvider(
+ cellX -> dp.getHotseatAdjustedTranslation(getContext(), cellX));
if (mQsb instanceof HorizontalInsettableView) {
HorizontalInsettableView insettableQsb = (HorizontalInsettableView) mQsb;
final float insetFraction = (float) dp.iconSizePx / dp.hotseatQsbWidth;
@@ -158,37 +187,40 @@ public class Hotseat extends CellLayout implements Insettable {
*/
public void adjustForBubbleBar(boolean isBubbleBarVisible) {
DeviceProfile dp = mActivity.getDeviceProfile();
- float adjustedBorderSpace = dp.getHotseatAdjustedBorderSpaceForBubbleBar(getContext());
- if (Float.compare(adjustedBorderSpace, 0f) == 0) {
- return;
- }
-
+ boolean shouldAdjust = isBubbleBarVisible
+ && dp.shouldAdjustHotseatOrQsbForBubbleBar(getContext());
+ boolean shouldAdjustHotseat = shouldAdjust
+ && dp.shouldAlignBubbleBarWithHotseat();
ShortcutAndWidgetContainer icons = getShortcutsAndWidgets();
- AnimatorSet animatorSet = new AnimatorSet();
- float borderSpaceDelta = adjustedBorderSpace - dp.hotseatBorderSpace;
-
// update the translation provider for future layout passes of hotseat icons.
- if (isBubbleBarVisible) {
- icons.setTranslationProvider(cellX -> dp.iconSizePx + cellX * borderSpaceDelta);
+ if (shouldAdjustHotseat) {
+ icons.setTranslationProvider(
+ cellX -> dp.getHotseatAdjustedTranslation(getContext(), cellX));
} else {
icons.setTranslationProvider(null);
}
-
+ AnimatorSet animatorSet = new AnimatorSet();
for (int i = 0; i < icons.getChildCount(); i++) {
View child = icons.getChildAt(i);
- float tx = isBubbleBarVisible ? dp.iconSizePx + i * borderSpaceDelta : 0;
- if (child instanceof Reorderable) {
- MultiTranslateDelegate mtd = ((Reorderable) child).getTranslateDelegate();
- animatorSet.play(
- mtd.getTranslationX(INDEX_BUBBLE_ADJUSTMENT_ANIM).animateToValue(tx));
- } else {
- animatorSet.play(ObjectAnimator.ofFloat(child, VIEW_TRANSLATE_X, tx));
+ if (child.getLayoutParams() instanceof CellLayoutLayoutParams lp) {
+ float tx = shouldAdjustHotseat
+ ? dp.getHotseatAdjustedTranslation(getContext(), lp.getCellX()) : 0;
+ if (child instanceof Reorderable) {
+ MultiTranslateDelegate mtd = ((Reorderable) child).getTranslateDelegate();
+ animatorSet.play(
+ mtd.getTranslationX(INDEX_BUBBLE_ADJUSTMENT_ANIM).animateToValue(tx));
+ } else {
+ animatorSet.play(ObjectAnimator.ofFloat(child, VIEW_TRANSLATE_X, tx));
+ }
}
}
+ //TODO(b/381109832) refactor & simplify adjustment logic
+ boolean shouldAdjustQsb =
+ shouldAdjustHotseat || (shouldAdjust && dp.shouldAlignBubbleBarWithQSB());
if (mQsb instanceof HorizontalInsettableView horizontalInsettableQsb) {
final float currentInsetFraction = horizontalInsettableQsb.getHorizontalInsets();
- final float targetInsetFraction =
- isBubbleBarVisible ? (float) dp.iconSizePx / dp.hotseatQsbWidth : 0;
+ final float targetInsetFraction = shouldAdjustQsb
+ ? (float) dp.iconSizePx / dp.hotseatQsbWidth : 0;
ValueAnimator qsbAnimator =
ValueAnimator.ofFloat(currentInsetFraction, targetInsetFraction);
qsbAnimator.addUpdateListener(animation -> {
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index 54aea38ef3..aefb2b1594 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -16,6 +16,8 @@
package com.android.launcher3;
+import static com.android.launcher3.LauncherPrefs.DB_FILE;
+import static com.android.launcher3.LauncherPrefs.FIXED_LANDSCAPE_MODE;
import static com.android.launcher3.LauncherPrefs.GRID_NAME;
import static com.android.launcher3.Utilities.dpiFromPx;
import static com.android.launcher3.testing.shared.ResourceUtils.INVALID_RESOURCE_HANDLE;
@@ -34,6 +36,7 @@ import android.content.res.XmlResourceParser;
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
+import android.os.Trace;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
@@ -57,11 +60,12 @@ import com.android.launcher3.provider.RestoreDbTask;
import com.android.launcher3.testing.shared.ResourceUtils;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.DisplayController.Info;
-import com.android.launcher3.util.LockedUserState;
import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.util.Partner;
+import com.android.launcher3.util.ResourceHelper;
import com.android.launcher3.util.SafeCloseable;
import com.android.launcher3.util.WindowBounds;
+import com.android.launcher3.util.window.CachedDisplayInfo;
import com.android.launcher3.util.window.WindowManagerProxy;
import org.xmlpull.v1.XmlPullParser;
@@ -84,9 +88,12 @@ public class InvariantDeviceProfile implements SafeCloseable {
public static final MainThreadInitializedObject INSTANCE =
new MainThreadInitializedObject<>(InvariantDeviceProfile::new);
+ public static final String GRID_NAME_PREFS_KEY = "idp_grid_name";
+
@Retention(RetentionPolicy.SOURCE)
@IntDef({TYPE_PHONE, TYPE_MULTI_DISPLAY, TYPE_TABLET})
- public @interface DeviceType {}
+ public @interface DeviceType {
+ }
public static final int TYPE_PHONE = 0;
public static final int TYPE_MULTI_DISPLAY = 1;
@@ -132,6 +139,7 @@ public class InvariantDeviceProfile implements SafeCloseable {
public int iconBitmapSize;
public int fillResIconDpi;
public @DeviceType int deviceType;
+ public Info displayInfo;
public PointF[] minCellSize;
@@ -185,6 +193,8 @@ public class InvariantDeviceProfile implements SafeCloseable {
@XmlRes
public int workspaceSpecsId = INVALID_RESOURCE_HANDLE;
@XmlRes
+ public int gridSizeSpecsId = INVALID_RESOURCE_HANDLE;;
+ @XmlRes
public int workspaceSpecsTwoPanelId = INVALID_RESOURCE_HANDLE;
@XmlRes
public int allAppsSpecsId = INVALID_RESOURCE_HANDLE;
@@ -207,6 +217,13 @@ public class InvariantDeviceProfile implements SafeCloseable {
@XmlRes
public int allAppsCellSpecsTwoPanelId = INVALID_RESOURCE_HANDLE;
+
+ /**
+ * Fixed landscape mode is the landscape on the phones.
+ */
+ public boolean isFixedLandscape = false;
+ private LauncherPrefChangeListener mLandscapeModePreferenceListener;
+
public String dbFile;
public int defaultLayoutId;
public int demoModeLayoutId;
@@ -222,18 +239,13 @@ public class InvariantDeviceProfile implements SafeCloseable {
private final ArrayList mChangeListeners = new ArrayList<>();
@VisibleForTesting
- public InvariantDeviceProfile() { }
+ public InvariantDeviceProfile() {
+ }
@TargetApi(23)
private InvariantDeviceProfile(Context context) {
String gridName = getCurrentGridName(context);
- String newGridName = initGrid(context, gridName);
- if (!newGridName.equals(gridName)) {
- LauncherPrefs.get(context).put(GRID_NAME, newGridName);
- }
- LockedUserState.get(context).runOnUserUnlocked(() ->
- new DeviceGridState(this).writeToPrefs(context));
-
+ initGrid(context, gridName);
DisplayController.INSTANCE.get(context).setPriorityListener(
(displayContext, info, flags) -> {
if ((flags & (CHANGE_DENSITY | CHANGE_SUPPORTED_BOUNDS
@@ -242,6 +254,21 @@ public class InvariantDeviceProfile implements SafeCloseable {
onConfigChanged(displayContext);
}
});
+ if (Flags.oneGridSpecs()) {
+ mLandscapeModePreferenceListener = (String s) -> {
+ if (isFixedLandscape != FIXED_LANDSCAPE_MODE.get(context)) {
+ MAIN_EXECUTOR.execute(() -> {
+ Trace.beginSection("InvariantDeviceProfile#setFixedLandscape");
+ onConfigChanged(context.getApplicationContext());
+ Trace.endSection();
+ });
+ }
+ };
+ LauncherPrefs.INSTANCE.get(context).addListener(
+ mLandscapeModePreferenceListener,
+ FIXED_LANDSCAPE_MODE
+ );
+ }
}
/**
@@ -267,8 +294,13 @@ public class InvariantDeviceProfile implements SafeCloseable {
@DeviceType int defaultDeviceType = defaultInfo.getDeviceType();
DisplayOption defaultDisplayOption = invDistWeightedInterpolate(
defaultInfo,
- getPredefinedDeviceProfiles(context, gridName, defaultDeviceType,
- /*allowDisabledGrid=*/false),
+ getPredefinedDeviceProfiles(
+ context,
+ gridName,
+ defaultInfo,
+ /*allowDisabledGrid=*/false,
+ FIXED_LANDSCAPE_MODE.get(context)
+ ),
defaultDeviceType);
Context displayContext = context.createDisplayContext(display);
@@ -276,8 +308,13 @@ public class InvariantDeviceProfile implements SafeCloseable {
@DeviceType int deviceType = myInfo.getDeviceType();
DisplayOption myDisplayOption = invDistWeightedInterpolate(
myInfo,
- getPredefinedDeviceProfiles(context, gridName, deviceType,
- /*allowDisabledGrid=*/false),
+ getPredefinedDeviceProfiles(
+ context,
+ gridName,
+ myInfo,
+ /*allowDisabledGrid=*/false,
+ FIXED_LANDSCAPE_MODE.get(context)
+ ),
deviceType);
DisplayOption result = new DisplayOption(defaultDisplayOption.grid)
@@ -294,40 +331,16 @@ public class InvariantDeviceProfile implements SafeCloseable {
System.arraycopy(defaultDisplayOption.borderSpaces, 0, result.borderSpaces, 0,
COUNT_SIZES);
- initGrid(context, myInfo, result, deviceType);
+ initGrid(context, myInfo, result);
}
@Override
public void close() {
DisplayController.INSTANCE.executeIfCreated(dc -> dc.setPriorityListener(null));
- }
-
- /**
- * Reinitialize the current grid after a restore, where some grids might now be disabled.
- */
- public void reinitializeAfterRestore(Context context) {
- String currentGridName = getCurrentGridName(context);
- String currentDbFile = dbFile;
- String newGridName = initGrid(context, currentGridName);
- String newDbFile = dbFile;
- FileLog.d(TAG, "Reinitializing grid after restore."
- + " currentGridName=" + currentGridName
- + ", currentDbFile=" + currentDbFile
- + ", newGridName=" + newGridName
- + ", newDbFile=" + newDbFile);
- if (!newDbFile.equals(currentDbFile)) {
- FileLog.d(TAG, "Restored grid is disabled : " + currentGridName
- + ", migrating to: " + newGridName
- + ", removing all other grid db files");
- for (String gridDbFile : LauncherFiles.GRID_DB_FILES) {
- if (gridDbFile.equals(currentDbFile)) {
- continue;
- }
- if (context.getDatabasePath(gridDbFile).delete()) {
- FileLog.d(TAG, "Removed old grid db file: " + gridDbFile);
- }
- }
- setCurrentGrid(context, newGridName);
+ if (mLandscapeModePreferenceListener != null) {
+ LauncherPrefs.INSTANCE.executeIfCreated(
+ lp -> lp.removeListener(mLandscapeModePreferenceListener, FIXED_LANDSCAPE_MODE)
+ );
}
}
@@ -336,18 +349,50 @@ public class InvariantDeviceProfile implements SafeCloseable {
}
private String initGrid(Context context, String gridName) {
+ FileLog.d(TAG, "Before initGrid:"
+ + "gridName:" + gridName
+ + ", dbFile:" + dbFile
+ + ", LauncherPrefs GRID_NAME:" + LauncherPrefs.get(context).get(GRID_NAME)
+ + ", LauncherPrefs DB_FILE:" + LauncherPrefs.get(context).get(DB_FILE));
Info displayInfo = DisplayController.INSTANCE.get(context).getInfo();
- @DeviceType int deviceType = displayInfo.getDeviceType();
+ List allOptions = getPredefinedDeviceProfiles(
+ context,
+ gridName,
+ displayInfo,
+ RestoreDbTask.isPending(context),
+ FIXED_LANDSCAPE_MODE.get(context)
+ );
+
+ // Filter out options that don't have the same number of columns as the grid
+ DeviceGridState deviceGridState = new DeviceGridState(context);
+ List allOptionsFilteredByColCount =
+ filterByColumnCount(allOptions, deviceGridState.getColumns());
- ArrayList allOptions =
- getPredefinedDeviceProfiles(context, gridName, deviceType,
- RestoreDbTask.isPending(context));
DisplayOption displayOption =
- invDistWeightedInterpolate(displayInfo, allOptions, deviceType);
- initGrid(context, displayInfo, displayOption, deviceType);
+ invDistWeightedInterpolate(displayInfo, allOptionsFilteredByColCount.isEmpty()
+ ? new ArrayList<>(allOptions)
+ : new ArrayList<>(allOptionsFilteredByColCount),
+ displayInfo.getDeviceType());
+
+ if (!displayOption.grid.name.equals(gridName)) {
+ LauncherPrefs.get(context).put(GRID_NAME, displayOption.grid.name);
+ }
+
+ initGrid(context, displayInfo, displayOption);
+ FileLog.d(TAG, "After initGrid:"
+ + "gridName:" + gridName
+ + ", dbFile:" + dbFile
+ + ", LauncherPrefs GRID_NAME:" + LauncherPrefs.get(context).get(GRID_NAME)
+ + ", LauncherPrefs DB_FILE:" + LauncherPrefs.get(context).get(DB_FILE));
return displayOption.grid.name;
}
+ private List filterByColumnCount(
+ List allOptions, int numColumns) {
+ return allOptions.stream().filter(
+ option -> option.grid.numColumns == numColumns).toList();
+ }
+
/**
* @deprecated This is a temporary solution because on the backup and restore case we modify the
* IDP, this resets it. b/332974074
@@ -362,8 +407,7 @@ public class InvariantDeviceProfile implements SafeCloseable {
return new InvariantDeviceProfile().initGrid(context, null);
}
- private void initGrid(Context context, Info displayInfo, DisplayOption displayOption,
- @DeviceType int deviceType) {
+ private void initGrid(Context context, Info displayInfo, DisplayOption displayOption) {
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
GridOption closestProfile = displayOption.grid;
numRows = closestProfile.numRows;
@@ -382,6 +426,7 @@ public class InvariantDeviceProfile implements SafeCloseable {
isScalable = closestProfile.isScalable;
devicePaddingId = closestProfile.devicePaddingId;
workspaceSpecsId = closestProfile.mWorkspaceSpecsId;
+ gridSizeSpecsId = closestProfile.mGridSizeSpecsId;
workspaceSpecsTwoPanelId = closestProfile.mWorkspaceSpecsTwoPanelId;
allAppsSpecsId = closestProfile.mAllAppsSpecsId;
allAppsSpecsTwoPanelId = closestProfile.mAllAppsSpecsTwoPanelId;
@@ -395,7 +440,8 @@ public class InvariantDeviceProfile implements SafeCloseable {
allAppsCellSpecsTwoPanelId = closestProfile.mAllAppsCellSpecsTwoPanelId;
numAllAppsRowsForCellHeightCalculation =
closestProfile.mNumAllAppsRowsForCellHeightCalculation;
- this.deviceType = deviceType;
+ this.deviceType = displayInfo.getDeviceType();
+ this.displayInfo = displayInfo;
inlineNavButtonsEndSpacing = closestProfile.inlineNavButtonsEndSpacing;
@@ -439,6 +485,9 @@ public class InvariantDeviceProfile implements SafeCloseable {
startAlignTaskbar = displayOption.startAlignTaskbar;
+ // Fixed Landscape mode
+ isFixedLandscape = closestProfile.mIsFixedLandscape;
+
// If the partner customization apk contains any grid overrides, apply them
// Supported overrides: numRows, numColumns, iconSize
applyPartnerDeviceProfileOverrides(context, metrics);
@@ -494,10 +543,17 @@ public class InvariantDeviceProfile implements SafeCloseable {
mChangeListeners.remove(listener);
}
-
- public void setCurrentGrid(Context context, String gridName) {
- LauncherPrefs.get(context).put(GRID_NAME, gridName);
- MAIN_EXECUTOR.execute(() -> onConfigChanged(context.getApplicationContext()));
+ /**
+ * Updates the current grid, this triggers a new IDP, reloads the database and triggers a grid
+ * migration.
+ */
+ public void setCurrentGrid(Context context, String newGridName) {
+ LauncherPrefs.get(context).put(GRID_NAME, newGridName);
+ MAIN_EXECUTOR.execute(() -> {
+ Trace.beginSection("InvariantDeviceProfile#setCurrentGrid");
+ onConfigChanged(context.getApplicationContext());
+ Trace.endSection();
+ });
}
private Object[] toModelState() {
@@ -521,8 +577,19 @@ public class InvariantDeviceProfile implements SafeCloseable {
}
}
- private static ArrayList getPredefinedDeviceProfiles(Context context,
- String gridName, @DeviceType int deviceType, boolean allowDisabledGrid) {
+ private static boolean firstGridFilter(GridOption gridOption, int deviceType,
+ boolean allowDisabledGrid, boolean isFixedLandscapeMode) {
+ return (gridOption.isEnabled(deviceType) || allowDisabledGrid)
+ && gridOption.filterByFlag(deviceType, isFixedLandscapeMode);
+ }
+
+ private static List getPredefinedDeviceProfiles(
+ Context context,
+ String gridName,
+ Info displayInfo,
+ boolean allowDisabledGrid,
+ boolean isFixedLandscapeMode
+ ) {
ArrayList profiles = new ArrayList<>();
try (XmlResourceParser parser = context.getResources().getXml(R.xml.device_profiles)) {
@@ -532,9 +599,10 @@ public class InvariantDeviceProfile implements SafeCloseable {
parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
if ((type == XmlPullParser.START_TAG)
&& GridOption.TAG_NAME.equals(parser.getName())) {
-
- GridOption gridOption = new GridOption(context, Xml.asAttributeSet(parser));
- if (gridOption.isEnabled(deviceType) || allowDisabledGrid) {
+ GridOption gridOption = new GridOption(
+ context, Xml.asAttributeSet(parser), displayInfo);
+ if (firstGridFilter(gridOption, displayInfo.getDeviceType(), allowDisabledGrid,
+ isFixedLandscapeMode)) {
final int displayDepth = parser.getDepth();
while (((type = parser.next()) != XmlPullParser.END_TAG
|| parser.getDepth() > displayDepth)
@@ -551,23 +619,25 @@ public class InvariantDeviceProfile implements SafeCloseable {
} catch (IOException | XmlPullParserException e) {
throw new RuntimeException(e);
}
-
ArrayList filteredProfiles = new ArrayList<>();
if (!TextUtils.isEmpty(gridName)) {
for (DisplayOption option : profiles) {
- if (gridName.equals(option.grid.name)
- && (option.grid.isEnabled(deviceType) || allowDisabledGrid)) {
+ if (gridName.equals(option.grid.name) && (option.grid.isEnabled(
+ displayInfo.getDeviceType()) || allowDisabledGrid)) {
filteredProfiles.add(option);
}
}
}
- if (filteredProfiles.isEmpty()) {
- // No grid found, use the default options
+ if (filteredProfiles.isEmpty() && TextUtils.isEmpty(gridName)) {
+ // Use the default options since gridName is empty and there's no valid grids.
for (DisplayOption option : profiles) {
if (option.canBeDefault) {
filteredProfiles.add(option);
}
}
+ } else if (filteredProfiles.isEmpty()) {
+ // In this case we had a grid selected but we couldn't find it.
+ filteredProfiles.addAll(profiles);
}
if (filteredProfiles.isEmpty()) {
throw new RuntimeException("No display option with canBeDefault=true");
@@ -575,6 +645,67 @@ public class InvariantDeviceProfile implements SafeCloseable {
return filteredProfiles;
}
+ /**
+ * Parses through the xml to find GridSize specs. Then calls findBestGridSize to get the
+ * correct grid size for this GridOption.
+ *
+ * @return the result of {@link #findBestGridSize(List, int, int)}.
+ */
+ private static GridSize getGridSize(ResourceHelper resourceHelper, Context context,
+ Info displayInfo) {
+ ArrayList gridSizes = new ArrayList<>();
+
+ try (XmlResourceParser parser = resourceHelper.getXml()) {
+ final int depth = parser.getDepth();
+ int type;
+ while (((type = parser.next()) != XmlPullParser.END_TAG
+ || parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
+ if ((type == XmlPullParser.START_TAG)
+ && "GridSize".equals(parser.getName())) {
+ gridSizes.add(new GridSize(context, Xml.asAttributeSet(parser)));
+ }
+ }
+ } catch (IOException | XmlPullParserException e) {
+ throw new RuntimeException(e);
+ }
+
+ // Finds the min width and height in dp for all displays.
+ int[] dimens = findMinWidthAndHeightDpForDevice(displayInfo);
+
+ return findBestGridSize(gridSizes, dimens[0], dimens[1]);
+ }
+
+ /**
+ * @return the biggest grid size that fits the display dimensions.
+ * If no best grid size is found, return null.
+ */
+ private static GridSize findBestGridSize(List list, int minWidthDp,
+ int minHeightDp) {
+ GridSize selectedGridSize = null;
+ for (GridSize item: list) {
+ if (minWidthDp >= item.mMinDeviceWidthDp && minHeightDp >= item.mMinDeviceHeightDp) {
+ if (selectedGridSize == null
+ || (selectedGridSize.mNumColumns <= item.mNumColumns
+ && selectedGridSize.mNumRows <= item.mNumRows)) {
+ selectedGridSize = item;
+ }
+ }
+ }
+ return selectedGridSize;
+ }
+
+ private static int[] findMinWidthAndHeightDpForDevice(Info displayInfo) {
+ int minDisplayWidthDp = Integer.MAX_VALUE;
+ int minDisplayHeightDp = Integer.MAX_VALUE;
+ for (CachedDisplayInfo display: displayInfo.getAllDisplays()) {
+ minDisplayWidthDp = Math.min(minDisplayWidthDp,
+ (int) dpiFromPx(display.size.x, DisplayMetrics.DENSITY_DEVICE_STABLE));
+ minDisplayHeightDp = Math.min(minDisplayHeightDp,
+ (int) dpiFromPx(display.size.y, DisplayMetrics.DENSITY_DEVICE_STABLE));
+ }
+ return new int[]{minDisplayWidthDp, minDisplayHeightDp};
+ }
+
/**
* Returns the GridOption associated to the given file name or null if the fileName is not
* supported.
@@ -592,7 +723,6 @@ public class InvariantDeviceProfile implements SafeCloseable {
* supported. Ej. 4x4 -> normal, 5x4 -> practical, etc.
* (Note: the name of the grid can be different for the same grid size depending of
* the values of the InvariantDeviceProfile)
- *
*/
public String getGridNameFromSize(Context context, Point size) {
return parseAllGridOptions(context).stream()
@@ -618,18 +748,18 @@ public class InvariantDeviceProfile implements SafeCloseable {
* @return all the grid options that can be shown on the device
*/
public List parseAllGridOptions(Context context) {
- return parseAllDefinedGridOptions(context)
+ return parseAllDefinedGridOptions(context, displayInfo)
.stream()
.filter(go -> go.isEnabled(deviceType))
+ .filter(go -> go.filterByFlag(deviceType, isFixedLandscape))
.collect(Collectors.toList());
}
/**
* @return all the grid options that can be shown on the device
*/
- public static List parseAllDefinedGridOptions(Context context) {
+ public static List parseAllDefinedGridOptions(Context context, Info displayInfo) {
List result = new ArrayList<>();
-
try (XmlResourceParser parser = context.getResources().getXml(R.xml.device_profiles)) {
final int depth = parser.getDepth();
int type;
@@ -637,7 +767,7 @@ public class InvariantDeviceProfile implements SafeCloseable {
|| parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
if ((type == XmlPullParser.START_TAG)
&& GridOption.TAG_NAME.equals(parser.getName())) {
- result.add(new GridOption(context, Xml.asAttributeSet(parser)));
+ result.add(new GridOption(context, Xml.asAttributeSet(parser), displayInfo));
}
}
} catch (IOException | XmlPullParserException e) {
@@ -704,7 +834,7 @@ public class InvariantDeviceProfile implements SafeCloseable {
}
private static DisplayOption invDistWeightedInterpolate(
- Info displayInfo, ArrayList points, @DeviceType int deviceType) {
+ Info displayInfo, List points, @DeviceType int deviceType) {
int minWidthPx = Integer.MAX_VALUE;
int minHeightPx = Integer.MAX_VALUE;
for (WindowBounds bounds : displayInfo.supportedBounds) {
@@ -728,7 +858,7 @@ public class InvariantDeviceProfile implements SafeCloseable {
float height = dpiFromPx(minHeightPx, displayInfo.getDensityDpi());
// Sort the profiles based on the closeness to the device size
- Collections.sort(points, (a, b) ->
+ points.sort((a, b) ->
Float.compare(dist(width, height, a.minWidthDps, a.minHeightDps),
dist(width, height, b.minWidthDps, b.minHeightDps)));
@@ -850,6 +980,7 @@ public class InvariantDeviceProfile implements SafeCloseable {
private static final int DONT_INLINE_QSB = 0;
public final String name;
+ public final String title;
public final int numRows;
public final int numColumns;
public final int numSearchContainerColumns;
@@ -876,6 +1007,7 @@ public class InvariantDeviceProfile implements SafeCloseable {
private final int demoModeLayoutId;
private final boolean isScalable;
+ private final boolean mIsDualGrid;
private final int devicePaddingId;
private final int mWorkspaceSpecsId;
private final int mWorkspaceSpecsTwoPanelId;
@@ -889,22 +1021,41 @@ public class InvariantDeviceProfile implements SafeCloseable {
private final int mWorkspaceCellSpecsTwoPanelId;
private final int mAllAppsCellSpecsId;
private final int mAllAppsCellSpecsTwoPanelId;
+ private final int mGridSizeSpecsId;
+ private final boolean mIsFixedLandscape;
+ private final boolean mIsOldGrid;
- public GridOption(Context context, AttributeSet attrs) {
+ public GridOption(Context context, AttributeSet attrs, Info displayInfo) {
TypedArray a = context.obtainStyledAttributes(
attrs, R.styleable.GridDisplayOption);
name = a.getString(R.styleable.GridDisplayOption_name);
- numRows = a.getInt(R.styleable.GridDisplayOption_numRows, 0);
- numColumns = a.getInt(R.styleable.GridDisplayOption_numColumns, 0);
+ title = a.getString(R.styleable.GridDisplayOption_title);
+ deviceCategory = a.getInt(R.styleable.GridDisplayOption_deviceCategory,
+ DEVICE_CATEGORY_ALL);
+ mGridSizeSpecsId = a.getResourceId(
+ R.styleable.GridDisplayOption_gridSizeSpecsId, INVALID_RESOURCE_HANDLE);
+ mIsDualGrid = a.getBoolean(R.styleable.GridDisplayOption_isDualGrid, false);
+ if (mGridSizeSpecsId != INVALID_RESOURCE_HANDLE) {
+ ResourceHelper resourceHelper = new ResourceHelper(context, mGridSizeSpecsId);
+ GridSize gridSize = getGridSize(resourceHelper, context, displayInfo);
+ numColumns = gridSize.mNumColumns;
+ numRows = gridSize.mNumRows;
+ dbFile = gridSize.mDbFile;
+ defaultLayoutId = gridSize.mDefaultLayoutId;
+ demoModeLayoutId = gridSize.mDemoModeLayoutId;
+ } else {
+ numRows = a.getInt(R.styleable.GridDisplayOption_numRows, 0);
+ numColumns = a.getInt(R.styleable.GridDisplayOption_numColumns, 0);
+ dbFile = a.getString(R.styleable.GridDisplayOption_dbFile);
+ defaultLayoutId = a.getResourceId(
+ R.styleable.GridDisplayOption_defaultLayoutId, 0);
+ demoModeLayoutId = a.getResourceId(
+ R.styleable.GridDisplayOption_demoModeLayoutId, defaultLayoutId);
+ }
+
numSearchContainerColumns = a.getInt(
R.styleable.GridDisplayOption_numSearchContainerColumns, numColumns);
- dbFile = a.getString(R.styleable.GridDisplayOption_dbFile);
- defaultLayoutId = a.getResourceId(
- R.styleable.GridDisplayOption_defaultLayoutId, 0);
- demoModeLayoutId = a.getResourceId(
- R.styleable.GridDisplayOption_demoModeLayoutId, defaultLayoutId);
-
allAppsStyle = a.getResourceId(R.styleable.GridDisplayOption_allAppsStyle,
R.style.AllAppsStyleDefault);
numAllAppsColumns = a.getInt(
@@ -964,8 +1115,6 @@ public class InvariantDeviceProfile implements SafeCloseable {
R.styleable.GridDisplayOption_isScalable, false);
devicePaddingId = a.getResourceId(
R.styleable.GridDisplayOption_devicePaddingId, INVALID_RESOURCE_HANDLE);
- deviceCategory = a.getInt(R.styleable.GridDisplayOption_deviceCategory,
- DEVICE_CATEGORY_ALL);
if (FeatureFlags.enableResponsiveWorkspace()) {
mWorkspaceSpecsId = a.getResourceId(
@@ -1019,6 +1168,9 @@ public class InvariantDeviceProfile implements SafeCloseable {
mNumAllAppsRowsForCellHeightCalculation = numRows;
}
+ mIsFixedLandscape = a.getBoolean(R.styleable.GridDisplayOption_isFixedLandscape, false);
+ mIsOldGrid = a.getBoolean(R.styleable.GridDisplayOption_isOldGrid, false);
+
int inlineForRotation = a.getInt(R.styleable.GridDisplayOption_inlineQsb,
DONT_INLINE_QSB);
inlineQsb[INDEX_DEFAULT] =
@@ -1048,6 +1200,59 @@ public class InvariantDeviceProfile implements SafeCloseable {
return false;
}
}
+
+ /**
+ * Returns true if the grid option should be used given the flags that are toggled on/off.
+ */
+ public boolean filterByFlag(int deviceType, boolean isFixedLandscape) {
+ if (deviceType == TYPE_TABLET) {
+ return Flags.oneGridRotationHandling() == mIsDualGrid;
+ }
+
+ // Here we return true if fixed landscape mode should be on.
+ if (mIsFixedLandscape || isFixedLandscape) {
+ return mIsFixedLandscape && isFixedLandscape && Flags.oneGridSpecs();
+ }
+
+ // Here we return true if we want to show the new grids.
+ if (mGridSizeSpecsId != INVALID_RESOURCE_HANDLE) {
+ return Flags.oneGridSpecs();
+ }
+
+ // Here we return true if we want to show the old grids.
+ if (mIsOldGrid) {
+ return !Flags.oneGridSpecs();
+ }
+
+ return true;
+ }
+ }
+
+ public static final class GridSize {
+ final int mNumRows;
+ final int mNumColumns;
+ final float mMinDeviceWidthDp;
+ final float mMinDeviceHeightDp;
+ final String mDbFile;
+ final int mDefaultLayoutId;
+ final int mDemoModeLayoutId;
+
+
+ GridSize(Context context, AttributeSet attrs) {
+ TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.GridSize);
+
+ mNumRows = (int) a.getFloat(R.styleable.GridSize_numGridRows, 0);
+ mNumColumns = (int) a.getFloat(R.styleable.GridSize_numGridColumns, 0);
+ mMinDeviceWidthDp = a.getFloat(R.styleable.GridSize_minDeviceWidthDp, 0);
+ mMinDeviceHeightDp = a.getFloat(R.styleable.GridSize_minDeviceHeightDp, 0);
+ mDbFile = a.getString(R.styleable.GridSize_dbFile);
+ mDefaultLayoutId = a.getResourceId(
+ R.styleable.GridSize_defaultLayoutId, 0);
+ mDemoModeLayoutId = a.getResourceId(
+ R.styleable.GridSize_demoModeLayoutId, mDefaultLayoutId);
+
+ a.recycle();
+ }
}
@VisibleForTesting
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index c6e217ffd5..51e5b51e2b 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -46,6 +46,7 @@ import static com.android.launcher3.LauncherConstants.SavedInstanceKeys.RUNTIME_
import static com.android.launcher3.LauncherConstants.SavedInstanceKeys.RUNTIME_STATE_PENDING_ACTIVITY_RESULT;
import static com.android.launcher3.LauncherConstants.SavedInstanceKeys.RUNTIME_STATE_PENDING_REQUEST_ARGS;
import static com.android.launcher3.LauncherConstants.SavedInstanceKeys.RUNTIME_STATE_PENDING_REQUEST_CODE;
+import static com.android.launcher3.LauncherConstants.SavedInstanceKeys.RUNTIME_STATE_RECREATE_TO_UPDATE_THEME;
import static com.android.launcher3.LauncherConstants.SavedInstanceKeys.RUNTIME_STATE_WIDGET_PANEL;
import static com.android.launcher3.LauncherConstants.TraceEvents.COLD_STARTUP_TRACE_COOKIE;
import static com.android.launcher3.LauncherConstants.TraceEvents.COLD_STARTUP_TRACE_METHOD_NAME;
@@ -224,15 +225,16 @@ import com.android.launcher3.testing.shared.TestProtocol;
import com.android.launcher3.touch.AllAppsSwipeController;
import com.android.launcher3.touch.ItemLongClickListener;
import com.android.launcher3.util.ActivityResultInfo;
-import com.android.launcher3.util.ActivityTracker;
import com.android.launcher3.util.BackPressHandler;
import com.android.launcher3.util.CannedAnimationCoordinator;
import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.ContextTracker;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.IntSet;
import com.android.launcher3.util.ItemInflater;
import com.android.launcher3.util.KeyboardShortcutsDelegate;
import com.android.launcher3.util.LockedUserState;
+import com.android.launcher3.util.MSDLPlayerWrapper;
import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.util.PendingRequestArgs;
import com.android.launcher3.util.PluginManagerWrapper;
@@ -279,6 +281,7 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Optional;
import java.util.function.Predicate;
import java.util.function.Supplier;
@@ -293,7 +296,8 @@ public class Launcher extends StatefulActivity
PluginListener {
public static final String TAG = "Launcher";
- public static final ActivityTracker ACTIVITY_TRACKER = new ActivityTracker<>();
+ public static final ContextTracker.ActivityTracker ACTIVITY_TRACKER =
+ new ContextTracker.ActivityTracker<>();
static final boolean LOGD = false;
@@ -413,6 +417,7 @@ public class Launcher extends StatefulActivity
private final List mBackPressedHandlers = new ArrayList<>();
private boolean mIsColdStartupAfterReboot;
+ private boolean mForceConfigUpdate;
private boolean mIsNaturalScrollingEnabled;
@@ -535,6 +540,7 @@ public class Launcher extends StatefulActivity
mPopupDataProvider = new PopupDataProvider(this::updateNotificationDots);
mWidgetPickerDataProvider = new WidgetPickerDataProvider();
+ PillColorProvider.getInstance(mWorkspace.getContext()).registerObserver();
boolean internalStateHandled = ACTIVITY_TRACKER.handleCreate(this);
if (internalStateHandled) {
@@ -757,10 +763,9 @@ public class Launcher extends StatefulActivity
protected void onHandleConfigurationChanged() {
Trace.beginSection("Launcher#onHandleconfigurationChanged");
try {
- if (!initDeviceProfile(mDeviceProfile.inv)) {
+ if (!initDeviceProfile(mDeviceProfile.inv) && !mForceConfigUpdate) {
return;
}
-
dispatchDeviceProfileChanged();
reapplyUi();
mDragLayer.recreateControllers();
@@ -771,10 +776,25 @@ public class Launcher extends StatefulActivity
mModel.rebindCallbacks();
updateDisallowBack();
} finally {
+ mForceConfigUpdate = false;
Trace.endSection();
}
}
+ private void updateFixedLandscape() {
+ if (!com.android.launcher3.Flags.oneGridSpecs()) {
+ return;
+ }
+ // When the flag oneGridSpecs is on we want to disable ALLOW_ROTATION which is replaced
+ // by FIXED_LANDSCAPE_MODE, ALLOW_ROTATION will only be used on Tablets afterwards.
+ if (getDeviceProfile().isPhone || getDeviceProfile().isTwoPanels) {
+ LauncherPrefs.get(this).put(LauncherPrefs.ALLOW_ROTATION, false);
+ }
+ getRotationHelper().setFixedLandscape(
+ Objects.requireNonNull(mDeviceProfile.inv).isFixedLandscape
+ );
+ }
+
public void onAssistantVisibilityChanged(float visibility) {
mHotseat.getQsb().setAlpha(1f - visibility);
}
@@ -803,6 +823,7 @@ public class Launcher extends StatefulActivity
mDeviceProfile.numShownHotseatIcons);
}
mModelWriter = mModel.getWriter(true, mCellPosMapper, this);
+ updateFixedLandscape();
return true;
}
@@ -1333,7 +1354,8 @@ public class Launcher extends StatefulActivity
NonConfigInstance lastInstance = (NonConfigInstance) getLastNonConfigurationInstance();
boolean forceRestore = lastInstance != null
- && (lastInstance.config.diff(mOldConfig) & CONFIG_UI_MODE) != 0;
+ && ((lastInstance.config.diff(mOldConfig) & CONFIG_UI_MODE) != 0
+ || savedState.getBoolean(RUNTIME_STATE_RECREATE_TO_UPDATE_THEME));
if (forceRestore || !state.shouldDisableRestore()) {
mStateManager.goToState(state, false /* animated */);
}
@@ -1343,7 +1365,8 @@ public class Launcher extends StatefulActivity
if (requestArgs != null) {
setWaitingForResult(requestArgs);
}
- mPendingActivityRequestCode = savedState.getInt(RUNTIME_STATE_PENDING_REQUEST_CODE);
+ mPendingActivityRequestCode = savedState.getInt(
+ RUNTIME_STATE_PENDING_REQUEST_CODE, mPendingActivityRequestCode);
mPendingActivityResult = savedState.getParcelable(RUNTIME_STATE_PENDING_ACTIVITY_RESULT);
@@ -1655,7 +1678,7 @@ public class Launcher extends StatefulActivity
if (FeatureFlags.enableSplitContextually()) {
handleSplitAnimationGoingToHome(LAUNCHER_SPLIT_SELECTION_EXIT_HOME);
}
- mOverlayManager.hideOverlay(isStarted() && !isForceInvisible());
+ mOverlayManager.hideOverlay(isStarted());
handleGestureContract(intent);
} else if (Intent.ACTION_ALL_APPS.equals(intent.getAction())) {
showAllAppsFromIntent(alreadyOnHome);
@@ -1777,7 +1800,7 @@ public class Launcher extends StatefulActivity
@Override
public void onDestroy() {
super.onDestroy();
- ACTIVITY_TRACKER.onActivityDestroyed(this);
+ ACTIVITY_TRACKER.onContextDestroyed(this);
SettingsCache.INSTANCE.get(this).unregister(TOUCHPAD_NATURAL_SCROLLING,
mNaturalScrollingChangedListener);
@@ -1800,6 +1823,7 @@ public class Launcher extends StatefulActivity
// changes while launcher is still loading.
getRootView().getViewTreeObserver().removeOnPreDrawListener(mOnInitialBindListener);
mOverlayManager.onActivityDestroyed();
+ PillColorProvider.getInstance(mWorkspace.getContext()).unregisterObserver();
}
public LauncherAccessibilityDelegate getAccessibilityDelegate() {
@@ -1873,13 +1897,18 @@ public class Launcher extends StatefulActivity
}
}
- // Exit spring loaded mode if necessary after adding the widget
- Runnable onComplete = MULTI_SELECT_EDIT_MODE.get() ? null
- : () -> mStateManager.goToState(NORMAL, SPRING_LOADED_EXIT_DELAY);
+ // Exit spring loaded mode if necessary after adding the widget; unless config activity was
+ // started.
+ Runnable onComplete = MULTI_SELECT_EDIT_MODE.get() ? null : () -> mStateManager.goToState(
+ NORMAL, SPRING_LOADED_EXIT_DELAY);
completeAddAppWidget(appWidgetId, info, boundWidget,
addFlowHandler.getProviderInfo(this), addFlowHandler.needsConfigure(),
false, widgetPreviewBitmap);
- mWorkspace.removeExtraEmptyScreenDelayed(delay, false, onComplete);
+ // Remove extra screen if widget drop concluded. If a config activity was started, extra
+ // screen will be removed when we get back its result.
+ if (!isActivityStarted) {
+ mWorkspace.removeExtraEmptyScreenDelayed(delay, false, onComplete);
+ }
}
public void addPendingItem(PendingAddItemInfo info, int container, int screenId,
@@ -2622,8 +2651,9 @@ public class Launcher extends StatefulActivity
* See {@code LauncherBindingDelegate}
*/
@Override
- public void bindAllWidgets(final List allWidgets) {
- mModelCallbacks.bindAllWidgets(allWidgets);
+ public void bindAllWidgets(@NonNull final List allWidgets,
+ @NonNull final List defaultWidgets) {
+ mModelCallbacks.bindAllWidgets(allWidgets, defaultWidgets);
}
@Override
@@ -2720,6 +2750,7 @@ public class Launcher extends StatefulActivity
mModel.dumpState(prefix, fd, writer, args);
mOverlayManager.dump(prefix, writer);
ACTIVITY_TRACKER.dump(prefix, writer);
+ MSDLPlayerWrapper.INSTANCE.get(getApplicationContext()).dump(prefix, writer);
}
/**
@@ -3148,6 +3179,13 @@ public class Launcher extends StatefulActivity
return mAnimationCoordinator;
}
+ /**
+ * Set to force config update when set to true next time onHandleConfigurationChanged is called.
+ */
+ public void setForceConfigUpdate(boolean forceConfigUpdate) {
+ mForceConfigUpdate = forceConfigUpdate;
+ }
+
@Override
public View.OnLongClickListener getAllAppsItemLongClickListener() {
return ItemLongClickListener.INSTANCE_ALL_APPS;
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index 15641ab516..a53238da3a 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -20,8 +20,12 @@ import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_RESOURC
import static android.content.Context.RECEIVER_EXPORTED;
import static com.android.launcher3.Flags.enableSmartspaceRemovalToggle;
+import static com.android.launcher3.InvariantDeviceProfile.GRID_NAME_PREFS_KEY;
+import static com.android.launcher3.LauncherPrefs.DB_FILE;
+import static com.android.launcher3.LauncherPrefs.GRID_NAME;
import static com.android.launcher3.LauncherPrefs.ICON_STATE;
import static com.android.launcher3.LauncherPrefs.THEMED_ICONS;
+import static com.android.launcher3.model.DeviceGridState.KEY_DB_FILE;
import static com.android.launcher3.model.LoaderTask.SMARTSPACE_ON_HOME_SCREEN;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
@@ -47,7 +51,9 @@ import com.android.launcher3.icons.IconCache;
import com.android.launcher3.icons.IconProvider;
import com.android.launcher3.icons.LauncherIconProvider;
import com.android.launcher3.icons.LauncherIcons;
+import com.android.launcher3.logging.FileLog;
import com.android.launcher3.model.ModelLauncherCallbacks;
+import com.android.launcher3.model.WidgetsFilterDataProvider;
import com.android.launcher3.notification.NotificationListener;
import com.android.launcher3.pm.InstallSessionHelper;
import com.android.launcher3.pm.InstallSessionTracker;
@@ -69,6 +75,7 @@ import java.util.Objects;
public class LauncherAppState implements SafeCloseable {
+ public static final String TAG = "LauncherAppState";
public static final String ACTION_FORCE_ROLOAD = "force-reload-launcher";
// We do not need any synchronization for this variable as its only written on UI thread.
@@ -163,8 +170,7 @@ public class LauncherAppState implements SafeCloseable {
LockedUserState.get(context).runOnUserUnlocked(() -> {
CustomWidgetManager cwm = CustomWidgetManager.INSTANCE.get(mContext);
- cwm.setWidgetRefreshCallback(mModel::refreshAndBindWidgetsAndShortcuts);
- mOnTerminateCallback.add(() -> cwm.setWidgetRefreshCallback(null));
+ mOnTerminateCallback.add(cwm.addWidgetRefreshCallback(mModel::rebindCallbacks)::close);
IconObserver observer = new IconObserver();
SafeCloseable iconChangeTracker = mIconProvider.registerIconChangeListener(
@@ -176,7 +182,7 @@ public class LauncherAppState implements SafeCloseable {
() -> LauncherPrefs.get(mContext).removeListener(observer, THEMED_ICONS));
InstallSessionTracker installSessionTracker =
- InstallSessionHelper.INSTANCE.get(context).registerInstallTracker(mModel);
+ InstallSessionHelper.INSTANCE.get(context).registerInstallTracker(callbacks);
mOnTerminateCallback.add(installSessionTracker::unregister);
});
@@ -198,7 +204,8 @@ public class LauncherAppState implements SafeCloseable {
mIconProvider = new LauncherIconProvider(context);
mIconCache = new IconCache(mContext, mInvariantDeviceProfile,
iconCacheFileName, mIconProvider);
- mModel = new LauncherModel(context, this, mIconCache, new AppFilter(mContext),
+ mModel = new LauncherModel(context, this, mIconCache,
+ WidgetsFilterDataProvider.Companion.newInstance(context), new AppFilter(mContext),
PackageManagerHelper.INSTANCE.get(context), iconCacheFileName != null);
mOnTerminateCallback.add(mIconCache::close);
mOnTerminateCallback.add(mModel::destroy);
@@ -266,7 +273,7 @@ public class LauncherAppState implements SafeCloseable {
}
private class IconObserver
- implements IconProvider.IconChangeListener, OnSharedPreferenceChangeListener {
+ implements IconProvider.IconChangeListener, LauncherPrefChangeListener {
@Override
public void onAppIconChanged(String packageName, UserHandle user) {
@@ -288,10 +295,16 @@ public class LauncherAppState implements SafeCloseable {
}
@Override
- public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
+ public void onPrefChanged(String key) {
if (Themes.KEY_THEMED_ICONS.equals(key)) {
mIconProvider.setIconThemeSupported(Themes.isThemedIconEnabled(mContext));
verifyIconChanged();
+ } else if (GRID_NAME_PREFS_KEY.equals(key)) {
+ FileLog.d(TAG, "onPrefChanged GRID_NAME changed: "
+ + LauncherPrefs.get(mContext).get(GRID_NAME));
+ } else if (KEY_DB_FILE.equals(key)) {
+ FileLog.d(TAG, "onPrefChanged DB_FILE changed: "
+ + LauncherPrefs.get(mContext).get(DB_FILE));
}
}
}
diff --git a/src/com/android/launcher3/LauncherApplication.java b/src/com/android/launcher3/LauncherApplication.java
index 4c82e5676f..678901b5b2 100644
--- a/src/com/android/launcher3/LauncherApplication.java
+++ b/src/com/android/launcher3/LauncherApplication.java
@@ -26,15 +26,25 @@ import com.android.launcher3.dagger.LauncherBaseAppComponent;
*/
public class LauncherApplication extends Application {
- private LauncherBaseAppComponent mAppComponent;
+ private volatile LauncherBaseAppComponent mAppComponent;
@Override
public void onCreate() {
super.onCreate();
MainProcessInitializer.initialize(this);
- initDagger();
}
public LauncherAppComponent getAppComponent() {
+ if (mAppComponent == null) {
+ synchronized (this) {
+ // Check for null again, as it may have been assigned on a different thread. This
+ // avoids holding synchronization locks everytime.
+ if (mAppComponent == null) {
+ // Initialize the dagger component on demand as content providers can get
+ // accessed before the Launcher application (b/36917845#comment4)
+ initDaggerComponent(DaggerLauncherAppComponent.builder());
+ }
+ }
+ }
// Since supertype setters will return a supertype.builder and @Component.Builder types
// must not have any generic types.
// We need to cast mAppComponent to {@link LauncherAppComponent} since appContext()
@@ -42,7 +52,10 @@ public class LauncherApplication extends Application {
return (LauncherAppComponent) mAppComponent;
}
- protected void initDagger() {
- mAppComponent = DaggerLauncherAppComponent.builder().appContext(this).build();
+ /**
+ * Init with the desired dagger component.
+ */
+ public void initDaggerComponent(LauncherAppComponent.Builder componentBuilder) {
+ mAppComponent = componentBuilder.appContext(this).build();
}
}
diff --git a/src/com/android/launcher3/LauncherBackupAgent.java b/src/com/android/launcher3/LauncherBackupAgent.java
index 2617b9337d..a96495de83 100644
--- a/src/com/android/launcher3/LauncherBackupAgent.java
+++ b/src/com/android/launcher3/LauncherBackupAgent.java
@@ -1,5 +1,7 @@
package com.android.launcher3;
+import static com.android.launcher3.LauncherPrefs.NO_DB_FILES_RESTORED;
+
import android.app.backup.BackupAgent;
import android.app.backup.BackupDataInput;
import android.app.backup.BackupDataOutput;
@@ -10,10 +12,13 @@ import com.android.launcher3.provider.RestoreDbTask;
import java.io.File;
import java.io.IOException;
+import java.util.Arrays;
+import java.util.stream.Collectors;
public class LauncherBackupAgent extends BackupAgent {
-
private static final String TAG = "LauncherBackupAgent";
+ private static final String DB_FILE_PREFIX = "launcher";
+ private static final String DB_FILE_SUFFIX = ".db";
@Override
public void onCreate() {
@@ -47,7 +52,34 @@ public class LauncherBackupAgent extends BackupAgent {
@Override
public void onRestoreFinished() {
- FileLog.d(TAG, "onRestoreFinished: set pending for RestoreDbTask");
RestoreDbTask.setPending(this);
+ FileLog.d(TAG, "onRestoreFinished: set pending for RestoreDbTask");
+ markIfFilesWereNotActuallyRestored();
+ }
+
+ /**
+ * When restore is finished, we check to see if any db files were successfully restored. If not,
+ * our restore will fail later, but will report a different cause. This is important to split
+ * out the metric failures that are launcher's fault, and those that are due to bugs in the
+ * backup/restore code itself.
+ */
+ private void markIfFilesWereNotActuallyRestored() {
+ File directory = new File(getDatabasePath(InvariantDeviceProfile.INSTANCE.get(this).dbFile)
+ .getParent());
+ if (!directory.exists()) {
+ FileLog.e(TAG, "restore failed as target database directory doesn't exist");
+ } else {
+ // Check for any db file that was restored, and collect as list
+ String fileNames = Arrays.stream(directory.listFiles())
+ .map(File::getName)
+ .filter(n -> n.startsWith(DB_FILE_PREFIX) && n.endsWith(DB_FILE_SUFFIX))
+ .collect(Collectors.joining(", "));
+ if (fileNames.isBlank()) {
+ FileLog.e(TAG, "no database files were successfully restored");
+ LauncherPrefs.get(this).putSync(NO_DB_FILES_RESTORED.to(true));
+ } else {
+ FileLog.d(TAG, "database files successfully restored: " + fileNames);
+ }
+ }
}
}
diff --git a/src/com/android/launcher3/LauncherConstants.java b/src/com/android/launcher3/LauncherConstants.java
index 445fb4134c..5cba82fc3d 100644
--- a/src/com/android/launcher3/LauncherConstants.java
+++ b/src/com/android/launcher3/LauncherConstants.java
@@ -67,5 +67,8 @@ public class LauncherConstants {
static final String RUNTIME_STATE_WIDGET_PANEL = "launcher.widget_panel";
// Type int[]
static final String RUNTIME_STATE_CURRENT_SCREEN_IDS = "launcher.current_screen_ids";
+ // Type: boolean
+ public static final String RUNTIME_STATE_RECREATE_TO_UPDATE_THEME =
+ "launcher.recreate_to_update_theme";
}
}
diff --git a/src/com/android/launcher3/LauncherFiles.java b/src/com/android/launcher3/LauncherFiles.java
index d730cea804..c702414d1c 100644
--- a/src/com/android/launcher3/LauncherFiles.java
+++ b/src/com/android/launcher3/LauncherFiles.java
@@ -16,11 +16,16 @@ public class LauncherFiles {
private static final String XML = ".xml";
public static final String LAUNCHER_DB = "launcher.db";
+ public static final String LAUNCHER_5_BY_8_DB = "launcher_5_by_8.db";
public static final String LAUNCHER_6_BY_5_DB = "launcher_6_by_5.db";
public static final String LAUNCHER_4_BY_5_DB = "launcher_4_by_5.db";
+ public static final String LAUNCHER_4_BY_6_DB = "launcher_4_by_6.db";
+ public static final String LAUNCHER_5_BY_6_DB = "launcher_5_by_6.db";
public static final String LAUNCHER_4_BY_4_DB = "launcher_4_by_4.db";
public static final String LAUNCHER_3_BY_3_DB = "launcher_3_by_3.db";
public static final String LAUNCHER_2_BY_2_DB = "launcher_2_by_2.db";
+ public static final String LAUNCHER_7_BY_3_DB = "launcher_7_by_3.db";
+ public static final String LAUNCHER_8_BY_3_DB = "launcher_8_by_3.db";
public static final String BACKUP_DB = "backup.db";
public static final String SHARED_PREFERENCES_KEY = "com.android.launcher3.prefs";
public static final String MANAGED_USER_PREFERENCES_KEY =
@@ -33,11 +38,16 @@ public class LauncherFiles {
public static final List GRID_DB_FILES = Collections.unmodifiableList(Arrays.asList(
LAUNCHER_DB,
+ LAUNCHER_5_BY_8_DB,
LAUNCHER_6_BY_5_DB,
LAUNCHER_4_BY_5_DB,
+ LAUNCHER_4_BY_6_DB,
+ LAUNCHER_5_BY_6_DB,
LAUNCHER_4_BY_4_DB,
LAUNCHER_3_BY_3_DB,
- LAUNCHER_2_BY_2_DB));
+ LAUNCHER_2_BY_2_DB,
+ LAUNCHER_7_BY_3_DB,
+ LAUNCHER_8_BY_3_DB));
public static final List OTHER_FILES = Collections.unmodifiableList(Arrays.asList(
BACKUP_DB,
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
deleted file mode 100644
index ca1b2a91f7..0000000000
--- a/src/com/android/launcher3/LauncherModel.java
+++ /dev/null
@@ -1,691 +0,0 @@
-/*
- * Copyright (C) 2008 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 static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_RESOURCE_UPDATED;
-
-import static com.android.launcher3.LauncherAppState.ACTION_FORCE_ROLOAD;
-import static com.android.launcher3.LauncherPrefs.WORK_EDU_STEP;
-import static com.android.launcher3.config.FeatureFlags.IS_STUDIO_BUILD;
-import static com.android.launcher3.icons.cache.BaseIconCache.EMPTY_CLASS_NAME;
-import static com.android.launcher3.model.PackageUpdatedTask.OP_UPDATE;
-import static com.android.launcher3.pm.UserCache.ACTION_PROFILE_AVAILABLE;
-import static com.android.launcher3.pm.UserCache.ACTION_PROFILE_UNAVAILABLE;
-import static com.android.launcher3.testing.shared.TestProtocol.sDebugTracing;
-import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
-import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageInstaller;
-import android.content.pm.ShortcutInfo;
-import android.os.UserHandle;
-import android.text.TextUtils;
-import android.util.Log;
-import android.util.Pair;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.WorkerThread;
-
-import com.android.launcher3.celllayout.CellPosMapper;
-import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.icons.IconCache;
-import com.android.launcher3.model.AddWorkspaceItemsTask;
-import com.android.launcher3.model.AllAppsList;
-import com.android.launcher3.model.BaseLauncherBinder;
-import com.android.launcher3.model.BgDataModel;
-import com.android.launcher3.model.BgDataModel.Callbacks;
-import com.android.launcher3.model.CacheDataUpdatedTask;
-import com.android.launcher3.model.ItemInstallQueue;
-import com.android.launcher3.model.LoaderTask;
-import com.android.launcher3.model.ModelDbController;
-import com.android.launcher3.model.ModelDelegate;
-import com.android.launcher3.model.ModelLauncherCallbacks;
-import com.android.launcher3.model.ModelTaskController;
-import com.android.launcher3.model.ModelWriter;
-import com.android.launcher3.model.PackageInstallStateChangedTask;
-import com.android.launcher3.model.PackageUpdatedTask;
-import com.android.launcher3.model.ReloadStringCacheTask;
-import com.android.launcher3.model.ShortcutsChangedTask;
-import com.android.launcher3.model.UserLockStateChangedTask;
-import com.android.launcher3.model.data.AppInfo;
-import com.android.launcher3.model.data.ItemInfo;
-import com.android.launcher3.model.data.WorkspaceItemInfo;
-import com.android.launcher3.pm.InstallSessionTracker;
-import com.android.launcher3.pm.PackageInstallInfo;
-import com.android.launcher3.pm.UserCache;
-import com.android.launcher3.shortcuts.ShortcutRequest;
-import com.android.launcher3.util.IntSet;
-import com.android.launcher3.util.ItemInfoMatcher;
-import com.android.launcher3.util.PackageManagerHelper;
-import com.android.launcher3.util.PackageUserKey;
-import com.android.launcher3.util.Preconditions;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.concurrent.CancellationException;
-import java.util.function.Consumer;
-import java.util.function.Supplier;
-
-/**
- * Maintains in-memory state of the Launcher. It is expected that there should be only one
- * LauncherModel object held in a static. Also provide APIs for updating the database state
- * for the Launcher.
- */
-public class LauncherModel implements InstallSessionTracker.Callback {
- private static final boolean DEBUG_RECEIVER = false;
-
- static final String TAG = "Launcher.Model";
-
- @NonNull
- private final LauncherAppState mApp;
- @NonNull
- private final PackageManagerHelper mPmHelper;
- @NonNull
- private final ModelDbController mModelDbController;
- @NonNull
- private final Object mLock = new Object();
- @Nullable
- private LoaderTask mLoaderTask;
- private boolean mIsLoaderTaskRunning;
-
- // only allow this once per reboot to reload work apps
- private boolean mShouldReloadWorkProfile = true;
-
- // Indicates whether the current model data is valid or not.
- // We start off with everything not loaded. After that, we assume that
- // our monitoring of the package manager provides all updates and we never
- // need to do a requery. This is only ever touched from the loader thread.
- private boolean mModelLoaded;
- private boolean mModelDestroyed = false;
- public boolean isModelLoaded() {
- synchronized (mLock) {
- return mModelLoaded && mLoaderTask == null && !mModelDestroyed;
- }
- }
-
- @NonNull
- private final ArrayList mCallbacksList = new ArrayList<>(1);
-
- // < only access in worker thread >
- @NonNull
- private final AllAppsList mBgAllAppsList;
-
- /**
- * All the static data should be accessed on the background thread, A lock should be acquired
- * on this object when accessing any data from this model.
- */
- @NonNull
- private final BgDataModel mBgDataModel = new BgDataModel();
-
- @NonNull
- private final ModelDelegate mModelDelegate;
-
- private int mLastLoadId = -1;
-
- // Runnable to check if the shortcuts permission has changed.
- @NonNull
- private final Runnable mDataValidationCheck = new Runnable() {
- @Override
- public void run() {
- if (mModelLoaded) {
- mModelDelegate.validateData();
- }
- }
- };
-
- LauncherModel(@NonNull final Context context, @NonNull final LauncherAppState app,
- @NonNull final IconCache iconCache, @NonNull final AppFilter appFilter,
- @NonNull final PackageManagerHelper pmHelper, final boolean isPrimaryInstance) {
- mApp = app;
- mPmHelper = pmHelper;
- mModelDbController = new ModelDbController(context);
- mBgAllAppsList = new AllAppsList(iconCache, appFilter);
- mModelDelegate = ModelDelegate.newInstance(context, app, mPmHelper, mBgAllAppsList,
- mBgDataModel, isPrimaryInstance);
- }
-
- @NonNull
- public ModelDelegate getModelDelegate() {
- return mModelDelegate;
- }
-
- public ModelDbController getModelDbController() {
- return mModelDbController;
- }
-
- public ModelLauncherCallbacks newModelCallbacks() {
- return new ModelLauncherCallbacks(this::enqueueModelUpdateTask);
- }
-
- /**
- * Adds the provided items to the workspace.
- */
- public void addAndBindAddedWorkspaceItems(
- @NonNull final List> itemList) {
- for (Callbacks cb : getCallbacks()) {
- cb.preAddApps();
- }
- enqueueModelUpdateTask(new AddWorkspaceItemsTask(itemList));
- }
-
- @NonNull
- public ModelWriter getWriter(final boolean verifyChanges, CellPosMapper cellPosMapper,
- @Nullable final Callbacks owner) {
- return new ModelWriter(mApp.getContext(), this, mBgDataModel, verifyChanges, cellPosMapper,
- owner);
- }
-
- /**
- * Called when the icon for an app changes, outside of package event
- */
- @WorkerThread
- public void onAppIconChanged(@NonNull final String packageName,
- @NonNull final UserHandle user) {
- // Update the icon for the calendar package
- Context context = mApp.getContext();
- enqueueModelUpdateTask(new PackageUpdatedTask(OP_UPDATE, user, packageName));
-
- List pinnedShortcuts = new ShortcutRequest(context, user)
- .forPackage(packageName).query(ShortcutRequest.PINNED);
- if (!pinnedShortcuts.isEmpty()) {
- enqueueModelUpdateTask(new ShortcutsChangedTask(packageName, pinnedShortcuts, user,
- false));
- }
- }
-
- /**
- * Called when the workspace items have drastically changed
- */
- public void onWorkspaceUiChanged() {
- MODEL_EXECUTOR.execute(mModelDelegate::workspaceLoadComplete);
- }
-
- /**
- * Called when the model is destroyed
- */
- public void destroy() {
- mModelDestroyed = true;
- MODEL_EXECUTOR.execute(mModelDelegate::destroy);
- }
-
- public void onBroadcastIntent(@NonNull final Intent intent) {
- if (DEBUG_RECEIVER || sDebugTracing) Log.d(TAG, "onReceive intent=" + intent);
- final String action = intent.getAction();
- if (Intent.ACTION_LOCALE_CHANGED.equals(action)) {
- // If we have changed locale we need to clear out the labels in all apps/workspace.
- forceReload();
- } else if (ACTION_DEVICE_POLICY_RESOURCE_UPDATED.equals(action)) {
- enqueueModelUpdateTask(new ReloadStringCacheTask(mModelDelegate));
- } else if (IS_STUDIO_BUILD && ACTION_FORCE_ROLOAD.equals(action)) {
- for (Callbacks cb : getCallbacks()) {
- if (cb instanceof Launcher) {
- ((Launcher) cb).recreate();
- }
- }
- }
- }
-
- /**
- * Called then there use a user event
- * @see UserCache#addUserEventListener
- */
- public void onUserEvent(UserHandle user, String action) {
- if (Intent.ACTION_MANAGED_PROFILE_AVAILABLE.equals(action)
- && mShouldReloadWorkProfile) {
- mShouldReloadWorkProfile = false;
- forceReload();
- } else if (Intent.ACTION_MANAGED_PROFILE_AVAILABLE.equals(action)
- || Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action)) {
- mShouldReloadWorkProfile = false;
- enqueueModelUpdateTask(new PackageUpdatedTask(
- PackageUpdatedTask.OP_USER_AVAILABILITY_CHANGE, user));
- } else if (UserCache.ACTION_PROFILE_LOCKED.equals(action)
- || UserCache.ACTION_PROFILE_UNLOCKED.equals(action)) {
- enqueueModelUpdateTask(new UserLockStateChangedTask(
- user, UserCache.ACTION_PROFILE_UNLOCKED.equals(action)));
- } else if (UserCache.ACTION_PROFILE_ADDED.equals(action)
- || UserCache.ACTION_PROFILE_REMOVED.equals(action)) {
- forceReload();
- } else if (ACTION_PROFILE_AVAILABLE.equals(action)
- || ACTION_PROFILE_UNAVAILABLE.equals(action)) {
- /*
- * This broadcast is only available when android.os.Flags.allowPrivateProfile() is set.
- * For Work-profile this broadcast will be sent in addition to
- * ACTION_MANAGED_PROFILE_AVAILABLE/UNAVAILABLE.
- * So effectively, this if block only handles the non-work profile case.
- */
- enqueueModelUpdateTask(new PackageUpdatedTask(
- PackageUpdatedTask.OP_USER_AVAILABILITY_CHANGE, user));
- }
- if (Intent.ACTION_MANAGED_PROFILE_REMOVED.equals(action)) {
- LauncherPrefs.get(mApp.getContext()).put(WORK_EDU_STEP, 0);
- }
- }
-
- /**
- * Reloads the workspace items from the DB and re-binds the workspace. This should generally
- * not be called as DB updates are automatically followed by UI update
- */
- public void forceReload() {
- synchronized (mLock) {
- // Stop any existing loaders first, so they don't set mModelLoaded to true later
- stopLoader();
- mModelLoaded = false;
- }
-
- // Start the loader if launcher is already running, otherwise the loader will run,
- // the next time launcher starts
- if (hasCallbacks()) {
- startLoader();
- }
- }
-
- /**
- * Rebinds all existing callbacks with already loaded model
- */
- public void rebindCallbacks() {
- if (hasCallbacks()) {
- startLoader();
- }
- }
-
- /**
- * Removes an existing callback
- */
- public void removeCallbacks(@NonNull final Callbacks callbacks) {
- synchronized (mCallbacksList) {
- Preconditions.assertUIThread();
- if (mCallbacksList.remove(callbacks)) {
- if (stopLoader()) {
- // Rebind existing callbacks
- startLoader();
- }
- }
- }
- }
-
- /**
- * Adds a callbacks to receive model updates
- * @return true if workspace load was performed synchronously
- */
- public boolean addCallbacksAndLoad(@NonNull final Callbacks callbacks) {
- synchronized (mLock) {
- addCallbacks(callbacks);
- return startLoader(new Callbacks[] { callbacks });
-
- }
- }
-
- /**
- * Adds a callbacks to receive model updates
- */
- public void addCallbacks(@NonNull final Callbacks callbacks) {
- Preconditions.assertUIThread();
- synchronized (mCallbacksList) {
- mCallbacksList.add(callbacks);
- }
- }
-
- /**
- * Starts the loader. Tries to bind {@params synchronousBindPage} synchronously if possible.
- * @return true if the page could be bound synchronously.
- */
- public boolean startLoader() {
- return startLoader(new Callbacks[0]);
- }
-
- private boolean startLoader(@NonNull final Callbacks[] newCallbacks) {
- // Enable queue before starting loader. It will get disabled in Launcher#finishBindingItems
- ItemInstallQueue.INSTANCE.get(mApp.getContext())
- .pauseModelPush(ItemInstallQueue.FLAG_LOADER_RUNNING);
- synchronized (mLock) {
- // If there is already one running, tell it to stop.
- boolean wasRunning = stopLoader();
- boolean bindDirectly = mModelLoaded && !mIsLoaderTaskRunning;
- boolean bindAllCallbacks = wasRunning || !bindDirectly || newCallbacks.length == 0;
- final Callbacks[] callbacksList = bindAllCallbacks ? getCallbacks() : newCallbacks;
-
- if (callbacksList.length > 0) {
- // Clear any pending bind-runnables from the synchronized load process.
- for (Callbacks cb : callbacksList) {
- MAIN_EXECUTOR.execute(cb::clearPendingBinds);
- }
-
- BaseLauncherBinder launcherBinder = new BaseLauncherBinder(
- mApp, mBgDataModel, mBgAllAppsList, callbacksList);
- if (bindDirectly) {
- // Divide the set of loaded items into those that we are binding synchronously,
- // and everything else that is to be bound normally (asynchronously).
- launcherBinder.bindWorkspace(bindAllCallbacks, /* isBindSync= */ true);
- // For now, continue posting the binding of AllApps as there are other
- // issues that arise from that.
- launcherBinder.bindAllApps();
- launcherBinder.bindDeepShortcuts();
- launcherBinder.bindWidgets();
- if (FeatureFlags.CHANGE_MODEL_DELEGATE_LOADING_ORDER.get()) {
- mModelDelegate.bindAllModelExtras(callbacksList);
- }
- return true;
- } else {
- stopLoader();
- mLoaderTask = new LoaderTask(
- mApp, mBgAllAppsList, mBgDataModel, mModelDelegate, launcherBinder);
-
- // Always post the loader task, instead of running directly
- // (even on same thread) so that we exit any nested synchronized blocks
- MODEL_EXECUTOR.post(mLoaderTask);
- }
- }
- }
- return false;
- }
-
- /**
- * If there is already a loader task running, tell it to stop.
- * @return true if an existing loader was stopped.
- */
- private boolean stopLoader() {
- synchronized (mLock) {
- LoaderTask oldTask = mLoaderTask;
- mLoaderTask = null;
- if (oldTask != null) {
- oldTask.stopLocked();
- return true;
- }
- return false;
- }
- }
-
- /**
- * Loads the model if not loaded
- * @param callback called with the data model upon successful load or null on model thread.
- */
- public void loadAsync(@NonNull final Consumer callback) {
- synchronized (mLock) {
- if (!mModelLoaded && !mIsLoaderTaskRunning) {
- startLoader();
- }
- }
- MODEL_EXECUTOR.post(() -> callback.accept(isModelLoaded() ? mBgDataModel : null));
- }
-
- @Override
- public void onInstallSessionCreated(@NonNull final PackageInstallInfo sessionInfo) {
- if (FeatureFlags.PROMISE_APPS_IN_ALL_APPS.get()) {
- enqueueModelUpdateTask((taskController, dataModel, apps) -> {
- apps.addPromiseApp(mApp.getContext(), sessionInfo);
- taskController.bindApplicationsIfNeeded();
- });
- }
- }
-
- @Override
- public void onSessionFailure(@NonNull final String packageName,
- @NonNull final UserHandle user) {
- enqueueModelUpdateTask((taskController, dataModel, apps) -> {
- IconCache iconCache = mApp.getIconCache();
- final IntSet removedIds = new IntSet();
- HashSet archivedWorkspaceItemsToCacheRefresh = new HashSet<>();
- boolean isAppArchived = PackageManagerHelper.INSTANCE.get(mApp.getContext())
- .isAppArchivedForUser(packageName, user);
- synchronized (dataModel) {
- if (isAppArchived) {
- // Remove package icon cache entry for archived app in case of a session
- // failure.
- mApp.getIconCache().remove(
- new ComponentName(packageName, packageName + EMPTY_CLASS_NAME),
- user);
- }
-
- for (ItemInfo info : dataModel.itemsIdMap) {
- if (info instanceof WorkspaceItemInfo
- && ((WorkspaceItemInfo) info).hasPromiseIconUi()
- && user.equals(info.user)
- && info.getIntent() != null) {
- if (TextUtils.equals(packageName, info.getIntent().getPackage())) {
- removedIds.add(info.id);
- }
- if (((WorkspaceItemInfo) info).isArchived()) {
- WorkspaceItemInfo workspaceItem = (WorkspaceItemInfo) info;
- // Refresh icons on the workspace for archived apps.
- iconCache.getTitleAndIcon(workspaceItem,
- workspaceItem.usingLowResIcon());
- archivedWorkspaceItemsToCacheRefresh.add(workspaceItem);
- }
- }
- }
-
- if (isAppArchived) {
- apps.updateIconsAndLabels(new HashSet<>(List.of(packageName)), user);
- }
- }
-
- if (!removedIds.isEmpty() && !isAppArchived) {
- taskController.deleteAndBindComponentsRemoved(
- ItemInfoMatcher.ofItemIds(removedIds),
- "removed because install session failed");
- }
- if (!archivedWorkspaceItemsToCacheRefresh.isEmpty()) {
- taskController.bindUpdatedWorkspaceItems(
- archivedWorkspaceItemsToCacheRefresh.stream().toList());
- }
- if (isAppArchived) {
- taskController.bindApplicationsIfNeeded();
- }
- });
- }
-
- @Override
- public void onPackageStateChanged(@NonNull final PackageInstallInfo installInfo) {
- enqueueModelUpdateTask(new PackageInstallStateChangedTask(installInfo));
- }
-
- /**
- * Updates the icons and label of all pending icons for the provided package name.
- */
- @Override
- public void onUpdateSessionDisplay(@NonNull final PackageUserKey key,
- @NonNull final PackageInstaller.SessionInfo info) {
- mApp.getIconCache().updateSessionCache(key, info);
-
- HashSet packages = new HashSet<>();
- packages.add(key.mPackageName);
- enqueueModelUpdateTask(new CacheDataUpdatedTask(
- CacheDataUpdatedTask.OP_SESSION_UPDATE, key.mUser, packages));
- }
-
- public class LoaderTransaction implements AutoCloseable {
-
- @NonNull
- private final LoaderTask mTask;
-
- private LoaderTransaction(@NonNull final LoaderTask task) throws CancellationException {
- synchronized (mLock) {
- if (mLoaderTask != task) {
- throw new CancellationException("Loader already stopped");
- }
- mLastLoadId++;
- mTask = task;
- mIsLoaderTaskRunning = true;
- mModelLoaded = false;
- }
- }
-
- public void commit() {
- synchronized (mLock) {
- // Everything loaded bind the data.
- mModelLoaded = true;
- }
- }
-
- @Override
- public void close() {
- synchronized (mLock) {
- // If we are still the last one to be scheduled, remove ourselves.
- if (mLoaderTask == mTask) {
- mLoaderTask = null;
- }
- mIsLoaderTaskRunning = false;
- }
- }
- }
-
- public LoaderTransaction beginLoader(@NonNull final LoaderTask task)
- throws CancellationException {
- return new LoaderTransaction(task);
- }
-
- /**
- * Refreshes the cached shortcuts if the shortcut permission has changed.
- * Current implementation simply reloads the workspace, but it can be optimized to
- * use partial updates similar to {@link UserCache}
- */
- public void validateModelDataOnResume() {
- MODEL_EXECUTOR.getHandler().removeCallbacks(mDataValidationCheck);
- MODEL_EXECUTOR.post(mDataValidationCheck);
- }
-
- /**
- * Called when the icons for packages have been updated in the icon cache.
- */
- public void onPackageIconsUpdated(@NonNull final HashSet updatedPackages,
- @NonNull final UserHandle user) {
- // If any package icon has changed (app was updated while launcher was dead),
- // update the corresponding shortcuts.
- enqueueModelUpdateTask(new CacheDataUpdatedTask(
- CacheDataUpdatedTask.OP_CACHE_UPDATE, user, updatedPackages));
- }
-
- /**
- * Called when the labels for the widgets has updated in the icon cache.
- */
- public void onWidgetLabelsUpdated(@NonNull final HashSet updatedPackages,
- @NonNull final UserHandle user) {
- enqueueModelUpdateTask((taskController, dataModel, apps) -> {
- dataModel.widgetsModel.onPackageIconsUpdated(updatedPackages, user, mApp);
- taskController.bindUpdatedWidgets(dataModel);
- });
- }
-
- public void enqueueModelUpdateTask(@NonNull final ModelUpdateTask task) {
- if (mModelDestroyed) {
- return;
- }
- MODEL_EXECUTOR.execute(() -> {
- if (!isModelLoaded()) {
- // Loader has not yet run.
- return;
- }
- ModelTaskController controller = new ModelTaskController(
- mApp, mBgDataModel, mBgAllAppsList, this, MAIN_EXECUTOR);
- task.execute(controller, mBgDataModel, mBgAllAppsList);
- });
- }
-
- /**
- * A task to be executed on the current callbacks on the UI thread.
- * If there is no current callbacks, the task is ignored.
- */
- public interface CallbackTask {
-
- void execute(@NonNull Callbacks callbacks);
- }
-
- public interface ModelUpdateTask {
-
- void execute(@NonNull ModelTaskController taskController,
- @NonNull BgDataModel dataModel, @NonNull AllAppsList apps);
- }
-
- public void updateAndBindWorkspaceItem(@NonNull final WorkspaceItemInfo si,
- @NonNull final ShortcutInfo info) {
- updateAndBindWorkspaceItem(() -> {
- si.updateFromDeepShortcutInfo(info, mApp.getContext());
- mApp.getIconCache().getShortcutIcon(si, info);
- return si;
- });
- }
-
- /**
- * Utility method to update a shortcut on the background thread.
- */
- public void updateAndBindWorkspaceItem(
- @NonNull final Supplier itemProvider) {
- enqueueModelUpdateTask((taskController, dataModel, apps) -> {
- WorkspaceItemInfo info = itemProvider.get();
- taskController.getModelWriter().updateItemInDatabase(info);
- ArrayList update = new ArrayList<>();
- update.add(info);
- taskController.bindUpdatedWorkspaceItems(update);
- });
- }
-
- public void refreshAndBindWidgetsAndShortcuts(@Nullable final PackageUserKey packageUser) {
- enqueueModelUpdateTask((taskController, dataModel, apps) -> {
- dataModel.widgetsModel.update(taskController.getApp(), packageUser);
- taskController.bindUpdatedWidgets(dataModel);
- });
- }
-
- public void dumpState(@Nullable final String prefix, @Nullable final FileDescriptor fd,
- @NonNull final PrintWriter writer, @NonNull final String[] args) {
- if (args.length > 0 && TextUtils.equals(args[0], "--all")) {
- writer.println(prefix + "All apps list: size=" + mBgAllAppsList.data.size());
- for (AppInfo info : mBgAllAppsList.data) {
- writer.println(prefix + " title=\"" + info.title
- + "\" bitmapIcon=" + info.bitmap.icon
- + " componentName=" + info.componentName.getPackageName());
- }
- writer.println();
- }
- mModelDelegate.dump(prefix, fd, writer, args);
- mBgDataModel.dump(prefix, fd, writer, args);
- }
-
- /**
- * Returns true if there are any callbacks attached to the model
- */
- public boolean hasCallbacks() {
- synchronized (mCallbacksList) {
- return !mCallbacksList.isEmpty();
- }
- }
-
- /**
- * Returns an array of currently attached callbacks
- */
- @NonNull
- public Callbacks[] getCallbacks() {
- synchronized (mCallbacksList) {
- return mCallbacksList.toArray(new Callbacks[mCallbacksList.size()]);
- }
- }
-
- /**
- * Returns the ID for the last model load. If the load ID doesn't match for a transaction, the
- * transaction should be ignored.
- */
- public int getLastLoadId() {
- return mLastLoadId;
- }
-}
diff --git a/src/com/android/launcher3/LauncherModel.kt b/src/com/android/launcher3/LauncherModel.kt
new file mode 100644
index 0000000000..b56df4624c
--- /dev/null
+++ b/src/com/android/launcher3/LauncherModel.kt
@@ -0,0 +1,514 @@
+/*
+ * Copyright (C) 2008 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.admin.DevicePolicyManager
+import android.content.Context
+import android.content.Intent
+import android.content.pm.ShortcutInfo
+import android.os.UserHandle
+import android.text.TextUtils
+import android.util.Log
+import android.util.Pair
+import androidx.annotation.WorkerThread
+import com.android.launcher3.celllayout.CellPosMapper
+import com.android.launcher3.icons.IconCache
+import com.android.launcher3.model.AddWorkspaceItemsTask
+import com.android.launcher3.model.AllAppsList
+import com.android.launcher3.model.BaseLauncherBinder
+import com.android.launcher3.model.BgDataModel
+import com.android.launcher3.model.CacheDataUpdatedTask
+import com.android.launcher3.model.ItemInstallQueue
+import com.android.launcher3.model.LoaderTask
+import com.android.launcher3.model.ModelDbController
+import com.android.launcher3.model.ModelDelegate
+import com.android.launcher3.model.ModelLauncherCallbacks
+import com.android.launcher3.model.ModelTaskController
+import com.android.launcher3.model.ModelWriter
+import com.android.launcher3.model.PackageUpdatedTask
+import com.android.launcher3.model.ReloadStringCacheTask
+import com.android.launcher3.model.ShortcutsChangedTask
+import com.android.launcher3.model.UserLockStateChangedTask
+import com.android.launcher3.model.WidgetsFilterDataProvider
+import com.android.launcher3.model.data.ItemInfo
+import com.android.launcher3.model.data.WorkspaceItemInfo
+import com.android.launcher3.pm.UserCache
+import com.android.launcher3.shortcuts.ShortcutRequest
+import com.android.launcher3.testing.shared.TestProtocol.sDebugTracing
+import com.android.launcher3.util.Executors.MAIN_EXECUTOR
+import com.android.launcher3.util.Executors.MODEL_EXECUTOR
+import com.android.launcher3.util.PackageManagerHelper
+import com.android.launcher3.util.PackageUserKey
+import com.android.launcher3.util.Preconditions
+import java.io.FileDescriptor
+import java.io.PrintWriter
+import java.util.concurrent.CancellationException
+import java.util.function.Consumer
+
+/**
+ * Maintains in-memory state of the Launcher. It is expected that there should be only one
+ * LauncherModel object held in a static. Also provide APIs for updating the database state for the
+ * Launcher.
+ */
+class LauncherModel(
+ private val context: Context,
+ private val mApp: LauncherAppState,
+ private val iconCache: IconCache,
+ private val widgetsFilterDataProvider: WidgetsFilterDataProvider,
+ appFilter: AppFilter,
+ mPmHelper: PackageManagerHelper,
+ isPrimaryInstance: Boolean,
+) {
+
+ private val mCallbacksList = ArrayList(1)
+
+ // < only access in worker thread >
+ private val mBgAllAppsList = AllAppsList(iconCache, appFilter)
+
+ /**
+ * All the static data should be accessed on the background thread, A lock should be acquired on
+ * this object when accessing any data from this model.
+ */
+ private val mBgDataModel = BgDataModel()
+
+ val modelDelegate: ModelDelegate =
+ ModelDelegate.newInstance(
+ context,
+ mApp,
+ mPmHelper,
+ mBgAllAppsList,
+ mBgDataModel,
+ isPrimaryInstance,
+ )
+
+ val modelDbController = ModelDbController(context)
+
+ private val mLock = Any()
+
+ private var mLoaderTask: LoaderTask? = null
+ private var mIsLoaderTaskRunning = false
+
+ // only allow this once per reboot to reload work apps
+ private var mShouldReloadWorkProfile = true
+
+ // Indicates whether the current model data is valid or not.
+ // We start off with everything not loaded. After that, we assume that
+ // our monitoring of the package manager provides all updates and we never
+ // need to do a requery. This is only ever touched from the loader thread.
+ private var mModelLoaded = false
+ private var mModelDestroyed = false
+
+ fun isModelLoaded() =
+ synchronized(mLock) { mModelLoaded && mLoaderTask == null && !mModelDestroyed }
+
+ /**
+ * Returns the ID for the last model load. If the load ID doesn't match for a transaction, the
+ * transaction should be ignored.
+ */
+ var lastLoadId: Int = -1
+ private set
+
+ // Runnable to check if the shortcuts permission has changed.
+ private val mDataValidationCheck = Runnable {
+ if (mModelLoaded) {
+ modelDelegate.validateData()
+ }
+ }
+
+ fun newModelCallbacks() = ModelLauncherCallbacks(this::enqueueModelUpdateTask)
+
+ /** Adds the provided items to the workspace. */
+ fun addAndBindAddedWorkspaceItems(itemList: List?>) {
+ callbacks.forEach { it.preAddApps() }
+ enqueueModelUpdateTask(AddWorkspaceItemsTask(itemList))
+ }
+
+ fun getWriter(
+ verifyChanges: Boolean,
+ cellPosMapper: CellPosMapper?,
+ owner: BgDataModel.Callbacks?,
+ ) = ModelWriter(mApp.context, this, mBgDataModel, verifyChanges, cellPosMapper, owner)
+
+ /** Returns the [WidgetsFilterDataProvider] that manages widget filters. */
+ fun getWidgetsFilterDataProvider(): WidgetsFilterDataProvider {
+ return widgetsFilterDataProvider
+ }
+
+ /** Called when the icon for an app changes, outside of package event */
+ @WorkerThread
+ fun onAppIconChanged(packageName: String, user: UserHandle) {
+ // Update the icon for the calendar package
+ enqueueModelUpdateTask(PackageUpdatedTask(PackageUpdatedTask.OP_UPDATE, user, packageName))
+ ShortcutRequest(context, user).forPackage(packageName).query(ShortcutRequest.PINNED).let {
+ if (it.isNotEmpty()) {
+ enqueueModelUpdateTask(ShortcutsChangedTask(packageName, it, user, false))
+ }
+ }
+ }
+
+ /** Called when the workspace items have drastically changed */
+ fun onWorkspaceUiChanged() {
+ MODEL_EXECUTOR.execute(modelDelegate::workspaceLoadComplete)
+ }
+
+ /** Called when the model is destroyed */
+ fun destroy() {
+ mModelDestroyed = true
+ MODEL_EXECUTOR.execute {
+ modelDelegate.destroy()
+ widgetsFilterDataProvider.destroy()
+ }
+ }
+
+ fun onBroadcastIntent(intent: Intent) {
+ if (DEBUG_RECEIVER || sDebugTracing) Log.d(TAG, "onReceive intent=$intent")
+ when (intent.action) {
+ Intent.ACTION_LOCALE_CHANGED,
+ LauncherAppState.ACTION_FORCE_ROLOAD ->
+ // If we have changed locale we need to clear out the labels in all apps/workspace.
+ forceReload()
+ DevicePolicyManager.ACTION_DEVICE_POLICY_RESOURCE_UPDATED ->
+ enqueueModelUpdateTask(ReloadStringCacheTask(this.modelDelegate))
+ }
+ }
+
+ /**
+ * Called then there use a user event
+ *
+ * @see UserCache.addUserEventListener
+ */
+ fun onUserEvent(user: UserHandle, action: String) {
+ when (action) {
+ Intent.ACTION_MANAGED_PROFILE_AVAILABLE -> {
+ if (mShouldReloadWorkProfile) {
+ forceReload()
+ } else {
+ enqueueModelUpdateTask(
+ PackageUpdatedTask(PackageUpdatedTask.OP_USER_AVAILABILITY_CHANGE, user)
+ )
+ }
+ mShouldReloadWorkProfile = false
+ }
+ Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE -> {
+ mShouldReloadWorkProfile = false
+ enqueueModelUpdateTask(
+ PackageUpdatedTask(PackageUpdatedTask.OP_USER_AVAILABILITY_CHANGE, user)
+ )
+ }
+ UserCache.ACTION_PROFILE_LOCKED ->
+ enqueueModelUpdateTask(UserLockStateChangedTask(user, false))
+ UserCache.ACTION_PROFILE_UNLOCKED ->
+ enqueueModelUpdateTask(UserLockStateChangedTask(user, true))
+ Intent.ACTION_MANAGED_PROFILE_REMOVED -> {
+ LauncherPrefs.get(mApp.context).put(LauncherPrefs.WORK_EDU_STEP, 0)
+ forceReload()
+ }
+ UserCache.ACTION_PROFILE_ADDED,
+ UserCache.ACTION_PROFILE_REMOVED -> forceReload()
+ UserCache.ACTION_PROFILE_AVAILABLE,
+ UserCache.ACTION_PROFILE_UNAVAILABLE -> {
+ // This broadcast is only available when android.os.Flags.allowPrivateProfile() is
+ // set. For Work-profile this broadcast will be sent in addition to
+ // ACTION_MANAGED_PROFILE_AVAILABLE/UNAVAILABLE. So effectively, this if block only
+ // handles the non-work profile case.
+ enqueueModelUpdateTask(
+ PackageUpdatedTask(PackageUpdatedTask.OP_USER_AVAILABILITY_CHANGE, user)
+ )
+ }
+ }
+ }
+
+ /**
+ * Reloads the workspace items from the DB and re-binds the workspace. This should generally not
+ * be called as DB updates are automatically followed by UI update
+ */
+ fun forceReload() {
+ synchronized(mLock) {
+ // Stop any existing loaders first, so they don't set mModelLoaded to true later
+ stopLoader()
+ mModelLoaded = false
+ }
+ rebindCallbacks()
+ }
+
+ /** Rebinds all existing callbacks with already loaded model */
+ fun rebindCallbacks() {
+ if (hasCallbacks()) {
+ startLoader()
+ }
+ }
+
+ /** Removes an existing callback */
+ fun removeCallbacks(callbacks: BgDataModel.Callbacks) {
+ synchronized(mCallbacksList) {
+ Preconditions.assertUIThread()
+ if (mCallbacksList.remove(callbacks)) {
+ if (stopLoader()) {
+ // Rebind existing callbacks
+ startLoader()
+ }
+ }
+ }
+ }
+
+ /**
+ * Adds a callbacks to receive model updates
+ *
+ * @return true if workspace load was performed synchronously
+ */
+ fun addCallbacksAndLoad(callbacks: BgDataModel.Callbacks): Boolean {
+ synchronized(mLock) {
+ addCallbacks(callbacks)
+ return startLoader(arrayOf(callbacks))
+ }
+ }
+
+ /** Adds a callbacks to receive model updates */
+ fun addCallbacks(callbacks: BgDataModel.Callbacks) {
+ Preconditions.assertUIThread()
+ synchronized(mCallbacksList) { mCallbacksList.add(callbacks) }
+ }
+
+ /**
+ * Starts the loader. Tries to bind {@params synchronousBindPage} synchronously if possible.
+ *
+ * @return true if the page could be bound synchronously.
+ */
+ fun startLoader() = startLoader(arrayOf())
+
+ private fun startLoader(newCallbacks: Array): Boolean {
+ // Enable queue before starting loader. It will get disabled in Launcher#finishBindingItems
+ ItemInstallQueue.INSTANCE.get(context).pauseModelPush(ItemInstallQueue.FLAG_LOADER_RUNNING)
+ synchronized(mLock) {
+ // If there is already one running, tell it to stop.
+ val wasRunning = stopLoader()
+ val bindDirectly = mModelLoaded && !mIsLoaderTaskRunning
+ val bindAllCallbacks = wasRunning || !bindDirectly || newCallbacks.isEmpty()
+ val callbacksList = if (bindAllCallbacks) callbacks else newCallbacks
+ if (callbacksList.isNotEmpty()) {
+ // Clear any pending bind-runnables from the synchronized load process.
+ callbacksList.forEach { MAIN_EXECUTOR.execute(it::clearPendingBinds) }
+
+ val launcherBinder =
+ BaseLauncherBinder(mApp, mBgDataModel, mBgAllAppsList, callbacksList)
+ if (bindDirectly) {
+ // Divide the set of loaded items into those that we are binding synchronously,
+ // and everything else that is to be bound normally (asynchronously).
+ launcherBinder.bindWorkspace(bindAllCallbacks, /* isBindSync= */ true)
+ // For now, continue posting the binding of AllApps as there are other
+ // issues that arise from that.
+ launcherBinder.bindAllApps()
+ launcherBinder.bindDeepShortcuts()
+ launcherBinder.bindWidgets()
+ return true
+ } else {
+ mLoaderTask =
+ LoaderTask(
+ mApp,
+ mBgAllAppsList,
+ mBgDataModel,
+ this.modelDelegate,
+ launcherBinder,
+ widgetsFilterDataProvider,
+ )
+
+ // Always post the loader task, instead of running directly
+ // (even on same thread) so that we exit any nested synchronized blocks
+ MODEL_EXECUTOR.post(mLoaderTask)
+ }
+ }
+ }
+ return false
+ }
+
+ /**
+ * If there is already a loader task running, tell it to stop.
+ *
+ * @return true if an existing loader was stopped.
+ */
+ private fun stopLoader(): Boolean {
+ synchronized(mLock) {
+ val oldTask: LoaderTask? = mLoaderTask
+ mLoaderTask = null
+ if (oldTask != null) {
+ oldTask.stopLocked()
+ return true
+ }
+ return false
+ }
+ }
+
+ /**
+ * Loads the model if not loaded
+ *
+ * @param callback called with the data model upon successful load or null on model thread.
+ */
+ fun loadAsync(callback: Consumer) {
+ synchronized(mLock) {
+ if (!mModelLoaded && !mIsLoaderTaskRunning) {
+ startLoader()
+ }
+ }
+ MODEL_EXECUTOR.post { callback.accept(if (isModelLoaded()) mBgDataModel else null) }
+ }
+
+ inner class LoaderTransaction(task: LoaderTask) : AutoCloseable {
+ private var mTask: LoaderTask? = null
+
+ init {
+ synchronized(mLock) {
+ if (mLoaderTask !== task) {
+ throw CancellationException("Loader already stopped")
+ }
+ this@LauncherModel.lastLoadId++
+ mTask = task
+ mIsLoaderTaskRunning = true
+ mModelLoaded = false
+ }
+ }
+
+ fun commit() {
+ synchronized(mLock) {
+ // Everything loaded bind the data.
+ mModelLoaded = true
+ }
+ }
+
+ override fun close() {
+ synchronized(mLock) {
+ // If we are still the last one to be scheduled, remove ourselves.
+ if (mLoaderTask === mTask) {
+ mLoaderTask = null
+ }
+ mIsLoaderTaskRunning = false
+ }
+ }
+ }
+
+ @Throws(CancellationException::class)
+ fun beginLoader(task: LoaderTask) = LoaderTransaction(task)
+
+ /**
+ * Refreshes the cached shortcuts if the shortcut permission has changed. Current implementation
+ * simply reloads the workspace, but it can be optimized to use partial updates similar to
+ * [UserCache]
+ */
+ fun validateModelDataOnResume() {
+ MODEL_EXECUTOR.handler.removeCallbacks(mDataValidationCheck)
+ MODEL_EXECUTOR.post(mDataValidationCheck)
+ }
+
+ /** Called when the icons for packages have been updated in the icon cache. */
+ fun onPackageIconsUpdated(updatedPackages: HashSet, user: UserHandle) {
+ // If any package icon has changed (app was updated while launcher was dead),
+ // update the corresponding shortcuts.
+ enqueueModelUpdateTask(
+ CacheDataUpdatedTask(CacheDataUpdatedTask.OP_CACHE_UPDATE, user, updatedPackages)
+ )
+ }
+
+ /** Called when the labels for the widgets has updated in the icon cache. */
+ fun onWidgetLabelsUpdated(updatedPackages: HashSet, user: UserHandle) {
+ enqueueModelUpdateTask { taskController, dataModel, _ ->
+ dataModel.widgetsModel.onPackageIconsUpdated(updatedPackages, user, mApp)
+ taskController.bindUpdatedWidgets(dataModel)
+ }
+ }
+
+ /** Called when the widget filters are refreshed and available to bind to the model. */
+ fun onWidgetFiltersLoaded() {
+ enqueueModelUpdateTask { taskController, dataModel, _ ->
+ dataModel.widgetsModel.updateWidgetFilters(widgetsFilterDataProvider)
+ taskController.bindUpdatedWidgets(dataModel)
+ }
+ }
+
+ fun enqueueModelUpdateTask(task: ModelUpdateTask) {
+ if (mModelDestroyed) {
+ return
+ }
+ MODEL_EXECUTOR.execute {
+ if (!isModelLoaded()) {
+ // Loader has not yet run.
+ return@execute
+ }
+ task.execute(
+ ModelTaskController(mApp, mBgDataModel, mBgAllAppsList, this, MAIN_EXECUTOR),
+ mBgDataModel,
+ mBgAllAppsList,
+ )
+ }
+ }
+
+ /**
+ * A task to be executed on the current callbacks on the UI thread. If there is no current
+ * callbacks, the task is ignored.
+ */
+ fun interface CallbackTask {
+ fun execute(callbacks: BgDataModel.Callbacks)
+ }
+
+ fun interface ModelUpdateTask {
+ fun execute(taskController: ModelTaskController, dataModel: BgDataModel, apps: AllAppsList)
+ }
+
+ fun updateAndBindWorkspaceItem(si: WorkspaceItemInfo, info: ShortcutInfo) {
+ enqueueModelUpdateTask { taskController, _, _ ->
+ si.updateFromDeepShortcutInfo(info, context)
+ iconCache.getShortcutIcon(si, info)
+ taskController.getModelWriter().updateItemInDatabase(si)
+ taskController.bindUpdatedWorkspaceItems(listOf(si))
+ }
+ }
+
+ fun refreshAndBindWidgetsAndShortcuts(packageUser: PackageUserKey?) {
+ enqueueModelUpdateTask { taskController, dataModel, _ ->
+ dataModel.widgetsModel.update(taskController.app, packageUser)
+ taskController.bindUpdatedWidgets(dataModel)
+ }
+ }
+
+ fun dumpState(prefix: String?, fd: FileDescriptor?, writer: PrintWriter, args: Array) {
+ if (args.isNotEmpty() && TextUtils.equals(args[0], "--all")) {
+ writer.println(prefix + "All apps list: size=" + mBgAllAppsList.data.size)
+ for (info in mBgAllAppsList.data) {
+ writer.println(
+ "$prefix title=\"${info.title}\" bitmapIcon=${info.bitmap.icon} componentName=${info.targetPackage}"
+ )
+ }
+ writer.println()
+ }
+ modelDelegate.dump(prefix, fd, writer, args)
+ mBgDataModel.dump(prefix, fd, writer, args)
+ }
+
+ /** Returns true if there are any callbacks attached to the model */
+ fun hasCallbacks() = synchronized(mCallbacksList) { mCallbacksList.isNotEmpty() }
+
+ /** Returns an array of currently attached callbacks */
+ val callbacks: Array
+ get() {
+ synchronized(mCallbacksList) {
+ return mCallbacksList.toTypedArray()
+ }
+ }
+
+ companion object {
+ private const val DEBUG_RECEIVER = false
+
+ const val TAG = "Launcher.Model"
+ }
+}
diff --git a/src/com/android/launcher3/LauncherPrefChangeListener.java b/src/com/android/launcher3/LauncherPrefChangeListener.java
new file mode 100644
index 0000000000..3e9a8464d9
--- /dev/null
+++ b/src/com/android/launcher3/LauncherPrefChangeListener.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2024 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.content.SharedPreferences;
+import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
+
+/**
+ * Listener for changes in [LauncherPrefs].
+ *
+ * The listener also serves as an [OnSharedPreferenceChangeListener] where
+ * [onSharedPreferenceChanged] delegates to [onPrefChanged]. Overriding [onSharedPreferenceChanged]
+ * breaks compatibility with [SharedPreferences].
+ */
+public interface LauncherPrefChangeListener extends OnSharedPreferenceChangeListener {
+
+ /** Callback invoked when the preference for [key] has changed. */
+ void onPrefChanged(String key);
+
+ @Override
+ default void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
+ onPrefChanged(key);
+ }
+}
diff --git a/src/com/android/launcher3/LauncherPrefs.kt b/src/com/android/launcher3/LauncherPrefs.kt
index 13181e87f2..5b9c2fa3a4 100644
--- a/src/com/android/launcher3/LauncherPrefs.kt
+++ b/src/com/android/launcher3/LauncherPrefs.kt
@@ -18,15 +18,16 @@ package com.android.launcher3
import android.content.Context
import android.content.Context.MODE_PRIVATE
import android.content.SharedPreferences
-import android.content.SharedPreferences.OnSharedPreferenceChangeListener
import androidx.annotation.VisibleForTesting
import com.android.launcher3.BuildConfig.WIDGET_ON_FIRST_SCREEN
+import com.android.launcher3.InvariantDeviceProfile.GRID_NAME_PREFS_KEY
import com.android.launcher3.LauncherFiles.DEVICE_PREFERENCES_KEY
import com.android.launcher3.LauncherFiles.SHARED_PREFERENCES_KEY
import com.android.launcher3.model.DeviceGridState
import com.android.launcher3.pm.InstallSessionHelper
import com.android.launcher3.provider.RestoreDbTask
import com.android.launcher3.provider.RestoreDbTask.FIRST_LOAD_AFTER_RESTORE_KEY
+import com.android.launcher3.settings.SettingsActivity
import com.android.launcher3.states.RotationHelper
import com.android.launcher3.util.DisplayController
import com.android.launcher3.util.MainThreadInitializedObject
@@ -34,11 +35,184 @@ import com.android.launcher3.util.SafeCloseable
import com.android.launcher3.util.Themes
/**
- * Use same context for shared preferences, so that we use a single cached instance
+ * Manages Launcher [SharedPreferences] through [Item] instances.
*
* TODO(b/262721340): Replace all direct SharedPreference refs with LauncherPrefs / Item methods.
*/
-class LauncherPrefs(private val encryptedContext: Context) : SafeCloseable {
+abstract class LauncherPrefs : SafeCloseable {
+
+ /** Returns the value with type [T] for [item]. */
+ abstract fun get(item: ContextualItem): T
+
+ /** Returns the value with type [T] for [item]. */
+ abstract fun get(item: ConstantItem): T
+
+ /** Stores the values for each item in preferences. */
+ abstract fun put(vararg itemsToValues: Pair- )
+
+ /** Stores the [value] with type [T] for [item] in preferences. */
+ abstract fun put(item: Item, value: T)
+
+ /** Synchronous version of [put]. */
+ abstract fun putSync(vararg itemsToValues: Pair
- )
+
+ /** Registers [listener] for [items]. */
+ abstract fun addListener(listener: LauncherPrefChangeListener, vararg items: Item)
+
+ /** Unregisters [listener] for [items]. */
+ abstract fun removeListener(listener: LauncherPrefChangeListener, vararg items: Item)
+
+ /** Returns `true` iff all [items] have a value. */
+ abstract fun has(vararg items: Item): Boolean
+
+ /** Removes the value for each item in [items]. */
+ abstract fun remove(vararg items: Item)
+
+ /** Synchronous version of [remove]. */
+ abstract fun removeSync(vararg items: Item)
+
+ companion object {
+ @VisibleForTesting const val BOOT_AWARE_PREFS_KEY = "boot_aware_prefs"
+
+ @JvmField
+ var INSTANCE = MainThreadInitializedObject { LauncherPrefsImpl(it) }
+
+ @JvmStatic fun get(context: Context): LauncherPrefs = INSTANCE.get(context)
+
+ const val TASKBAR_PINNING_KEY = "TASKBAR_PINNING_KEY"
+ const val TASKBAR_PINNING_DESKTOP_MODE_KEY = "TASKBAR_PINNING_DESKTOP_MODE_KEY"
+ const val SHOULD_SHOW_SMARTSPACE_KEY = "SHOULD_SHOW_SMARTSPACE_KEY"
+ @JvmField
+ val ICON_STATE = nonRestorableItem("pref_icon_shape_path", "", EncryptionType.ENCRYPTED)
+
+ @JvmField
+ val ENABLE_TWOLINE_ALLAPPS_TOGGLE = backedUpItem("pref_enable_two_line_toggle", false)
+ @JvmField
+ val THEMED_ICONS = backedUpItem(Themes.KEY_THEMED_ICONS, false, EncryptionType.ENCRYPTED)
+ @JvmField val PROMISE_ICON_IDS = backedUpItem(InstallSessionHelper.PROMISE_ICON_IDS, "")
+ @JvmField val WORK_EDU_STEP = backedUpItem("showed_work_profile_edu", 0)
+ @JvmField
+ val WORKSPACE_SIZE =
+ backedUpItem(DeviceGridState.KEY_WORKSPACE_SIZE, "", EncryptionType.ENCRYPTED)
+ @JvmField
+ val HOTSEAT_COUNT =
+ backedUpItem(DeviceGridState.KEY_HOTSEAT_COUNT, -1, EncryptionType.ENCRYPTED)
+ @JvmField
+ val TASKBAR_PINNING =
+ backedUpItem(TASKBAR_PINNING_KEY, false, EncryptionType.DEVICE_PROTECTED)
+ @JvmField
+ val TASKBAR_PINNING_IN_DESKTOP_MODE =
+ backedUpItem(TASKBAR_PINNING_DESKTOP_MODE_KEY, true, EncryptionType.DEVICE_PROTECTED)
+
+ @JvmField
+ val DEVICE_TYPE =
+ backedUpItem(
+ DeviceGridState.KEY_DEVICE_TYPE,
+ InvariantDeviceProfile.TYPE_PHONE,
+ EncryptionType.ENCRYPTED,
+ )
+ @JvmField
+ val DB_FILE = backedUpItem(DeviceGridState.KEY_DB_FILE, "", EncryptionType.ENCRYPTED)
+ @JvmField
+ val SHOULD_SHOW_SMARTSPACE =
+ backedUpItem(
+ SHOULD_SHOW_SMARTSPACE_KEY,
+ WIDGET_ON_FIRST_SCREEN,
+ EncryptionType.DEVICE_PROTECTED,
+ )
+ @JvmField
+ val RESTORE_DEVICE =
+ backedUpItem(
+ RestoreDbTask.RESTORED_DEVICE_TYPE,
+ InvariantDeviceProfile.TYPE_PHONE,
+ EncryptionType.ENCRYPTED,
+ )
+ @JvmField
+ val NO_DB_FILES_RESTORED =
+ nonRestorableItem("no_db_files_restored", false, EncryptionType.DEVICE_PROTECTED)
+ @JvmField
+ val IS_FIRST_LOAD_AFTER_RESTORE =
+ nonRestorableItem(FIRST_LOAD_AFTER_RESTORE_KEY, false, EncryptionType.ENCRYPTED)
+ @JvmField val APP_WIDGET_IDS = backedUpItem(RestoreDbTask.APPWIDGET_IDS, "")
+ @JvmField val OLD_APP_WIDGET_IDS = backedUpItem(RestoreDbTask.APPWIDGET_OLD_IDS, "")
+
+ @JvmField
+ val GRID_NAME =
+ ConstantItem(
+ GRID_NAME_PREFS_KEY,
+ isBackedUp = true,
+ defaultValue = null,
+ encryptionType = EncryptionType.ENCRYPTED,
+ type = String::class.java,
+ )
+ @JvmField
+ val ALLOW_ROTATION =
+ backedUpItem(RotationHelper.ALLOW_ROTATION_PREFERENCE_KEY, Boolean::class.java) {
+ RotationHelper.getAllowRotationDefaultValue(DisplayController.INSTANCE.get(it).info)
+ }
+
+ @JvmField
+ val FIXED_LANDSCAPE_MODE = backedUpItem(SettingsActivity.FIXED_LANDSCAPE_MODE, false)
+
+ // Preferences for widget configurations
+ @JvmField
+ val RECONFIGURABLE_WIDGET_EDUCATION_TIP_SEEN =
+ backedUpItem("launcher.reconfigurable_widget_education_tip_seen", false)
+
+ @JvmStatic
+ fun backedUpItem(
+ sharedPrefKey: String,
+ defaultValue: T,
+ encryptionType: EncryptionType = EncryptionType.ENCRYPTED,
+ ): ConstantItem =
+ ConstantItem(sharedPrefKey, isBackedUp = true, defaultValue, encryptionType)
+
+ @JvmStatic
+ fun backedUpItem(
+ sharedPrefKey: String,
+ type: Class,
+ encryptionType: EncryptionType = EncryptionType.ENCRYPTED,
+ defaultValueFromContext: (c: Context) -> T,
+ ): ContextualItem =
+ ContextualItem(
+ sharedPrefKey,
+ isBackedUp = true,
+ defaultValueFromContext,
+ encryptionType,
+ type,
+ )
+
+ @JvmStatic
+ fun nonRestorableItem(
+ sharedPrefKey: String,
+ defaultValue: T,
+ encryptionType: EncryptionType = EncryptionType.ENCRYPTED,
+ ): ConstantItem =
+ ConstantItem(sharedPrefKey, isBackedUp = false, defaultValue, encryptionType)
+
+ @Deprecated("Don't use shared preferences directly. Use other LauncherPref methods.")
+ @JvmStatic
+ fun getPrefs(context: Context): SharedPreferences {
+ // Use application context for shared preferences, so we use single cached instance
+ return context.applicationContext.getSharedPreferences(
+ SHARED_PREFERENCES_KEY,
+ MODE_PRIVATE,
+ )
+ }
+
+ @Deprecated("Don't use shared preferences directly. Use other LauncherPref methods.")
+ @JvmStatic
+ fun getDevicePrefs(context: Context): SharedPreferences {
+ // Use application context for shared preferences, so we use a single cached instance
+ return context.applicationContext.getSharedPreferences(
+ DEVICE_PREFERENCES_KEY,
+ MODE_PRIVATE,
+ )
+ }
+ }
+}
+
+private class LauncherPrefsImpl(private val encryptedContext: Context) : LauncherPrefs() {
private val deviceProtectedStorageContext =
encryptedContext.createDeviceProtectedStorageContext()
@@ -54,11 +228,11 @@ class LauncherPrefs(private val encryptedContext: Context) : SafeCloseable {
else item.encryptedPrefs
/** Wrapper around `getInner` for a `ContextualItem` */
- fun get(item: ContextualItem): T =
+ override fun get(item: ContextualItem): T =
getInner(item, item.defaultValueFromContext(encryptedContext))
/** Wrapper around `getInner` for an `Item` */
- fun get(item: ConstantItem): T = getInner(item, item.defaultValue)
+ override fun get(item: ConstantItem): T = getInner(item, item.defaultValue)
/**
* Retrieves the value for an [Item] from [SharedPreferences]. It handles method typing via the
@@ -97,17 +271,17 @@ class LauncherPrefs(private val encryptedContext: Context) : SafeCloseable {
* prepareToPutValue(itemsToValues) for every distinct `SharedPreferences` file present in the
* provided item configurations.
*/
- fun put(vararg itemsToValues: Pair
- ): Unit =
+ override fun put(vararg itemsToValues: Pair
- ): Unit =
prepareToPutValues(itemsToValues).forEach { it.apply() }
/** See referenced `put` method above. */
- fun put(item: Item, value: T): Unit = put(item.to(value))
+ override fun put(item: Item, value: T): Unit = put(item.to(value))
/**
* Synchronously stores all the values provided according to their associated Item
* configuration.
*/
- fun putSync(vararg itemsToValues: Pair
- ): Unit =
+ override fun putSync(vararg itemsToValues: Pair
- ): Unit =
prepareToPutValues(itemsToValues).forEach { it.commit() }
/**
@@ -152,7 +326,7 @@ class LauncherPrefs(private val encryptedContext: Context) : SafeCloseable {
@Suppress("UNCHECKED_CAST")
private fun SharedPreferences.Editor.putValue(
item: Item,
- value: Any?
+ value: Any?,
): SharedPreferences.Editor =
when (item.type) {
String::class.java -> putString(item.sharedPrefKey, value as? String)
@@ -176,7 +350,7 @@ class LauncherPrefs(private val encryptedContext: Context) : SafeCloseable {
* `SharedPreferences` files associated with the provided list of items. The listener will need
* to filter update notifications so they don't activate for non-relevant updates.
*/
- fun addListener(listener: OnSharedPreferenceChangeListener, vararg items: Item) {
+ override fun addListener(listener: LauncherPrefChangeListener, vararg items: Item) {
items
.map { chooseSharedPreferences(it) }
.distinct()
@@ -187,7 +361,7 @@ class LauncherPrefs(private val encryptedContext: Context) : SafeCloseable {
* Stops the listener from getting notified of any more updates to any of the
* `SharedPreferences` files associated with any of the provided list of [Item].
*/
- fun removeListener(listener: OnSharedPreferenceChangeListener, vararg items: Item) {
+ override fun removeListener(listener: LauncherPrefChangeListener, vararg items: Item) {
// If a listener is not registered to a SharedPreference, unregistering it does nothing
items
.map { chooseSharedPreferences(it) }
@@ -199,7 +373,7 @@ class LauncherPrefs(private val encryptedContext: Context) : SafeCloseable {
* Checks if all the provided [Item] have values stored in their corresponding
* `SharedPreferences` files.
*/
- fun has(vararg items: Item): Boolean {
+ override fun has(vararg items: Item): Boolean {
items
.groupBy { chooseSharedPreferences(it) }
.forEach { (prefs, itemsSublist) ->
@@ -211,10 +385,10 @@ class LauncherPrefs(private val encryptedContext: Context) : SafeCloseable {
/**
* Asynchronously removes the [Item]'s value from its corresponding `SharedPreferences` file.
*/
- fun remove(vararg items: Item) = prepareToRemove(items).forEach { it.apply() }
+ override fun remove(vararg items: Item) = prepareToRemove(items).forEach { it.apply() }
/** Synchronously removes the [Item]'s value from its corresponding `SharedPreferences` file. */
- fun removeSync(vararg items: Item) = prepareToRemove(items).forEach { it.commit() }
+ override fun removeSync(vararg items: Item) = prepareToRemove(items).forEach { it.commit() }
/**
* Removes the key value pairs stored in `SharedPreferences` for each corresponding Item. If the
@@ -244,138 +418,6 @@ class LauncherPrefs(private val encryptedContext: Context) : SafeCloseable {
}
override fun close() {}
-
- companion object {
- @VisibleForTesting const val BOOT_AWARE_PREFS_KEY = "boot_aware_prefs"
-
- @JvmField var INSTANCE = MainThreadInitializedObject { LauncherPrefs(it) }
-
- @JvmStatic fun get(context: Context): LauncherPrefs = INSTANCE.get(context)
-
- const val TASKBAR_PINNING_KEY = "TASKBAR_PINNING_KEY"
- const val TASKBAR_PINNING_DESKTOP_MODE_KEY = "TASKBAR_PINNING_DESKTOP_MODE_KEY"
- const val SHOULD_SHOW_SMARTSPACE_KEY = "SHOULD_SHOW_SMARTSPACE_KEY"
- @JvmField
- val ICON_STATE = nonRestorableItem("pref_icon_shape_path", "", EncryptionType.ENCRYPTED)
-
- @JvmField
- val ENABLE_TWOLINE_ALLAPPS_TOGGLE = backedUpItem("pref_enable_two_line_toggle", false)
- @JvmField
- val THEMED_ICONS = backedUpItem(Themes.KEY_THEMED_ICONS, false, EncryptionType.ENCRYPTED)
- @JvmField val PROMISE_ICON_IDS = backedUpItem(InstallSessionHelper.PROMISE_ICON_IDS, "")
- @JvmField val WORK_EDU_STEP = backedUpItem("showed_work_profile_edu", 0)
- @JvmField
- val WORKSPACE_SIZE =
- backedUpItem(DeviceGridState.KEY_WORKSPACE_SIZE, "", EncryptionType.ENCRYPTED)
- @JvmField
- val HOTSEAT_COUNT =
- backedUpItem(DeviceGridState.KEY_HOTSEAT_COUNT, -1, EncryptionType.ENCRYPTED)
- @JvmField
- val TASKBAR_PINNING =
- backedUpItem(TASKBAR_PINNING_KEY, false, EncryptionType.DEVICE_PROTECTED)
- @JvmField
- val TASKBAR_PINNING_IN_DESKTOP_MODE =
- backedUpItem(TASKBAR_PINNING_DESKTOP_MODE_KEY, true, EncryptionType.DEVICE_PROTECTED)
-
- @JvmField
- val DEVICE_TYPE =
- backedUpItem(
- DeviceGridState.KEY_DEVICE_TYPE,
- InvariantDeviceProfile.TYPE_PHONE,
- EncryptionType.ENCRYPTED
- )
- @JvmField
- val DB_FILE = backedUpItem(DeviceGridState.KEY_DB_FILE, "", EncryptionType.ENCRYPTED)
- @JvmField
- val SHOULD_SHOW_SMARTSPACE =
- backedUpItem(
- SHOULD_SHOW_SMARTSPACE_KEY,
- WIDGET_ON_FIRST_SCREEN,
- EncryptionType.DEVICE_PROTECTED
- )
- @JvmField
- val RESTORE_DEVICE =
- backedUpItem(
- RestoreDbTask.RESTORED_DEVICE_TYPE,
- InvariantDeviceProfile.TYPE_PHONE,
- EncryptionType.ENCRYPTED
- )
- @JvmField
- val IS_FIRST_LOAD_AFTER_RESTORE =
- nonRestorableItem(FIRST_LOAD_AFTER_RESTORE_KEY, false, EncryptionType.ENCRYPTED)
- @JvmField val APP_WIDGET_IDS = backedUpItem(RestoreDbTask.APPWIDGET_IDS, "")
- @JvmField val OLD_APP_WIDGET_IDS = backedUpItem(RestoreDbTask.APPWIDGET_OLD_IDS, "")
- @JvmField
- val GRID_NAME =
- ConstantItem(
- "idp_grid_name",
- isBackedUp = true,
- defaultValue = null,
- encryptionType = EncryptionType.ENCRYPTED,
- type = String::class.java
- )
- @JvmField
- val ALLOW_ROTATION =
- backedUpItem(RotationHelper.ALLOW_ROTATION_PREFERENCE_KEY, Boolean::class.java) {
- RotationHelper.getAllowRotationDefaultValue(DisplayController.INSTANCE.get(it).info)
- }
-
- // Preferences for widget configurations
- @JvmField
- val RECONFIGURABLE_WIDGET_EDUCATION_TIP_SEEN =
- backedUpItem("launcher.reconfigurable_widget_education_tip_seen", false)
-
- @JvmStatic
- fun backedUpItem(
- sharedPrefKey: String,
- defaultValue: T,
- encryptionType: EncryptionType = EncryptionType.ENCRYPTED
- ): ConstantItem =
- ConstantItem(sharedPrefKey, isBackedUp = true, defaultValue, encryptionType)
-
- @JvmStatic
- fun backedUpItem(
- sharedPrefKey: String,
- type: Class,
- encryptionType: EncryptionType = EncryptionType.ENCRYPTED,
- defaultValueFromContext: (c: Context) -> T
- ): ContextualItem =
- ContextualItem(
- sharedPrefKey,
- isBackedUp = true,
- defaultValueFromContext,
- encryptionType,
- type
- )
-
- @JvmStatic
- fun nonRestorableItem(
- sharedPrefKey: String,
- defaultValue: T,
- encryptionType: EncryptionType = EncryptionType.ENCRYPTED
- ): ConstantItem =
- ConstantItem(sharedPrefKey, isBackedUp = false, defaultValue, encryptionType)
-
- @Deprecated("Don't use shared preferences directly. Use other LauncherPref methods.")
- @JvmStatic
- fun getPrefs(context: Context): SharedPreferences {
- // Use application context for shared preferences, so we use single cached instance
- return context.applicationContext.getSharedPreferences(
- SHARED_PREFERENCES_KEY,
- MODE_PRIVATE
- )
- }
-
- @Deprecated("Don't use shared preferences directly. Use other LauncherPref methods.")
- @JvmStatic
- fun getDevicePrefs(context: Context): SharedPreferences {
- // Use application context for shared preferences, so we use a single cached instance
- return context.applicationContext.getSharedPreferences(
- DEVICE_PREFERENCES_KEY,
- MODE_PRIVATE
- )
- }
- }
}
abstract class Item {
@@ -395,7 +437,7 @@ data class ConstantItem(
val defaultValue: T,
override val encryptionType: EncryptionType,
// The default value can be null. If so, the type needs to be explicitly stated, or else NPE
- override val type: Class = defaultValue!!::class.java
+ override val type: Class = defaultValue!!::class.java,
) : Item() {
fun get(c: Context): T = LauncherPrefs.get(c).get(this)
@@ -406,7 +448,7 @@ data class ContextualItem(
override val isBackedUp: Boolean,
private val defaultSupplier: (c: Context) -> T,
override val encryptionType: EncryptionType,
- override val type: Class
+ override val type: Class,
) : Item() {
private var default: T? = null
diff --git a/src/com/android/launcher3/LauncherRootView.java b/src/com/android/launcher3/LauncherRootView.java
index 7176733786..a5b95c75f1 100644
--- a/src/com/android/launcher3/LauncherRootView.java
+++ b/src/com/android/launcher3/LauncherRootView.java
@@ -10,7 +10,7 @@ import android.view.ViewDebug;
import android.view.WindowInsets;
import com.android.launcher3.graphics.SysUiScrim;
-import com.android.launcher3.statemanager.StatefulActivity;
+import com.android.launcher3.statemanager.StatefulContainer;
import com.android.launcher3.util.window.WindowManagerProxy;
import java.util.Collections;
@@ -20,7 +20,7 @@ public class LauncherRootView extends InsettableFrameLayout {
private final Rect mTempRect = new Rect();
- private final StatefulActivity mActivity;
+ private final StatefulContainer mStatefulContainer;
@ViewDebug.ExportedProperty(category = "launcher")
private static final List SYSTEM_GESTURE_EXCLUSION_RECT =
@@ -36,25 +36,29 @@ public class LauncherRootView extends InsettableFrameLayout {
public LauncherRootView(Context context, AttributeSet attrs) {
super(context, attrs);
- mActivity = StatefulActivity.fromContext(context);
+ mStatefulContainer = StatefulContainer.fromContext(context);
mSysUiScrim = new SysUiScrim(this);
}
private void handleSystemWindowInsets(Rect insets) {
// Update device profile before notifying the children.
- mActivity.getDeviceProfile().updateInsets(insets);
+ mStatefulContainer.getDeviceProfile().updateInsets(insets);
boolean resetState = !insets.equals(mInsets);
setInsets(insets);
if (resetState) {
- mActivity.getStateManager().reapplyState(true /* cancelCurrentAnimation */);
+ mStatefulContainer.getStateManager().reapplyState(true /* cancelCurrentAnimation */);
}
}
@Override
public WindowInsets onApplyWindowInsets(WindowInsets insets) {
- mActivity.handleConfigurationChanged(mActivity.getResources().getConfiguration());
+ mStatefulContainer.handleConfigurationChanged(
+ mStatefulContainer.getContext().getResources().getConfiguration());
+ return updateInsets(insets);
+ }
+ private WindowInsets updateInsets(WindowInsets insets) {
insets = WindowManagerProxy.INSTANCE.get(getContext())
.normalizeWindowInsets(getContext(), insets, mTempRect);
handleSystemWindowInsets(mTempRect);
@@ -72,7 +76,11 @@ public class LauncherRootView extends InsettableFrameLayout {
}
public void dispatchInsets() {
- mActivity.getDeviceProfile().updateInsets(mInsets);
+ if (isAttachedToWindow()) {
+ updateInsets(getRootWindowInsets());
+ } else {
+ mStatefulContainer.getDeviceProfile().updateInsets(mInsets);
+ }
super.setInsets(mInsets);
}
diff --git a/src/com/android/launcher3/LauncherSettings.java b/src/com/android/launcher3/LauncherSettings.java
index 87ac193397..1d2d1611de 100644
--- a/src/com/android/launcher3/LauncherSettings.java
+++ b/src/com/android/launcher3/LauncherSettings.java
@@ -16,8 +16,12 @@
package com.android.launcher3;
+import static android.util.Base64.NO_PADDING;
+import static android.util.Base64.NO_WRAP;
+
import android.database.sqlite.SQLiteDatabase;
import android.provider.BaseColumns;
+import android.util.Base64;
import androidx.annotation.NonNull;
@@ -354,8 +358,17 @@ public class LauncherSettings {
* Launcher settings
*/
public static final class Settings {
- public static final String LAYOUT_DIGEST_KEY = "launcher3.layout.provider.blob";
+ public static final String LAYOUT_PROVIDER_KEY = "launcher3.layout.provider";
public static final String LAYOUT_DIGEST_LABEL = "launcher-layout";
public static final String LAYOUT_DIGEST_TAG = "ignore";
+ public static final String BLOB_KEY_PREFIX = "blob://";
+
+ /**
+ * Creates a key to be used for {@link #LAYOUT_PROVIDER_KEY}
+ * @param digest byte[] representing the message digest for the blob handle
+ */
+ public static String createBlobProviderKey(byte[] digest) {
+ return BLOB_KEY_PREFIX + Base64.encodeToString(digest, NO_WRAP | NO_PADDING);
+ }
}
}
diff --git a/src/com/android/launcher3/LauncherState.java b/src/com/android/launcher3/LauncherState.java
index 102189b530..7d5e481432 100644
--- a/src/com/android/launcher3/LauncherState.java
+++ b/src/com/android/launcher3/LauncherState.java
@@ -375,8 +375,14 @@ public abstract class LauncherState implements BaseState {
}
public PageAlphaProvider getWorkspacePageAlphaProvider(Launcher launcher) {
- if ((this != NORMAL && this != HINT_STATE)
- || !launcher.getDeviceProfile().shouldFadeAdjacentWorkspaceScreens()) {
+ DeviceProfile dp = launcher.getDeviceProfile();
+ boolean shouldFadeAdjacentScreens = (this == NORMAL || this == HINT_STATE)
+ && dp.shouldFadeAdjacentWorkspaceScreens();
+ // Avoid showing adjacent screens behind handheld All Apps sheet.
+ if (Flags.allAppsSheetForHandheld() && dp.isPhone && this == ALL_APPS) {
+ shouldFadeAdjacentScreens = true;
+ }
+ if (!shouldFadeAdjacentScreens) {
return DEFAULT_ALPHA_PROVIDER;
}
final int centerPage = launcher.getWorkspace().getNextPage();
diff --git a/src/com/android/launcher3/ModelCallbacks.kt b/src/com/android/launcher3/ModelCallbacks.kt
index 496d517e9a..5d3252514e 100644
--- a/src/com/android/launcher3/ModelCallbacks.kt
+++ b/src/com/android/launcher3/ModelCallbacks.kt
@@ -252,8 +252,11 @@ class ModelCallbacks(private var launcher: Launcher) : BgDataModel.Callbacks {
PopupContainerWithArrow.dismissInvalidPopup(launcher)
}
- override fun bindAllWidgets(allWidgets: List) {
- launcher.widgetPickerDataProvider.setWidgets(allWidgets, /* defaultWidgets= */ listOf())
+ override fun bindAllWidgets(
+ allWidgets: List,
+ defaultWidgets: List,
+ ) {
+ launcher.widgetPickerDataProvider.setWidgets(allWidgets, defaultWidgets)
}
/** Returns the ids of the workspaces to bind. */
diff --git a/src/com/android/launcher3/PillColorPorovider.kt b/src/com/android/launcher3/PillColorPorovider.kt
new file mode 100644
index 0000000000..347c5d6581
--- /dev/null
+++ b/src/com/android/launcher3/PillColorPorovider.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2024 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.content.Context
+import android.database.ContentObserver
+import android.graphics.Paint
+import android.net.Uri
+import android.provider.Settings
+import com.android.launcher3.util.Executors.ORDERED_BG_EXECUTOR
+
+class PillColorProvider private constructor(c: Context) {
+ private val context = c.applicationContext
+
+ private val matchaUri by lazy { Settings.Secure.getUriFor(MATCHA_SETTING) }
+ var appTitlePillPaint = Paint()
+ private set
+
+ var appTitleTextPaint = Paint()
+ private set
+
+ private var isMatchaEnabledInternal = 0
+
+ var isMatchaEnabled = isMatchaEnabledInternal != 0
+
+ private val pillColorObserver =
+ object : ContentObserver(ORDERED_BG_EXECUTOR.handler) {
+ override fun onChange(selfChange: Boolean, uri: Uri?) {
+ if (uri == matchaUri) {
+ isMatchaEnabledInternal =
+ Settings.Secure.getInt(context.contentResolver, MATCHA_SETTING, 0)
+ isMatchaEnabled = isMatchaEnabledInternal != 0
+ }
+ }
+ }
+
+ fun registerObserver() {
+ context.contentResolver.registerContentObserver(matchaUri, false, pillColorObserver)
+ setup()
+ }
+
+ fun unregisterObserver() {
+ context.contentResolver.unregisterContentObserver(pillColorObserver)
+ }
+
+ fun setup() {
+ appTitlePillPaint.color =
+ context.resources.getColor(
+ R.color.material_color_surface_container_lowest,
+ context.theme,
+ )
+ appTitleTextPaint.color =
+ context.resources.getColor(R.color.material_color_on_surface, context.theme)
+ isMatchaEnabledInternal = Settings.Secure.getInt(context.contentResolver, MATCHA_SETTING, 0)
+ isMatchaEnabled = isMatchaEnabledInternal != 0
+ }
+
+ companion object {
+ private var INSTANCE: PillColorProvider? = null
+ private const val MATCHA_SETTING = "matcha_enable"
+
+ // TODO: Replace with a Dagger injection that is a singleton.
+ @JvmStatic
+ fun getInstance(context: Context): PillColorProvider {
+ if (INSTANCE == null) {
+ INSTANCE = PillColorProvider(context)
+ }
+ return INSTANCE!!
+ }
+ }
+}
diff --git a/src/com/android/launcher3/SecondaryDropTarget.java b/src/com/android/launcher3/SecondaryDropTarget.java
index 8d1e61f9ba..f4d3146ed5 100644
--- a/src/com/android/launcher3/SecondaryDropTarget.java
+++ b/src/com/android/launcher3/SecondaryDropTarget.java
@@ -22,7 +22,6 @@ import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.LauncherActivityInfo;
import android.content.pm.LauncherApps;
-import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.UserHandle;
@@ -44,7 +43,7 @@ import com.android.launcher3.logging.StatsLogManager.StatsLogger;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.ItemInfoWithIcon;
import com.android.launcher3.pm.UserCache;
-import com.android.launcher3.util.PackageManagerHelper;
+import com.android.launcher3.util.ApplicationInfoWrapper;
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
import java.net.URISyntaxException;
@@ -304,10 +303,11 @@ public class SecondaryDropTarget extends ButtonDropTarget implements OnAlarmList
.setData(Uri.fromParts("package", cn.getPackageName(), cn.getClassName()))
.putExtra(Intent.EXTRA_USER, info.user);
context.startActivity(i);
- FileLog.d(TAG, "start uninstall activity " + cn.getPackageName());
+ FileLog.d(TAG, "start uninstall activity from drop target " + cn.getPackageName());
return cn;
} catch (URISyntaxException e) {
- Log.e(TAG, "Failed to parse intent to start uninstall activity for item=" + info);
+ Log.e(TAG, "Failed to parse intent to start drop target uninstall activity for"
+ + " item=" + info);
return null;
}
}
@@ -342,9 +342,8 @@ public class SecondaryDropTarget extends ButtonDropTarget implements OnAlarmList
}
public void onLauncherResume() {
- // We use MATCH_UNINSTALLED_PACKAGES as the app can be on SD card as well.
- if (PackageManagerHelper.INSTANCE.get(mContext).getApplicationInfo(mPackageName,
- mDragObject.dragInfo.user, PackageManager.MATCH_UNINSTALLED_PACKAGES) == null) {
+ if (new ApplicationInfoWrapper(mContext, mPackageName, mDragObject.dragInfo.user)
+ .getInfo() == null) {
mDragObject.dragSource = mOriginal;
mOriginal.onDropCompleted(SecondaryDropTarget.this, mDragObject, true);
mStatsLogManager.logger().withInstanceId(mDragObject.logInstanceId)
diff --git a/src/com/android/launcher3/SessionCommitReceiver.java b/src/com/android/launcher3/SessionCommitReceiver.java
index 6168e41464..ea5eb8f284 100644
--- a/src/com/android/launcher3/SessionCommitReceiver.java
+++ b/src/com/android/launcher3/SessionCommitReceiver.java
@@ -73,8 +73,9 @@ public class SessionCommitReceiver extends BroadcastReceiver {
|| alreadyAddedPromiseIcon) {
FileLog.d(LOG,
String.format(Locale.ENGLISH,
- "Removing PromiseIcon for package: %s, install reason: %d,"
- + " alreadyAddedPromiseIcon: %s",
+ "Removing unneeded PromiseIcon for package: %s"
+ + ", install reason: %d,"
+ + " alreadyAddedPromiseIcon: %s",
info.getAppPackageName(),
info.getInstallReason(),
alreadyAddedPromiseIcon
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index f8ac48a7df..ebcb5da814 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -16,8 +16,6 @@
package com.android.launcher3;
-import static android.graphics.drawable.AdaptiveIconDrawable.getExtraInsetFraction;
-
import static com.android.launcher3.BuildConfig.WIDGET_ON_FIRST_SCREEN;
import static com.android.launcher3.Flags.enableSmartspaceAsAWidget;
import static com.android.launcher3.icons.BitmapInfo.FLAG_THEMED;
@@ -36,9 +34,6 @@ import android.content.pm.LauncherApps;
import android.content.pm.ShortcutInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.BlendMode;
-import android.graphics.BlendModeColorFilter;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.LightingColorFilter;
@@ -49,10 +44,8 @@ import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.AdaptiveIconDrawable;
-import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
-import android.graphics.drawable.InsetDrawable;
import android.os.Build;
import android.os.Build.VERSION_CODES;
import android.os.DeadObjectException;
@@ -83,9 +76,8 @@ import androidx.core.graphics.ColorUtils;
import com.android.launcher3.dragndrop.FolderAdaptiveIcon;
import com.android.launcher3.graphics.TintedDrawableSpan;
import com.android.launcher3.icons.BitmapInfo;
+import com.android.launcher3.icons.CacheableShortcutInfo;
import com.android.launcher3.icons.LauncherIcons;
-import com.android.launcher3.icons.ShortcutCachingLogic;
-import com.android.launcher3.icons.ThemedIconDrawable;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.ItemInfoWithIcon;
import com.android.launcher3.pm.ShortcutConfigActivityInfo;
@@ -420,6 +412,25 @@ public final class Utilities {
return mapRange(interpolator.getInterpolation(progress), toMin, toMax);
}
+ /**
+ * Maps t from one range to another range.
+ * @param t The value to map.
+ * @param fromMin The lower bound of the range that t is being mapped from.
+ * @param fromMax The upper bound of the range that t is being mapped from.
+ * @param toMin The lower bound of the range that t is being mapped to.
+ * @param toMax The upper bound of the range that t is being mapped to.
+ * @return The mapped value of t.
+ */
+ public static int mapToRange(int t, int fromMin, int fromMax, int toMin, int toMax,
+ Interpolator interpolator) {
+ if (fromMin == fromMax || toMin == toMax) {
+ Log.e(TAG, "mapToRange: range has 0 length");
+ return toMin;
+ }
+ float progress = getProgress(t, fromMin, fromMax);
+ return (int) mapRange(interpolator.getInterpolation(progress), toMin, toMax);
+ }
+
/** Bounds t between a lower and upper bound and maps the result to a range. */
public static float mapBoundToRange(float t, float lowerBound, float upperBound,
float toMin, float toMax, Interpolator interpolator) {
@@ -596,6 +607,14 @@ public final class Utilities {
return options;
}
+ /**
+ * Utility method to know if a device's primary language is English.
+ */
+ public static boolean isEnglishLanguage(Context context) {
+ return context.getResources().getConfiguration().locale.getLanguage()
+ .equals(Locale.ENGLISH.getLanguage());
+ }
+
/**
* Returns the full drawable for info as multiple layers of AdaptiveIconDrawable. The second
* drawable in the Pair is the badge used with the icon.
@@ -626,8 +645,7 @@ public final class Utilities {
if (activityInfo == null) {
return null;
}
- mainIcon = appState.getIconProvider().getIcon(
- activityInfo, appState.getInvariantDeviceProfile().fillResIconDpi);
+ mainIcon = appState.getIconCache().getFullResIcon(activityInfo.getActivityInfo());
} else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
List siList = ShortcutKey.fromItemInfo(info)
.buildRequest(context)
@@ -636,7 +654,7 @@ public final class Utilities {
return null;
} else {
ShortcutInfo si = siList.get(0);
- mainIcon = ShortcutCachingLogic.getIcon(context, si,
+ mainIcon = CacheableShortcutInfo.getIcon(context, si,
appState.getInvariantDeviceProfile().fillResIconDpi);
// Only fetch badge if the icon is on workspace
if (info.id != ItemInfo.NO_ID && badge == null) {
@@ -670,29 +688,19 @@ public final class Utilities {
return null;
}
- // Inject monochrome icon drawable
+ // Inject theme icon drawable
if (ATLEAST_T && useTheme) {
- result.mutate();
- int[] colors = ThemedIconDrawable.getColors(context);
- Drawable mono = result.getMonochrome();
-
- if (mono != null) {
- mono.setTint(colors[1]);
- } else if (info instanceof ItemInfoWithIcon iiwi) {
- // Inject a previously generated monochrome icon
- Bitmap monoBitmap = iiwi.bitmap.getMono();
- if (monoBitmap != null) {
- // Use BitmapDrawable instead of FastBitmapDrawable so that the colorState is
- // preserved in constantState
- mono = new BitmapDrawable(monoBitmap);
- mono.setColorFilter(new BlendModeColorFilter(colors[1], BlendMode.SRC_IN));
- // Inset the drawable according to the AdaptiveIconDrawable layers
- mono = new InsetDrawable(mono, getExtraInsetFraction() / 2);
+ try (LauncherIcons li = LauncherIcons.obtain(context)) {
+ if (li.getThemeController() != null) {
+ AdaptiveIconDrawable themed = li.getThemeController().createThemedAdaptiveIcon(
+ context,
+ result,
+ info instanceof ItemInfoWithIcon iiwi ? iiwi.bitmap : null);
+ if (themed != null) {
+ result = themed;
+ }
}
}
- if (mono != null) {
- result = new AdaptiveIconDrawable(new ColorDrawable(colors[0]), mono);
- }
}
if (badge == null) {
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 09e33be34d..1804dd67d2 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -16,6 +16,7 @@
package com.android.launcher3;
+import static com.android.launcher3.AbstractFloatingView.TYPE_WIDGET_RESIZE_FRAME;
import static com.android.launcher3.BubbleTextView.DISPLAY_FOLDER;
import static com.android.launcher3.Flags.enableSmartspaceRemovalToggle;
import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY;
@@ -115,6 +116,7 @@ import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.IntSet;
import com.android.launcher3.util.IntSparseArrayMap;
import com.android.launcher3.util.LauncherBindableItemsContainer;
+import com.android.launcher3.util.MSDLPlayerWrapper;
import com.android.launcher3.util.OverlayEdgeEffect;
import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.util.RunnableList;
@@ -132,6 +134,8 @@ import com.android.launcher3.widget.util.WidgetSizes;
import com.android.systemui.plugins.shared.LauncherOverlayManager.LauncherOverlayCallbacks;
import com.android.systemui.plugins.shared.LauncherOverlayManager.LauncherOverlayTouchProxy;
+import com.google.android.msdl.data.model.MSDLToken;
+
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
@@ -298,6 +302,8 @@ public class Workspace extends PagedView
private final StatsLogManager mStatsLogManager;
+ private final MSDLPlayerWrapper mMSDLPlayerWrapper;
+
/**
* Used to inflate the Workspace from XML.
*
@@ -330,6 +336,7 @@ public class Workspace extends PagedView
setMotionEventSplittingEnabled(true);
setOnTouchListener(new WorkspaceTouchListener(mLauncher, this));
mStatsLogManager = StatsLogManager.newInstance(context);
+ mMSDLPlayerWrapper = MSDLPlayerWrapper.INSTANCE.get(context);
}
@Override
@@ -1222,6 +1229,10 @@ public class Workspace extends PagedView
}
protected void onPageBeginTransition() {
+ // Widget resize frame doesn't receive events to close when talkback is enabled. For that
+ // case, close it here.
+ AbstractFloatingView.closeOpenViews(mLauncher, false, TYPE_WIDGET_RESIZE_FRAME);
+
super.onPageBeginTransition();
updateChildrenLayersEnabled();
}
@@ -2674,7 +2685,9 @@ public class Workspace extends PagedView
ItemInfo info = dragObject.dragInfo;
boolean userFolderPending = willCreateUserFolder(info, mDragOverView, false);
if (mDragMode == DRAG_MODE_NONE && userFolderPending) {
-
+ if (Flags.msdlFeedback()) {
+ mMSDLPlayerWrapper.playToken(MSDLToken.DRAG_INDICATOR_DISCRETE);
+ }
mFolderCreateBg = new PreviewBackground(getContext());
mFolderCreateBg.setup(mLauncher, mLauncher, null,
mDragOverView.getMeasuredWidth(), mDragOverView.getPaddingTop());
diff --git a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
index 814d1427ec..68a6e62f36 100644
--- a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
+++ b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
@@ -48,6 +48,8 @@ import com.android.launcher3.model.data.WorkspaceItemFactory;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.popup.ArrowPopup;
import com.android.launcher3.popup.PopupContainerWithArrow;
+import com.android.launcher3.shortcuts.DeepShortcutTextView;
+import com.android.launcher3.shortcuts.DeepShortcutView;
import com.android.launcher3.touch.ItemLongClickListener;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.IntSet;
@@ -104,11 +106,15 @@ public class LauncherAccessibilityDelegate extends BaseAccessibilityDelegate out) {
// If the request came from keyboard, do not add custom shortcuts as that is already
// exposed as a direct shortcut
- if (ShortcutUtil.supportsShortcuts(item)) {
+ if (isNotInShortcutMenu(host) && ShortcutUtil.supportsShortcuts(item)) {
out.add(mActions.get(DEEP_SHORTCUTS));
}
@@ -415,7 +421,6 @@ public class LauncherAccessibilityDelegate extends BaseAccessibilityDelegate
ScrimView.ScrimDrawingController {
- public static final FloatProperty> BOTTOM_SHEET_ALPHA =
- new FloatProperty<>("bottomSheetAlpha") {
- @Override
- public Float get(ActivityAllAppsContainerView> containerView) {
- return containerView.mBottomSheetAlpha;
- }
-
- @Override
- public void setValue(ActivityAllAppsContainerView> containerView, float v) {
- containerView.setBottomSheetAlpha(v);
- }
- };
-
public static final float PULL_MULTIPLIER = .02f;
public static final float FLING_VELOCITY_MULTIPLIER = 1200f;
protected static final String BUNDLE_KEY_CURRENT_PAGE = "launcher.allapps.current_page";
@@ -166,6 +154,7 @@ public class ActivityAllAppsContainerView
private final RectF mTmpRectF = new RectF();
protected AllAppsPagedView mViewPager;
protected FloatingHeaderView mHeader;
+ protected final List mAdditionalHeaderRows = new ArrayList<>();
protected View mBottomSheetBackground;
protected RecyclerViewFastScroller mFastScroller;
private ConstraintLayout mFastScrollLetterLayout;
@@ -191,8 +180,6 @@ public class ActivityAllAppsContainerView
private ScrimView mScrimView;
private int mHeaderColor;
private int mBottomSheetBackgroundColor;
- private float mBottomSheetAlpha = 1f;
- private boolean mForceBottomSheetVisible;
private int mTabsProtectionAlpha;
@Nullable private AllAppsTransitionController mAllAppsTransitionController;
@@ -278,6 +265,8 @@ public class ActivityAllAppsContainerView
getLayoutInflater().inflate(R.layout.all_apps_content, this);
mHeader = findViewById(R.id.all_apps_header);
+ mAdditionalHeaderRows.clear();
+ mAdditionalHeaderRows.addAll(getAdditionalHeaderRows());
mBottomSheetBackground = findViewById(R.id.bottom_sheet_background);
mBottomSheetHandleArea = findViewById(R.id.bottom_sheet_handle_area);
mSearchRecyclerView = findViewById(R.id.search_results_list_view);
@@ -296,10 +285,18 @@ public class ActivityAllAppsContainerView
// Add the search box above everything else in this container (if the flag is enabled,
// it's added to drag layer in onAttach instead).
addView(mSearchContainer);
+ // The search container is visually at the top of the all apps UI, and should thus be
+ // focused by default. It's added to end of the children list, so it needs to be
+ // explicitly marked as focused by default.
+ mSearchContainer.setFocusedByDefault(true);
}
mSearchUiManager = (SearchUiManager) mSearchContainer;
}
+ public List getAdditionalHeaderRows() {
+ return List.of();
+ }
+
@Override
protected void onFinishInflate() {
super.onFinishInflate();
@@ -318,8 +315,7 @@ public class ActivityAllAppsContainerView
0,
0 // Bottom left
};
- mBottomSheetBackgroundColor =
- Themes.getAttrColor(getContext(), R.attr.materialColorSurfaceDim);
+ mBottomSheetBackgroundColor = getContext().getColor(R.color.materialColorSurfaceDim);
updateBackgroundVisibility(mActivityContext.getDeviceProfile());
mSearchUiManager.initializeSearch(this);
}
@@ -347,20 +343,6 @@ public class ActivityAllAppsContainerView
return mSearchUiManager;
}
- public View getBottomSheetBackground() {
- return mBottomSheetBackground;
- }
-
- /**
- * Temporarily force the bottom sheet to be visible on non-tablets.
- *
- * @param force {@code true} means bottom sheet will be visible on phones until {@code reset()}.
- */
- public void forceBottomSheetVisible(boolean force) {
- mForceBottomSheetVisible = force;
- updateBackgroundVisibility(mActivityContext.getDeviceProfile());
- }
-
public View getSearchView() {
return mSearchContainer;
}
@@ -492,7 +474,7 @@ public class ActivityAllAppsContainerView
if (mHeader != null && mHeader.getVisibility() == VISIBLE) {
mHeader.reset(animate);
}
- forceBottomSheetVisible(false);
+ updateBackgroundVisibility(mActivityContext.getDeviceProfile());
// Reset the base recycler view after transitioning home.
updateHeaderScroll(0);
if (exitSearch) {
@@ -722,7 +704,7 @@ public class ActivityAllAppsContainerView
post(() -> mAH.get(AdapterHolder.WORK).applyPadding());
} else {
- mWorkManager.detachWorkModeSwitch();
+ mWorkManager.detachWorkUtilityViews();
mViewPager = null;
}
@@ -740,6 +722,8 @@ public class ActivityAllAppsContainerView
}
void setupHeader() {
+ mAdditionalHeaderRows.forEach(row -> mHeader.onPluginDisconnected(row));
+
mHeader.setVisibility(View.VISIBLE);
boolean tabsHidden = !mUsingTabs;
mHeader.setup(
@@ -757,6 +741,7 @@ public class ActivityAllAppsContainerView
adapterHolder.mRecyclerView.scrollToTop();
}
});
+ mAdditionalHeaderRows.forEach(row -> mHeader.onPluginConnected(row, mActivityContext));
removeCustomRules(mHeader);
if (isSearchBarFloating()) {
@@ -776,6 +761,22 @@ public class ActivityAllAppsContainerView
}
}
+ @Override
+ public void addChildrenForAccessibility(ArrayList arrayList) {
+ super.addChildrenForAccessibility(arrayList);
+ if (!Flags.floatingSearchBar()) {
+ // Searchbox container is visually at the top of the all apps UI but it's present in
+ // end of the children list.
+ // We need to move the searchbox to the top in a11y tree for a11y services to read the
+ // all apps screen in same as visual order.
+ arrayList.stream().filter(v -> v.getId() == R.id.search_container_all_apps)
+ .findFirst().ifPresent(v -> {
+ arrayList.remove(v);
+ arrayList.add(0, v);
+ });
+ }
+ }
+
protected void updateHeaderScroll(int scrolledOffset) {
float prog1 = Utilities.boundToRange((float) scrolledOffset / mHeaderThreshold, 0f, 1f);
int headerColor = getHeaderColor(prog1);
@@ -998,18 +999,13 @@ public class ActivityAllAppsContainerView
}
protected void updateBackgroundVisibility(DeviceProfile deviceProfile) {
- boolean visible = deviceProfile.isTablet || mForceBottomSheetVisible;
- mBottomSheetBackground.setVisibility(visible ? View.VISIBLE : View.GONE);
- // Note: For tablets, the opaque background and header protection are added in drawOnScrim.
+ mBottomSheetBackground.setVisibility(
+ deviceProfile.shouldShowAllAppsOnSheet() ? View.VISIBLE : View.GONE);
+ // Note: The opaque sheet background and header protection are added in drawOnScrim.
// For the taskbar entrypoint, the scrim is drawn by its abstract slide in view container,
// so its header protection is derived from this scrim instead.
}
- private void setBottomSheetAlpha(float alpha) {
- // Bottom sheet alpha is always 1 for tablets.
- mBottomSheetAlpha = mActivityContext.getDeviceProfile().isTablet ? 1f : alpha;
- }
-
@VisibleForTesting
public void onAppsUpdated() {
mHasWorkApps = Stream.of(mAllAppsStore.getApps())
@@ -1147,8 +1143,8 @@ public class ActivityAllAppsContainerView
applyAdapterSideAndBottomPaddings(grid);
MarginLayoutParams mlp = (MarginLayoutParams) getLayoutParams();
- // Ignore left/right insets on tablet because we are already centered in-screen.
- if (grid.isTablet) {
+ // Ignore left/right insets on bottom sheet because we are already centered in-screen.
+ if (grid.shouldShowAllAppsOnSheet()) {
mlp.leftMargin = mlp.rightMargin = 0;
} else {
mlp.leftMargin = insets.left;
@@ -1193,8 +1189,10 @@ public class ActivityAllAppsContainerView
super.dispatchDraw(canvas);
if (mNavBarScrimHeight > 0) {
- canvas.drawRect(0, getHeight() - mNavBarScrimHeight, getWidth(), getHeight(),
- mNavBarScrimPaint);
+ float left = (getWidth() - getWidth() / getScaleX()) / 2;
+ float top = getHeight() / 2f + (getHeight() / 2f - mNavBarScrimHeight) / getScaleY();
+ canvas.drawRect(left, top, getWidth() / getScaleX(),
+ top + mNavBarScrimHeight / getScaleY(), mNavBarScrimPaint);
}
}
@@ -1253,8 +1251,8 @@ public class ActivityAllAppsContainerView
/** Called in Launcher#bindStringCache() to update the UI when cache is updated. */
public void updateWorkUI() {
setDeviceManagementResources();
- if (mWorkManager.getWorkModeSwitch() != null) {
- mWorkManager.getWorkModeSwitch().updateStringFromCache();
+ if (mWorkManager.getWorkUtilityView() != null) {
+ mWorkManager.getWorkUtilityView().updateStringFromCache();
}
inflateWorkCardsIfNeeded();
}
@@ -1360,6 +1358,17 @@ public class ActivityAllAppsContainerView
invalidateHeader();
}
+ @Override
+ public void setScaleY(float scaleY) {
+ super.setScaleY(scaleY);
+ if (predictiveBackThreeButtonNav() && mNavBarScrimHeight > 0) {
+ // Call invalidate to prevent navbar scrim from scaling. The navbar scrim is drawn
+ // directly onto the canvas. To prevent it from being scaled with the canvas, there's a
+ // counter scale applied in dispatchDraw.
+ invalidate(20, getHeight() - mNavBarScrimHeight, getWidth(), getHeight());
+ }
+ }
+
/**
* Set {@link Animator.AnimatorListener} on {@link mAllAppsTransitionController} to observe
* animation of backing out of all apps search view to all apps view.
@@ -1394,7 +1403,7 @@ public class ActivityAllAppsContainerView
// Draw full background panel for tablets.
if (hasBottomSheet) {
mHeaderPaint.setColor(mBottomSheetBackgroundColor);
- mHeaderPaint.setAlpha((int) (255 * mBottomSheetAlpha));
+ mHeaderPaint.setAlpha(255);
mTmpRectF.set(
leftWithScale,
@@ -1577,8 +1586,8 @@ public class ActivityAllAppsContainerView
void applyPadding() {
if (mRecyclerView != null) {
int bottomOffset = 0;
- if (isWork() && mWorkManager.getWorkModeSwitch() != null) {
- bottomOffset = mInsets.bottom + mWorkManager.getWorkModeSwitch().getHeight();
+ if (isWork() && mWorkManager.getWorkUtilityView() != null) {
+ bottomOffset = mInsets.bottom + mWorkManager.getWorkUtilityView().getHeight();
} else if (isMain() && mPrivateProfileManager != null) {
Optional privateSpaceHeaderItem = mAppsList.getAdapterItems()
.stream()
diff --git a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
index df383bf9f6..b6ba2644fd 100644
--- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
+++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
@@ -124,6 +124,12 @@ public class AllAppsGridAdapter extends
getRowsNotForAccessibility(mApps.getAdapterItems().size() - 1);
}
+ @Override
+ public int getColumnCountForAccessibility(RecyclerView.Recycler recycler,
+ RecyclerView.State state) {
+ return mAppsPerRow;
+ }
+
@Override
public void onInitializeAccessibilityNodeInfoForItem(RecyclerView.Recycler recycler,
RecyclerView.State state, View host, AccessibilityNodeInfoCompat info) {
diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
index 4e1e95011b..51d1c9f028 100644
--- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
@@ -331,6 +331,9 @@ public class AllAppsRecyclerView extends FastScrollRecyclerView {
public void setLettersToScrollLayout(
List fastScrollSections) {
+ if (fastScrollSections.isEmpty()) {
+ return;
+ }
if (mLetterList != null) {
mLetterList.removeAllViews();
}
@@ -346,17 +349,12 @@ public class AllAppsRecyclerView extends FastScrollRecyclerView {
(LetterListTextView) LayoutInflater.from(context).inflate(
R.layout.fast_scroller_letter_list_text_view, mLetterList, false);
int viewId = View.generateViewId();
- textView.setId(viewId);
+ textView.apply(sectionInfo /* FastScrollSectionInfo */, viewId /* viewId */);
sectionInfo.setId(viewId);
- textView.setText(sectionInfo.sectionName);
if (i == fastScrollSections.size() - 1) {
// The last section info is just a duplicate so that user can scroll to the bottom.
textView.setVisibility(INVISIBLE);
}
- ConstraintLayout.LayoutParams lp = new ConstraintLayout.LayoutParams(
- MATCH_CONSTRAINT, WRAP_CONTENT);
- lp.dimensionRatio = "v,1:1";
- textView.setLayoutParams(lp);
textViews.add(textView);
mLetterList.addView(textView);
}
@@ -369,6 +367,8 @@ public class AllAppsRecyclerView extends FastScrollRecyclerView {
mLetterList.addView(lastLetterListTextView);
constraintTextViewsVertically(mLetterList, textViews);
mLetterList.setVisibility(VISIBLE);
+ // Set the alpha to 0 to avoid the letter list being shown when it shouldn't be.
+ mLetterList.setAlpha(0);
}
private void constraintTextViewsVertically(ConstraintLayout constraintLayout,
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index c6852e015c..8554de566d 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -16,7 +16,6 @@
package com.android.launcher3.allapps;
import static com.android.app.animation.Interpolators.DECELERATE_1_7;
-import static com.android.app.animation.Interpolators.INSTANT;
import static com.android.app.animation.Interpolators.LINEAR;
import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
@@ -28,7 +27,6 @@ import static com.android.launcher3.UtilitiesKt.CLIP_CHILDREN_FALSE_MODIFIER;
import static com.android.launcher3.UtilitiesKt.modifyAttributesOnViewTree;
import static com.android.launcher3.UtilitiesKt.restoreAttributesOnViewTree;
import static com.android.launcher3.anim.PropertySetter.NO_ANIM_PROPERTY_SETTER;
-import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_BOTTOM_SHEET_FADE;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_FADE;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_VERTICAL_PROGRESS;
import static com.android.launcher3.util.SystemUiController.FLAG_DARK_NAV;
@@ -45,9 +43,9 @@ import android.view.animation.Interpolator;
import androidx.annotation.FloatRange;
import androidx.annotation.Nullable;
-import com.android.app.animation.Interpolators;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
+import com.android.launcher3.Flags;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.R;
@@ -57,14 +55,16 @@ import com.android.launcher3.anim.PropertySetter;
import com.android.launcher3.statemanager.StateManager.StateHandler;
import com.android.launcher3.states.StateAnimationConfig;
import com.android.launcher3.touch.AllAppsSwipeController;
+import com.android.launcher3.util.MSDLPlayerWrapper;
import com.android.launcher3.util.MultiPropertyFactory;
import com.android.launcher3.util.MultiPropertyFactory.MultiProperty;
import com.android.launcher3.util.MultiValueAlpha;
import com.android.launcher3.util.ScrollableLayoutManager;
import com.android.launcher3.util.Themes;
-import com.android.launcher3.util.VibratorWrapper;
import com.android.launcher3.views.ScrimView;
+import com.google.android.msdl.data.model.MSDLToken;
+
/**
* Handles AllApps view transition.
* 1) Slides all apps view using direct manipulation
@@ -106,7 +106,7 @@ public class AllAppsTransitionController
@Override
public Float get(AllAppsTransitionController controller) {
- if (controller.mIsTablet) {
+ if (controller.mShouldShowAllAppsOnSheet) {
return controller.mAppsView.getActiveRecyclerView().getTranslationY();
} else {
return controller.getAppsViewPullbackTranslationY().getValue();
@@ -115,7 +115,7 @@ public class AllAppsTransitionController
@Override
public void setValue(AllAppsTransitionController controller, float translation) {
- if (controller.mIsTablet) {
+ if (controller.mShouldShowAllAppsOnSheet) {
controller.mAppsView.getActiveRecyclerView().setTranslationY(translation);
controller.getAppsViewPullbackTranslationY().setValue(
ALL_APPS_PULL_BACK_TRANSLATION_DEFAULT);
@@ -134,7 +134,7 @@ public class AllAppsTransitionController
@Override
public Float get(AllAppsTransitionController controller) {
- if (controller.mIsTablet) {
+ if (controller.mShouldShowAllAppsOnSheet) {
return controller.mAppsView.getActiveRecyclerView().getAlpha();
} else {
return controller.getAppsViewPullbackAlpha().getValue();
@@ -143,7 +143,7 @@ public class AllAppsTransitionController
@Override
public void setValue(AllAppsTransitionController controller, float alpha) {
- if (controller.mIsTablet) {
+ if (controller.mShouldShowAllAppsOnSheet) {
controller.mAppsView.getActiveRecyclerView().setAlpha(alpha);
controller.getAppsViewPullbackAlpha().setValue(
ALL_APPS_PULL_BACK_ALPHA_DEFAULT);
@@ -168,6 +168,7 @@ public class AllAppsTransitionController
@Nullable private Animator.AnimatorListener mAllAppsSearchBackAnimationListener;
private boolean mIsVerticalLayout;
+ private boolean mShouldShowAllAppsOnSheet;
// Animation in this class is controlled by a single variable {@link mProgress}.
// Visually, it represents top y coordinate of the all apps container if multiplied with
@@ -183,24 +184,22 @@ public class AllAppsTransitionController
private MultiValueAlpha mAppsViewAlpha;
private MultiPropertyFactory mAppsViewTranslationY;
- private boolean mIsTablet;
-
private boolean mHasScaleEffect;
- private final VibratorWrapper mVibratorWrapper;
+ private final MSDLPlayerWrapper mMSDLPlayerWrapper;
public AllAppsTransitionController(Launcher l) {
mLauncher = l;
DeviceProfile dp = mLauncher.getDeviceProfile();
mProgress = 1f;
mIsVerticalLayout = dp.isVerticalBarLayout();
- mIsTablet = dp.isTablet;
+ mShouldShowAllAppsOnSheet = dp.shouldShowAllAppsOnSheet();
mNavScrimFlag = Themes.getAttrBoolean(l, R.attr.isMainColorDark)
? FLAG_DARK_NAV : FLAG_LIGHT_NAV;
setShiftRange(dp.allAppsShiftRange);
mAllAppScale.value = 1;
mLauncher.addOnDeviceProfileChangeListener(this);
- mVibratorWrapper = VibratorWrapper.INSTANCE.get(mLauncher.getApplicationContext());
+ mMSDLPlayerWrapper = MSDLPlayerWrapper.INSTANCE.get(mLauncher.getApplicationContext());
}
public float getShiftRange() {
@@ -217,7 +216,7 @@ public class AllAppsTransitionController
mLauncher.getWorkspace().getPageIndicator().setTranslationY(0);
}
- mIsTablet = dp.isTablet;
+ mShouldShowAllAppsOnSheet = dp.shouldShowAllAppsOnSheet();
}
/**
@@ -280,10 +279,9 @@ public class AllAppsTransitionController
return;
}
- float deceleratedProgress = Interpolators.BACK_GESTURE.getInterpolation(backProgress);
float scaleProgress = ScrollableLayoutManager.PREDICTIVE_BACK_MIN_SCALE
+ (1 - ScrollableLayoutManager.PREDICTIVE_BACK_MIN_SCALE)
- * (1 - deceleratedProgress);
+ * (1 - backProgress);
mAllAppScale.updateValue(scaleProgress);
}
@@ -373,8 +371,16 @@ public class AllAppsTransitionController
setAlphas(toState, config, builder);
// This controls both haptics for tapping on QSB and going to all apps.
if (ALL_APPS.equals(toState) && mLauncher.isInState(NORMAL)) {
- mLauncher.getAppsView().performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY,
- HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
+ if (Flags.msdlFeedback()) {
+ if (config.isUserControlled()) {
+ mMSDLPlayerWrapper.playToken(MSDLToken.SWIPE_THRESHOLD_INDICATOR);
+ } else {
+ mMSDLPlayerWrapper.playToken(MSDLToken.TAP_HIGH_EMPHASIS);
+ }
+ } else {
+ mLauncher.getAppsView().performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY,
+ HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
+ }
}
}
@@ -395,10 +401,6 @@ public class AllAppsTransitionController
setter.setFloat(getAppsViewPullbackAlpha(), MultiPropertyFactory.MULTI_PROPERTY_VALUE,
hasAllAppsContent ? 1 : 0, allAppsFade);
- setter.setFloat(mLauncher.getAppsView(),
- ActivityAllAppsContainerView.BOTTOM_SHEET_ALPHA, hasAllAppsContent ? 1 : 0,
- config.getInterpolator(ANIM_ALL_APPS_BOTTOM_SHEET_FADE, INSTANT));
-
boolean shouldProtectHeader = !config.hasAnimationFlag(StateAnimationConfig.SKIP_SCRIM)
&& (ALL_APPS == state || mLauncher.getStateManager().getState() == ALL_APPS);
mScrimView.setDrawingController(shouldProtectHeader ? mAppsView : null);
diff --git a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
index 8e44d65d50..709b52a174 100644
--- a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
+++ b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
@@ -106,6 +106,7 @@ public class AlphabeticalAppsList implement
// The of ordered component names as a result of a search query
private final ArrayList mSearchResults = new ArrayList<>();
private final SpannableString mPrivateProfileAppScrollerBadge;
+ private final SpannableString mPrivateProfileDividerBadge;
private BaseAllAppsAdapter mAdapter;
private AppInfoComparator mAppNameComparator;
private int mNumAppsPerRowAllApps;
@@ -124,9 +125,14 @@ public class AlphabeticalAppsList implement
mAllAppsStore.addUpdateListener(this);
}
mPrivateProfileAppScrollerBadge = new SpannableString(" ");
- mPrivateProfileAppScrollerBadge.setSpan(new ImageSpan(context,
+ mPrivateProfileAppScrollerBadge.setSpan(new ImageSpan(context, Flags.letterFastScroller()
+ ? R.drawable.ic_private_profile_letter_list_fast_scroller_badge :
R.drawable.ic_private_profile_app_scroller_badge, ImageSpan.ALIGN_CENTER),
0, 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ mPrivateProfileDividerBadge = new SpannableString(" ");
+ mPrivateProfileDividerBadge.setSpan(new ImageSpan(context,
+ R.drawable.ic_private_profile_divider_badge, ImageSpan.ALIGN_CENTER),
+ 0, 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
/** Set the number of apps per row when device profile changes. */
@@ -404,6 +410,11 @@ public class AlphabeticalAppsList implement
// Add system apps separator.
if (Flags.privateSpaceSysAppsSeparation()) {
position = mPrivateProviderManager.addSystemAppsDivider(mAdapterItems);
+ if (Flags.letterFastScroller()) {
+ FastScrollSectionInfo sectionInfo =
+ new FastScrollSectionInfo(mPrivateProfileDividerBadge, position);
+ mFastScrollerSections.add(sectionInfo);
+ }
}
// Add system apps.
position = addAppsWithSections(split.get(false), position);
@@ -437,8 +448,11 @@ public class AlphabeticalAppsList implement
Log.d(TAG, "addAppsWithSections: adding sectionName: " + sectionName
+ " with appInfoTitle: " + info.title);
lastSectionName = sectionName;
- mFastScrollerSections.add(new FastScrollSectionInfo(hasPrivateApps ?
- mPrivateProfileAppScrollerBadge : sectionName, position));
+ boolean usePrivateAppScrollerBadge = !Flags.letterFastScroller() && hasPrivateApps;
+ FastScrollSectionInfo sectionInfo = new FastScrollSectionInfo(
+ usePrivateAppScrollerBadge ?
+ mPrivateProfileAppScrollerBadge : sectionName, position);
+ mFastScrollerSections.add(sectionInfo);
}
position++;
}
diff --git a/src/com/android/launcher3/allapps/FloatingHeaderView.java b/src/com/android/launcher3/allapps/FloatingHeaderView.java
index ac06ab40e0..81935119ef 100644
--- a/src/com/android/launcher3/allapps/FloatingHeaderView.java
+++ b/src/com/android/launcher3/allapps/FloatingHeaderView.java
@@ -15,6 +15,8 @@
*/
package com.android.launcher3.allapps;
+import static com.android.launcher3.allapps.FloatingHeaderRow.NO_ROWS;
+
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Point;
@@ -109,11 +111,11 @@ public class FloatingHeaderView extends LinearLayout implements
// This is initialized once during inflation and stays constant after that. Fixed views
// cannot be added or removed dynamically.
- private FloatingHeaderRow[] mFixedRows = FloatingHeaderRow.NO_ROWS;
+ private FloatingHeaderRow[] mFixedRows = NO_ROWS;
// Array of all fixed rows and plugin rows. This is initialized every time a plugin is
// enabled or disabled, and represent the current set of all rows.
- private FloatingHeaderRow[] mAllRows = FloatingHeaderRow.NO_ROWS;
+ private FloatingHeaderRow[] mAllRows = NO_ROWS;
public FloatingHeaderView(@NonNull Context context) {
this(context, null);
@@ -180,6 +182,10 @@ public class FloatingHeaderView extends LinearLayout implements
@Override
public void onPluginConnected(AllAppsRow allAppsRowPlugin, Context context) {
+ if (mPluginRows.containsKey(allAppsRowPlugin)) {
+ // Plugin has already been connected
+ return;
+ }
PluginHeaderRow headerRow = new PluginHeaderRow(allAppsRowPlugin, this);
addView(headerRow.mView, indexOfChild(mTabLayout));
mPluginRows.put(allAppsRowPlugin, headerRow);
@@ -211,6 +217,9 @@ public class FloatingHeaderView extends LinearLayout implements
@Override
public void onPluginDisconnected(AllAppsRow plugin) {
PluginHeaderRow row = mPluginRows.get(plugin);
+ if (row == null) {
+ return;
+ }
removeView(row.mView);
mPluginRows.remove(plugin);
recreateAllRowsArray();
diff --git a/src/com/android/launcher3/allapps/LetterListTextView.java b/src/com/android/launcher3/allapps/LetterListTextView.java
index 9326d79f18..e3fea3ce23 100644
--- a/src/com/android/launcher3/allapps/LetterListTextView.java
+++ b/src/com/android/launcher3/allapps/LetterListTextView.java
@@ -16,6 +16,9 @@
package com.android.launcher3.allapps;
+import static androidx.constraintlayout.widget.ConstraintSet.MATCH_CONSTRAINT;
+import static androidx.constraintlayout.widget.ConstraintSet.WRAP_CONTENT;
+
import android.content.Context;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
@@ -23,11 +26,11 @@ import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.widget.TextView;
+import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.core.graphics.ColorUtils;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
-import com.android.launcher3.util.Themes;
/**
* A TextView that is used to display the letter list in the fast scroller.
@@ -38,8 +41,6 @@ public class LetterListTextView extends TextView {
private final Drawable mLetterBackground;
private final int mLetterListTextWidthAndHeight;
private final int mTextColor;
- private final int mBackgroundColor;
- private final int mSelectedColor;
public LetterListTextView(Context context) {
this(context, null, 0);
@@ -54,9 +55,7 @@ public class LetterListTextView extends TextView {
mLetterBackground = context.getDrawable(R.drawable.bg_letter_list_text);
mLetterListTextWidthAndHeight = context.getResources().getDimensionPixelSize(
R.dimen.fastscroll_list_letter_size);
- mTextColor = Themes.getAttrColor(context, R.attr.materialColorOnSurface);
- mBackgroundColor = Themes.getAttrColor(context, R.attr.materialColorSurfaceContainer);
- mSelectedColor = Themes.getAttrColor(context, R.attr.materialColorOnSecondary);
+ mTextColor = context.getColor(R.color.materialColorOnSurface);
}
@Override
@@ -70,6 +69,20 @@ public class LetterListTextView extends TextView {
setVisibility(VISIBLE);
}
+ /**
+ * Applies a viewId to the letter list text view and sets the background and text based on the
+ * sectionInfo.
+ */
+ public void apply(AlphabeticalAppsList.FastScrollSectionInfo fastScrollSectionInfo,
+ int viewId) {
+ setId(viewId);
+ setText(fastScrollSectionInfo.sectionName);
+ ConstraintLayout.LayoutParams lp = new ConstraintLayout.LayoutParams(
+ MATCH_CONSTRAINT, WRAP_CONTENT);
+ lp.dimensionRatio = "v,1:1";
+ setLayoutParams(lp);
+ }
+
/**
* Animates the letter list text view based on the current finger position.
*
@@ -83,26 +96,11 @@ public class LetterListTextView extends TextView {
float cutOffMin = currentFingerY - (getHeight() * 2);
float cutOffMax = currentFingerY + (getHeight() * 2);
float cutOffDistance = cutOffMax - cutOffMin;
- // Update the background blend color
boolean isWithinAnimationBounds = getY() < cutOffMax && getY() > cutOffMin;
- if (isWithinAnimationBounds) {
- getBackground().setColorFilter(new PorterDuffColorFilter(
- getBlendColorBasedOnYPosition(currentFingerY, cutOffDistance),
- PorterDuff.Mode.MULTIPLY));
- } else {
- getBackground().setColorFilter(new PorterDuffColorFilter(
- mBackgroundColor, PorterDuff.Mode.MULTIPLY));
- }
translateBasedOnYPosition(currentFingerY, cutOffDistance, isWithinAnimationBounds);
scaleBasedOnYPosition(currentFingerY, cutOffDistance, isWithinAnimationBounds);
}
- private int getBlendColorBasedOnYPosition(int y, float cutOffDistance) {
- float raisedCosineBlend = (float) Math.cos(((y - getY()) / (cutOffDistance)) * Math.PI);
- float blendRatio = Utilities.boundToRange(raisedCosineBlend, 0f, 1f);
- return ColorUtils.blendARGB(mBackgroundColor, mSelectedColor, blendRatio);
- }
-
private void scaleBasedOnYPosition(int y, float cutOffDistance,
boolean isWithinAnimationBounds) {
float raisedCosineScale = (float) Math.cos(((y - getY()) / (cutOffDistance)) * Math.PI)
diff --git a/src/com/android/launcher3/allapps/PrivateProfileManager.java b/src/com/android/launcher3/allapps/PrivateProfileManager.java
index e215cabcd7..609edd2c99 100644
--- a/src/com/android/launcher3/allapps/PrivateProfileManager.java
+++ b/src/com/android/launcher3/allapps/PrivateProfileManager.java
@@ -45,6 +45,7 @@ import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.Intent;
+import android.os.Trace;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.Log;
@@ -66,6 +67,7 @@ import com.android.launcher3.BuildConfig;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Flags;
import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimatedPropertySetter;
import com.android.launcher3.anim.PropertySetter;
import com.android.launcher3.icons.BitmapInfo;
@@ -219,7 +221,8 @@ public class PrivateProfileManager extends UserProfileManager {
* when animation is not running.
*/
public void reset() {
- // Ensure the state of the header views is what it should be before animating.
+ Trace.beginSection("PrivateProfileManager#reset");
+ // Ensure the state of the header view is what it should be before animating.
updateView();
getMainRecyclerView().setChildAttachedConsumer(null);
int previousState = getCurrentState();
@@ -238,6 +241,7 @@ public class PrivateProfileManager extends UserProfileManager {
executeLock();
}
addPrivateSpaceDecorator(updatedState);
+ Trace.endSection();
}
/** Returns whether or not Private Space Settings Page is available. */
@@ -292,30 +296,11 @@ public class PrivateProfileManager extends UserProfileManager {
}
}
- @Override
public void setQuietMode(boolean enable) {
- UI_HELPER_EXECUTOR.post(() ->
- mUserCache.getUserProfiles()
- .stream()
- .filter(getUserMatcher())
- .findFirst()
- .ifPresent(userHandle -> setQuietModeSafely(enable, userHandle)));
+ setQuietMode(enable, mAllApps.mActivityContext);
mReadyToAnimate = true;
}
- /**
- * Sets Quiet Mode for Private Profile.
- * If {@link SecurityException} is thrown, prompts the user to set this launcher as HOME app.
- */
- private void setQuietModeSafely(boolean enable, UserHandle userHandle) {
- try {
- mUserManager.requestQuietModeEnabled(enable, userHandle);
- } catch (SecurityException ex) {
- ApiWrapper.INSTANCE.get(mAllApps.mActivityContext)
- .assignDefaultHomeRole(mAllApps.mActivityContext);
- }
- }
-
/**
* Expand the private space after the app list has been added and updated from
* {@link AlphabeticalAppsList#onAppsUpdated()}
@@ -330,7 +315,9 @@ public class PrivateProfileManager extends UserProfileManager {
/** Collapses the private space before the app list has been updated. */
void executeLock() {
+ Trace.beginSection("PrivateProfileManager#executeLock");
MAIN_EXECUTOR.execute(() -> updatePrivateStateAnimator(false));
+ Trace.endSection();
}
void setAnimationRunning(boolean isAnimationRunning) {
@@ -377,6 +364,7 @@ public class PrivateProfileManager extends UserProfileManager {
if (mPSHeader == null) {
return;
}
+ Trace.beginSection("PrivateProfileManager#updateView");
Log.d(TAG, "bindPrivateSpaceHeaderViewElements: " + "Updating view with state: "
+ getCurrentState());
mPSHeader.setAlpha(1);
@@ -434,6 +422,8 @@ public class PrivateProfileManager extends UserProfileManager {
lockPill.setVisibility(GONE);
}
}
+ mPSHeader.invalidate();
+ Trace.endSection();
}
/** Sets the enablement of the profile when header or button is clicked. */
@@ -473,7 +463,8 @@ public class PrivateProfileManager extends UserProfileManager {
break;
}
// Make the private space apps gone to "collapse".
- if (mFloatingMaskView == null && isPrivateSpaceItem(currentItem)) {
+ if ((mFloatingMaskView == null && isPrivateSpaceItem(currentItem)) ||
+ currentItem.viewType == VIEW_TYPE_PRIVATE_SPACE_SYS_APPS_DIVIDER) {
RecyclerView.ViewHolder viewHolder =
allAppsRecyclerView.findViewHolderForAdapterPosition(i);
if (viewHolder != null) {
@@ -699,7 +690,9 @@ public class PrivateProfileManager extends UserProfileManager {
mAllApps.mAH.get(MAIN).mRecyclerView.removeItemDecoration(
mPrivateAppsSectionDecorator);
// Call onAppsUpdated() because it may be canceled when this animation occurs.
- mAllApps.getPersonalAppList().onAppsUpdated();
+ if (!Utilities.isRunningInTestHarness()) {
+ mAllApps.getPersonalAppList().onAppsUpdated();
+ }
if (isPrivateSpaceHidden()) {
// TODO (b/325455879): Figure out if we can avoid this.
getMainRecyclerView().getAdapter().notifyDataSetChanged();
@@ -835,6 +828,7 @@ public class PrivateProfileManager extends UserProfileManager {
ActivityAllAppsContainerView>.AdapterHolder mainAdapterHolder = mAllApps.mAH.get(MAIN);
List adapterItems =
mainAdapterHolder.mAppsList.getAdapterItems();
+ Trace.beginSection("PrivateProfileManager#expandPrivateSpace");
if (Flags.enablePrivateSpace() && Flags.privateSpaceAnimation()
&& mAllApps.isPersonalTab()) {
// Animate the text and settings icon.
@@ -844,6 +838,7 @@ public class PrivateProfileManager extends UserProfileManager {
getPsHeaderHeight(), deviceProfile.allAppsCellHeightPx);
updatePrivateStateAnimator(true);
}
+ Trace.endSection();
}
private void exitSearchAndExpand() {
diff --git a/src/com/android/launcher3/allapps/SectionDecorationHandler.java b/src/com/android/launcher3/allapps/SectionDecorationHandler.java
index eaeb8bbdd3..cae76ec0a6 100644
--- a/src/com/android/launcher3/allapps/SectionDecorationHandler.java
+++ b/src/com/android/launcher3/allapps/SectionDecorationHandler.java
@@ -27,7 +27,6 @@ import android.view.View;
import androidx.annotation.Nullable;
import com.android.launcher3.R;
-import com.android.launcher3.util.Themes;
public class SectionDecorationHandler {
@@ -60,10 +59,8 @@ public class SectionDecorationHandler {
mContext = context;
mFillAlpha = fillAlpha;
- mFocusColor = Themes.getAttrColor(context,
- R.attr.materialColorSurfaceBright); // UX recommended
- mFillColor = Themes.getAttrColor(context,
- R.attr.materialColorSurfaceContainerHigh); // UX recommended
+ mFocusColor = context.getColor(R.color.materialColorSurfaceBright); // UX recommended
+ mFillColor = context.getColor(R.color.materialColorSurfaceContainerHigh); // UX recommended
mIsTopLeftRound = isTopLeftRound;
mIsTopRightRound = isTopRightRound;
diff --git a/src/com/android/launcher3/allapps/UserProfileManager.java b/src/com/android/launcher3/allapps/UserProfileManager.java
index 93b6b29b34..765c29cccc 100644
--- a/src/com/android/launcher3/allapps/UserProfileManager.java
+++ b/src/com/android/launcher3/allapps/UserProfileManager.java
@@ -18,6 +18,7 @@ package com.android.launcher3.allapps;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+import android.content.Context;
import android.os.UserHandle;
import android.os.UserManager;
@@ -26,6 +27,7 @@ import androidx.annotation.IntDef;
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.pm.UserCache;
+import com.android.launcher3.util.ApiWrapper;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -69,14 +71,26 @@ public abstract class UserProfileManager {
}
/** Sets quiet mode as enabled/disabled for the profile type. */
- protected void setQuietMode(boolean enabled) {
+ protected void setQuietMode(boolean enabled, Context context) {
UI_HELPER_EXECUTOR.post(() ->
mUserCache.getUserProfiles()
.stream()
.filter(getUserMatcher())
.findFirst()
.ifPresent(userHandle ->
- mUserManager.requestQuietModeEnabled(enabled, userHandle)));
+ setQuietModeSafely(enabled, userHandle, context)));
+ }
+
+ /**
+ * Sets Quiet Mode for Private Profile.
+ * If {@link SecurityException} is thrown, prompts the user to set this launcher as HOME app.
+ */
+ private void setQuietModeSafely(boolean enable, UserHandle userHandle, Context context) {
+ try {
+ mUserManager.requestQuietModeEnabled(enable, userHandle);
+ } catch (SecurityException ex) {
+ ApiWrapper.INSTANCE.get(context).assignDefaultHomeRole(context);
+ }
}
/** Sets current state for the profile type. */
diff --git a/src/com/android/launcher3/allapps/WorkModeSwitch.java b/src/com/android/launcher3/allapps/WorkModeSwitch.java
deleted file mode 100644
index 60495742fb..0000000000
--- a/src/com/android/launcher3/allapps/WorkModeSwitch.java
+++ /dev/null
@@ -1,203 +0,0 @@
-/*
- * Copyright (C) 2020 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.allapps;
-
-import android.content.Context;
-import android.graphics.Rect;
-import android.util.AttributeSet;
-import android.view.WindowInsets;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import androidx.annotation.NonNull;
-import androidx.core.graphics.Insets;
-import androidx.core.view.WindowInsetsCompat;
-
-import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.Insettable;
-import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.anim.KeyboardInsetAnimationCallback;
-import com.android.launcher3.model.StringCache;
-import com.android.launcher3.views.ActivityContext;
-/**
- * Work profile toggle switch shown at the bottom of AllApps work tab
- */
-public class WorkModeSwitch extends LinearLayout implements Insettable,
- KeyboardInsetAnimationCallback.KeyboardInsetListener {
-
- private static final int FLAG_FADE_ONGOING = 1 << 1;
- private static final int FLAG_TRANSLATION_ONGOING = 1 << 2;
- private static final int FLAG_PROFILE_TOGGLE_ONGOING = 1 << 3;
- private static final int SCROLL_THRESHOLD_DP = 10;
-
- private final Rect mInsets = new Rect();
- private final Rect mImeInsets = new Rect();
- private int mFlags;
- private final ActivityContext mActivityContext;
- private final Context mContext;
-
- // Threshold when user scrolls up/down to determine when should button extend/collapse
- private final int mScrollThreshold;
- private TextView mTextView;
-
-
- public WorkModeSwitch(@NonNull Context context) {
- this(context, null, 0);
- }
-
- public WorkModeSwitch(@NonNull Context context, @NonNull AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public WorkModeSwitch(@NonNull Context context, @NonNull AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- mContext = context;
- mScrollThreshold = Utilities.dpToPx(SCROLL_THRESHOLD_DP);
- mActivityContext = ActivityContext.lookupContext(getContext());
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
-
- mTextView = findViewById(R.id.pause_text);
- setSelected(true);
- KeyboardInsetAnimationCallback keyboardInsetAnimationCallback =
- new KeyboardInsetAnimationCallback(this);
- setWindowInsetsAnimationCallback(keyboardInsetAnimationCallback);
-
- setInsets(mActivityContext.getDeviceProfile().getInsets());
- updateStringFromCache();
- }
-
- @Override
- public void setInsets(Rect insets) {
- mInsets.set(insets);
- updateTranslationY();
- MarginLayoutParams lp = (MarginLayoutParams) getLayoutParams();
- if (lp != null) {
- int bottomMargin = getResources().getDimensionPixelSize(R.dimen.work_fab_margin_bottom);
- DeviceProfile dp = ActivityContext.lookupContext(getContext()).getDeviceProfile();
- if (mActivityContext.getAppsView().isSearchBarFloating()) {
- bottomMargin += dp.hotseatQsbHeight;
- }
-
- if (!dp.isGestureMode && dp.isTaskbarPresent) {
- bottomMargin += dp.taskbarHeight;
- }
-
- lp.bottomMargin = bottomMargin;
- }
- }
-
- @Override
- protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- super.onLayout(changed, left, top, right, bottom);
- boolean isRtl = Utilities.isRtl(getResources());
- int shift = mActivityContext.getDeviceProfile().getAllAppsIconStartMargin(mContext);
- setTranslationX(isRtl ? shift : -shift);
- }
-
- @Override
- public boolean isEnabled() {
- return super.isEnabled() && getVisibility() == VISIBLE && mFlags == 0;
- }
-
- public void animateVisibility(boolean visible) {
- clearAnimation();
- if (visible) {
- setFlag(FLAG_FADE_ONGOING);
- setVisibility(VISIBLE);
- extend();
- animate().alpha(1).withEndAction(() -> removeFlag(FLAG_FADE_ONGOING)).start();
- } else if (getVisibility() != GONE) {
- setFlag(FLAG_FADE_ONGOING);
- animate().alpha(0).withEndAction(() -> {
- removeFlag(FLAG_FADE_ONGOING);
- setVisibility(GONE);
- }).start();
- }
- }
-
- @Override
- public WindowInsets onApplyWindowInsets(WindowInsets insets) {
- WindowInsetsCompat windowInsetsCompat =
- WindowInsetsCompat.toWindowInsetsCompat(insets, this);
- if (windowInsetsCompat.isVisible(WindowInsetsCompat.Type.ime())) {
- setInsets(mImeInsets, windowInsetsCompat.getInsets(WindowInsetsCompat.Type.ime()));
- } else {
- mImeInsets.setEmpty();
- }
- updateTranslationY();
- return super.onApplyWindowInsets(insets);
- }
-
- void updateTranslationY() {
- setTranslationY(-mImeInsets.bottom);
- }
-
- @Override
- public void setTranslationY(float translationY) {
- // Always translate at least enough for nav bar insets.
- super.setTranslationY(Math.min(translationY, -mInsets.bottom));
- }
-
- private void setInsets(Rect rect, Insets insets) {
- rect.set(insets.left, insets.top, insets.right, insets.bottom);
- }
-
- public Rect getImeInsets() {
- return mImeInsets;
- }
-
- @Override
- public void onTranslationStart() {
- setFlag(FLAG_TRANSLATION_ONGOING);
- }
-
- @Override
- public void onTranslationEnd() {
- removeFlag(FLAG_TRANSLATION_ONGOING);
- }
-
- private void setFlag(int flag) {
- mFlags |= flag;
- }
-
- private void removeFlag(int flag) {
- mFlags &= ~flag;
- }
-
- public void extend() {
- mTextView.setVisibility(VISIBLE);
- }
-
- public void shrink(){
- mTextView.setVisibility(GONE);
- }
-
- public int getScrollThreshold() {
- return mScrollThreshold;
- }
-
- public void updateStringFromCache(){
- StringCache cache = mActivityContext.getStringCache();
- if (cache != null) {
- mTextView.setText(cache.workProfilePauseButton);
- }
- }
-}
diff --git a/src/com/android/launcher3/allapps/WorkPausedCard.java b/src/com/android/launcher3/allapps/WorkPausedCard.java
index e1eeabe258..a14ac9819a 100644
--- a/src/com/android/launcher3/allapps/WorkPausedCard.java
+++ b/src/com/android/launcher3/allapps/WorkPausedCard.java
@@ -79,7 +79,6 @@ public class WorkPausedCard extends LinearLayout implements View.OnClickListener
@Override
public void onClick(View view) {
- setEnabled(false);
mActivityContext.getAppsView().getWorkManager().setWorkProfileEnabled(true);
mActivityContext.getStatsLogManager().logger().log(LAUNCHER_TURN_ON_WORK_APPS_TAP);
}
diff --git a/src/com/android/launcher3/allapps/WorkProfileManager.java b/src/com/android/launcher3/allapps/WorkProfileManager.java
index 96998a3e38..6d7d1930fd 100644
--- a/src/com/android/launcher3/allapps/WorkProfileManager.java
+++ b/src/com/android/launcher3/allapps/WorkProfileManager.java
@@ -58,7 +58,7 @@ public class WorkProfileManager extends UserProfileManager
implements PersonalWorkSlidingTabStrip.OnActivePageChangedListener {
private static final String TAG = "WorkProfileManager";
private final ActivityAllAppsContainerView> mAllApps;
- private WorkModeSwitch mWorkModeSwitch;
+ private WorkUtilityView mWorkUtilityView;
private final Predicate mWorkProfileMatcher;
public WorkProfileManager(
@@ -74,20 +74,20 @@ public class WorkProfileManager extends UserProfileManager
*/
public void setWorkProfileEnabled(boolean enabled) {
updateCurrentState(STATE_TRANSITION);
- setQuietMode(!enabled);
+ setQuietMode(!enabled, mAllApps.mActivityContext);
}
@Override
public void onActivePageChanged(int page) {
- updateWorkFAB(page);
+ updateWorkUtilityViews(page);
}
- private void updateWorkFAB(int page) {
- if (mWorkModeSwitch != null) {
+ private void updateWorkUtilityViews(int page) {
+ if (mWorkUtilityView != null) {
if (page == MAIN || page == SEARCH) {
- mWorkModeSwitch.animateVisibility(false);
+ mWorkUtilityView.animateVisibility(false);
} else if (page == WORK && getCurrentState() == STATE_ENABLED) {
- mWorkModeSwitch.animateVisibility(true);
+ mWorkUtilityView.animateVisibility(true);
}
}
}
@@ -104,10 +104,10 @@ public class WorkProfileManager extends UserProfileManager
}
boolean isEnabled = !mAllApps.getAppsStore().hasModelFlag(quietModeFlag);
updateCurrentState(isEnabled ? STATE_ENABLED : STATE_DISABLED);
- if (mWorkModeSwitch != null) {
+ if (mWorkUtilityView != null) {
// reset the position of the button and clear IME insets.
- mWorkModeSwitch.getImeInsets().setEmpty();
- mWorkModeSwitch.updateTranslationY();
+ mWorkUtilityView.getImeInsets().setEmpty();
+ mWorkUtilityView.updateTranslationY();
}
}
@@ -116,54 +116,54 @@ public class WorkProfileManager extends UserProfileManager
if (getAH() != null) {
getAH().mAppsList.updateAdapterItems();
}
- if (mWorkModeSwitch != null) {
- updateWorkFAB(mAllApps.getCurrentPage());
+ if (mWorkUtilityView != null) {
+ updateWorkUtilityViews(mAllApps.getCurrentPage());
}
if (getCurrentState() == STATE_ENABLED) {
- attachWorkModeSwitch();
+ attachWorkUtilityViews();
} else if (getCurrentState() == STATE_DISABLED) {
- detachWorkModeSwitch();
+ detachWorkUtilityViews();
}
}
/**
* Creates and attaches for profile toggle button to {@link ActivityAllAppsContainerView}
*/
- public boolean attachWorkModeSwitch() {
+ public boolean attachWorkUtilityViews() {
if (!mAllApps.getAppsStore().hasModelFlag(
FLAG_HAS_SHORTCUT_PERMISSION | FLAG_QUIET_MODE_CHANGE_PERMISSION)) {
Log.e(TAG, "unable to attach work mode switch; Missing required permissions");
return false;
}
- if (mWorkModeSwitch == null) {
- mWorkModeSwitch = (WorkModeSwitch) mAllApps.getLayoutInflater().inflate(
- R.layout.work_mode_fab, mAllApps, false);
+ if (mWorkUtilityView == null) {
+ mWorkUtilityView = (WorkUtilityView) mAllApps.getLayoutInflater().inflate(
+ R.layout.work_mode_utility_view, mAllApps, false);
}
- if (mWorkModeSwitch.getParent() == null) {
- mAllApps.addView(mWorkModeSwitch);
+ if (mWorkUtilityView.getParent() == null) {
+ mAllApps.addView(mWorkUtilityView);
}
if (mAllApps.getCurrentPage() != WORK) {
- mWorkModeSwitch.animateVisibility(false);
+ mWorkUtilityView.animateVisibility(false);
}
if (getAH() != null) {
getAH().applyPadding();
}
- mWorkModeSwitch.setOnClickListener(this::onWorkFabClicked);
+ mWorkUtilityView.getWorkFAB().setOnClickListener(this::onWorkFabClicked);
return true;
}
/**
* Removes work profile toggle button from {@link ActivityAllAppsContainerView}
*/
- public void detachWorkModeSwitch() {
- if (mWorkModeSwitch != null && mWorkModeSwitch.getParent() == mAllApps) {
- mAllApps.removeView(mWorkModeSwitch);
+ public void detachWorkUtilityViews() {
+ if (mWorkUtilityView != null && mWorkUtilityView.getParent() == mAllApps) {
+ mAllApps.removeView(mWorkUtilityView);
}
- mWorkModeSwitch = null;
+ mWorkUtilityView = null;
}
@Nullable
- public WorkModeSwitch getWorkModeSwitch() {
- return mWorkModeSwitch;
+ public WorkUtilityView getWorkUtilityView() {
+ return mWorkUtilityView;
}
private ActivityAllAppsContainerView.AdapterHolder getAH() {
@@ -199,7 +199,7 @@ public class WorkProfileManager extends UserProfileManager
}
private void onWorkFabClicked(View view) {
- if (getCurrentState() == STATE_ENABLED && mWorkModeSwitch.isEnabled()) {
+ if (getCurrentState() == STATE_ENABLED && mWorkUtilityView.isEnabled()) {
logEvents(LAUNCHER_TURN_OFF_WORK_APPS_TAP);
setWorkProfileEnabled(false);
}
@@ -216,7 +216,7 @@ public class WorkProfileManager extends UserProfileManager
}
@Override
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
- WorkModeSwitch fab = getWorkModeSwitch();
+ WorkUtilityView fab = getWorkUtilityView();
if (fab == null){
return;
}
diff --git a/src/com/android/launcher3/allapps/WorkUtilityView.java b/src/com/android/launcher3/allapps/WorkUtilityView.java
new file mode 100644
index 0000000000..b263639c86
--- /dev/null
+++ b/src/com/android/launcher3/allapps/WorkUtilityView.java
@@ -0,0 +1,414 @@
+/*
+ * Copyright (C) 2020 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.allapps;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Rect;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowInsets;
+import android.widget.ImageButton;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
+import androidx.core.graphics.Insets;
+import androidx.core.view.WindowInsetsCompat;
+
+import com.android.app.animation.Interpolators;
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Flags;
+import com.android.launcher3.Insettable;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.anim.AnimatedPropertySetter;
+import com.android.launcher3.anim.KeyboardInsetAnimationCallback;
+import com.android.launcher3.model.StringCache;
+import com.android.launcher3.views.ActivityContext;
+
+import java.util.ArrayList;
+
+/**
+ * Work profile utility ViewGroup that is shown at the bottom of AllApps work tab
+ */
+public class WorkUtilityView extends LinearLayout implements Insettable,
+ KeyboardInsetAnimationCallback.KeyboardInsetListener {
+
+ private static final int TEXT_EXPAND_OPACITY_DURATION = 300;
+ private static final int TEXT_COLLAPSE_OPACITY_DURATION = 50;
+ private static final int EXPAND_COLLAPSE_DURATION = 300;
+ private static final int TEXT_ALPHA_EXPAND_DELAY = 80;
+ private static final int TEXT_ALPHA_COLLAPSE_DELAY = 0;
+ private static final int WORK_SCHEDULER_OPACITY_DURATION =
+ (int) (EXPAND_COLLAPSE_DURATION * 0.75f);
+ private static final int FLAG_FADE_ONGOING = 1 << 1;
+ private static final int FLAG_TRANSLATION_ONGOING = 1 << 2;
+ private static final int FLAG_IS_EXPAND = 1 << 3;
+ private static final int SCROLL_THRESHOLD_DP = 10;
+ private static final float WORK_SCHEDULER_SCALE_MIN = 0.25f;
+ private static final float WORK_SCHEDULER_SCALE_MAX = 1f;
+
+ private final Rect mInsets = new Rect();
+ private final Rect mImeInsets = new Rect();
+ private int mFlags;
+ private final ActivityContext mActivityContext;
+ private final Context mContext;
+ private final int mTextMarginStart;
+ private final int mTextMarginEnd;
+ private final int mIconMarginStart;
+ private final String mWorkSchedulerIntentAction;
+
+ // Threshold when user scrolls up/down to determine when should button extend/collapse
+ private final int mScrollThreshold;
+ private ValueAnimator mPauseFABAnim;
+ private View mWorkFAB;
+ private TextView mPauseText;
+ private ImageView mWorkIcon;
+ private ImageButton mSchedulerButton;
+
+ public WorkUtilityView(@NonNull Context context) {
+ this(context, null, 0);
+ }
+
+ public WorkUtilityView(@NonNull Context context, @NonNull AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public WorkUtilityView(@NonNull Context context, @NonNull AttributeSet attrs,
+ int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ mContext = context;
+ mScrollThreshold = Utilities.dpToPx(SCROLL_THRESHOLD_DP);
+ mActivityContext = ActivityContext.lookupContext(getContext());
+ mTextMarginStart = mContext.getResources().getDimensionPixelSize(
+ R.dimen.work_fab_text_start_margin);
+ mTextMarginEnd = mContext.getResources().getDimensionPixelSize(
+ R.dimen.work_fab_text_end_margin);
+ mIconMarginStart = mContext.getResources().getDimensionPixelSize(
+ R.dimen.work_fab_icon_start_margin_expanded);
+ mWorkSchedulerIntentAction = mContext.getResources().getString(
+ R.string.work_profile_scheduler_intent);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+
+ mPauseText = findViewById(R.id.pause_text);
+ mWorkIcon = findViewById(R.id.work_icon);
+ mWorkFAB = findViewById(R.id.work_mode_toggle);
+ mSchedulerButton = findViewById(R.id.work_scheduler);
+ setSelected(true);
+ KeyboardInsetAnimationCallback keyboardInsetAnimationCallback =
+ new KeyboardInsetAnimationCallback(this);
+ setWindowInsetsAnimationCallback(keyboardInsetAnimationCallback);
+ // Expand is the default state upon initialization.
+ addFlag(FLAG_IS_EXPAND);
+ setInsets(mActivityContext.getDeviceProfile().getInsets());
+ updateStringFromCache();
+ mSchedulerButton.setVisibility(GONE);
+ mSchedulerButton.setOnClickListener(null);
+ if (shouldUseScheduler()) {
+ mSchedulerButton.setVisibility(VISIBLE);
+ mSchedulerButton.setOnClickListener(view ->
+ mContext.startActivity(new Intent(mWorkSchedulerIntentAction)));
+ }
+ }
+
+ @Override
+ public void setInsets(Rect insets) {
+ mInsets.set(insets);
+ updateTranslationY();
+ MarginLayoutParams lp = (MarginLayoutParams) getLayoutParams();
+ if (lp != null) {
+ int bottomMargin = getResources().getDimensionPixelSize(R.dimen.work_fab_margin_bottom);
+ DeviceProfile dp = ActivityContext.lookupContext(getContext()).getDeviceProfile();
+ if (mActivityContext.getAppsView().isSearchBarFloating()) {
+ bottomMargin += dp.hotseatQsbHeight;
+ }
+
+ if (!dp.isGestureMode && dp.isTaskbarPresent) {
+ bottomMargin += dp.taskbarHeight;
+ }
+
+ lp.bottomMargin = bottomMargin;
+ }
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+ boolean isRtl = Utilities.isRtl(getResources());
+ int shift = mActivityContext.getDeviceProfile().getAllAppsIconStartMargin(mContext);
+ setTranslationX(isRtl ? shift : -shift);
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return super.isEnabled() && getVisibility() == VISIBLE;
+ }
+
+ public void animateVisibility(boolean visible) {
+ clearAnimation();
+ if (visible) {
+ addFlag(FLAG_FADE_ONGOING);
+ setVisibility(VISIBLE);
+ extend();
+ animate().alpha(1).withEndAction(() -> removeFlag(FLAG_FADE_ONGOING)).start();
+ } else if (getVisibility() != GONE) {
+ addFlag(FLAG_FADE_ONGOING);
+ animate().alpha(0).withEndAction(() -> {
+ removeFlag(FLAG_FADE_ONGOING);
+ setVisibility(GONE);
+ }).start();
+ }
+ }
+
+ @Override
+ public WindowInsets onApplyWindowInsets(WindowInsets insets) {
+ WindowInsetsCompat windowInsetsCompat =
+ WindowInsetsCompat.toWindowInsetsCompat(insets, this);
+ if (windowInsetsCompat.isVisible(WindowInsetsCompat.Type.ime())) {
+ setInsets(mImeInsets, windowInsetsCompat.getInsets(WindowInsetsCompat.Type.ime()));
+ shrink();
+ } else {
+ mImeInsets.setEmpty();
+ extend();
+ }
+ updateTranslationY();
+ return super.onApplyWindowInsets(insets);
+ }
+
+ void updateTranslationY() {
+ setTranslationY(-mImeInsets.bottom);
+ }
+
+ @Override
+ public void setTranslationY(float translationY) {
+ // Always translate at least enough for nav bar insets.
+ super.setTranslationY(Math.min(translationY, -mInsets.bottom));
+ }
+
+ private ValueAnimator animateSchedulerScale(boolean isExpanding) {
+ float scaleFrom = isExpanding ? WORK_SCHEDULER_SCALE_MIN : WORK_SCHEDULER_SCALE_MAX;
+ float scaleTo = isExpanding ? WORK_SCHEDULER_SCALE_MAX : WORK_SCHEDULER_SCALE_MIN;
+ ValueAnimator schedulerScaleAnim = ObjectAnimator.ofFloat(scaleFrom, scaleTo);
+ schedulerScaleAnim.setDuration(EXPAND_COLLAPSE_DURATION);
+ schedulerScaleAnim.setInterpolator(Interpolators.STANDARD);
+ schedulerScaleAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator valueAnimator) {
+ float scale = (float) valueAnimator.getAnimatedValue();
+ mSchedulerButton.setScaleX(scale);
+ mSchedulerButton.setScaleY(scale);
+ }
+ });
+ schedulerScaleAnim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ if (isExpanding) {
+ mSchedulerButton.setVisibility(VISIBLE);
+ }
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (!isExpanding) {
+ mSchedulerButton.setVisibility(GONE);
+ }
+ }
+ });
+ return schedulerScaleAnim;
+ }
+
+ private ValueAnimator animateSchedulerAlpha(boolean isExpanding) {
+ float alphaFrom = isExpanding ? 0 : 1;
+ float alphaTo = isExpanding ? 1 : 0;
+ ValueAnimator schedulerAlphaAnim = ObjectAnimator.ofFloat(alphaFrom, alphaTo);
+ schedulerAlphaAnim.setDuration(WORK_SCHEDULER_OPACITY_DURATION);
+ schedulerAlphaAnim.setStartDelay(isExpanding ? 0 :
+ EXPAND_COLLAPSE_DURATION - WORK_SCHEDULER_OPACITY_DURATION);
+ schedulerAlphaAnim.setInterpolator(Interpolators.STANDARD);
+ schedulerAlphaAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator valueAnimator) {
+ mSchedulerButton.setAlpha((float) valueAnimator.getAnimatedValue());
+ }
+ });
+ return schedulerAlphaAnim;
+ }
+
+ private void animateWorkUtilityViews(boolean isExpanding) {
+ if (!shouldAnimate(isExpanding)) {
+ return;
+ }
+ AnimatorSet animatorSet = new AnimatedPropertySetter().buildAnim();
+ mPauseText.measure(0,0);
+ int currentWidth = mPauseText.getWidth();
+ int fullWidth = mPauseText.getMeasuredWidth();
+ float from = isExpanding ? 0 : currentWidth;
+ float to = isExpanding ? fullWidth : 0;
+ mPauseFABAnim = ObjectAnimator.ofFloat(from, to);
+ mPauseFABAnim.setDuration(EXPAND_COLLAPSE_DURATION);
+ mPauseFABAnim.setInterpolator(Interpolators.STANDARD);
+ mPauseFABAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator valueAnimator) {
+ float translation = (float) valueAnimator.getAnimatedValue();
+ float translationFraction = translation / fullWidth;
+ ViewGroup.MarginLayoutParams textViewLayoutParams =
+ (ViewGroup.MarginLayoutParams) mPauseText.getLayoutParams();
+ textViewLayoutParams.width = (int) translation;
+ textViewLayoutParams.setMarginStart((int) (mTextMarginStart * translationFraction));
+ textViewLayoutParams.setMarginEnd((int) (mTextMarginEnd * translationFraction));
+ mPauseText.setLayoutParams(textViewLayoutParams);
+ ViewGroup.MarginLayoutParams iconLayoutParams =
+ (ViewGroup.MarginLayoutParams) mWorkIcon.getLayoutParams();
+ iconLayoutParams.setMarginStart((int) (mIconMarginStart * translationFraction));
+ mWorkIcon.setLayoutParams(iconLayoutParams);
+ }
+ });
+ mPauseFABAnim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animator) {
+ if (isExpanding) {
+ addFlag(FLAG_IS_EXPAND);
+ } else {
+ mPauseText.setVisibility(GONE);
+ removeFlag(FLAG_IS_EXPAND);
+ }
+ mPauseText.setHorizontallyScrolling(false);
+ mPauseText.setEllipsize(TextUtils.TruncateAt.END);
+ }
+
+ @Override
+ public void onAnimationStart(Animator animator) {
+ mPauseText.setHorizontallyScrolling(true);
+ mPauseText.setVisibility(VISIBLE);
+ mPauseText.setEllipsize(null);
+ }
+ });
+ ArrayList animatorList = new ArrayList<>();
+ animatorList.add(mPauseFABAnim);
+ animatorList.add(updatePauseTextAlpha(isExpanding));
+ if (shouldUseScheduler()) {
+ animatorList.add(animateSchedulerScale(isExpanding));
+ animatorList.add(animateSchedulerAlpha(isExpanding));
+ }
+ animatorSet.playTogether(animatorList);
+ animatorSet.start();
+ }
+
+
+ private ValueAnimator updatePauseTextAlpha(boolean expand) {
+ float from = expand ? 0 : 1;
+ float to = expand ? 1 : 0;
+ ValueAnimator alphaAnim = ObjectAnimator.ofFloat(from, to);
+ alphaAnim.setDuration(expand ? TEXT_EXPAND_OPACITY_DURATION
+ : TEXT_COLLAPSE_OPACITY_DURATION);
+ alphaAnim.setStartDelay(expand ? TEXT_ALPHA_EXPAND_DELAY : TEXT_ALPHA_COLLAPSE_DELAY);
+ alphaAnim.setInterpolator(Interpolators.LINEAR);
+ alphaAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator valueAnimator) {
+ mPauseText.setAlpha((float) valueAnimator.getAnimatedValue());
+ }
+ });
+ return alphaAnim;
+ }
+
+ private void setInsets(Rect rect, Insets insets) {
+ rect.set(insets.left, insets.top, insets.right, insets.bottom);
+ }
+
+ public Rect getImeInsets() {
+ return mImeInsets;
+ }
+
+ @Override
+ public void onTranslationStart() {
+ addFlag(FLAG_TRANSLATION_ONGOING);
+ }
+
+ @Override
+ public void onTranslationEnd() {
+ removeFlag(FLAG_TRANSLATION_ONGOING);
+ }
+
+ private void addFlag(int flag) {
+ mFlags |= flag;
+ }
+
+ private void removeFlag(int flag) {
+ mFlags &= ~flag;
+ }
+
+ private boolean containsFlag(int flag) {
+ return (mFlags & flag) == flag;
+ }
+
+ public void extend() {
+ animateWorkUtilityViews(true);
+ }
+
+ public void shrink() {
+ animateWorkUtilityViews(false);
+ }
+
+ /**
+ * Determines if the button should animate based on current state. It should animate the button
+ * only if it is not in the same state it is animating to.
+ */
+ private boolean shouldAnimate(boolean expanding) {
+ return expanding != containsFlag(FLAG_IS_EXPAND)
+ && (mPauseFABAnim == null || !mPauseFABAnim.isRunning());
+ }
+
+ public int getScrollThreshold() {
+ return mScrollThreshold;
+ }
+
+ public View getWorkFAB() {
+ return mWorkFAB;
+ }
+
+ public void updateStringFromCache(){
+ StringCache cache = mActivityContext.getStringCache();
+ if (cache != null) {
+ mPauseText.setText(cache.workProfilePauseButton);
+ }
+ }
+
+ @VisibleForTesting
+ boolean shouldUseScheduler() {
+ return Flags.workSchedulerInWorkProfile() && !mWorkSchedulerIntentAction.isEmpty();
+ }
+
+ @VisibleForTesting
+ ImageButton getSchedulerButton() {
+ return mSchedulerButton;
+ }
+}
diff --git a/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java b/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
index de3bb9efe1..fe11ee20d1 100644
--- a/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
+++ b/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
@@ -118,8 +118,14 @@ public class AllAppsSearchBarController
@Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
- if (actionId == EditorInfo.IME_ACTION_SEARCH || actionId == EditorInfo.IME_ACTION_GO) {
- Log.i(TAG, "User tapped ime search button");
+ if (actionId == EditorInfo.IME_ACTION_SEARCH || actionId == EditorInfo.IME_ACTION_GO || (
+ actionId == EditorInfo.IME_NULL && event != null
+ && event.getAction() == KeyEvent.ACTION_DOWN)) {
+ if (actionId == EditorInfo.IME_NULL) {
+ Log.i(TAG, "User pressed ENTER key");
+ } else {
+ Log.i(TAG, "User tapped ime search button");
+ }
// selectFocusedView should return SearchTargetEvent that is passed onto onClick
return mLauncher.getAppsView().getMainAdapterProvider().launchHighlightedItem();
}
@@ -144,6 +150,7 @@ public class AllAppsSearchBarController
mCallback.clearSearchResult();
mInput.reset();
mInput.clearFocus();
+ mInput.hideKeyboard();
mQuery = null;
}
diff --git a/src/com/android/launcher3/backuprestore/LauncherRestoreEventLogger.kt b/src/com/android/launcher3/backuprestore/LauncherRestoreEventLogger.kt
index e6654b1547..1502811099 100644
--- a/src/com/android/launcher3/backuprestore/LauncherRestoreEventLogger.kt
+++ b/src/com/android/launcher3/backuprestore/LauncherRestoreEventLogger.kt
@@ -16,32 +16,52 @@ open class LauncherRestoreEventLogger : ResourceBasedOverride {
@Retention(AnnotationRetention.SOURCE)
@StringDef(
RestoreError.PROFILE_DELETED,
- RestoreError.MISSING_INFO,
RestoreError.MISSING_WIDGET_PROVIDER,
- RestoreError.INVALID_LOCATION,
+ RestoreError.OVERLAPPING_ITEM,
+ RestoreError.INVALID_WIDGET_SIZE,
+ RestoreError.INVALID_WIDGET_CONTAINER,
RestoreError.SHORTCUT_NOT_FOUND,
- RestoreError.APP_NOT_INSTALLED,
+ RestoreError.APP_NO_TARGET_PACKAGE,
+ RestoreError.APP_NO_DB_INTENT,
+ RestoreError.APP_NO_LAUNCH_INTENT,
+ RestoreError.APP_NOT_RESTORED_OR_INSTALLING,
+ RestoreError.APP_NOT_INSTALLED_EXTERNAL_MEDIA,
RestoreError.WIDGETS_DISABLED,
RestoreError.PROFILE_NOT_RESTORED,
RestoreError.WIDGET_REMOVED,
+ RestoreError.DATABASE_FILE_NOT_RESTORED,
RestoreError.GRID_MIGRATION_FAILURE,
RestoreError.NO_SEARCH_WIDGET,
- RestoreError.INVALID_WIDGET_ID
+ RestoreError.INVALID_WIDGET_ID,
+ RestoreError.OTHER_WIDGET_INFLATION_FAIL,
+ RestoreError.UNSPECIFIED_WIDGET_INFLATION_RESULT,
+ RestoreError.UNRESTORED_PENDING_WIDGET,
+ RestoreError.INVALID_CUSTOM_WIDGET_ID,
)
annotation class RestoreError {
companion object {
const val PROFILE_DELETED = "user_profile_deleted"
- const val MISSING_INFO = "missing_information_when_loading"
const val MISSING_WIDGET_PROVIDER = "missing_widget_provider"
- const val INVALID_LOCATION = "invalid_size_or_location"
+ const val OVERLAPPING_ITEM = "overlapping_item"
+ const val INVALID_WIDGET_SIZE = "invalid_widget_size"
+ const val INVALID_WIDGET_CONTAINER = "invalid_widget_container"
const val SHORTCUT_NOT_FOUND = "shortcut_not_found"
- const val APP_NOT_INSTALLED = "app_not_installed"
+ const val APP_NO_TARGET_PACKAGE = "app_no_target_package"
+ const val APP_NO_DB_INTENT = "app_no_db_intent"
+ const val APP_NO_LAUNCH_INTENT = "app_no_launch_intent"
+ const val APP_NOT_RESTORED_OR_INSTALLING = "app_not_restored_or_installed"
+ const val APP_NOT_INSTALLED_EXTERNAL_MEDIA = "app_not_installed_external_media"
const val WIDGETS_DISABLED = "widgets_disabled"
const val PROFILE_NOT_RESTORED = "profile_not_restored"
+ const val DATABASE_FILE_NOT_RESTORED = "db_file_not_restored"
const val WIDGET_REMOVED = "widget_not_found"
const val GRID_MIGRATION_FAILURE = "grid_migration_failed"
const val NO_SEARCH_WIDGET = "no_search_widget"
const val INVALID_WIDGET_ID = "invalid_widget_id"
+ const val OTHER_WIDGET_INFLATION_FAIL = "other_widget_fail"
+ const val UNSPECIFIED_WIDGET_INFLATION_RESULT = "unspecified_widget_inflation_result"
+ const val UNRESTORED_PENDING_WIDGET = "unrestored_pending_widget"
+ const val INVALID_CUSTOM_WIDGET_ID = "invalid_custom_widget_id"
}
}
@@ -52,7 +72,7 @@ open class LauncherRestoreEventLogger : ResourceBasedOverride {
return ResourceBasedOverride.Overrides.getObject(
LauncherRestoreEventLogger::class.java,
context,
- R.string.launcher_restore_event_logger_class
+ R.string.launcher_restore_event_logger_class,
)
}
}
@@ -117,7 +137,7 @@ open class LauncherRestoreEventLogger : ResourceBasedOverride {
open fun logFavoritesItemsRestoreFailed(
favoritesId: Int,
count: Int,
- @RestoreError error: String?
+ @RestoreError error: String?,
) {
// no-op
}
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index 8fe1b34077..9e38824158 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -62,17 +62,6 @@ public final class FeatureFlags {
* and set a default value for the flag. This will be the default value on Debug builds.
*
*/
- // TODO(Block 3): Clean up flags
- public static final BooleanFlag ENABLE_WORKSPACE_LOADING_OPTIMIZATION = getDebugFlag(251502424,
- "ENABLE_WORKSPACE_LOADING_OPTIMIZATION", DISABLED,
- "load the current workspace screen visible to the user before the rest rather than "
- + "loading all of them at once.");
-
- public static final BooleanFlag CHANGE_MODEL_DELEGATE_LOADING_ORDER = getDebugFlag(251502424,
- "CHANGE_MODEL_DELEGATE_LOADING_ORDER", DISABLED,
- "changes the timing of the loading and binding of delegate items during "
- + "data preparation for loading the home screen");
-
// TODO(Block 6): Clean up flags
public static final BooleanFlag SECONDARY_DRAG_N_DROP_TO_PIN = getDebugFlag(270395140,
"SECONDARY_DRAG_N_DROP_TO_PIN", DISABLED,
diff --git a/src/com/android/launcher3/contextualeducation/ContextualEduStatsManager.java b/src/com/android/launcher3/contextualeducation/ContextualEduStatsManager.java
index da13546c78..5664174014 100644
--- a/src/com/android/launcher3/contextualeducation/ContextualEduStatsManager.java
+++ b/src/com/android/launcher3/contextualeducation/ContextualEduStatsManager.java
@@ -16,22 +16,25 @@
package com.android.launcher3.contextualeducation;
-import static com.android.launcher3.util.MainThreadInitializedObject.forOverride;
-
-import com.android.launcher3.R;
-import com.android.launcher3.util.MainThreadInitializedObject;
-import com.android.launcher3.util.ResourceBasedOverride;
-import com.android.launcher3.util.SafeCloseable;
+import com.android.launcher3.dagger.LauncherAppSingleton;
+import com.android.launcher3.dagger.LauncherBaseAppComponent;
+import com.android.launcher3.util.DaggerSingletonObject;
import com.android.systemui.contextualeducation.GestureType;
+import javax.inject.Inject;
+
/**
* A class to update contextual education data. It is a no-op implementation and could be
- * overridden by changing the resource value [R.string.contextual_edu_manager_class] to provide
- * a real implementation.
+ * overridden through dagger modules to provide a real implementation.
*/
-public class ContextualEduStatsManager implements ResourceBasedOverride, SafeCloseable {
- public static final MainThreadInitializedObject INSTANCE =
- forOverride(ContextualEduStatsManager.class, R.string.contextual_edu_manager_class);
+@LauncherAppSingleton
+public class ContextualEduStatsManager {
+ public static final DaggerSingletonObject INSTANCE =
+ new DaggerSingletonObject<>(LauncherBaseAppComponent::getContextualEduStatsManager);
+
+ @Inject
+ public ContextualEduStatsManager() { }
+
/**
* Updates contextual education stats when a gesture is triggered
@@ -40,8 +43,4 @@ public class ContextualEduStatsManager implements ResourceBasedOverride, SafeClo
*/
public void updateEduStats(boolean isTrackpadGesture, GestureType gestureType) {
}
-
- @Override
- public void close() {
- }
}
diff --git a/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java b/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java
index 0a50e8bc9e..1e3df1e0fb 100644
--- a/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java
+++ b/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java
@@ -18,7 +18,21 @@ package com.android.launcher3.dagger;
import android.content.Context;
+import com.android.launcher3.contextualeducation.ContextualEduStatsManager;
+import com.android.launcher3.graphics.IconShape;
+import com.android.launcher3.model.ItemInstallQueue;
+import com.android.launcher3.pm.InstallSessionHelper;
+import com.android.launcher3.util.ApiWrapper;
import com.android.launcher3.util.DaggerSingletonTracker;
+import com.android.launcher3.util.DynamicResource;
+import com.android.launcher3.util.MSDLPlayerWrapper;
+import com.android.launcher3.util.PackageManagerHelper;
+import com.android.launcher3.util.PluginManagerWrapper;
+import com.android.launcher3.util.ScreenOnTracker;
+import com.android.launcher3.util.SettingsCache;
+import com.android.launcher3.util.VibratorWrapper;
+import com.android.launcher3.util.window.RefreshRateTracker;
+import com.android.launcher3.widget.custom.CustomWidgetManager;
import dagger.BindsInstance;
@@ -32,6 +46,21 @@ import dagger.BindsInstance;
*/
public interface LauncherBaseAppComponent {
DaggerSingletonTracker getDaggerSingletonTracker();
+ ApiWrapper getApiWrapper();
+ ContextualEduStatsManager getContextualEduStatsManager();
+ CustomWidgetManager getCustomWidgetManager();
+ DynamicResource getDynamicResource();
+ IconShape getIconShape();
+ InstallSessionHelper getInstallSessionHelper();
+ ItemInstallQueue getItemInstallQueue();
+ RefreshRateTracker getRefreshRateTracker();
+ ScreenOnTracker getScreenOnTracker();
+ SettingsCache getSettingsCache();
+ PackageManagerHelper getPackageManagerHelper();
+ PluginManagerWrapper getPluginManagerWrapper();
+ VibratorWrapper getVibratorWrapper();
+ MSDLPlayerWrapper getMSDLPlayerWrapper();
+
/** Builder for LauncherBaseAppComponent. */
interface Builder {
@BindsInstance Builder appContext(@ApplicationContext Context context);
diff --git a/src/com/android/launcher3/dagger/LauncherComponentProvider.kt b/src/com/android/launcher3/dagger/LauncherComponentProvider.kt
new file mode 100644
index 0000000000..5015e5474a
--- /dev/null
+++ b/src/com/android/launcher3/dagger/LauncherComponentProvider.kt
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2024 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.dagger
+
+import android.content.Context
+import android.view.LayoutInflater
+import com.android.launcher3.LauncherApplication
+
+/**
+ * Utility class to extract LauncherAppComponent from a context.
+ *
+ * If the context doesn't provide LauncherAppComponent by default, it creates a new one and
+ * associate it with that context
+ */
+object LauncherComponentProvider {
+
+ @JvmStatic
+ fun get(c: Context): LauncherAppComponent {
+ val app = c.applicationContext
+ if (app is LauncherApplication) return app.appComponent
+
+ val inflater = LayoutInflater.from(app)
+ val existingFilter = inflater.filter
+ if (existingFilter is Holder) return existingFilter.component
+
+ // Create a new component
+ return Holder(
+ DaggerLauncherAppComponent.builder().appContext(app).build()
+ as LauncherAppComponent,
+ existingFilter,
+ )
+ .apply { inflater.filter = this }
+ .component
+ }
+
+ private data class Holder(
+ val component: LauncherAppComponent,
+ private val filter: LayoutInflater.Filter?,
+ ) : LayoutInflater.Filter {
+
+ override fun onLoadClass(clazz: Class<*>?) = filter?.onLoadClass(clazz) ?: true
+ }
+}
diff --git a/src/com/android/launcher3/dragndrop/AddItemActivity.java b/src/com/android/launcher3/dragndrop/AddItemActivity.java
index 85eb39bab8..25de4791e5 100644
--- a/src/com/android/launcher3/dragndrop/AddItemActivity.java
+++ b/src/com/android/launcher3/dragndrop/AddItemActivity.java
@@ -68,7 +68,7 @@ import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.PackageItemInfo;
import com.android.launcher3.pm.PinRequestHelper;
import com.android.launcher3.util.ApiWrapper;
-import com.android.launcher3.util.PackageManagerHelper;
+import com.android.launcher3.util.ApplicationInfoWrapper;
import com.android.launcher3.util.SystemUiController;
import com.android.launcher3.views.AbstractSlideInView;
import com.android.launcher3.views.BaseDragLayer;
@@ -164,8 +164,8 @@ public class AddItemActivity extends BaseActivity
finish();
return;
}
- ApplicationInfo info = PackageManagerHelper.INSTANCE.get(this)
- .getApplicationInfo(targetApp.packageName, targetApp.user, 0);
+ ApplicationInfo info = new ApplicationInfoWrapper(
+ this, targetApp.packageName, targetApp.user).getInfo();
if (info == null) {
finish();
return;
@@ -281,7 +281,7 @@ public class AddItemActivity extends BaseActivity
new PinShortcutRequestActivityInfo(mRequest, this);
mWidgetCell.getWidgetView().setTag(new PendingAddShortcutInfo(shortcutInfo));
applyWidgetItemAsync(
- () -> new WidgetItem(shortcutInfo, mApp.getIconCache(), getPackageManager()));
+ () -> new WidgetItem(shortcutInfo, mApp.getIconCache()));
return new PackageItemInfo(mRequest.getShortcutInfo().getPackage(),
mRequest.getShortcutInfo().getUserHandle());
}
diff --git a/src/com/android/launcher3/dragndrop/BaseItemDragListener.java b/src/com/android/launcher3/dragndrop/BaseItemDragListener.java
index 981e3a65a4..43c148a762 100644
--- a/src/com/android/launcher3/dragndrop/BaseItemDragListener.java
+++ b/src/com/android/launcher3/dragndrop/BaseItemDragListener.java
@@ -34,7 +34,7 @@ import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.DragSource;
import com.android.launcher3.DropTarget.DragObject;
import com.android.launcher3.Launcher;
-import com.android.launcher3.util.ActivityTracker.SchedulerCallback;
+import com.android.launcher3.util.ContextTracker.SchedulerCallback;
import com.android.launcher3.widget.PendingItemDragHelper;
import java.util.UUID;
@@ -74,9 +74,9 @@ public abstract class BaseItemDragListener implements View.OnDragListener, DragS
}
@Override
- public boolean init(Launcher launcher, boolean alreadyOnHome) {
- AbstractFloatingView.closeAllOpenViews(launcher, alreadyOnHome);
- launcher.getStateManager().goToState(NORMAL, alreadyOnHome /* animated */);
+ public boolean init(Launcher launcher, boolean isHomeStarted) {
+ AbstractFloatingView.closeAllOpenViews(launcher, /* animate= */ isHomeStarted);
+ launcher.getStateManager().goToState(NORMAL, /* animated= */ isHomeStarted);
launcher.getDragLayer().setOnDragListener(this);
launcher.getRotationHelper().setStateHandlerRequest(REQUEST_LOCK);
diff --git a/src/com/android/launcher3/dragndrop/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java
index 8b1f42b91f..a24f3ff1e7 100644
--- a/src/com/android/launcher3/dragndrop/DragLayer.java
+++ b/src/com/android/launcher3/dragndrop/DragLayer.java
@@ -121,7 +121,7 @@ public class DragLayer extends BaseDragLayer implements LauncherOverla
@Override
public void recreateControllers() {
- mControllers = mActivity.createTouchControllers();
+ mControllers = mContainer.createTouchControllers();
}
public ViewGroupFocusHelper getFocusIndicatorHelper() {
@@ -134,15 +134,15 @@ public class DragLayer extends BaseDragLayer implements LauncherOverla
}
private boolean isEventOverAccessibleDropTargetBar(MotionEvent ev) {
- return isInAccessibleDrag() && isEventOverView(mActivity.getDropTargetBar(), ev);
+ return isInAccessibleDrag() && isEventOverView(mContainer.getDropTargetBar(), ev);
}
@Override
public boolean onInterceptHoverEvent(MotionEvent ev) {
- if (mActivity == null || mActivity.getWorkspace() == null) {
+ if (mContainer == null || mContainer.getWorkspace() == null) {
return false;
}
- AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(mActivity);
+ AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(mContainer);
if (!(topView instanceof Folder)) {
return false;
} else {
@@ -197,7 +197,7 @@ public class DragLayer extends BaseDragLayer implements LauncherOverla
private boolean isInAccessibleDrag() {
- return mActivity.getAccessibilityDelegate().isInAccessibleDrag();
+ return mContainer.getAccessibilityDelegate().isInAccessibleDrag();
}
@Override
@@ -210,12 +210,12 @@ public class DragLayer extends BaseDragLayer implements LauncherOverla
@Override
public void addChildrenForAccessibility(ArrayList childrenForAccessibility) {
- View topView = AbstractFloatingView.getTopOpenViewWithType(mActivity,
+ View topView = AbstractFloatingView.getTopOpenViewWithType(mContainer,
AbstractFloatingView.TYPE_ACCESSIBLE);
if (topView != null) {
addAccessibleChildToList(topView, childrenForAccessibility);
if (isInAccessibleDrag()) {
- addAccessibleChildToList(mActivity.getDropTargetBar(), childrenForAccessibility);
+ addAccessibleChildToList(mContainer.getDropTargetBar(), childrenForAccessibility);
}
} else {
super.addChildrenForAccessibility(childrenForAccessibility);
@@ -420,14 +420,14 @@ public class DragLayer extends BaseDragLayer implements LauncherOverla
public void onViewAdded(View child) {
super.onViewAdded(child);
updateChildIndices();
- mActivity.onDragLayerHierarchyChanged();
+ mContainer.onDragLayerHierarchyChanged();
}
@Override
public void onViewRemoved(View child) {
super.onViewRemoved(child);
updateChildIndices();
- mActivity.onDragLayerHierarchyChanged();
+ mContainer.onDragLayerHierarchyChanged();
}
@Override
diff --git a/src/com/android/launcher3/dragndrop/DragView.java b/src/com/android/launcher3/dragndrop/DragView.java
index bcee4420b1..67fe889b94 100644
--- a/src/com/android/launcher3/dragndrop/DragView.java
+++ b/src/com/android/launcher3/dragndrop/DragView.java
@@ -224,7 +224,6 @@ public abstract class DragView extends Fram
measure(makeMeasureSpec(width, EXACTLY), makeMeasureSpec(height, EXACTLY));
mBlurSizeOutline = getResources().getDimensionPixelSize(R.dimen.blur_size_medium_outline);
- setElevation(getResources().getDimension(R.dimen.drag_elevation));
setWillNotDraw(false);
}
diff --git a/src/com/android/launcher3/dragndrop/LauncherDragController.java b/src/com/android/launcher3/dragndrop/LauncherDragController.java
index 29fc6139f7..4aa3673586 100644
--- a/src/com/android/launcher3/dragndrop/LauncherDragController.java
+++ b/src/com/android/launcher3/dragndrop/LauncherDragController.java
@@ -119,6 +119,9 @@ public class LauncherDragController extends DragController {
initialDragViewScale,
dragViewScaleOnDrop,
scalePx);
+ // During a drag, we don't want to expose the descendendants of drag view to a11y users,
+ // since those decendents are not a valid position in the workspace.
+ dragView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
dragView.setItemInfo(dragInfo);
mDragObject.dragComplete = false;
diff --git a/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java b/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java
index 0f3cad66a2..a6a50d7252 100644
--- a/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java
+++ b/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java
@@ -30,7 +30,6 @@ import android.content.Context;
import android.content.Intent;
import android.content.pm.LauncherApps;
import android.content.pm.LauncherApps.PinItemRequest;
-import android.content.pm.PackageManager;
import android.content.pm.ShortcutInfo;
import android.graphics.drawable.Drawable;
import android.os.Build;
@@ -40,7 +39,7 @@ import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.R;
-import com.android.launcher3.icons.IconCache;
+import com.android.launcher3.icons.cache.BaseIconCache;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.pm.PinRequestHelper;
import com.android.launcher3.pm.ShortcutConfigActivityInfo;
@@ -69,7 +68,8 @@ public class PinShortcutRequestActivityInfo extends ShortcutConfigActivityInfo {
public PinShortcutRequestActivityInfo(
ShortcutInfo si, Supplier requestSupplier, Context context) {
- super(new ComponentName(si.getPackage(), STUB_COMPONENT_CLASS), si.getUserHandle());
+ super(new ComponentName(si.getPackage(), STUB_COMPONENT_CLASS),
+ si.getUserHandle(), context);
mRequestSupplier = requestSupplier;
mInfo = si;
mContext = context;
@@ -81,12 +81,12 @@ public class PinShortcutRequestActivityInfo extends ShortcutConfigActivityInfo {
}
@Override
- public CharSequence getLabel(PackageManager pm) {
+ public CharSequence getLabel() {
return mInfo.getShortLabel();
}
@Override
- public Drawable getFullResIcon(IconCache cache) {
+ public Drawable getFullResIcon(BaseIconCache cache) {
Drawable d = mContext.getSystemService(LauncherApps.class)
.getShortcutIconDrawable(mInfo, LauncherAppState.getIDP(mContext).fillResIconDpi);
if (d == null) {
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index 7bec768b64..5defef3889 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -317,10 +317,7 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo
| InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS
| InputType.TYPE_TEXT_FLAG_CAP_WORDS);
mFolderName.forceDisableSuggestions(true);
- mFolderName.setPadding(mFolderName.getPaddingLeft(),
- (getFooterHeight() - mFolderName.getLineHeight()) / 2,
- mFolderName.getPaddingRight(),
- (getFooterHeight() - mFolderName.getLineHeight()) / 2);
+
mKeyboardInsetAnimationCallback = new KeyboardInsetAnimationCallback(this);
setWindowInsetsAnimationCallback(mKeyboardInsetAnimationCallback);
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index de1bcc34db..9ff647526d 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -177,12 +177,16 @@ public class FolderIcon extends FrameLayout implements FolderListener, FloatingI
FolderIcon icon = inflateIcon(resId, activityContext, group, folderInfo);
folder.setFolderIcon(icon);
folder.bind(folderInfo);
+
icon.setFolder(folder);
+ folderInfo.addListener(icon);
return icon;
}
/**
- * Builds a FolderIcon to be added to the Launcher
+ * Builds a FolderIcon to be added to the activity.
+ * This method doesn't add any listeners to the FolderInfo, and hence any changes to the info
+ * will not be reflected in the folder.
*/
public static FolderIcon inflateIcon(int resId, ActivityContext activity,
@Nullable ViewGroup group, FolderInfo folderInfo) {
@@ -228,8 +232,6 @@ public class FolderIcon extends FrameLayout implements FolderListener, FloatingI
icon.mPreviewVerifier.setFolderInfo(folderInfo);
icon.updatePreviewItems(false);
- folderInfo.addListener(icon);
-
return icon;
}
diff --git a/src/com/android/launcher3/folder/FolderPagedView.java b/src/com/android/launcher3/folder/FolderPagedView.java
index 9dc2d240c4..fe261945a0 100644
--- a/src/com/android/launcher3/folder/FolderPagedView.java
+++ b/src/com/android/launcher3/folder/FolderPagedView.java
@@ -373,8 +373,9 @@ public class FolderPagedView extends PagedView implements Cli
// Update footer
mPageIndicator.setVisibility(getPageCount() > 1 ? View.VISIBLE : View.GONE);
// Set the gravity as LEFT or RIGHT instead of START, as START depends on the actual text.
- mFolder.getFolderName().setGravity(getPageCount() > 1
- ? (mIsRtl ? Gravity.RIGHT : Gravity.LEFT) : Gravity.CENTER_HORIZONTAL);
+ int horizontalGravity = getPageCount() > 1
+ ? (mIsRtl ? Gravity.RIGHT : Gravity.LEFT) : Gravity.CENTER_HORIZONTAL;
+ mFolder.getFolderName().setGravity(horizontalGravity | Gravity.CENTER_VERTICAL);
}
public int getDesiredWidth() {
diff --git a/src/com/android/launcher3/graphics/GridCustomizationsProvider.java b/src/com/android/launcher3/graphics/GridCustomizationsProvider.java
index 27ec838b98..836ae98bcf 100644
--- a/src/com/android/launcher3/graphics/GridCustomizationsProvider.java
+++ b/src/com/android/launcher3/graphics/GridCustomizationsProvider.java
@@ -36,49 +36,71 @@ import android.os.Messenger;
import android.text.TextUtils;
import android.util.Log;
+import androidx.annotation.NonNull;
+
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.InvariantDeviceProfile.GridOption;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherModel;
import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.model.BgDataModel;
+import com.android.launcher3.shapes.AppShape;
+import com.android.launcher3.shapes.AppShapesProvider;
import com.android.launcher3.util.Executors;
import com.android.launcher3.util.Preconditions;
import com.android.launcher3.util.RunnableList;
import com.android.systemui.shared.Flags;
+import java.lang.ref.WeakReference;
import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
import java.util.Set;
-import java.util.WeakHashMap;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
/**
* Exposes various launcher grid options and allows the caller to change them.
* APIs:
- * /list_options: List the various available grip options, has following columns
- * name: name of the grid
+ * /shape_options: List of various available shape options, where each has following fields
+ * shape_key: key of the shape option
+ * title: translated title of the shape option
+ * path: path of the shape, assuming drawn on 100x100 view port
+ * is_default: true if this shape option is currently set to the system
+ *
+ * /list_options: List the various available grid options, where each has following fields
+ * name: key of the grid option
* rows: number of rows in the grid
* cols: number of columns in the grid
* preview_count: number of previews available for this grid option. The preview uri
* looks like /preview//
- * is_default: true if this grid is currently active
+ * is_default: true if this grid option is currently set to the system
*
- * /preview: Opens a file stream for the grid preview
+ * /get_preview: Open a file stream for the grid preview
*
- * /default_grid: Call update to set the current grid, with values
- * name: name of the grid to apply
+ * /default_grid: Call update to set the current shape and grid, with values
+ * shape_key: key of the shape to apply
+ * name: key of the grid to apply
*/
public class GridCustomizationsProvider extends ContentProvider {
private static final String TAG = "GridCustomizationsProvider";
private static final String KEY_NAME = "name";
+ private static final String KEY_GRID_TITLE = "grid_title";
private static final String KEY_ROWS = "rows";
private static final String KEY_COLS = "cols";
private static final String KEY_PREVIEW_COUNT = "preview_count";
+ // is_default means if a certain option is currently set to the system
private static final String KEY_IS_DEFAULT = "is_default";
+ private static final String KEY_SHAPE_KEY = "shape_key";
+ private static final String KEY_SHAPE_TITLE = "shape_title";
+ private static final String KEY_PATH = "path";
+ // list_options is the key for grid option list
private static final String KEY_LIST_OPTIONS = "/list_options";
+ private static final String KEY_SHAPE_OPTIONS = "/shape_options";
+ // default_grid is for setting grid and shape to system settings
private static final String KEY_DEFAULT_GRID = "/default_grid";
private static final String METHOD_GET_PREVIEW = "get_preview";
@@ -94,11 +116,13 @@ public class GridCustomizationsProvider extends ContentProvider {
public static final String KEY_GRID_NAME = "grid_name";
private static final int MESSAGE_ID_UPDATE_PREVIEW = 1337;
+ private static final int MESSAGE_ID_UPDATE_SHAPE = 2586;
private static final int MESSAGE_ID_UPDATE_GRID = 7414;
+ private static final int MESSAGE_ID_UPDATE_COLOR = 856;
// Set of all active previews used to track duplicate memory allocations
private final Set mActivePreviews =
- Collections.newSetFromMap(new WeakHashMap<>());
+ Collections.newSetFromMap(new ConcurrentHashMap<>());
@Override
public boolean onCreate() {
@@ -108,14 +132,42 @@ public class GridCustomizationsProvider extends ContentProvider {
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
- switch (uri.getPath()) {
+ Context context = getContext();
+ String path = uri.getPath();
+ if (context == null || path == null) {
+ return null;
+ }
+
+ switch (path) {
+ case KEY_SHAPE_OPTIONS: {
+ if (Flags.newCustomizationPickerUi()) {
+ MatrixCursor cursor = new MatrixCursor(new String[]{
+ KEY_SHAPE_KEY, KEY_SHAPE_TITLE, KEY_PATH, KEY_IS_DEFAULT});
+ List shapes = AppShapesProvider.INSTANCE.getShapes();
+ for (int i = 0; i < shapes.size(); i++) {
+ AppShape shape = shapes.get(i);
+ cursor.newRow()
+ .add(KEY_SHAPE_KEY, shape.getKey())
+ .add(KEY_SHAPE_TITLE, shape.getTitle())
+ .add(KEY_PATH, shape.getPath())
+ // TODO (b/348664593): We should fetch the currently-set shape
+ // option from the preferences.
+ .add(KEY_IS_DEFAULT, i == 0);
+ }
+ return cursor;
+ } else {
+ return null;
+ }
+ }
case KEY_LIST_OPTIONS: {
MatrixCursor cursor = new MatrixCursor(new String[]{
- KEY_NAME, KEY_ROWS, KEY_COLS, KEY_PREVIEW_COUNT, KEY_IS_DEFAULT});
+ KEY_NAME, KEY_GRID_TITLE, KEY_ROWS, KEY_COLS, KEY_PREVIEW_COUNT,
+ KEY_IS_DEFAULT});
InvariantDeviceProfile idp = InvariantDeviceProfile.INSTANCE.get(getContext());
for (GridOption gridOption : idp.parseAllGridOptions(getContext())) {
cursor.newRow()
.add(KEY_NAME, gridOption.name)
+ .add(KEY_GRID_TITLE, gridOption.title)
.add(KEY_ROWS, gridOption.numRows)
.add(KEY_COLS, gridOption.numColumns)
.add(KEY_PREVIEW_COUNT, 1)
@@ -159,6 +211,14 @@ public class GridCustomizationsProvider extends ContentProvider {
}
switch (path) {
case KEY_DEFAULT_GRID: {
+ if (Flags.newCustomizationPickerUi()) {
+ String shapeKey = values.getAsString(KEY_SHAPE_KEY);
+ Optional optionalShape = AppShapesProvider.INSTANCE.getShapes()
+ .stream().filter(shape -> shape.getKey().equals(shapeKey)).findFirst();
+ String pathToSet = optionalShape.map(AppShape::getPath).orElse(null);
+ // TODO (b/348664593): Apply shapeName to the system. This needs to be a
+ // synchronous call.
+ }
String gridName = values.getAsString(KEY_NAME);
InvariantDeviceProfile idp = InvariantDeviceProfile.INSTANCE.get(context);
// Verify that this is a valid grid option
@@ -216,20 +276,30 @@ public class GridCustomizationsProvider extends ContentProvider {
}
@Override
- public Bundle call(String method, String arg, Bundle extras) {
- if (getContext().checkPermission("android.permission.BIND_WALLPAPER",
+ public Bundle call(@NonNull String method, String arg, Bundle extras) {
+ Context context = getContext();
+ if (context == null) {
+ return null;
+ }
+
+ if (context.checkPermission("android.permission.BIND_WALLPAPER",
Binder.getCallingPid(), Binder.getCallingUid())
!= PackageManager.PERMISSION_GRANTED) {
return null;
}
- if (!METHOD_GET_PREVIEW.equals(method)) {
+ if (METHOD_GET_PREVIEW.equals(method)) {
+ return getPreview(extras);
+ } else {
return null;
}
- return getPreview(extras);
}
private synchronized Bundle getPreview(Bundle request) {
+ Context context = getContext();
+ if (context == null) {
+ return null;
+ }
RunnableList lifeCycleTracker = new RunnableList();
try {
PreviewSurfaceRenderer renderer = new PreviewSurfaceRenderer(
@@ -248,8 +318,15 @@ public class GridCustomizationsProvider extends ContentProvider {
Bundle result = new Bundle();
result.putParcelable(KEY_SURFACE_PACKAGE, renderer.getSurfacePackage());
- Messenger messenger =
- new Messenger(new Handler(UI_HELPER_EXECUTOR.getLooper(), observer));
+ mActivePreviews.add(observer);
+ lifeCycleTracker.add(() -> mActivePreviews.remove(observer));
+
+ // Wrap the callback in a weak reference. This ensures that the callback is not kept
+ // alive due to the Messenger's IBinder
+ Messenger messenger = new Messenger(new Handler(
+ UI_HELPER_EXECUTOR.getLooper(),
+ new WeakCallbackWrapper(observer)));
+
Message msg = Message.obtain();
msg.replyTo = messenger;
result.putParcelable(KEY_CALLBACK, msg);
@@ -267,7 +344,9 @@ public class GridCustomizationsProvider extends ContentProvider {
public final PreviewSurfaceRenderer renderer;
public boolean destroyed = false;
- PreviewLifecycleObserver(RunnableList lifeCycleTracker, PreviewSurfaceRenderer renderer) {
+ PreviewLifecycleObserver(
+ RunnableList lifeCycleTracker,
+ PreviewSurfaceRenderer renderer) {
this.lifeCycleTracker = lifeCycleTracker;
this.renderer = renderer;
lifeCycleTracker.add(() -> destroyed = true);
@@ -283,12 +362,28 @@ public class GridCustomizationsProvider extends ContentProvider {
case MESSAGE_ID_UPDATE_PREVIEW:
renderer.hideBottomRow(message.getData().getBoolean(KEY_HIDE_BOTTOM_ROW));
break;
+ case MESSAGE_ID_UPDATE_SHAPE:
+ if (Flags.newCustomizationPickerUi()) {
+ String shapeKey = message.getData().getString(KEY_SHAPE_KEY);
+ Optional optionalShape = AppShapesProvider.INSTANCE.getShapes()
+ .stream()
+ .filter(shape -> shape.getKey().equals(shapeKey))
+ .findFirst();
+ String pathToSet = optionalShape.map(AppShape::getPath).orElse(null);
+ // TODO (b/348664593): Update launcher preview with the given shape
+ }
+ break;
case MESSAGE_ID_UPDATE_GRID:
String gridName = message.getData().getString(KEY_GRID_NAME);
if (!TextUtils.isEmpty(gridName)) {
renderer.updateGrid(gridName);
}
break;
+ case MESSAGE_ID_UPDATE_COLOR:
+ if (Flags.newCustomizationPickerUi()) {
+ renderer.previewColor(message.getData());
+ }
+ break;
default:
// Unknown command, destroy lifecycle
Log.d(TAG, "Unknown preview command: " + message.what + ", destroying preview");
@@ -313,4 +408,34 @@ public class GridCustomizationsProvider extends ContentProvider {
&& plo.renderer.getDisplayId() == renderer.getDisplayId();
}
}
+
+ /**
+ * A WeakReference wrapper around Handler.Callback to avoid passing hard-reference over IPC
+ * when using a Messenger
+ */
+ private static class WeakCallbackWrapper implements Handler.Callback {
+
+ private final WeakReference mActual;
+ private final Message mCleanupMessage;
+
+ WeakCallbackWrapper(Handler.Callback actual) {
+ mActual = new WeakReference<>(actual);
+ mCleanupMessage = new Message();
+ }
+
+ @Override
+ public boolean handleMessage(Message message) {
+ Handler.Callback actual = mActual.get();
+ return actual != null && actual.handleMessage(message);
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ super.finalize();
+ Handler.Callback actual = mActual.get();
+ if (actual != null) {
+ actual.handleMessage(mCleanupMessage);
+ }
+ }
+ }
}
diff --git a/src/com/android/launcher3/graphics/IconShape.java b/src/com/android/launcher3/graphics/IconShape.java
index 5f8f2dcf7c..cb14587fb7 100644
--- a/src/com/android/launcher3/graphics/IconShape.java
+++ b/src/com/android/launcher3/graphics/IconShape.java
@@ -41,10 +41,12 @@ import android.view.ViewOutlineProvider;
import com.android.launcher3.R;
import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
+import com.android.launcher3.dagger.ApplicationContext;
+import com.android.launcher3.dagger.LauncherAppSingleton;
+import com.android.launcher3.dagger.LauncherBaseAppComponent;
import com.android.launcher3.icons.GraphicsUtils;
import com.android.launcher3.icons.IconNormalizer;
-import com.android.launcher3.util.MainThreadInitializedObject;
-import com.android.launcher3.util.SafeCloseable;
+import com.android.launcher3.util.DaggerSingletonObject;
import com.android.launcher3.views.ClipPathView;
import org.xmlpull.v1.XmlPullParser;
@@ -54,19 +56,22 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
+import javax.inject.Inject;
+
/**
* Abstract representation of the shape of an icon shape
*/
-public final class IconShape implements SafeCloseable {
-
- public static final MainThreadInitializedObject INSTANCE =
- new MainThreadInitializedObject<>(IconShape::new);
+@LauncherAppSingleton
+public final class IconShape {
+ public static DaggerSingletonObject INSTANCE =
+ new DaggerSingletonObject<>(LauncherBaseAppComponent::getIconShape);
private ShapeDelegate mDelegate = new Circle();
private float mNormalizationScale = ICON_VISIBLE_AREA_FACTOR;
- private IconShape(Context context) {
+ @Inject
+ public IconShape(@ApplicationContext Context context) {
pickBestShape(context);
}
@@ -78,9 +83,6 @@ public final class IconShape implements SafeCloseable {
return mNormalizationScale;
}
- @Override
- public void close() { }
-
/**
* Initializes the shape which is closest to the {@link AdaptiveIconDrawable}
*/
diff --git a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
index 40c0cc65c9..f0e4fc444c 100644
--- a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
+++ b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
@@ -98,6 +98,7 @@ import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
import com.android.launcher3.widget.LauncherWidgetHolder;
import com.android.launcher3.widget.LocalColorExtractor;
import com.android.launcher3.widget.util.WidgetSizes;
+import com.android.systemui.shared.Flags;
import java.util.ArrayList;
import java.util.Collections;
@@ -150,6 +151,14 @@ public class LauncherPreviewRenderer extends ContextWrapper
InvariantDeviceProfile idp,
WallpaperColors wallpaperColorsOverride,
@Nullable final SparseArray launcherWidgetSpanInfo) {
+ this(context, idp, null, wallpaperColorsOverride, launcherWidgetSpanInfo);
+ }
+
+ public LauncherPreviewRenderer(Context context,
+ InvariantDeviceProfile idp,
+ SparseIntArray previewColorOverride,
+ WallpaperColors wallpaperColorsOverride,
+ @Nullable final SparseArray launcherWidgetSpanInfo) {
super(context);
mUiHandler = new Handler(Looper.getMainLooper());
@@ -206,12 +215,29 @@ public class LauncherPreviewRenderer extends ContextWrapper
mWorkspaceScreens.put(Workspace.SECOND_SCREEN_ID, rightPanel);
}
- WallpaperColors wallpaperColors = wallpaperColorsOverride != null
- ? wallpaperColorsOverride
- : WallpaperManager.getInstance(context).getWallpaperColors(FLAG_SYSTEM);
- mWallpaperColorResources = wallpaperColors != null
- ? LocalColorExtractor.newInstance(context).generateColorsOverride(wallpaperColors)
- : null;
+ if (Flags.newCustomizationPickerUi()) {
+ if (previewColorOverride != null) {
+ mWallpaperColorResources = previewColorOverride;
+ } else if (wallpaperColorsOverride != null) {
+ mWallpaperColorResources = LocalColorExtractor.newInstance(
+ context).generateColorsOverride(wallpaperColorsOverride);
+ } else {
+ WallpaperColors wallpaperColors = WallpaperManager.getInstance(
+ context).getWallpaperColors(FLAG_SYSTEM);
+ mWallpaperColorResources = wallpaperColors != null
+ ? LocalColorExtractor.newInstance(context).generateColorsOverride(
+ wallpaperColors)
+ : null;
+ }
+ } else {
+ WallpaperColors wallpaperColors = wallpaperColorsOverride != null
+ ? wallpaperColorsOverride
+ : WallpaperManager.getInstance(context).getWallpaperColors(FLAG_SYSTEM);
+ mWallpaperColorResources = wallpaperColors != null
+ ? LocalColorExtractor.newInstance(context).generateColorsOverride(
+ wallpaperColors)
+ : null;
+ }
mAppWidgetHost = new LauncherPreviewAppWidgetHost(context);
}
diff --git a/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java b/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
index 1b23d75527..a7cf1a7876 100644
--- a/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
+++ b/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
@@ -16,6 +16,8 @@
package com.android.launcher3.graphics;
+import static android.content.res.Configuration.UI_MODE_NIGHT_NO;
+import static android.content.res.Configuration.UI_MODE_NIGHT_YES;
import static android.view.Display.DEFAULT_DISPLAY;
import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME;
@@ -25,6 +27,7 @@ import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
import android.app.WallpaperColors;
import android.appwidget.AppWidgetProviderInfo;
import android.content.Context;
+import android.content.res.Configuration;
import android.database.Cursor;
import android.hardware.display.DisplayManager;
import android.os.Bundle;
@@ -32,6 +35,7 @@ import android.os.IBinder;
import android.util.Log;
import android.util.Size;
import android.util.SparseArray;
+import android.util.SparseIntArray;
import android.view.ContextThemeWrapper;
import android.view.Display;
import android.view.SurfaceControlViewHost;
@@ -54,7 +58,7 @@ import com.android.launcher3.graphics.LauncherPreviewRenderer.PreviewContext;
import com.android.launcher3.model.BaseLauncherBinder;
import com.android.launcher3.model.BgDataModel;
import com.android.launcher3.model.BgDataModel.Callbacks;
-import com.android.launcher3.model.GridSizeMigrationUtil;
+import com.android.launcher3.model.GridSizeMigrationDBController;
import com.android.launcher3.model.LoaderTask;
import com.android.launcher3.model.ModelDbController;
import com.android.launcher3.provider.LauncherDbUtils;
@@ -81,6 +85,9 @@ public class PreviewSurfaceRenderer {
private static final String KEY_VIEW_HEIGHT = "height";
private static final String KEY_DISPLAY_ID = "display_id";
private static final String KEY_COLORS = "wallpaper_colors";
+ private static final String KEY_COLOR_RESOURCE_IDS = "color_resource_ids";
+ private static final String KEY_COLOR_VALUES = "color_values";
+ private static final String KEY_DARK_MODE = "use_dark_mode";
private Context mContext;
private final IBinder mHostToken;
@@ -91,12 +98,13 @@ public class PreviewSurfaceRenderer {
private final int mDisplayId;
private final Display mDisplay;
private final WallpaperColors mWallpaperColors;
+ private SparseIntArray mPreviewColorOverride;
+ @Nullable private Boolean mDarkMode;
private final RunnableList mLifeCycleTracker;
private final SurfaceControlViewHost mSurfaceControlViewHost;
private boolean mDestroyed = false;
- private LauncherPreviewRenderer mRenderer;
private boolean mHideQsb;
@Nullable private FrameLayout mViewRoot = null;
@@ -110,6 +118,9 @@ public class PreviewSurfaceRenderer {
mGridName = InvariantDeviceProfile.getCurrentGridName(context);
}
mWallpaperColors = bundle.getParcelable(KEY_COLORS);
+ if (Flags.newCustomizationPickerUi()) {
+ updateColorOverrides(bundle);
+ }
mHideQsb = bundle.getBoolean(GridCustomizationsProvider.KEY_HIDE_BOTTOM_ROW);
mHostToken = bundle.getBinder(KEY_HOST_TOKEN);
@@ -212,8 +223,32 @@ public class PreviewSurfaceRenderer {
* @param hide True to hide and false to show.
*/
public void hideBottomRow(boolean hide) {
- if (mRenderer != null) {
- mRenderer.hideBottomRow(hide);
+ mHideQsb = hide;
+ loadAsync();
+ }
+
+ /**
+ * Updates the colors of the preview.
+ *
+ * @param bundle Bundle with an int array of color ids and an int array of overriding colors.
+ */
+ public void previewColor(Bundle bundle) {
+ updateColorOverrides(bundle);
+ loadAsync();
+ }
+
+ private void updateColorOverrides(Bundle bundle) {
+ mDarkMode =
+ bundle.containsKey(KEY_DARK_MODE) ? bundle.getBoolean(KEY_DARK_MODE) : null;
+ int[] ids = bundle.getIntArray(KEY_COLOR_RESOURCE_IDS);
+ int[] colors = bundle.getIntArray(KEY_COLOR_VALUES);
+ if (ids != null && colors != null) {
+ mPreviewColorOverride = new SparseIntArray();
+ for (int i = 0; i < ids.length; i++) {
+ mPreviewColorOverride.put(ids[i], colors[i]);
+ }
+ } else {
+ mPreviewColorOverride = null;
}
}
@@ -223,21 +258,50 @@ public class PreviewSurfaceRenderer {
*/
private Context getPreviewContext() {
Context context = mContext.createDisplayContext(mDisplay);
- if (mWallpaperColors == null) {
- return new ContextThemeWrapper(context,
- Themes.getActivityThemeRes(context));
+ if (mDarkMode != null) {
+ Configuration configuration = new Configuration(
+ context.getResources().getConfiguration());
+ if (mDarkMode) {
+ configuration.uiMode &= ~UI_MODE_NIGHT_NO;
+ configuration.uiMode |= UI_MODE_NIGHT_YES;
+ } else {
+ configuration.uiMode &= ~UI_MODE_NIGHT_YES;
+ configuration.uiMode |= UI_MODE_NIGHT_NO;
+ }
+ context = context.createConfigurationContext(configuration);
+ }
+ if (Flags.newCustomizationPickerUi()) {
+ if (mPreviewColorOverride != null) {
+ LocalColorExtractor.newInstance(context)
+ .applyColorsOverride(context, mPreviewColorOverride);
+ } else if (mWallpaperColors != null) {
+ LocalColorExtractor.newInstance(context)
+ .applyColorsOverride(context, mWallpaperColors);
+ }
+ if (mWallpaperColors != null) {
+ return new ContextThemeWrapper(context,
+ Themes.getActivityThemeRes(context, mWallpaperColors.getColorHints()));
+ } else {
+ return new ContextThemeWrapper(context,
+ Themes.getActivityThemeRes(context));
+ }
+ } else {
+ if (mWallpaperColors == null) {
+ return new ContextThemeWrapper(context,
+ Themes.getActivityThemeRes(context));
+ }
+ LocalColorExtractor.newInstance(context)
+ .applyColorsOverride(context, mWallpaperColors);
+ return new ContextThemeWrapper(context,
+ Themes.getActivityThemeRes(context, mWallpaperColors.getColorHints()));
}
- LocalColorExtractor.newInstance(context)
- .applyColorsOverride(context, mWallpaperColors);
- return new ContextThemeWrapper(context,
- Themes.getActivityThemeRes(context, mWallpaperColors.getColorHints()));
}
@WorkerThread
private void loadModelData() {
final Context inflationContext = getPreviewContext();
final InvariantDeviceProfile idp = new InvariantDeviceProfile(inflationContext, mGridName);
- if (GridSizeMigrationUtil.needsToMigrate(inflationContext, idp)) {
+ if (GridSizeMigrationDBController.needsToMigrate(inflationContext, idp)) {
// Start the migration
PreviewContext previewContext = new PreviewContext(inflationContext, idp);
// Copy existing data to preview DB
@@ -258,7 +322,9 @@ public class PreviewSurfaceRenderer {
bgModel,
LauncherAppState.getInstance(previewContext).getModel().getModelDelegate(),
new BaseLauncherBinder(LauncherAppState.getInstance(previewContext), bgModel,
- /* bgAllAppsList= */ null, new Callbacks[0])) {
+ /* bgAllAppsList= */ null, new Callbacks[0]),
+ LauncherAppState.getInstance(
+ previewContext).getModel().getWidgetsFilterDataProvider()) {
@Override
public void run() {
@@ -300,10 +366,16 @@ public class PreviewSurfaceRenderer {
if (mDestroyed) {
return;
}
- mRenderer = new LauncherPreviewRenderer(inflationContext, idp,
- mWallpaperColors, launcherWidgetSpanInfo);
- mRenderer.hideBottomRow(mHideQsb);
- View view = mRenderer.getRenderedView(dataModel, widgetProviderInfoMap);
+ LauncherPreviewRenderer renderer;
+ if (Flags.newCustomizationPickerUi()) {
+ renderer = new LauncherPreviewRenderer(inflationContext, idp, mPreviewColorOverride,
+ mWallpaperColors, launcherWidgetSpanInfo);
+ } else {
+ renderer = new LauncherPreviewRenderer(inflationContext, idp,
+ mWallpaperColors, launcherWidgetSpanInfo);
+ }
+ renderer.hideBottomRow(mHideQsb);
+ View view = renderer.getRenderedView(dataModel, widgetProviderInfoMap);
// This aspect scales the view to fit in the surface and centers it
final float scale = Math.min(mWidth / (float) view.getMeasuredWidth(),
mHeight / (float) view.getMeasuredHeight());
diff --git a/src/com/android/launcher3/graphics/SysUiScrim.java b/src/com/android/launcher3/graphics/SysUiScrim.java
index 077ddfc665..d59fc1934c 100644
--- a/src/com/android/launcher3/graphics/SysUiScrim.java
+++ b/src/com/android/launcher3/graphics/SysUiScrim.java
@@ -32,10 +32,10 @@ import android.view.View;
import androidx.annotation.ColorInt;
import androidx.annotation.VisibleForTesting;
-import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.R;
import com.android.launcher3.anim.AnimatedFloat;
+import com.android.launcher3.statemanager.StatefulContainer;
import com.android.launcher3.testing.shared.ResourceUtils;
import com.android.launcher3.util.ScreenOnTracker;
import com.android.launcher3.util.ScreenOnTracker.ScreenOnListener;
@@ -84,7 +84,7 @@ public class SysUiScrim implements View.OnAttachStateChangeListener {
private final int mBottomMaskHeight;
private final View mRoot;
- private final BaseDraggingActivity mActivity;
+ private final StatefulContainer mContainer;
private final boolean mHideSysUiScrim;
private boolean mSkipScrimAnimationForTest = false;
@@ -94,8 +94,8 @@ public class SysUiScrim implements View.OnAttachStateChangeListener {
public SysUiScrim(View view) {
mRoot = view;
- mActivity = BaseDraggingActivity.fromContext(view.getContext());
- DisplayMetrics dm = mActivity.getResources().getDisplayMetrics();
+ mContainer = StatefulContainer.fromContext(view.getContext());
+ DisplayMetrics dm = mContainer.getContext().getResources().getDisplayMetrics();
mTopMaskHeight = ResourceUtils.pxFromDp(TOP_MASK_HEIGHT_DP, dm);
mBottomMaskHeight = ResourceUtils.pxFromDp(BOTTOM_MASK_HEIGHT_DP, dm);
@@ -130,7 +130,7 @@ public class SysUiScrim implements View.OnAttachStateChangeListener {
ObjectAnimator oa = mSysUiAnimMultiplier.animateToValue(1);
oa.setDuration(600);
- oa.setStartDelay(mActivity.getWindow().getTransitionBackgroundFadeDuration());
+ oa.setStartDelay(mContainer.getWindow().getTransitionBackgroundFadeDuration());
oa.start();
mAnimateScrimOnNextDraw = false;
}
@@ -166,19 +166,19 @@ public class SysUiScrim implements View.OnAttachStateChangeListener {
* horizontal
*/
public void onInsetsChanged(Rect insets) {
- DeviceProfile dp = mActivity.getDeviceProfile();
+ DeviceProfile dp = mContainer.getDeviceProfile();
mDrawTopScrim = insets.top > 0;
mDrawBottomScrim = !dp.isVerticalBarLayout() && !dp.isGestureMode && !dp.isTaskbarPresent;
}
@Override
public void onViewAttachedToWindow(View view) {
- ScreenOnTracker.INSTANCE.get(mActivity).addListener(mScreenOnListener);
+ ScreenOnTracker.INSTANCE.get(mContainer.getContext()).addListener(mScreenOnListener);
}
@Override
public void onViewDetachedFromWindow(View view) {
- ScreenOnTracker.INSTANCE.get(mActivity).removeListener(mScreenOnListener);
+ ScreenOnTracker.INSTANCE.get(mContainer.getContext()).removeListener(mScreenOnListener);
}
/**
@@ -213,7 +213,7 @@ public class SysUiScrim implements View.OnAttachStateChangeListener {
}
private Bitmap createDitheredAlphaMask(int height, @ColorInt int[] colors, float[] positions) {
- DisplayMetrics dm = mActivity.getResources().getDisplayMetrics();
+ DisplayMetrics dm = mContainer.getContext().getResources().getDisplayMetrics();
int width = ResourceUtils.pxFromDp(ALPHA_MASK_BITMAP_WIDTH_DP, dm);
Bitmap dst = Bitmap.createBitmap(width, height, Bitmap.Config.ALPHA_8);
Canvas c = new Canvas(dst);
diff --git a/src/com/android/launcher3/icons/CacheableShortcutInfo.kt b/src/com/android/launcher3/icons/CacheableShortcutInfo.kt
new file mode 100644
index 0000000000..a78da2312e
--- /dev/null
+++ b/src/com/android/launcher3/icons/CacheableShortcutInfo.kt
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.icons
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.pm.LauncherActivityInfo
+import android.content.pm.LauncherApps
+import android.content.pm.ShortcutInfo
+import android.graphics.drawable.Drawable
+import android.os.UserHandle
+import android.util.Log
+import com.android.launcher3.BuildConfig
+import com.android.launcher3.LauncherAppState
+import com.android.launcher3.icons.BaseIconFactory.IconOptions
+import com.android.launcher3.icons.cache.BaseIconCache
+import com.android.launcher3.icons.cache.CachingLogic
+import com.android.launcher3.shortcuts.ShortcutKey
+import com.android.launcher3.util.ApplicationInfoWrapper
+import com.android.launcher3.util.PackageUserKey
+import com.android.launcher3.util.Themes
+
+/** Wrapper over ShortcutInfo to provide extra information related to ShortcutInfo */
+class CacheableShortcutInfo(val shortcutInfo: ShortcutInfo, val appInfo: ApplicationInfoWrapper) {
+
+ constructor(
+ info: ShortcutInfo,
+ ctx: Context,
+ ) : this(info, ApplicationInfoWrapper(ctx, info.getPackage(), info.userHandle))
+
+ companion object {
+ private const val TAG = "CacheableShortcutInfo"
+
+ /**
+ * Similar to [LauncherApps.getShortcutIconDrawable] with additional Launcher specific
+ * checks
+ */
+ @JvmStatic
+ fun getIcon(context: Context, shortcutInfo: ShortcutInfo, density: Int): Drawable? {
+ if (!BuildConfig.WIDGETS_ENABLED) {
+ return null
+ }
+ try {
+ return context
+ .getSystemService(LauncherApps::class.java)
+ .getShortcutIconDrawable(shortcutInfo, density)
+ } catch (e: Exception) {
+ Log.e(TAG, "Failed to get shortcut icon", e)
+ return null
+ }
+ }
+
+ /**
+ * Converts the provided list of Shortcuts to CacheableShortcuts by using the application
+ * info from the provided list of apps
+ */
+ @JvmStatic
+ fun convertShortcutsToCacheableShortcuts(
+ shortcuts: List,
+ activities: List,
+ ): List {
+ // Create a map of package to applicationInfo
+ val appMap =
+ activities.associateBy(
+ { PackageUserKey(it.componentName.packageName, it.user) },
+ { it.applicationInfo },
+ )
+
+ return shortcuts.map {
+ CacheableShortcutInfo(
+ it,
+ ApplicationInfoWrapper(appMap[PackageUserKey(it.getPackage(), it.userHandle)]),
+ )
+ }
+ }
+ }
+}
+
+/** Caching logic for CacheableShortcutInfo. */
+object CacheableShortcutCachingLogic : CachingLogic {
+
+ override fun getComponent(info: CacheableShortcutInfo): ComponentName =
+ ShortcutKey.fromInfo(info.shortcutInfo).componentName
+
+ override fun getUser(info: CacheableShortcutInfo): UserHandle = info.shortcutInfo.userHandle
+
+ override fun getLabel(info: CacheableShortcutInfo): CharSequence? = info.shortcutInfo.shortLabel
+
+ override fun getApplicationInfo(info: CacheableShortcutInfo) = info.appInfo.getInfo()
+
+ override fun loadIcon(context: Context, cache: BaseIconCache, info: CacheableShortcutInfo) =
+ LauncherIcons.obtain(context).use { li ->
+ CacheableShortcutInfo.getIcon(
+ context,
+ info.shortcutInfo,
+ LauncherAppState.getIDP(context).fillResIconDpi,
+ )
+ ?.let { d ->
+ li.createBadgedIconBitmap(
+ d,
+ IconOptions().setExtractedColor(Themes.getColorAccent(context)),
+ )
+ } ?: BitmapInfo.LOW_RES_INFO
+ }
+
+ override fun getFreshnessIdentifier(
+ item: CacheableShortcutInfo,
+ provider: IconProvider,
+ ): String? =
+ // Manifest shortcuts get updated on every reboot. Don't include their change timestamp as
+ // it gets covered by the app's version
+ (if (item.shortcutInfo.isDeclaredInManifest) ""
+ else item.shortcutInfo.lastChangedTimestamp.toString()) +
+ "-" +
+ provider.getStateForApp(getApplicationInfo(item))
+}
diff --git a/src/com/android/launcher3/icons/IconCache.java b/src/com/android/launcher3/icons/IconCache.java
index 587dc2731a..e7c4024a18 100644
--- a/src/com/android/launcher3/icons/IconCache.java
+++ b/src/com/android/launcher3/icons/IconCache.java
@@ -29,14 +29,10 @@ import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.LauncherActivityInfo;
import android.content.pm.LauncherApps;
-import android.content.pm.PackageInfo;
import android.content.pm.PackageInstaller;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ShortcutInfo;
import android.database.Cursor;
import android.database.sqlite.SQLiteException;
-import android.graphics.drawable.Drawable;
import android.os.Looper;
import android.os.Process;
import android.os.Trace;
@@ -55,8 +51,8 @@ import com.android.launcher3.Flags;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.Utilities;
import com.android.launcher3.icons.cache.BaseIconCache;
+import com.android.launcher3.icons.cache.CachedObject;
import com.android.launcher3.icons.cache.CachedObjectCachingLogic;
-import com.android.launcher3.icons.cache.CachingLogic;
import com.android.launcher3.icons.cache.LauncherActivityCachingLogic;
import com.android.launcher3.logging.FileLog;
import com.android.launcher3.model.data.AppInfo;
@@ -66,8 +62,8 @@ import com.android.launcher3.model.data.PackageItemInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.pm.InstallSessionHelper;
import com.android.launcher3.pm.UserCache;
-import com.android.launcher3.shortcuts.ShortcutKey;
import com.android.launcher3.util.CancellableTask;
+import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.InstantAppResolver;
import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.widget.WidgetSections;
@@ -96,10 +92,6 @@ public class IconCache extends BaseIconCache {
private final Predicate mIsUsingFallbackOrNonDefaultIconCheck = w ->
w.bitmap != null && (w.bitmap.isNullOrLowRes() || !isDefaultIcon(w.bitmap, w.user));
- private final CachingLogic mComponentWithLabelCachingLogic;
- private final CachingLogic mLauncherActivityInfoCachingLogic;
- private final CachingLogic mShortcutCachingLogic;
-
private final LauncherApps mLauncherApps;
private final UserCache mUserManager;
private final InstantAppResolver mInstantAppResolver;
@@ -113,10 +105,6 @@ public class IconCache extends BaseIconCache {
IconProvider iconProvider) {
super(context, dbFileName, MODEL_EXECUTOR.getLooper(),
idp.fillResIconDpi, idp.iconBitmapSize, true /* inMemoryCache */, iconProvider);
- mComponentWithLabelCachingLogic = new CachedObjectCachingLogic(
- context, false /* loadIcons */, false /* addToMemCache */);
- mLauncherActivityInfoCachingLogic = LauncherActivityCachingLogic.INSTANCE;
- mShortcutCachingLogic = new ShortcutCachingLogic();
mLauncherApps = mContext.getSystemService(LauncherApps.class);
mUserManager = UserCache.INSTANCE.get(mContext);
mInstantAppResolver = InstantAppResolver.newInstance(mContext);
@@ -148,16 +136,9 @@ public class IconCache extends BaseIconCache {
public synchronized void updateIconsForPkg(@NonNull final String packageName,
@NonNull final UserHandle user) {
removeIconsForPkg(packageName, user);
- try {
- PackageInfo info = mPackageManager.getPackageInfo(packageName,
- PackageManager.GET_UNINSTALLED_PACKAGES);
- long userSerial = mUserManager.getSerialNumberForUser(user);
- for (LauncherActivityInfo app : mLauncherApps.getActivityList(packageName, user)) {
- addIconToDBAndMemCache(app, mLauncherActivityInfoCachingLogic, info, userSerial,
- false /*replace existing*/);
- }
- } catch (NameNotFoundException e) {
- Log.d(TAG, "Package not found", e);
+ long userSerial = mUserManager.getSerialNumberForUser(user);
+ for (LauncherActivityInfo app : mLauncherApps.getActivityList(packageName, user)) {
+ addIconToDBAndMemCache(app, LauncherActivityCachingLogic.INSTANCE, userSerial);
}
}
@@ -209,7 +190,7 @@ public class IconCache extends BaseIconCache {
CancellableTask request = new CancellableTask<>(
task, MAIN_EXECUTOR, caller::reapplyItemInfo, endRunnable);
- Utilities.postAsyncCallback(mWorkerHandler, request);
+ Utilities.postAsyncCallback(workerHandler, request);
return request;
}
@@ -224,28 +205,16 @@ public class IconCache extends BaseIconCache {
* Updates {@param application} only if a valid entry is found.
*/
public synchronized void updateTitleAndIcon(AppInfo application) {
- boolean preferPackageIcon = application.isArchived();
CacheEntry entry = cacheLocked(application.componentName,
- application.user, () -> null, mLauncherActivityInfoCachingLogic,
- false, application.usingLowResIcon());
- if (entry.bitmap == null || isDefaultIcon(entry.bitmap, application.user)) {
- return;
- }
-
- if (preferPackageIcon) {
- String packageName = application.getTargetPackage();
- CacheEntry packageEntry =
- cacheLocked(new ComponentName(packageName, packageName + EMPTY_CLASS_NAME),
- application.user, () -> null, mLauncherActivityInfoCachingLogic,
- true, application.usingLowResIcon());
- applyPackageEntry(packageEntry, application, entry);
- } else {
+ application.user, () -> null, LauncherActivityCachingLogic.INSTANCE,
+ application.usingLowResIcon() ? LookupFlag.USE_LOW_RES : LookupFlag.DEFAULT);
+ if (entry.bitmap != null || !isDefaultIcon(entry.bitmap, application.user)) {
applyCacheEntry(entry, application);
}
}
/**
- * Fill in {@param info} with the icon and label for {@param activityInfo}
+ * Fill in {@code info} with the icon and label for {@code activityInfo}
*/
@SuppressWarnings("NewApi")
public synchronized void getTitleAndIcon(ItemInfoWithIcon info,
@@ -253,33 +222,45 @@ public class IconCache extends BaseIconCache {
boolean isAppArchived = Flags.enableSupportForArchiving() && activityInfo != null
&& activityInfo.getActivityInfo().isArchived;
// If we already have activity info, no need to use package icon
- getTitleAndIcon(info, () -> activityInfo, isAppArchived, useLowResIcon,
- isAppArchived);
+ getTitleAndIcon(info, () -> activityInfo, isAppArchived, useLowResIcon);
}
/**
- * Fill in {@param info} with the icon for {@param si}
+ * Fill in {@code info} with the icon for {@code si}
*/
public void getShortcutIcon(ItemInfoWithIcon info, ShortcutInfo si) {
+ getShortcutIcon(info, new CacheableShortcutInfo(si, mContext));
+ }
+
+ /**
+ * Fill in {@code info} with the icon for {@code si}
+ */
+ public void getShortcutIcon(ItemInfoWithIcon info, CacheableShortcutInfo si) {
getShortcutIcon(info, si, mIsUsingFallbackOrNonDefaultIconCheck);
}
/**
- * Fill in {@param info} with the icon and label for {@param si}. If the icon is not
+ * Fill in {@code info} with the icon and label for {@code si}. If the icon is not
* available, and fallback check returns true, it keeps the old icon.
+ * Shortcut entries are not kept in memory since they are not frequently used
*/
- public void getShortcutIcon(T info, ShortcutInfo si,
+ public void getShortcutIcon(T info, CacheableShortcutInfo si,
@NonNull Predicate fallbackIconCheck) {
- BitmapInfo bitmapInfo = cacheLocked(ShortcutKey.fromInfo(si).componentName,
- si.getUserHandle(), () -> si, mShortcutCachingLogic, false, false).bitmap;
+ UserHandle user = CacheableShortcutCachingLogic.INSTANCE.getUser(si);
+ BitmapInfo bitmapInfo = cacheLocked(
+ CacheableShortcutCachingLogic.INSTANCE.getComponent(si),
+ user,
+ () -> si,
+ CacheableShortcutCachingLogic.INSTANCE,
+ LookupFlag.SKIP_ADD_TO_MEM_CACHE).bitmap;
if (bitmapInfo.isNullOrLowRes()) {
- bitmapInfo = getDefaultIcon(si.getUserHandle());
+ bitmapInfo = getDefaultIcon(user);
}
- if (isDefaultIcon(bitmapInfo, si.getUserHandle()) && fallbackIconCheck.test(info)) {
+ if (isDefaultIcon(bitmapInfo, user) && fallbackIconCheck.test(info)) {
return;
}
- info.bitmap = bitmapInfo.withBadgeInfo(getShortcutInfoBadge(si));
+ info.bitmap = bitmapInfo.withBadgeInfo(getShortcutInfoBadge(si.getShortcutInfo()));
}
/**
@@ -333,17 +314,17 @@ public class IconCache extends BaseIconCache {
} else {
Intent intent = info.getIntent();
getTitleAndIcon(info, () -> mLauncherApps.resolveActivity(intent, info.user),
- true, useLowResIcon, info.isArchived());
+ true, useLowResIcon);
}
}
/**
* Loads and returns the icon for the provided object without adding it to memCache
*/
- public synchronized String getTitleNoCache(ComponentWithLabel info) {
+ public synchronized String getTitleNoCache(CachedObject info) {
CacheEntry entry = cacheLocked(info.getComponent(), info.getUser(), () -> info,
- mComponentWithLabelCachingLogic, false /* usePackageIcon */,
- true /* useLowResIcon */);
+ CachedObjectCachingLogic.INSTANCE,
+ LookupFlag.USE_LOW_RES | LookupFlag.SKIP_ADD_TO_MEM_CACHE);
return Utilities.trim(entry.title);
}
@@ -354,39 +335,14 @@ public class IconCache extends BaseIconCache {
@NonNull ItemInfoWithIcon infoInOut,
@NonNull Supplier activityInfoProvider,
boolean usePkgIcon, boolean useLowResIcon) {
+ int lookupFlags = LookupFlag.DEFAULT;
+ if (usePkgIcon) lookupFlags |= LookupFlag.USE_PACKAGE_ICON;
+ if (useLowResIcon) lookupFlags |= LookupFlag.USE_LOW_RES;
CacheEntry entry = cacheLocked(infoInOut.getTargetComponent(), infoInOut.user,
- activityInfoProvider, mLauncherActivityInfoCachingLogic, usePkgIcon,
- useLowResIcon);
+ activityInfoProvider, LauncherActivityCachingLogic.INSTANCE, lookupFlags);
applyCacheEntry(entry, infoInOut);
}
- /**
- * Fill in {@param mWorkspaceItemInfo} with the icon and label for {@param info}
- */
- public synchronized void getTitleAndIcon(
- @NonNull ItemInfoWithIcon infoInOut,
- @NonNull Supplier activityInfoProvider,
- boolean usePkgIcon, boolean useLowResIcon, boolean preferPackageEntry) {
- CacheEntry entry = cacheLocked(infoInOut.getTargetComponent(), infoInOut.user,
- activityInfoProvider, mLauncherActivityInfoCachingLogic, usePkgIcon,
- useLowResIcon);
- if (preferPackageEntry) {
- String packageName = infoInOut.getTargetPackage();
- CacheEntry packageEntry = cacheLocked(
- new ComponentName(packageName, packageName + EMPTY_CLASS_NAME),
- infoInOut.user, activityInfoProvider, mLauncherActivityInfoCachingLogic,
- usePkgIcon, useLowResIcon);
- applyPackageEntry(packageEntry, infoInOut, entry);
- } else if (useLowResIcon || !entry.bitmap.isNullOrLowRes()
- || infoInOut.bitmap.isNullOrLowRes()) {
- // Only use cache entry if it will not downgrade the current bitmap in infoInOut
- applyCacheEntry(entry, infoInOut);
- } else {
- Log.d(TAG, "getTitleAndIcon: Cache entry bitmap was a downgrade of existing bitmap"
- + " in ItemInfo. Skipping.");
- }
- }
-
/**
* Creates an sql cursor for a query of a set of ItemInfoWithIcon icons and titles.
*
@@ -484,10 +440,9 @@ public class IconCache extends BaseIconCache {
cn,
/* user = */ sectionKey.first,
() -> duplicateIconRequests.get(0).launcherActivityInfo,
- mLauncherActivityInfoCachingLogic,
- c,
- /* usePackageIcon= */ false,
- /* useLowResIcons = */ sectionKey.second);
+ LauncherActivityCachingLogic.INSTANCE,
+ sectionKey.second ? LookupFlag.USE_LOW_RES : LookupFlag.DEFAULT,
+ c);
for (IconRequestInfo iconRequest : duplicateIconRequests) {
applyCacheEntry(entry, iconRequest.itemInfo);
@@ -534,7 +489,7 @@ public class IconCache extends BaseIconCache {
loadFallbackIcon(
lai,
entry,
- mLauncherActivityInfoCachingLogic,
+ LauncherActivityCachingLogic.INSTANCE,
/* usePackageIcon= */ false,
/* usePackageTitle= */ loadFallbackTitle,
cn,
@@ -544,7 +499,7 @@ public class IconCache extends BaseIconCache {
loadFallbackTitle(
lai,
entry,
- mLauncherActivityInfoCachingLogic,
+ LauncherActivityCachingLogic.INSTANCE,
sectionKey.first);
}
@@ -603,28 +558,30 @@ public class IconCache extends BaseIconCache {
info.title = Utilities.trim(entry.title);
info.contentDescription = entry.contentDescription;
info.bitmap = entry.bitmap;
+ // Clear any previously set appTitle, if the packageOverride is no longer valid
+ info.appTitle = null;
if (entry.bitmap == null) {
// TODO: entry.bitmap can never be null, so this should not happen at all.
Log.wtf(TAG, "Cannot find bitmap from the cache, default icon was loaded.");
info.bitmap = getDefaultIcon(info.user);
}
- }
- protected void applyPackageEntry(@NonNull final CacheEntry packageEntry,
- @NonNull final ItemInfoWithIcon info, @NonNull final CacheEntry fallbackEntry) {
+ // apply package override
+ if (!Flags.enableSupportForArchiving() || !info.isArchived()) {
+ return;
+ }
+ String targetPackage = info.getTargetPackage();
+ if (targetPackage == null) {
+ return;
+ }
+ CacheEntry packageEntry = getInMemoryPackageEntryLocked(targetPackage, info.user);
+ if (packageEntry == null || packageEntry.bitmap.isLowRes()) {
+ return;
+ }
+ info.appTitle = Utilities.trim(info.title);
info.title = Utilities.trim(packageEntry.title);
- info.appTitle = Utilities.trim(fallbackEntry.title);
info.contentDescription = packageEntry.contentDescription;
info.bitmap = packageEntry.bitmap;
- if (packageEntry.bitmap == null) {
- // TODO: entry.bitmap can never be null, so this should not happen at all.
- Log.wtf(TAG, "Cannot find bitmap from the cache, default icon was loaded.");
- info.bitmap = getDefaultIcon(info.user);
- }
- }
-
- public Drawable getFullResIcon(LauncherActivityInfo info) {
- return mIconProvider.getIcon(info, mIconDpi);
}
public void updateSessionCache(PackageUserKey key, PackageInstaller.SessionInfo info) {
@@ -632,6 +589,11 @@ public class IconCache extends BaseIconCache {
info.getAppLabel());
}
+ @VisibleForTesting
+ synchronized boolean isItemInDb(ComponentKey cacheKey) {
+ return getEntryFromDBLocked(cacheKey, new CacheEntry(), false);
+ }
+
/**
* Interface for receiving itemInfo with high-res icon.
*/
diff --git a/src/com/android/launcher3/icons/LauncherIconProvider.java b/src/com/android/launcher3/icons/LauncherIconProvider.java
index c4d5f2b5b5..78a31285f9 100644
--- a/src/com/android/launcher3/icons/LauncherIconProvider.java
+++ b/src/com/android/launcher3/icons/LauncherIconProvider.java
@@ -16,14 +16,18 @@
package com.android.launcher3.icons;
import android.content.Context;
+import android.content.pm.ApplicationInfo;
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Log;
+import androidx.annotation.NonNull;
+
import com.android.launcher3.R;
import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.util.ApiWrapper;
import com.android.launcher3.util.Themes;
import org.xmlpull.v1.XmlPullParser;
@@ -70,6 +74,11 @@ public class LauncherIconProvider extends IconProvider {
return super.getSystemIconState() + (mSupportsIconTheme ? ",with-theme" : ",no-theme");
}
+ @Override
+ protected String getApplicationInfoHash(@NonNull ApplicationInfo appInfo) {
+ return ApiWrapper.INSTANCE.get(mContext).getApplicationInfoHash(appInfo);
+ }
+
private Map getThemedIconMap() {
if (mThemedIconMap != null) {
return mThemedIconMap;
diff --git a/src/com/android/launcher3/icons/LauncherIcons.java b/src/com/android/launcher3/icons/LauncherIcons.java
index 884d448b6c..839dfb70ea 100644
--- a/src/com/android/launcher3/icons/LauncherIcons.java
+++ b/src/com/android/launcher3/icons/LauncherIcons.java
@@ -17,15 +17,13 @@
package com.android.launcher3.icons;
import android.content.Context;
-import android.graphics.drawable.AdaptiveIconDrawable;
-import android.graphics.drawable.Drawable;
import android.os.UserHandle;
import androidx.annotation.NonNull;
-import com.android.launcher3.Flags;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.graphics.IconShape;
+import com.android.launcher3.icons.mono.MonoIconThemeController;
import com.android.launcher3.pm.UserCache;
import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.util.SafeCloseable;
@@ -57,13 +55,13 @@ public class LauncherIcons extends BaseIconFactory implements AutoCloseable {
private final ConcurrentLinkedQueue mPool;
- private MonochromeIconFactory mMonochromeIconFactory;
-
protected LauncherIcons(Context context, int fillResIconDpi, int iconBitmapSize,
ConcurrentLinkedQueue pool) {
super(context, fillResIconDpi, iconBitmapSize,
IconShape.INSTANCE.get(context).getShape().enableShapeDetection());
- mMonoIconEnabled = Themes.isThemedIconEnabled(context);
+ if (Themes.isThemedIconEnabled(context)) {
+ mThemeController = new MonoIconThemeController();
+ }
mPool = pool;
}
@@ -75,18 +73,6 @@ public class LauncherIcons extends BaseIconFactory implements AutoCloseable {
mPool.add(this);
}
- @Override
- protected Drawable getMonochromeDrawable(AdaptiveIconDrawable base) {
- Drawable mono = super.getMonochromeDrawable(base);
- if (mono != null || !Flags.forceMonochromeAppIcons()) {
- return mono;
- }
- if (mMonochromeIconFactory == null) {
- mMonochromeIconFactory = new MonochromeIconFactory(mIconBitmapSize);
- }
- return mMonochromeIconFactory.wrap(base);
- }
-
@NonNull
@Override
protected UserIconInfo getUserInfo(@NonNull UserHandle user) {
diff --git a/src/com/android/launcher3/icons/MonochromeIconFactory.java b/src/com/android/launcher3/icons/MonochromeIconFactory.java
deleted file mode 100644
index 2854d51631..0000000000
--- a/src/com/android/launcher3/icons/MonochromeIconFactory.java
+++ /dev/null
@@ -1,172 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.icons;
-
-import static android.graphics.Paint.FILTER_BITMAP_FLAG;
-
-import android.annotation.TargetApi;
-import android.graphics.Bitmap;
-import android.graphics.Bitmap.Config;
-import android.graphics.BlendMode;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.ColorFilter;
-import android.graphics.ColorMatrix;
-import android.graphics.ColorMatrixColorFilter;
-import android.graphics.Paint;
-import android.graphics.PixelFormat;
-import android.graphics.Rect;
-import android.graphics.drawable.AdaptiveIconDrawable;
-import android.graphics.drawable.Drawable;
-import android.os.Build;
-
-import androidx.annotation.WorkerThread;
-
-import com.android.launcher3.icons.BaseIconFactory.ClippedMonoDrawable;
-
-import java.nio.ByteBuffer;
-
-/**
- * Utility class to generate monochrome icons version for a given drawable.
- */
-@TargetApi(Build.VERSION_CODES.TIRAMISU)
-public class MonochromeIconFactory extends Drawable {
-
- private final Bitmap mFlatBitmap;
- private final Canvas mFlatCanvas;
- private final Paint mCopyPaint;
-
- private final Bitmap mAlphaBitmap;
- private final Canvas mAlphaCanvas;
- private final byte[] mPixels;
-
- private final int mBitmapSize;
- private final int mEdgePixelLength;
-
- private final Paint mDrawPaint;
- private final Rect mSrcRect;
-
- MonochromeIconFactory(int iconBitmapSize) {
- float extraFactor = AdaptiveIconDrawable.getExtraInsetFraction();
- float viewPortScale = 1 / (1 + 2 * extraFactor);
- mBitmapSize = Math.round(iconBitmapSize * 2 * viewPortScale);
- mPixels = new byte[mBitmapSize * mBitmapSize];
- mEdgePixelLength = mBitmapSize * (mBitmapSize - iconBitmapSize) / 2;
-
- mFlatBitmap = Bitmap.createBitmap(mBitmapSize, mBitmapSize, Config.ARGB_8888);
- mFlatCanvas = new Canvas(mFlatBitmap);
-
- mAlphaBitmap = Bitmap.createBitmap(mBitmapSize, mBitmapSize, Config.ALPHA_8);
- mAlphaCanvas = new Canvas(mAlphaBitmap);
-
- mDrawPaint = new Paint(FILTER_BITMAP_FLAG);
- mDrawPaint.setColor(Color.WHITE);
- mSrcRect = new Rect(0, 0, mBitmapSize, mBitmapSize);
-
- mCopyPaint = new Paint(FILTER_BITMAP_FLAG);
- mCopyPaint.setBlendMode(BlendMode.SRC);
-
- // Crate a color matrix which converts the icon to grayscale and then uses the average
- // of RGB components as the alpha component.
- ColorMatrix satMatrix = new ColorMatrix();
- satMatrix.setSaturation(0);
- float[] vals = satMatrix.getArray();
- vals[15] = vals[16] = vals[17] = .3333f;
- vals[18] = vals[19] = 0;
- mCopyPaint.setColorFilter(new ColorMatrixColorFilter(vals));
- }
-
- private void drawDrawable(Drawable drawable) {
- if (drawable != null) {
- drawable.setBounds(0, 0, mBitmapSize, mBitmapSize);
- drawable.draw(mFlatCanvas);
- }
- }
-
- /**
- * Creates a monochrome version of the provided drawable
- */
- @WorkerThread
- public Drawable wrap(AdaptiveIconDrawable icon) {
- mFlatCanvas.drawColor(Color.BLACK);
- drawDrawable(icon.getBackground());
- drawDrawable(icon.getForeground());
- generateMono();
- return new ClippedMonoDrawable(this);
- }
-
- @WorkerThread
- private void generateMono() {
- mAlphaCanvas.drawBitmap(mFlatBitmap, 0, 0, mCopyPaint);
-
- // Scale the end points:
- ByteBuffer buffer = ByteBuffer.wrap(mPixels);
- buffer.rewind();
- mAlphaBitmap.copyPixelsToBuffer(buffer);
-
- int min = 0xFF;
- int max = 0;
- for (byte b : mPixels) {
- min = Math.min(min, b & 0xFF);
- max = Math.max(max, b & 0xFF);
- }
-
- if (min < max) {
- // rescale pixels to increase contrast
- float range = max - min;
-
- // In order to check if the colors should be flipped, we just take the average color
- // of top and bottom edge which should correspond to be background color. If the edge
- // colors have more opacity, we flip the colors;
- int sum = 0;
- for (int i = 0; i < mEdgePixelLength; i++) {
- sum += (mPixels[i] & 0xFF);
- sum += (mPixels[mPixels.length - 1 - i] & 0xFF);
- }
- float edgeAverage = sum / (mEdgePixelLength * 2f);
- float edgeMapped = (edgeAverage - min) / range;
- boolean flipColor = edgeMapped > .5f;
-
- for (int i = 0; i < mPixels.length; i++) {
- int p = mPixels[i] & 0xFF;
- int p2 = Math.round((p - min) * 0xFF / range);
- mPixels[i] = flipColor ? (byte) (255 - p2) : (byte) (p2);
- }
- buffer.rewind();
- mAlphaBitmap.copyPixelsFromBuffer(buffer);
- }
- }
-
- @Override
- public void draw(Canvas canvas) {
- canvas.drawBitmap(mAlphaBitmap, mSrcRect, getBounds(), mDrawPaint);
- }
-
- @Override
- public int getOpacity() {
- return PixelFormat.TRANSLUCENT;
- }
-
- @Override
- public void setAlpha(int i) {
- mDrawPaint.setAlpha(i);
- }
-
- @Override
- public void setColorFilter(ColorFilter colorFilter) {
- mDrawPaint.setColorFilter(colorFilter);
- }
-}
diff --git a/src/com/android/launcher3/icons/ShortcutCachingLogic.java b/src/com/android/launcher3/icons/ShortcutCachingLogic.java
deleted file mode 100644
index 7bb39e1230..0000000000
--- a/src/com/android/launcher3/icons/ShortcutCachingLogic.java
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.icons;
-
-import static com.android.launcher3.BuildConfig.WIDGETS_ENABLED;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.pm.LauncherApps;
-import android.content.pm.PackageInfo;
-import android.content.pm.ShortcutInfo;
-import android.graphics.drawable.Drawable;
-import android.os.UserHandle;
-import android.text.TextUtils;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.icons.BaseIconFactory.IconOptions;
-import com.android.launcher3.icons.cache.BaseIconCache;
-import com.android.launcher3.icons.cache.CachingLogic;
-import com.android.launcher3.shortcuts.ShortcutKey;
-import com.android.launcher3.util.Themes;
-
-/**
- * Caching logic for shortcuts.
- */
-public class ShortcutCachingLogic implements CachingLogic {
-
- private static final String TAG = "ShortcutCachingLogic";
-
- @Override
- @NonNull
- public ComponentName getComponent(@NonNull ShortcutInfo info) {
- return ShortcutKey.fromInfo(info).componentName;
- }
-
- @NonNull
- @Override
- public UserHandle getUser(@NonNull ShortcutInfo info) {
- return info.getUserHandle();
- }
-
- @NonNull
- @Override
- public CharSequence getLabel(@NonNull ShortcutInfo info) {
- return info.getShortLabel();
- }
-
- @Override
- @NonNull
- public CharSequence getDescription(@NonNull ShortcutInfo object,
- @NonNull CharSequence fallback) {
- CharSequence label = object.getLongLabel();
- return TextUtils.isEmpty(label) ? fallback : label;
- }
-
- @NonNull
- @Override
- public BitmapInfo loadIcon(@NonNull Context context, @NonNull BaseIconCache cache,
- @NonNull ShortcutInfo info) {
- try (LauncherIcons li = LauncherIcons.obtain(context)) {
- Drawable unbadgedDrawable = ShortcutCachingLogic.getIcon(
- context, info, LauncherAppState.getIDP(context).fillResIconDpi);
- if (unbadgedDrawable == null) return BitmapInfo.LOW_RES_INFO;
- return li.createBadgedIconBitmap(unbadgedDrawable,
- new IconOptions().setExtractedColor(Themes.getColorAccent(context)));
- }
- }
-
- @Override
- public long getLastUpdatedTime(@Nullable ShortcutInfo shortcutInfo,
- @NonNull PackageInfo info) {
- if (shortcutInfo == null) {
- return info.lastUpdateTime;
- }
- return Math.max(shortcutInfo.getLastChangedTimestamp(), info.lastUpdateTime);
- }
-
- @Override
- public boolean addToMemCache() {
- return false;
- }
-
- /**
- * Similar to {@link LauncherApps#getShortcutIconDrawable(ShortcutInfo, int)} with additional
- * Launcher specific checks
- */
- public static Drawable getIcon(Context context, ShortcutInfo shortcutInfo, int density) {
- if (!WIDGETS_ENABLED) {
- return null;
- }
- try {
- return context.getSystemService(LauncherApps.class)
- .getShortcutIconDrawable(shortcutInfo, density);
- } catch (SecurityException | IllegalStateException | NullPointerException e) {
- Log.e(TAG, "Failed to get shortcut icon", e);
- return null;
- }
- }
-}
diff --git a/src/com/android/launcher3/logging/StatsLogManager.java b/src/com/android/launcher3/logging/StatsLogManager.java
index fbd24d89f0..dbab52a67e 100644
--- a/src/com/android/launcher3/logging/StatsLogManager.java
+++ b/src/com/android/launcher3/logging/StatsLogManager.java
@@ -175,6 +175,10 @@ public class StatsLogManager implements ResourceBasedOverride {
@UiEvent(doc = "User searched for a widget in the widget picker.")
LAUNCHER_WIDGETSTRAY_SEARCHED(819),
+ @UiEvent(doc = "User clicked on view all button to expand the displayed list in the "
+ + "widget picker.")
+ LAUNCHER_WIDGETSTRAY_EXPAND_PRESS(1978),
+
@UiEvent(doc = "A dragged item is dropped on 'Remove' button in the target bar")
LAUNCHER_ITEM_DROPPED_ON_REMOVE(465),
@@ -221,6 +225,9 @@ public class StatsLogManager implements ResourceBasedOverride {
@UiEvent(doc = "User tapped on desktop icon on a task menu.")
LAUNCHER_SYSTEM_SHORTCUT_DESKTOP_TAP(1706),
+ @UiEvent(doc = "Use tapped on external display icon on a task menu,")
+ LAUNCHER_SYSTEM_SHORTCUT_EXTERNAL_DISPLAY_TAP(1957),
+
@UiEvent(doc = "User tapped on pause app system shortcut.")
LAUNCHER_SYSTEM_SHORTCUT_PAUSE_TAP(521),
@@ -798,6 +805,51 @@ public class StatsLogManager implements ResourceBasedOverride {
@UiEvent(doc = "User long pressed on the taskbar IME switcher button")
LAUNCHER_TASKBAR_IME_SWITCHER_BUTTON_LONGPRESS(1798),
+ @UiEvent(doc = "Failed to launch assistant due to Google assistant not available")
+ LAUNCHER_LAUNCH_ASSISTANT_FAILED_NOT_AVAILABLE(1465),
+
+ @UiEvent(doc = "Failed to launch assistant due to service error")
+ LAUNCHER_LAUNCH_ASSISTANT_FAILED_SERVICE_ERROR(1466),
+
+ @UiEvent(doc = "User launched assistant by long-pressing nav handle")
+ LAUNCHER_LAUNCH_ASSISTANT_SUCCESSFUL_NAV_HANDLE(1467),
+
+ @UiEvent(doc = "Failed to launch due to Contextual Search not available")
+ LAUNCHER_LAUNCH_OMNI_FAILED_NOT_AVAILABLE(1471),
+
+ @UiEvent(doc = "Failed to launch due to Contextual Search setting disabled")
+ LAUNCHER_LAUNCH_OMNI_FAILED_SETTING_DISABLED(1632),
+
+ @UiEvent(doc = "User launched Contextual Search by long-pressing home in 3-button mode")
+ LAUNCHER_LAUNCH_OMNI_SUCCESSFUL_HOME(1481),
+
+ @UiEvent(doc = "User launched Contextual Search by using accessibility System Action")
+ LAUNCHER_LAUNCH_OMNI_SUCCESSFUL_SYSTEM_ACTION(1492),
+
+ @UiEvent(doc = "User launched Contextual Search by long pressing the meta key")
+ LAUNCHER_LAUNCH_OMNI_SUCCESSFUL_META(1606),
+
+ @UiEvent(doc = "Contextual Search invocation was attempted over the notification shade")
+ LAUNCHER_LAUNCH_OMNI_ATTEMPTED_OVER_NOTIFICATION_SHADE(1485),
+
+ @UiEvent(doc = "The Contextual Search all entrypoints toggle value in Settings")
+ LAUNCHER_SETTINGS_OMNI_ALL_ENTRYPOINTS_TOGGLE_VALUE(1633),
+
+ @UiEvent(doc = "Contextual Search invocation was attempted over the keyguard")
+ LAUNCHER_LAUNCH_OMNI_ATTEMPTED_OVER_KEYGUARD(1501),
+
+ @UiEvent(doc = "Contextual Search invocation was attempted while splitscreen is active")
+ LAUNCHER_LAUNCH_OMNI_ATTEMPTED_SPLITSCREEN(1505),
+
+ @UiEvent(doc = "User long press nav handle and a long press runnable was created.")
+ LAUNCHER_OMNI_GET_LONG_PRESS_RUNNABLE(1545),
+
+ // One Grid Flags
+ @UiEvent(doc = "User sets the device in Fixed Landscape")
+ FIXED_LANDSCAPE_TOGGLE_ENABLE(2014),
+
+ @UiEvent(doc = "User sets the device in Fixed Landscape")
+ FIXED_LANDSCAPE_TOGGLE_DISABLED(2020),
// ADD MORE
;
@@ -828,6 +880,10 @@ public class StatsLogManager implements ResourceBasedOverride {
@UiEvent(doc = "The duration of asynchronous loading workspace")
LAUNCHER_LATENCY_STARTUP_WORKSPACE_LOADER_ASYNC(1367),
+
+ @UiEvent(doc = "Time passed between Contextual Search runnable creation and execution. This"
+ + " ensures that Recent animations have finished before Contextual Search starts.")
+ LAUNCHER_LATENCY_OMNI_RUNNABLE(1546),
;
private final int mId;
diff --git a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
index 427fb970be..55bcb7036a 100644
--- a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
+++ b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
@@ -41,6 +41,7 @@ import com.android.launcher3.model.data.WorkspaceItemFactory;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.pm.InstallSessionHelper;
import com.android.launcher3.pm.PackageInstallInfo;
+import com.android.launcher3.util.ApplicationInfoWrapper;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.PackageManagerHelper;
@@ -103,8 +104,8 @@ public class AddWorkspaceItemsTask implements ModelUpdateTask {
}
// b/139663018 Short-circuit this logic if the icon is a system app
- if (PackageManagerHelper.isSystemApp(context,
- Objects.requireNonNull(item.getIntent()))) {
+ if (new ApplicationInfoWrapper(context,
+ Objects.requireNonNull(item.getIntent())).isSystem()) {
continue;
}
diff --git a/src/com/android/launcher3/model/AllAppsList.java b/src/com/android/launcher3/model/AllAppsList.java
index 1f60f132df..7bc92733de 100644
--- a/src/com/android/launcher3/model/AllAppsList.java
+++ b/src/com/android/launcher3/model/AllAppsList.java
@@ -40,6 +40,7 @@ import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.pm.PackageInstallInfo;
import com.android.launcher3.pm.UserCache;
import com.android.launcher3.util.ApiWrapper;
+import com.android.launcher3.util.ApplicationInfoWrapper;
import com.android.launcher3.util.FlagOp;
import com.android.launcher3.util.PackageManagerHelper;
import com.android.launcher3.util.SafeCloseable;
@@ -169,8 +170,8 @@ public class AllAppsList {
public AppInfo addPromiseApp(
Context context, PackageInstallInfo installInfo, boolean loadIcon) {
// only if not yet installed
- if (PackageManagerHelper.INSTANCE.get(context)
- .isAppInstalled(installInfo.packageName, installInfo.user)) {
+ if (new ApplicationInfoWrapper(context, installInfo.packageName, installInfo.user)
+ .isInstalled()) {
return null;
}
AppInfo promiseAppInfo = new AppInfo(installInfo);
diff --git a/src/com/android/launcher3/model/BaseLauncherBinder.java b/src/com/android/launcher3/model/BaseLauncherBinder.java
index 5fad576ab9..014a28becd 100644
--- a/src/com/android/launcher3/model/BaseLauncherBinder.java
+++ b/src/com/android/launcher3/model/BaseLauncherBinder.java
@@ -24,6 +24,8 @@ import static com.android.launcher3.model.ModelUtils.filterCurrentWorkspaceItems
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+import static java.util.Collections.emptyList;
+
import android.os.Process;
import android.os.Trace;
import android.util.Log;
@@ -36,7 +38,6 @@ import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherModel.CallbackTask;
import com.android.launcher3.LauncherSettings;
-import com.android.launcher3.Workspace;
import com.android.launcher3.celllayout.CellPosMapper;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.model.BgDataModel.Callbacks;
@@ -44,6 +45,7 @@ import com.android.launcher3.model.BgDataModel.FixedContainerItems;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
+import com.android.launcher3.model.data.PackageItemInfo;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.IntSet;
@@ -59,12 +61,11 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
-import java.util.Set;
import java.util.concurrent.Executor;
+import java.util.function.Predicate;
import java.util.stream.Collectors;
/**
@@ -100,66 +101,35 @@ public class BaseLauncherBinder {
public void bindWorkspace(boolean incrementBindId, boolean isBindSync) {
Trace.beginSection("BaseLauncherBinder#bindWorkspace");
try {
- if (FeatureFlags.ENABLE_WORKSPACE_LOADING_OPTIMIZATION.get()) {
- DisjointWorkspaceBinder workspaceBinder =
- initWorkspaceBinder(incrementBindId, mBgDataModel.collectWorkspaceScreens());
- workspaceBinder.bindCurrentWorkspacePages(isBindSync);
- workspaceBinder.bindOtherWorkspacePages();
- } else {
- bindWorkspaceAllAtOnce(incrementBindId, isBindSync);
+ // Save a copy of all the bg-thread collections
+ ArrayList workspaceItems = new ArrayList<>();
+ ArrayList appWidgets = new ArrayList<>();
+ final IntArray orderedScreenIds = new IntArray();
+ ArrayList extraItems = new ArrayList<>();
+ final int workspaceItemCount;
+ synchronized (mBgDataModel) {
+ workspaceItems.addAll(mBgDataModel.workspaceItems);
+ appWidgets.addAll(mBgDataModel.appWidgets);
+ orderedScreenIds.addAll(mBgDataModel.collectWorkspaceScreens());
+ mBgDataModel.extraItems.forEach(extraItems::add);
+ if (incrementBindId) {
+ mBgDataModel.lastBindId++;
+ mBgDataModel.lastLoadId = mApp.getModel().getLastLoadId();
+ }
+ mMyBindingId = mBgDataModel.lastBindId;
+ workspaceItemCount = mBgDataModel.itemsIdMap.size();
+ }
+
+ for (Callbacks cb : mCallbacksList) {
+ new UnifiedWorkspaceBinder(cb, mUiExecutor, mApp, mBgDataModel, mMyBindingId,
+ workspaceItems, appWidgets, extraItems, orderedScreenIds)
+ .bind(isBindSync, workspaceItemCount);
}
} finally {
Trace.endSection();
}
}
- /**
- * Initializes the WorkspaceBinder for binding.
- *
- * @param incrementBindId this is used to stop previously started binding tasks that are
- * obsolete but still queued.
- * @param workspacePages this allows the Launcher to add the correct workspace screens.
- */
- public DisjointWorkspaceBinder initWorkspaceBinder(boolean incrementBindId,
- IntArray workspacePages) {
-
- synchronized (mBgDataModel) {
- if (incrementBindId) {
- mBgDataModel.lastBindId++;
- mBgDataModel.lastLoadId = mApp.getModel().getLastLoadId();
- }
- mMyBindingId = mBgDataModel.lastBindId;
- return new DisjointWorkspaceBinder(workspacePages);
- }
- }
-
- private void bindWorkspaceAllAtOnce(boolean incrementBindId, boolean isBindSync) {
- // Save a copy of all the bg-thread collections
- ArrayList workspaceItems = new ArrayList<>();
- ArrayList appWidgets = new ArrayList<>();
- final IntArray orderedScreenIds = new IntArray();
- ArrayList extraItems = new ArrayList<>();
- final int workspaceItemCount;
- synchronized (mBgDataModel) {
- workspaceItems.addAll(mBgDataModel.workspaceItems);
- appWidgets.addAll(mBgDataModel.appWidgets);
- orderedScreenIds.addAll(mBgDataModel.collectWorkspaceScreens());
- mBgDataModel.extraItems.forEach(extraItems::add);
- if (incrementBindId) {
- mBgDataModel.lastBindId++;
- mBgDataModel.lastLoadId = mApp.getModel().getLastLoadId();
- }
- mMyBindingId = mBgDataModel.lastBindId;
- workspaceItemCount = mBgDataModel.itemsIdMap.size();
- }
-
- for (Callbacks cb : mCallbacksList) {
- new UnifiedWorkspaceBinder(cb, mUiExecutor, mApp, mBgDataModel, mMyBindingId,
- workspaceItems, appWidgets, extraItems, orderedScreenIds)
- .bind(isBindSync, workspaceItemCount);
- }
- }
-
/**
* BindDeepShortcuts is abstract because it is a no-op for the go launcher.
*/
@@ -196,9 +166,17 @@ public class BaseLauncherBinder {
if (!WIDGETS_ENABLED) {
return;
}
+ Map>
+ widgetsByPackageItem = mBgDataModel.widgetsModel.getWidgetsByPackageItem();
List widgets = new WidgetsListBaseEntriesBuilder(mApp.getContext())
- .build(mBgDataModel.widgetsModel.getWidgetsByPackageItem());
- executeCallbacksTask(c -> c.bindAllWidgets(widgets), mUiExecutor);
+ .build(widgetsByPackageItem);
+ Predicate filter = mBgDataModel.widgetsModel.getDefaultWidgetsFilter();
+ List defaultWidgets =
+ filter != null ? new WidgetsListBaseEntriesBuilder(
+ mApp.getContext()).build(widgetsByPackageItem,
+ mBgDataModel.widgetsModel.getDefaultWidgetsFilter()) : emptyList();
+
+ executeCallbacksTask(c -> c.bindAllWidgets(widgets, defaultWidgets), mUiExecutor);
}
/**
@@ -347,10 +325,8 @@ public class BaseLauncherBinder {
bindItemsInChunks(currentWorkspaceItems, ITEMS_CHUNK, mUiExecutor);
bindItemsInChunks(currentAppWidgets, 1, mUiExecutor);
}
- if (!FeatureFlags.CHANGE_MODEL_DELEGATE_LOADING_ORDER.get()) {
- mExtraItems.forEach(item ->
- executeCallbacksTask(c -> c.bindExtraContainerItems(item), mUiExecutor));
- }
+ mExtraItems.forEach(item ->
+ executeCallbacksTask(c -> c.bindExtraContainerItems(item), mUiExecutor));
RunnableList pendingTasks = new RunnableList();
Executor pendingExecutor = pendingTasks::add;
@@ -441,126 +417,4 @@ public class BaseLauncherBinder {
});
}
}
-
- private class DisjointWorkspaceBinder {
- private final IntArray mOrderedScreenIds;
- private final IntSet mCurrentScreenIds = new IntSet();
- private final Set mBoundItemIds = new HashSet<>();
-
- protected DisjointWorkspaceBinder(IntArray orderedScreenIds) {
- mOrderedScreenIds = orderedScreenIds;
-
- for (Callbacks cb : mCallbacksList) {
- mCurrentScreenIds.addAll(cb.getPagesToBindSynchronously(orderedScreenIds));
- }
- if (mCurrentScreenIds.size() == 0) {
- mCurrentScreenIds.add(Workspace.FIRST_SCREEN_ID);
- }
- }
-
- /**
- * Binds the currently loaded items in the Data Model. Also signals to the Callbacks[]
- * that these items have been bound and their respective screens are ready to be shown.
- *
- * If this method is called after all the items on the workspace screen have already been
- * loaded, it will bind all workspace items immediately, and bindOtherWorkspacePages() will
- * not bind any items.
- */
- protected void bindCurrentWorkspacePages(boolean isBindSync) {
- // Save a copy of all the bg-thread collections
- ArrayList workspaceItems;
- ArrayList appWidgets;
- ArrayList fciList = new ArrayList<>();
- final int workspaceItemCount;
- synchronized (mBgDataModel) {
- workspaceItems = new ArrayList<>(mBgDataModel.workspaceItems);
- appWidgets = new ArrayList<>(mBgDataModel.appWidgets);
- if (!FeatureFlags.CHANGE_MODEL_DELEGATE_LOADING_ORDER.get()) {
- mBgDataModel.extraItems.forEach(fciList::add);
- }
- workspaceItemCount = mBgDataModel.itemsIdMap.size();
- }
-
- workspaceItems.forEach(it -> mBoundItemIds.add(it.id));
- appWidgets.forEach(it -> mBoundItemIds.add(it.id));
- if (!FeatureFlags.CHANGE_MODEL_DELEGATE_LOADING_ORDER.get()) {
- fciList.forEach(item ->
- executeCallbacksTask(c -> c.bindExtraContainerItems(item), mUiExecutor));
- }
-
- sortWorkspaceItemsSpatially(mApp.getInvariantDeviceProfile(), workspaceItems);
-
- // Tell the workspace that we're about to start binding items
- executeCallbacksTask(c -> {
- c.clearPendingBinds();
- c.startBinding();
- }, mUiExecutor);
-
- // Bind workspace screens
- executeCallbacksTask(c -> c.bindScreens(mOrderedScreenIds), mUiExecutor);
-
- bindWorkspaceItems(workspaceItems);
- bindAppWidgets(appWidgets);
- executeCallbacksTask(c -> {
- MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
-
- RunnableList onCompleteSignal = new RunnableList();
- onCompleteSignal.executeAllAndDestroy();
- c.onInitialBindComplete(mCurrentScreenIds, new RunnableList(), onCompleteSignal,
- workspaceItemCount, isBindSync);
- }, mUiExecutor);
- }
-
- protected void bindOtherWorkspacePages() {
- // Save a copy of all the bg-thread collections
- ArrayList workspaceItems;
- ArrayList appWidgets;
-
- synchronized (mBgDataModel) {
- workspaceItems = new ArrayList<>(mBgDataModel.workspaceItems);
- appWidgets = new ArrayList<>(mBgDataModel.appWidgets);
- }
-
- workspaceItems.removeIf(it -> mBoundItemIds.contains(it.id));
- appWidgets.removeIf(it -> mBoundItemIds.contains(it.id));
-
- sortWorkspaceItemsSpatially(mApp.getInvariantDeviceProfile(), workspaceItems);
-
- bindWorkspaceItems(workspaceItems);
- bindAppWidgets(appWidgets);
-
- executeCallbacksTask(c -> c.finishBindingItems(mCurrentScreenIds), mUiExecutor);
- mUiExecutor.execute(() -> {
- MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
- ItemInstallQueue.INSTANCE.get(mApp.getContext())
- .resumeModelPush(FLAG_LOADER_RUNNING);
- });
-
- StringCache cacheClone = mBgDataModel.stringCache.clone();
- executeCallbacksTask(c -> c.bindStringCache(cacheClone), mUiExecutor);
- }
-
- private void bindWorkspaceItems(final ArrayList workspaceItems) {
- // Bind the workspace items
- int count = workspaceItems.size();
- for (int i = 0; i < count; i += ITEMS_CHUNK) {
- final int start = i;
- final int chunkSize = (i + ITEMS_CHUNK <= count) ? ITEMS_CHUNK : (count - i);
- executeCallbacksTask(
- c -> c.bindItems(workspaceItems.subList(start, start + chunkSize), false),
- mUiExecutor);
- }
- }
-
- private void bindAppWidgets(List appWidgets) {
- // Bind the widgets, one at a time
- int count = appWidgets.size();
- for (int i = 0; i < count; i++) {
- final ItemInfo widget = appWidgets.get(i);
- executeCallbacksTask(
- c -> c.bindItems(Collections.singletonList(widget), false),
- mUiExecutor);
- }
- }
- }
}
diff --git a/src/com/android/launcher3/model/BgDataModel.java b/src/com/android/launcher3/model/BgDataModel.java
index 9a9fa5b352..b9b1e98ab9 100644
--- a/src/com/android/launcher3/model/BgDataModel.java
+++ b/src/com/android/launcher3/model/BgDataModel.java
@@ -537,7 +537,13 @@ public class BgDataModel {
default void bindWidgetsRestored(ArrayList widgets) { }
default void bindRestoreItemsChange(HashSet updates) { }
default void bindWorkspaceComponentsRemoved(Predicate matcher) { }
- default void bindAllWidgets(List widgets) { }
+
+ /**
+ * Binds the app widgets to the providers that share widgets with the UI.
+ */
+ default void bindAllWidgets(@NonNull List widgets,
+ @NonNull List defaultWidgets) {
+ }
default void bindSmartspaceWidget() { }
/** Called when workspace has been bound. */
diff --git a/src/com/android/launcher3/model/DatabaseHelper.java b/src/com/android/launcher3/model/DatabaseHelper.java
index 8368256164..ed4f492a80 100644
--- a/src/com/android/launcher3/model/DatabaseHelper.java
+++ b/src/com/android/launcher3/model/DatabaseHelper.java
@@ -56,6 +56,7 @@ import java.io.File;
import java.net.URISyntaxException;
import java.util.Arrays;
import java.util.Locale;
+import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.ToLongFunction;
import java.util.stream.Collectors;
@@ -79,8 +80,8 @@ public class DatabaseHelper extends NoLocaleSQLiteHelper implements
private final Context mContext;
private final ToLongFunction mUserSerialProvider;
private final Runnable mOnEmptyDbCreateCallback;
+ private final AtomicInteger mMaxItemId = new AtomicInteger(-1);
- private int mMaxItemId = -1;
public boolean mHotseatRestoreTableExists;
/**
@@ -97,21 +98,19 @@ public class DatabaseHelper extends NoLocaleSQLiteHelper implements
protected void initIds() {
// In the case where neither onCreate nor onUpgrade gets called, we read the maxId from
// the DB here
- if (mMaxItemId == -1) {
- mMaxItemId = initializeMaxItemId(getWritableDatabase());
- }
+ mMaxItemId.compareAndSet(-1, initializeMaxItemId(getWritableDatabase()));
}
@Override
public void onCreate(SQLiteDatabase db) {
if (LOGD) Log.d(TAG, "creating new launcher database");
- mMaxItemId = 1;
+ mMaxItemId.set(1);
addTableToDb(db, getDefaultUserSerial(), false /* optional */);
// Fresh and clean launcher DB.
- mMaxItemId = initializeMaxItemId(db);
+ mMaxItemId.set(initializeMaxItemId(db));
mOnEmptyDbCreateCallback.run();
}
@@ -451,11 +450,10 @@ public class DatabaseHelper extends NoLocaleSQLiteHelper implements
// after that point
@Override
public int generateNewItemId() {
- if (mMaxItemId < 0) {
+ if (mMaxItemId.get() < 0) {
throw new RuntimeException("Error: max item id was not initialized");
}
- mMaxItemId += 1;
- return mMaxItemId;
+ return mMaxItemId.incrementAndGet();
}
/**
@@ -484,7 +482,7 @@ public class DatabaseHelper extends NoLocaleSQLiteHelper implements
public void checkId(ContentValues values) {
int id = values.getAsInteger(Favorites._ID);
- mMaxItemId = Math.max(id, mMaxItemId);
+ mMaxItemId.accumulateAndGet(id, Math::max);
}
private int initializeMaxItemId(SQLiteDatabase db) {
@@ -508,7 +506,7 @@ public class DatabaseHelper extends NoLocaleSQLiteHelper implements
int count = loader.loadLayout(db);
// Ensure that the max ids are initialized
- mMaxItemId = initializeMaxItemId(db);
+ mMaxItemId.set(initializeMaxItemId(db));
return count;
}
diff --git a/src/com/android/launcher3/model/DbEntry.kt b/src/com/android/launcher3/model/DbEntry.kt
new file mode 100644
index 0000000000..b79d312d1c
--- /dev/null
+++ b/src/com/android/launcher3/model/DbEntry.kt
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2024 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.model
+
+import android.content.ContentValues
+import android.content.Intent
+import android.util.Log
+import com.android.launcher3.LauncherSettings
+import com.android.launcher3.LauncherSettings.Favorites.CELLX
+import com.android.launcher3.LauncherSettings.Favorites.CELLY
+import com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION
+import com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET
+import com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR
+import com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_FOLDER
+import com.android.launcher3.LauncherSettings.Favorites.SCREEN
+import com.android.launcher3.LauncherSettings.Favorites.SPANX
+import com.android.launcher3.LauncherSettings.Favorites.SPANY
+import com.android.launcher3.model.data.ItemInfo
+import com.android.launcher3.util.ContentWriter
+import java.net.URISyntaxException
+import java.util.Objects
+
+class DbEntry : ItemInfo(), Comparable {
+ @JvmField var mIntent: String? = null
+ @JvmField var mProvider: String? = null
+ @JvmField var mFolderItems: MutableMap> = HashMap()
+
+ /** Id of the specific widget. */
+ @JvmField var appWidgetId: Int = NO_ID
+
+ /** Comparator according to the reading order */
+ override fun compareTo(other: DbEntry): Int {
+ if (screenId != other.screenId) {
+ return screenId.compareTo(other.screenId)
+ }
+ if (cellY != other.cellY) {
+ return cellY.compareTo(other.cellY)
+ }
+ return cellX.compareTo(other.cellX)
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other !is DbEntry) return false
+ return getEntryMigrationId() == other.getEntryMigrationId()
+ }
+
+ override fun hashCode(): Int = Objects.hash(getEntryMigrationId())
+
+ /**
+ * Puts the updated DbEntry values into ContentValues which we then use to insert the entry to
+ * the DB.
+ */
+ fun updateContentValues(values: ContentValues) =
+ values.apply {
+ put(SCREEN, screenId)
+ put(CELLX, cellX)
+ put(CELLY, cellY)
+ put(SPANX, spanX)
+ put(SPANY, spanY)
+ }
+
+ override fun writeToValues(writer: ContentWriter) {
+ super.writeToValues(writer)
+ writer.put(LauncherSettings.Favorites.APPWIDGET_ID, appWidgetId)
+ }
+
+ override fun readFromValues(values: ContentValues) {
+ super.readFromValues(values)
+ appWidgetId = values.getAsInteger(LauncherSettings.Favorites.APPWIDGET_ID)
+ }
+
+ /**
+ * This id is not used in the DB is only used while doing the migration and it identifies an
+ * entry on each workspace. For example two calculator icons would have the same migration id
+ * even thought they have different database ids.
+ */
+ private fun getEntryMigrationId(): String? {
+ when (itemType) {
+ ITEM_TYPE_FOLDER,
+ ITEM_TYPE_APP_PAIR -> return getFolderMigrationId()
+ ITEM_TYPE_APPWIDGET ->
+ // mProvider is the app the widget belongs to and appWidgetId it's the unique
+ // is of the widget, we need both because if you remove a widget and then add it
+ // again, then it can change and the WidgetProvider would not know the widget.
+ return mProvider + appWidgetId
+ ITEM_TYPE_APPLICATION -> {
+ val intentStr = mIntent?.let { cleanIntentString(it) }
+ try {
+ val i = Intent.parseUri(intentStr, 0)
+ return Objects.requireNonNull(i.component).toString()
+ } catch (e: Exception) {
+ return intentStr
+ }
+ }
+
+ else -> return mIntent?.let { cleanIntentString(it) }
+ }
+ }
+
+ /**
+ * This method should return an id that should be the same for two folders containing the same
+ * elements.
+ */
+ private fun getFolderMigrationId(): String =
+ mFolderItems.keys
+ .map { intentString: String ->
+ mFolderItems[intentString]?.size.toString() + cleanIntentString(intentString)
+ }
+ .sorted()
+ .joinToString(",")
+
+ /**
+ * This is needed because sourceBounds can change and make the id of two equal items different.
+ */
+ private fun cleanIntentString(intentStr: String): String {
+ try {
+ return Intent.parseUri(intentStr, 0).apply { sourceBounds = null }.toURI()
+ } catch (e: URISyntaxException) {
+ Log.e(TAG, "Unable to parse Intent string", e)
+ return intentStr
+ }
+ }
+
+ companion object {
+ private const val TAG = "DbEntry"
+ }
+}
diff --git a/src/com/android/launcher3/model/DeviceGridState.java b/src/com/android/launcher3/model/DeviceGridState.java
index 729b3814a4..90af2155e1 100644
--- a/src/com/android/launcher3/model/DeviceGridState.java
+++ b/src/com/android/launcher3/model/DeviceGridState.java
@@ -156,10 +156,16 @@ public class DeviceGridState implements Comparable {
}
public Integer getColumns() {
+ if (TextUtils.isEmpty(mGridSizeString)) {
+ return -1;
+ }
return Integer.parseInt(String.valueOf(mGridSizeString.split(",")[0]));
}
public Integer getRows() {
+ if (TextUtils.isEmpty(mGridSizeString)) {
+ return -1;
+ }
return Integer.parseInt(String.valueOf(mGridSizeString.split(",")[1]));
}
diff --git a/src/com/android/launcher3/model/GridSizeMigrationUtil.java b/src/com/android/launcher3/model/GridSizeMigrationDBController.java
similarity index 81%
rename from src/com/android/launcher3/model/GridSizeMigrationUtil.java
rename to src/com/android/launcher3/model/GridSizeMigrationDBController.java
index 3bddc2ca94..03f5727abf 100644
--- a/src/com/android/launcher3/model/GridSizeMigrationUtil.java
+++ b/src/com/android/launcher3/model/GridSizeMigrationDBController.java
@@ -17,17 +17,18 @@
package com.android.launcher3.model;
import static com.android.launcher3.Flags.enableSmartspaceRemovalToggle;
+import static com.android.launcher3.Flags.oneGridSpecs;
import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME;
import static com.android.launcher3.LauncherSettings.Favorites.TMP_TABLE;
import static com.android.launcher3.Utilities.SHOULD_SHOW_FIRST_PAGE_WIDGET;
import static com.android.launcher3.model.LoaderTask.SMARTSPACE_ON_HOME_SCREEN;
import static com.android.launcher3.provider.LauncherDbUtils.copyTable;
import static com.android.launcher3.provider.LauncherDbUtils.dropTable;
+import static com.android.launcher3.provider.LauncherDbUtils.shiftTableByXCells;
import android.content.ComponentName;
import android.content.ContentValues;
import android.content.Context;
-import android.content.Intent;
import android.database.Cursor;
import android.database.DatabaseUtils;
import android.database.sqlite.SQLiteDatabase;
@@ -38,21 +39,17 @@ import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
-import com.android.launcher3.Flags;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.Utilities;
import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.provider.LauncherDbUtils.SQLiteTransaction;
-import com.android.launcher3.util.ContentWriter;
import com.android.launcher3.util.GridOccupancy;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
import com.android.launcher3.widget.WidgetManagerHelper;
-import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@@ -60,7 +57,6 @@ import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
-import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
@@ -68,12 +64,12 @@ import java.util.stream.Collectors;
* This class takes care of shrinking the workspace (by maximum of one row and one column), as a
* result of restoring from a larger device or device density change.
*/
-public class GridSizeMigrationUtil {
+public class GridSizeMigrationDBController {
- private static final String TAG = "GridSizeMigrationUtil";
+ private static final String TAG = "GridSizeMigrationDBController";
private static final boolean DEBUG = true;
- private GridSizeMigrationUtil() {
+ private GridSizeMigrationDBController() {
// Util class should not be instantiated
}
@@ -84,16 +80,22 @@ public class GridSizeMigrationUtil {
return needsToMigrate(new DeviceGridState(context), new DeviceGridState(idp));
}
- private static boolean needsToMigrate(
+ static boolean needsToMigrate(
DeviceGridState srcDeviceState, DeviceGridState destDeviceState) {
boolean needsToMigrate = !destDeviceState.isCompatible(srcDeviceState);
if (needsToMigrate) {
Log.i(TAG, "Migration is needed. destDeviceState: " + destDeviceState
+ ", srcDeviceState: " + srcDeviceState);
+ } else {
+ Log.i(TAG, "Migration is not needed. destDeviceState: " + destDeviceState
+ + ", srcDeviceState: " + srcDeviceState);
}
return needsToMigrate;
}
+ /**
+ * @return all the workspace and hotseat entries in the db.
+ */
@VisibleForTesting
public static List readAllEntries(SQLiteDatabase db, String tableName,
Context context) {
@@ -117,25 +119,33 @@ public class GridSizeMigrationUtil {
@NonNull DeviceGridState srcDeviceState,
@NonNull DeviceGridState destDeviceState,
@NonNull DatabaseHelper target,
- @NonNull SQLiteDatabase source) {
-
- Log.i("b/360462379", "Going from " + srcDeviceState.getColumns() + "x"
- + srcDeviceState.getRows());
- Log.i("b/360462379", "Going to " + destDeviceState.getColumns() + "x"
- + destDeviceState.getRows());
+ @NonNull SQLiteDatabase source,
+ boolean isDestNewDb) {
if (!needsToMigrate(srcDeviceState, destDeviceState)) {
- Log.i("b/360462379", "Does not need to migrate.");
return true;
}
- if (Flags.enableGridMigrationFix()
+ if (isDestNewDb
&& srcDeviceState.getColumns().equals(destDeviceState.getColumns())
&& srcDeviceState.getRows() < destDeviceState.getRows()) {
- Log.i("b/360462379", "Grid migration fix entry point.");
// Only use this strategy when comparing the previous grid to the new grid and the
// columns are the same and the destination has more rows
copyTable(source, TABLE_NAME, target.getWritableDatabase(), TABLE_NAME, context);
+
+ if (oneGridSpecs()) {
+ DbReader destReader = new DbReader(
+ target.getWritableDatabase(), TABLE_NAME, context);
+ boolean shouldShiftCells = shouldShiftCells(destReader, srcDeviceState.getRows());
+ if (shouldShiftCells) {
+ shiftTableByXCells(
+ target.getWritableDatabase(),
+ (destDeviceState.getRows() - srcDeviceState.getRows()),
+ TABLE_NAME);
+ }
+ }
+
+ // Save current configuration, so that the migration does not run again.
destDeviceState.writeToPrefs(context);
return true;
}
@@ -196,7 +206,7 @@ public class GridSizeMigrationUtil {
Collectors.joining(",\n", "[", "]"))
+ "\n Removing Items:"
+ dstWorkspaceItems.stream().filter(entry ->
- toBeRemoved.contains(entry.id)).map(DbEntry::toString).collect(
+ toBeRemoved.contains(entry.id)).map(DbEntry::toString).collect(
Collectors.joining(",\n", "[", "]"))
+ "\n Adding Workspace Items:"
+ workspaceToBeAdded.stream().map(DbEntry::toString).collect(
@@ -293,7 +303,7 @@ public class GridSizeMigrationUtil {
});
}
- private static void insertEntryInDb(DatabaseHelper helper, DbEntry entry,
+ static void insertEntryInDb(DatabaseHelper helper, DbEntry entry,
String srcTableName, String destTableName, List idsInUse) {
int id = copyEntryAndUpdate(helper, entry, srcTableName, destTableName, idsInUse);
if (entry.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER
@@ -332,10 +342,9 @@ public class GridSizeMigrationUtil {
} else {
values.put(LauncherSettings.Favorites.CONTAINER, folderId);
}
- newId = helper.generateNewItemId();
- while (idsInUse.contains(newId)) {
+ do {
newId = helper.generateNewItemId();
- }
+ } while (idsInUse.contains(newId));
values.put(LauncherSettings.Favorites._ID, newId);
helper.getWritableDatabase().insert(destTableName, null, values);
}
@@ -343,7 +352,7 @@ public class GridSizeMigrationUtil {
return newId;
}
- private static void removeEntryFromDb(SQLiteDatabase db, String tableName, IntArray entryIds) {
+ static void removeEntryFromDb(SQLiteDatabase db, String tableName, IntArray entryIds) {
db.delete(tableName,
Utilities.createDbSelectionQuery(LauncherSettings.Favorites._ID, entryIds), null);
}
@@ -389,7 +398,7 @@ public class GridSizeMigrationUtil {
private static boolean findPlacementForEntry(@NonNull final DbEntry entry,
@NonNull final Point next, @NonNull final Point trg,
@NonNull final GridOccupancy occupied, final int screenId) {
- for (int y = next.y; y < trg.y; y++) {
+ for (int y = next.y; y < trg.y; y++) {
for (int x = next.x; x < trg.x; x++) {
boolean fits = occupied.isRegionVacant(x, y, entry.spanX, entry.spanY);
boolean minFits = occupied.isRegionVacant(x, y, entry.minSpanX,
@@ -415,7 +424,7 @@ public class GridSizeMigrationUtil {
private static void solveHotseatPlacement(
@NonNull final DatabaseHelper helper, final int hotseatSize,
@NonNull final DbReader srcReader, @NonNull final DbReader destReader,
- @NonNull final List