summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/com/android/launcher3/GridType.kt33
-rw-r--r--src/com/android/launcher3/InvariantDeviceProfile.java16
-rw-r--r--src/com/android/launcher3/Launcher.java12
-rw-r--r--src/com/android/launcher3/LauncherPrefs.kt4
-rw-r--r--src/com/android/launcher3/allapps/AlphabeticalAppsList.java1
-rw-r--r--src/com/android/launcher3/allapps/PrivateProfileManager.java13
-rw-r--r--src/com/android/launcher3/config/FeatureFlags.java5
-rw-r--r--src/com/android/launcher3/dagger/LauncherAppModule.java1
-rw-r--r--src/com/android/launcher3/dagger/LauncherBaseAppComponent.java2
-rw-r--r--src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java11
-rw-r--r--src/com/android/launcher3/folder/Folder.java26
-rw-r--r--src/com/android/launcher3/icons/IconCache.java8
-rw-r--r--src/com/android/launcher3/logging/StatsLogManager.java6
-rw-r--r--src/com/android/launcher3/model/DeviceGridState.java20
-rw-r--r--src/com/android/launcher3/model/GridSizeMigrationDBController.java17
-rw-r--r--src/com/android/launcher3/model/GridSizeMigrationLogic.kt12
-rw-r--r--src/com/android/launcher3/model/LoaderCursor.java1
-rw-r--r--src/com/android/launcher3/model/LoaderTask.java7
-rw-r--r--src/com/android/launcher3/model/WorkspaceItemProcessor.kt15
-rw-r--r--src/com/android/launcher3/pageindicators/PageIndicatorDots.java7
-rw-r--r--src/com/android/launcher3/popup/PopupContainerWithArrow.java7
-rw-r--r--src/com/android/launcher3/shortcuts/DeepShortcutTextView.java7
-rw-r--r--src/com/android/launcher3/widget/LauncherAppWidgetHost.java68
-rw-r--r--src/com/android/launcher3/widget/LauncherWidgetHolder.java161
-rw-r--r--src/com/android/launcher3/widget/ListenableAppWidgetHost.kt72
-rw-r--r--src/com/android/launcher3/widget/PendingAppWidgetHostView.java2
26 files changed, 344 insertions, 190 deletions
diff --git a/src/com/android/launcher3/GridType.kt b/src/com/android/launcher3/GridType.kt
new file mode 100644
index 0000000000..d006b8f35f
--- /dev/null
+++ b/src/com/android/launcher3/GridType.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2025 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 androidx.annotation.IntDef
+
+/** The type of grid. */
+@IntDef(GridType.GRID_TYPE_ONE_GRID, GridType.GRID_TYPE_NON_ONE_GRID, GridType.GRID_TYPE_ANY)
+@Retention(AnnotationRetention.SOURCE)
+annotation class GridType {
+ companion object {
+ /** These are grids that use one grid spec. */
+ const val GRID_TYPE_ONE_GRID = 1
+ /** These are grids that don't use one grid spec. */
+ const val GRID_TYPE_NON_ONE_GRID = 2
+ /** Any grid type. */
+ const val GRID_TYPE_ANY = GRID_TYPE_NON_ONE_GRID or GRID_TYPE_ONE_GRID
+ }
+}
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index f189549463..15a4fc4072 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -16,6 +16,9 @@
package com.android.launcher3;
+import static com.android.launcher3.GridType.GRID_TYPE_ANY;
+import static com.android.launcher3.GridType.GRID_TYPE_NON_ONE_GRID;
+import static com.android.launcher3.GridType.GRID_TYPE_ONE_GRID;
import static com.android.launcher3.LauncherPrefs.DB_FILE;
import static com.android.launcher3.LauncherPrefs.ENABLE_TWOLINE_ALLAPPS_TOGGLE;
import static com.android.launcher3.LauncherPrefs.FIXED_LANDSCAPE_MODE;
@@ -241,6 +244,8 @@ public class InvariantDeviceProfile {
*/
public boolean isFixedLandscape = false;
+ @GridType
+ public int gridType;
public String dbFile;
public int defaultLayoutId;
public int demoModeLayoutId;
@@ -369,6 +374,7 @@ public class InvariantDeviceProfile {
numColumns = closestProfile.numColumns;
numSearchContainerColumns = closestProfile.numSearchContainerColumns;
dbFile = closestProfile.dbFile;
+ gridType = closestProfile.gridType;
defaultLayoutId = closestProfile.defaultLayoutId;
demoModeLayoutId = closestProfile.demoModeLayoutId;
@@ -936,10 +942,7 @@ public class InvariantDeviceProfile {
private static final int DEVICE_CATEGORY_PHONE = 1 << 0;
private static final int DEVICE_CATEGORY_TABLET = 1 << 1;
private static final int DEVICE_CATEGORY_MULTI_DISPLAY = 1 << 2;
- private static final int GRID_TYPE_ONE_GRID = 1 << 0;
- private static final int GRID_TYPE_NON_ONE_GRID = 1 << 1;
- private static final int GRID_TYPE_ALL = 1 << 2;
- private static final int DEVICE_CATEGORY_ALL =
+ private static final int DEVICE_CATEGORY_ANY =
DEVICE_CATEGORY_PHONE | DEVICE_CATEGORY_TABLET | DEVICE_CATEGORY_MULTI_DISPLAY;
private static final int INLINE_QSB_FOR_PORTRAIT = 1 << 0;
@@ -955,6 +958,7 @@ public class InvariantDeviceProfile {
public final int numColumns;
public final int numSearchContainerColumns;
public final int deviceCategory;
+ @GridType
public final int gridType;
private final int[] numFolderRows = new int[COUNT_SIZES];
@@ -1003,7 +1007,7 @@ public class InvariantDeviceProfile {
gridIconId = a.getResourceId(
R.styleable.GridDisplayOption_gridIconId, INVALID_RESOURCE_HANDLE);
deviceCategory = a.getInt(R.styleable.GridDisplayOption_deviceCategory,
- DEVICE_CATEGORY_ALL);
+ DEVICE_CATEGORY_ANY);
mGridSizeSpecsId = a.getResourceId(
R.styleable.GridDisplayOption_gridSizeSpecsId, INVALID_RESOURCE_HANDLE);
mIsDualGrid = a.getBoolean(R.styleable.GridDisplayOption_isDualGrid, false);
@@ -1141,7 +1145,7 @@ public class InvariantDeviceProfile {
}
mIsFixedLandscape = a.getBoolean(R.styleable.GridDisplayOption_isFixedLandscape, false);
- gridType = a.getInt(R.styleable.GridDisplayOption_gridType, GRID_TYPE_ALL);
+ gridType = a.getInt(R.styleable.GridDisplayOption_gridType, GRID_TYPE_ANY);
int inlineForRotation = a.getInt(R.styleable.GridDisplayOption_inlineQsb,
DONT_INLINE_QSB);
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 5c9392d69d..d5b3ed5645 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -532,11 +532,14 @@ public class Launcher extends StatefulActivity<LauncherState>
mAllAppsController = new AllAppsTransitionController(this);
mStateManager = new StateManager<>(this, NORMAL);
+ mAppWidgetManager = new WidgetManagerHelper(this);
+ mAppWidgetHolder = LauncherWidgetHolder.newInstance(this);
+ mAppWidgetHolder.setAppWidgetRemovedCallback(
+ appWidgetId -> getWorkspace().removeWidget(appWidgetId));
+
setupViews();
updateDisallowBack();
- mAppWidgetManager = new WidgetManagerHelper(this);
- mAppWidgetHolder = createAppWidgetHolder();
mAppWidgetHolder.startListening();
mAppWidgetHolder.addProviderChangeListener(() -> refreshAndBindWidgetsForPackageUser(null));
mItemInflater = new ItemInflater<>(this, mAppWidgetHolder, getItemOnClickListener(),
@@ -1614,11 +1617,6 @@ public class Launcher extends StatefulActivity<LauncherState>
return instance;
}
- protected LauncherWidgetHolder createAppWidgetHolder() {
- return LauncherWidgetHolder.HolderFactory.newFactory(this).newInstance(
- this, appWidgetId -> getWorkspace().removeWidget(appWidgetId));
- }
-
@Override
protected void onNewIntent(Intent intent) {
if (Utilities.isRunningInTestHarness()) {
diff --git a/src/com/android/launcher3/LauncherPrefs.kt b/src/com/android/launcher3/LauncherPrefs.kt
index 7a04b0f950..30c4529613 100644
--- a/src/com/android/launcher3/LauncherPrefs.kt
+++ b/src/com/android/launcher3/LauncherPrefs.kt
@@ -20,6 +20,7 @@ import android.content.Context.MODE_PRIVATE
import android.content.SharedPreferences
import androidx.annotation.VisibleForTesting
import com.android.launcher3.BuildConfig.WIDGET_ON_FIRST_SCREEN
+import com.android.launcher3.GridType.Companion.GRID_TYPE_ANY
import com.android.launcher3.InvariantDeviceProfile.GRID_NAME_PREFS_KEY
import com.android.launcher3.InvariantDeviceProfile.NON_FIXED_LANDSCAPE_GRID_NAME_PREFS_KEY
import com.android.launcher3.LauncherFiles.DEVICE_PREFERENCES_KEY
@@ -266,6 +267,9 @@ constructor(@ApplicationContext private val encryptedContext: Context) {
@JvmField
val DB_FILE = backedUpItem(DeviceGridState.KEY_DB_FILE, "", EncryptionType.ENCRYPTED)
@JvmField
+ val GRID_TYPE =
+ backedUpItem(DeviceGridState.KEY_GRID_TYPE, GRID_TYPE_ANY, EncryptionType.ENCRYPTED)
+ @JvmField
val SHOULD_SHOW_SMARTSPACE =
backedUpItem(
SHOULD_SHOW_SMARTSPACE_KEY,
diff --git a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
index f223eaa600..bf02e03f99 100644
--- a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
+++ b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
@@ -435,6 +435,7 @@ public class AlphabeticalAppsList<T extends Context & ActivityContext> implement
}
if (currentItem.itemInfo != null && Objects.equals(
currentItem.itemInfo.getTargetPackage(), PRIVATE_SPACE_PACKAGE)) {
+ currentItem.itemInfo.bitmap = mPrivateProviderManager.preparePSBitmapInfo();
currentItem.itemInfo.bitmap.creationFlags |= FLAG_NO_BADGE;
currentItem.itemInfo.contentDescription =
mPrivateProviderManager.getPsAppContentDesc();
diff --git a/src/com/android/launcher3/allapps/PrivateProfileManager.java b/src/com/android/launcher3/allapps/PrivateProfileManager.java
index 1bc1b17d15..0e6a5b867b 100644
--- a/src/com/android/launcher3/allapps/PrivateProfileManager.java
+++ b/src/com/android/launcher3/allapps/PrivateProfileManager.java
@@ -190,15 +190,11 @@ public class PrivateProfileManager extends UserProfileManager {
/** Adds Private Space install app button to the layout. */
public void addPrivateSpaceInstallAppButton(List<BaseAllAppsAdapter.AdapterItem> adapterItems) {
Context context = mAllApps.getContext();
- // Prepare bitmapInfo
- Intent.ShortcutIconResource shortcut = Intent.ShortcutIconResource.fromContext(
- context, com.android.launcher3.R.drawable.private_space_install_app_icon);
- BitmapInfo bitmapInfo = LauncherIcons.obtain(context).createIconBitmap(shortcut);
PrivateSpaceInstallAppButtonInfo itemInfo = new PrivateSpaceInstallAppButtonInfo();
itemInfo.title = context.getResources().getString(R.string.ps_add_button_label);
itemInfo.intent = mAppInstallerIntent;
- itemInfo.bitmap = bitmapInfo;
+ itemInfo.bitmap = preparePSBitmapInfo();
itemInfo.contentDescription = context.getResources().getString(
com.android.launcher3.R.string.ps_add_button_content_description);
itemInfo.runtimeStatusFlags |= FLAG_NOT_PINNABLE;
@@ -218,6 +214,13 @@ public class PrivateProfileManager extends UserProfileManager {
.get(mAllApps.getContext()).getValue(PRIVATE_SPACE_HIDE_WHEN_LOCKED_URI, 0);
}
+ BitmapInfo preparePSBitmapInfo() {
+ Context context = mAllApps.getContext();
+ Intent.ShortcutIconResource shortcut = Intent.ShortcutIconResource.fromContext(
+ context, com.android.launcher3.R.drawable.private_space_install_app_icon);
+ return LauncherIcons.obtain(context).createIconBitmap(shortcut);
+ }
+
/**
* Resets the current state of Private Profile, w.r.t. to Launcher. The decorator should only
* be applied upon expand before animating. When collapsing, reset() will remove the decorator
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index 44dcc06791..d987841410 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -163,11 +163,6 @@ public final class FeatureFlags {
"ENABLE_WIDGET_TRANSITION_FOR_RESIZING", DISABLED,
"Enable widget transition animation when resizing the widgets");
- // TODO(Block 25): Clean up flags
- public static final BooleanFlag ENABLE_WIDGET_HOST_IN_BACKGROUND = getDebugFlag(270394384,
- "ENABLE_WIDGET_HOST_IN_BACKGROUND", ENABLED,
- "Enable background widget updates listening for widget holder");
-
// TODO(Block 27): Clean up flags
public static final BooleanFlag ENABLE_OVERLAY_CONNECTION_OPTIM = getDebugFlag(270392629,
"ENABLE_OVERLAY_CONNECTION_OPTIM", DISABLED,
diff --git a/src/com/android/launcher3/dagger/LauncherAppModule.java b/src/com/android/launcher3/dagger/LauncherAppModule.java
index c58a414304..0fd32190c4 100644
--- a/src/com/android/launcher3/dagger/LauncherAppModule.java
+++ b/src/com/android/launcher3/dagger/LauncherAppModule.java
@@ -23,6 +23,7 @@ import dagger.Module;
ApiWrapperModule.class,
PluginManagerWrapperModule.class,
StaticObjectModule.class,
+ WidgetModule.class,
AppModule.class
})
public class LauncherAppModule {
diff --git a/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java b/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java
index c49909772e..f86772e5f5 100644
--- a/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java
+++ b/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java
@@ -46,6 +46,7 @@ import com.android.launcher3.util.VibratorWrapper;
import com.android.launcher3.util.WallpaperColorHints;
import com.android.launcher3.util.window.RefreshRateTracker;
import com.android.launcher3.util.window.WindowManagerProxy;
+import com.android.launcher3.widget.LauncherWidgetHolder.WidgetHolderFactory;
import com.android.launcher3.widget.custom.CustomWidgetManager;
import dagger.BindsInstance;
@@ -89,6 +90,7 @@ public interface LauncherBaseAppComponent {
WidgetsFilterDataProvider getWidgetsFilterDataProvider();
LoaderCursorFactory getLoaderCursorFactory();
+ WidgetHolderFactory getWidgetHolderFactory();
/** Builder for LauncherBaseAppComponent. */
interface Builder {
diff --git a/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java b/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java
index 929e52e1fc..813ed3ec7c 100644
--- a/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java
+++ b/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java
@@ -38,7 +38,6 @@ import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import com.android.launcher3.folder.FolderIcon;
-import com.android.launcher3.folder.PreviewBackground;
import com.android.launcher3.icons.BitmapRenderer;
import com.android.launcher3.util.Preconditions;
import com.android.launcher3.views.ActivityContext;
@@ -143,7 +142,6 @@ public class FolderAdaptiveIcon extends AdaptiveIconDrawable {
icon.getPreviewBounds(sTmpRect);
final int previewSize = sTmpRect.width();
- PreviewBackground bg = icon.getFolderBackground();
final int margin = (size - previewSize) / 2;
final float previewShiftX = -sTmpRect.left + margin;
final float previewShiftY = -sTmpRect.top + margin;
@@ -162,11 +160,10 @@ public class FolderAdaptiveIcon extends AdaptiveIconDrawable {
foregroundCanvas.restore();
// Draw background
- Paint backgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
- backgroundPaint.setColor(bg.getBgColor());
- bg.drawShadow(backgroundCanvas);
- backgroundCanvas.drawPaint(backgroundPaint);
- bg.drawBackgroundStroke(backgroundCanvas);
+ backgroundCanvas.save();
+ backgroundCanvas.translate(previewShiftX, previewShiftY);
+ icon.getFolderBackground().drawBackground(backgroundCanvas);
+ backgroundCanvas.restore();
}
@Override
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index 967af053fb..996c5e7f55 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -18,6 +18,7 @@ package com.android.launcher3.folder;
import static android.text.TextUtils.isEmpty;
+import static com.android.launcher3.Flags.enableLauncherVisualRefresh;
import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY;
import static com.android.launcher3.LauncherState.EDIT_MODE;
import static com.android.launcher3.LauncherState.NORMAL;
@@ -299,6 +300,13 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo
mContent.setFolder(this);
mPageIndicator = findViewById(R.id.folder_page_indicator);
+ if (enableLauncherVisualRefresh()) {
+ MarginLayoutParams params = ((MarginLayoutParams) mPageIndicator.getLayoutParams());
+ int horizontalMargin = getContext().getResources()
+ .getDimensionPixelSize(R.dimen.folder_footer_horiz_padding);
+ params.setMarginStart(horizontalMargin);
+ params.setMarginEnd(horizontalMargin);
+ }
mFooter = findViewById(R.id.folder_footer);
mFooterHeight = dp.folderFooterHeightPx;
mFolderName = findViewById(R.id.folder_name);
@@ -312,7 +320,6 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo
| InputType.TYPE_TEXT_FLAG_CAP_WORDS);
mFolderName.forceDisableSuggestions(true);
-
mKeyboardInsetAnimationCallback = new KeyboardInsetAnimationCallback(this);
setWindowInsetsAnimationCallback(mKeyboardInsetAnimationCallback);
}
@@ -1270,6 +1277,23 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo
}
/**
+ * If the Folder Title has less than 100dp of available width, we hide it. The reason we do this
+ * calculation in onSizeChange is because this callback is called 1x when the folder is opened.
+ * <p>
+ * The PageIndicator and the Folder Title share the same horizontal linear layout, but both
+ * are dynamically sized. Therefore, we are setting visibility of the folder title AFTER the
+ * layout is measured.
+ */
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ super.onSizeChanged(w, h, oldw, oldh);
+ int minTitleWidth = getResources().getDimensionPixelSize(R.dimen.folder_title_min_width);
+ if (enableLauncherVisualRefresh() && mFolderName.getMeasuredWidth() < minTitleWidth) {
+ mFolderName.setVisibility(View.GONE);
+ }
+ }
+
+ /**
* Rearranges the children based on their rank.
*/
public void rearrangeChildren() {
diff --git a/src/com/android/launcher3/icons/IconCache.java b/src/com/android/launcher3/icons/IconCache.java
index 119a6b122b..1e80d03727 100644
--- a/src/com/android/launcher3/icons/IconCache.java
+++ b/src/com/android/launcher3/icons/IconCache.java
@@ -159,9 +159,15 @@ public class IconCache extends BaseIconCache {
*/
public synchronized void updateIconsForPkg(@NonNull final String packageName,
@NonNull final UserHandle user) {
+ List<LauncherActivityInfo> apps = mLauncherApps.getActivityList(packageName, user);
+ if (Flags.restoreArchivedAppIconsFromDb()
+ && apps.stream().anyMatch(app -> app.getApplicationInfo().isArchived)) {
+ // When archiving app icon, don't delete old icon so it can be re-used.
+ return;
+ }
removeIconsForPkg(packageName, user);
long userSerial = mUserManager.getSerialNumberForUser(user);
- for (LauncherActivityInfo app : mLauncherApps.getActivityList(packageName, user)) {
+ for (LauncherActivityInfo app : apps) {
addIconToDBAndMemCache(app, LauncherActivityCachingLogic.INSTANCE, userSerial);
}
}
diff --git a/src/com/android/launcher3/logging/StatsLogManager.java b/src/com/android/launcher3/logging/StatsLogManager.java
index 44d2e266f7..40b597f838 100644
--- a/src/com/android/launcher3/logging/StatsLogManager.java
+++ b/src/com/android/launcher3/logging/StatsLogManager.java
@@ -890,6 +890,12 @@ public class StatsLogManager implements ResourceBasedOverride {
@UiEvent(doc = "Row shift grid migration occurred")
LAUNCHER_ROW_SHIFT_GRID_MIGRATION(2201),
+ @UiEvent(doc = "Do standard migration when upgrading to one grid")
+ LAUNCHER_STANDARD_ONE_GRID_MIGRATION(2205),
+
+ @UiEvent(doc = "Do row shift migration when upgrading to one grid")
+ LAUNCHER_ROW_SHIFT_ONE_GRID_MIGRATION(2206),
+
// ADD MORE
;
diff --git a/src/com/android/launcher3/model/DeviceGridState.java b/src/com/android/launcher3/model/DeviceGridState.java
index 96ce4c83db..32ea4b5fd7 100644
--- a/src/com/android/launcher3/model/DeviceGridState.java
+++ b/src/com/android/launcher3/model/DeviceGridState.java
@@ -19,6 +19,7 @@ package com.android.launcher3.model;
import static com.android.launcher3.InvariantDeviceProfile.DeviceType;
import static com.android.launcher3.LauncherPrefs.DB_FILE;
import static com.android.launcher3.LauncherPrefs.DEVICE_TYPE;
+import static com.android.launcher3.LauncherPrefs.GRID_TYPE;
import static com.android.launcher3.LauncherPrefs.HOTSEAT_COUNT;
import static com.android.launcher3.LauncherPrefs.WORKSPACE_SIZE;
@@ -41,17 +42,21 @@ public class DeviceGridState implements Comparable<DeviceGridState> {
public static final String KEY_HOTSEAT_COUNT = "migration_src_hotseat_count";
public static final String KEY_DEVICE_TYPE = "migration_src_device_type";
public static final String KEY_DB_FILE = "migration_src_db_file";
+ public static final String KEY_GRID_TYPE = "migration_src_grid_type";
private final String mGridSizeString;
private final int mNumHotseat;
private final @DeviceType int mDeviceType;
private final String mDbFile;
+ private final int mGridType;
- public DeviceGridState(int columns, int row, int numHotseat, int deviceType, String dbFile) {
+ public DeviceGridState(int columns, int row, int numHotseat, int deviceType, String dbFile,
+ int gridType) {
mGridSizeString = String.format(Locale.ENGLISH, "%d,%d", columns, row);
mNumHotseat = numHotseat;
mDeviceType = deviceType;
mDbFile = dbFile;
+ mGridType = gridType;
}
public DeviceGridState(InvariantDeviceProfile idp) {
@@ -59,6 +64,7 @@ public class DeviceGridState implements Comparable<DeviceGridState> {
mNumHotseat = idp.numDatabaseHotseatIcons;
mDeviceType = idp.deviceType;
mDbFile = idp.dbFile;
+ mGridType = idp.gridType;
}
public DeviceGridState(Context context) {
@@ -70,6 +76,7 @@ public class DeviceGridState implements Comparable<DeviceGridState> {
mNumHotseat = lp.get(HOTSEAT_COUNT);
mDeviceType = lp.get(DEVICE_TYPE);
mDbFile = lp.get(DB_FILE);
+ mGridType = lp.get(GRID_TYPE);
}
/**
@@ -94,6 +101,13 @@ public class DeviceGridState implements Comparable<DeviceGridState> {
}
/**
+ * Returns the grid type.
+ */
+ public int getGridType() {
+ return mGridType;
+ }
+
+ /**
* Stores the device state to shared preferences
*/
public void writeToPrefs(Context context) {
@@ -101,7 +115,9 @@ public class DeviceGridState implements Comparable<DeviceGridState> {
WORKSPACE_SIZE.to(mGridSizeString),
HOTSEAT_COUNT.to(mNumHotseat),
DEVICE_TYPE.to(mDeviceType),
- DB_FILE.to(mDbFile));
+ DB_FILE.to(mDbFile),
+ GRID_TYPE.to(mGridType));
+
}
/**
diff --git a/src/com/android/launcher3/model/GridSizeMigrationDBController.java b/src/com/android/launcher3/model/GridSizeMigrationDBController.java
index 3e4394373a..12ba07de4e 100644
--- a/src/com/android/launcher3/model/GridSizeMigrationDBController.java
+++ b/src/com/android/launcher3/model/GridSizeMigrationDBController.java
@@ -17,11 +17,16 @@
package com.android.launcher3.model;
import static com.android.launcher3.Flags.enableSmartspaceRemovalToggle;
+import static com.android.launcher3.GridType.GRID_TYPE_NON_ONE_GRID;
+import static com.android.launcher3.GridType.GRID_TYPE_ONE_GRID;
+import static com.android.launcher3.InvariantDeviceProfile.TYPE_TABLET;
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.logging.StatsLogManager.LauncherEvent.LAUNCHER_ROW_SHIFT_GRID_MIGRATION;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ROW_SHIFT_ONE_GRID_MIGRATION;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_STANDARD_GRID_MIGRATION;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_STANDARD_ONE_GRID_MIGRATION;
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;
@@ -157,6 +162,9 @@ public class GridSizeMigrationDBController {
// Save current configuration, so that the migration does not run again.
destDeviceState.writeToPrefs(context);
t.commit();
+ if (isOneGridMigration(srcDeviceState, destDeviceState)) {
+ statsLogManager.logger().log(LAUNCHER_ROW_SHIFT_ONE_GRID_MIGRATION);
+ }
statsLogManager.logger().log(LAUNCHER_ROW_SHIFT_GRID_MIGRATION);
return true;
}
@@ -169,6 +177,9 @@ public class GridSizeMigrationDBController {
destDeviceState.getNumHotseat(), targetSize, srcDeviceState, destDeviceState);
dropTable(t.getDb(), TMP_TABLE);
t.commit();
+ if (isOneGridMigration(srcDeviceState, destDeviceState)) {
+ statsLogManager.logger().log(LAUNCHER_STANDARD_ONE_GRID_MIGRATION);
+ }
statsLogManager.logger().log(LAUNCHER_STANDARD_GRID_MIGRATION);
return true;
} catch (Exception e) {
@@ -291,6 +302,12 @@ public class GridSizeMigrationDBController {
return true;
}
+ protected static boolean isOneGridMigration(DeviceGridState srcDeviceState,
+ DeviceGridState destDeviceState) {
+ return srcDeviceState.getDeviceType() != TYPE_TABLET
+ && srcDeviceState.getGridType() == GRID_TYPE_NON_ONE_GRID
+ && destDeviceState.getGridType() == GRID_TYPE_ONE_GRID;
+ }
/**
* Calculate the differences between {@code src} (denoted by A) and {@code dest}
* (denoted by B).
diff --git a/src/com/android/launcher3/model/GridSizeMigrationLogic.kt b/src/com/android/launcher3/model/GridSizeMigrationLogic.kt
index 2957e3c608..d88f6ccfc6 100644
--- a/src/com/android/launcher3/model/GridSizeMigrationLogic.kt
+++ b/src/com/android/launcher3/model/GridSizeMigrationLogic.kt
@@ -32,8 +32,11 @@ import com.android.launcher3.config.FeatureFlags
import com.android.launcher3.logging.FileLog
import com.android.launcher3.logging.StatsLogManager
import com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ROW_SHIFT_GRID_MIGRATION
+import com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ROW_SHIFT_ONE_GRID_MIGRATION
import com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_STANDARD_GRID_MIGRATION
+import com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_STANDARD_ONE_GRID_MIGRATION
import com.android.launcher3.model.GridSizeMigrationDBController.DbReader
+import com.android.launcher3.model.GridSizeMigrationDBController.isOneGridMigration
import com.android.launcher3.provider.LauncherDbUtils.SQLiteTransaction
import com.android.launcher3.provider.LauncherDbUtils.copyTable
import com.android.launcher3.provider.LauncherDbUtils.dropTable
@@ -95,7 +98,12 @@ class GridSizeMigrationLogic {
// Save current configuration, so that the migration does not run again.
destDeviceState.writeToPrefs(context)
t.commit()
+
+ if (isOneGridMigration(srcDeviceState, destDeviceState)) {
+ statsLogManager.logger().log(LAUNCHER_ROW_SHIFT_ONE_GRID_MIGRATION)
+ }
statsLogManager.logger().log(LAUNCHER_ROW_SHIFT_GRID_MIGRATION)
+
return
}
@@ -125,6 +133,10 @@ class GridSizeMigrationLogic {
dropTable(t.db, TMP_TABLE)
t.commit()
+
+ if (isOneGridMigration(srcDeviceState, destDeviceState)) {
+ statsLogManager.logger().log(LAUNCHER_STANDARD_ONE_GRID_MIGRATION)
+ }
statsLogManager.logger().log(LAUNCHER_STANDARD_GRID_MIGRATION)
}
} catch (e: Exception) {
diff --git a/src/com/android/launcher3/model/LoaderCursor.java b/src/com/android/launcher3/model/LoaderCursor.java
index efe61572fe..8f116bbd6b 100644
--- a/src/com/android/launcher3/model/LoaderCursor.java
+++ b/src/com/android/launcher3/model/LoaderCursor.java
@@ -326,6 +326,7 @@ public class LoaderCursor extends CursorWrapper {
// the fallback icon
if (!loadIconFromDb(info)) {
+ FileLog.d(TAG, "loadIconFromDb failed, getting from cache - intent=" + intent);
mIconCache.getTitleAndIcon(info, DEFAULT_LOOKUP_FLAG);
}
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index 78e5d898cd..6dc20de490 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -762,7 +762,7 @@ public class LoaderTask implements Runnable {
}
IconRequestInfo<AppInfo> iconRequestInfo = getAppInfoIconRequestInfo(
- appInfo, app, mWorkspaceIconRequestInfos);
+ appInfo, app, mWorkspaceIconRequestInfos, mIsRestoreFromBackup);
allAppsItemRequestInfos.add(iconRequestInfo);
mBgAllAppsList.add(appInfo, app, false);
}
@@ -831,9 +831,10 @@ public class LoaderTask implements Runnable {
IconRequestInfo<AppInfo> getAppInfoIconRequestInfo(
AppInfo appInfo,
LauncherActivityInfo activityInfo,
- List<IconRequestInfo<WorkspaceItemInfo>> workspaceRequestInfos
+ List<IconRequestInfo<WorkspaceItemInfo>> workspaceRequestInfos,
+ boolean isRestoreFromBackup
) {
- if (Flags.restoreArchivedAppIconsFromDb()) {
+ if (Flags.restoreArchivedAppIconsFromDb() && isRestoreFromBackup) {
Optional<IconRequestInfo<WorkspaceItemInfo>> workspaceIconRequest =
workspaceRequestInfos.stream()
.filter(request -> appInfo.getTargetComponent().equals(
diff --git a/src/com/android/launcher3/model/WorkspaceItemProcessor.kt b/src/com/android/launcher3/model/WorkspaceItemProcessor.kt
index 7b8f21866a..bf71099f41 100644
--- a/src/com/android/launcher3/model/WorkspaceItemProcessor.kt
+++ b/src/com/android/launcher3/model/WorkspaceItemProcessor.kt
@@ -524,15 +524,14 @@ class WorkspaceItemProcessor(
WidgetInflater.TYPE_PENDING -> {
tempPackageKey.update(component.packageName, c.user)
val si = installingPkgs[tempPackageKey]
-
+ val isArchived =
+ ApplicationInfoWrapper(context, component.packageName, c.user).isArchived()
if (
!c.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_RESTORE_STARTED) &&
!isSafeMode &&
(si == null) &&
(lapi == null) &&
- !(Flags.enableSupportForArchiving() &&
- ApplicationInfoWrapper(context, component.packageName, c.user)
- .isArchived())
+ !isArchived
) {
// Restore never started
c.markDeleted(
@@ -559,7 +558,13 @@ class WorkspaceItemProcessor(
appWidgetInfo.providerName,
appWidgetInfo.user,
)
- iconCache.getTitleAndIconForApp(appWidgetInfo.pendingItemInfo, DEFAULT_LOOKUP_FLAG)
+ val iconLookupFlag =
+ if (isArchived && Flags.restoreArchivedAppIconsFromDb()) {
+ DEFAULT_LOOKUP_FLAG.withSkipAddToMemCache()
+ } else {
+ DEFAULT_LOOKUP_FLAG
+ }
+ iconCache.getTitleAndIconForApp(appWidgetInfo.pendingItemInfo, iconLookupFlag)
}
WidgetInflater.TYPE_REAL ->
WidgetSizes.updateWidgetSizeRangesAsync(
diff --git a/src/com/android/launcher3/pageindicators/PageIndicatorDots.java b/src/com/android/launcher3/pageindicators/PageIndicatorDots.java
index 384f87623a..8f80515e36 100644
--- a/src/com/android/launcher3/pageindicators/PageIndicatorDots.java
+++ b/src/com/android/launcher3/pageindicators/PageIndicatorDots.java
@@ -600,6 +600,13 @@ public class PageIndicatorDots extends View implements Insettable, PageIndicator
}
if (Math.round(mCurrentPosition) == i) {
sLastActiveRect.set(sTempRect);
+ if (mCurrentPosition == 0) {
+ // The outline is calculated before onDraw is called. If the user has
+ // paginated, closed the folder, and opened the folder again, the
+ // first drawn outline will use stale bounds.
+ // Invalidation is cheap, and is only needed when scroll is 0.
+ invalidateOutline();
+ }
}
canvas.drawRoundRect(sTempRect, mDotRadius, mDotRadius, mPaginationPaint);
diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
index 39f68bfe70..9a226df8db 100644
--- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java
+++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
@@ -24,7 +24,9 @@ import static com.android.launcher3.Utilities.squaredTouchSlop;
import static com.android.launcher3.allapps.AlphabeticalAppsList.PRIVATE_SPACE_PACKAGE;
import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_NOT_PINNABLE;
import static com.android.launcher3.popup.PopupPopulator.MAX_SHORTCUTS;
+import static com.android.launcher3.shortcuts.DeepShortcutTextView.GOOGLE_SANS_FLEX_LABEL_LARGE;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+import static com.android.wm.shell.Flags.enableGsf;
import android.animation.AnimatorSet;
import android.animation.LayoutTransition;
@@ -32,6 +34,7 @@ import android.content.Context;
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
+import android.graphics.Typeface;
import android.os.Handler;
import android.os.Looper;
import android.util.AttributeSet;
@@ -479,6 +482,10 @@ public class PopupContainerWithArrow<T extends Context & ActivityContext>
if (view instanceof DeepShortcutView) {
// System shortcut takes entire row with icon and text
final DeepShortcutView shortcutView = (DeepShortcutView) view;
+ if (enableGsf()) {
+ shortcutView.getBubbleText().setTypeface(
+ Typeface.create(GOOGLE_SANS_FLEX_LABEL_LARGE, Typeface.NORMAL));
+ }
info.setIconAndLabelFor(shortcutView.getIconView(), shortcutView.getBubbleText());
} else if (view instanceof ImageView) {
// System shortcut is just an icon
diff --git a/src/com/android/launcher3/shortcuts/DeepShortcutTextView.java b/src/com/android/launcher3/shortcuts/DeepShortcutTextView.java
index ded2ceee42..b1d095b52e 100644
--- a/src/com/android/launcher3/shortcuts/DeepShortcutTextView.java
+++ b/src/com/android/launcher3/shortcuts/DeepShortcutTextView.java
@@ -16,9 +16,12 @@
package com.android.launcher3.shortcuts;
+import static com.android.wm.shell.Flags.enableGsf;
+
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Rect;
+import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.text.TextUtils;
import android.util.AttributeSet;
@@ -31,6 +34,7 @@ import com.android.launcher3.Utilities;
* A {@link BubbleTextView} that has the shortcut icon on the left and drag handle on the right.
*/
public class DeepShortcutTextView extends BubbleTextView {
+ public static final String GOOGLE_SANS_FLEX_LABEL_LARGE = "variable-label-large";
private boolean mShowLoadingState;
private Drawable mLoadingStatePlaceholder;
@@ -47,6 +51,9 @@ public class DeepShortcutTextView extends BubbleTextView {
public DeepShortcutTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
showLoadingState(true);
+ if (enableGsf()) {
+ setTypeface(Typeface.create(GOOGLE_SANS_FLEX_LABEL_LARGE, Typeface.NORMAL));
+ }
}
@Override
diff --git a/src/com/android/launcher3/widget/LauncherAppWidgetHost.java b/src/com/android/launcher3/widget/LauncherAppWidgetHost.java
index 91b899c2ef..63d2954e70 100644
--- a/src/com/android/launcher3/widget/LauncherAppWidgetHost.java
+++ b/src/com/android/launcher3/widget/LauncherAppWidgetHost.java
@@ -16,8 +16,6 @@
package com.android.launcher3.widget;
-import static com.android.launcher3.widget.LauncherWidgetHolder.APPWIDGET_HOST_ID;
-
import android.appwidget.AppWidgetHost;
import android.appwidget.AppWidgetProviderInfo;
import android.content.Context;
@@ -26,49 +24,18 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
-import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.util.Executors;
-import com.android.launcher3.widget.LauncherWidgetHolder.ProviderChangedListener;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.function.IntConsumer;
-
/**
* Specific {@link AppWidgetHost} that creates our {@link LauncherAppWidgetHostView}
* which correctly captures all long-press events. This ensures that users can
* always pick up and move widgets.
*/
-class LauncherAppWidgetHost extends AppWidgetHost {
- @NonNull
- private final List<ProviderChangedListener> mProviderChangeListeners;
-
- @NonNull
- private final Context mContext;
-
- @Nullable
- private final IntConsumer mAppWidgetRemovedCallback;
+class LauncherAppWidgetHost extends ListenableAppWidgetHost {
@Nullable
private ListenableHostView mViewToRecycle;
- public LauncherAppWidgetHost(@NonNull Context context,
- @Nullable IntConsumer appWidgetRemovedCallback,
- List<ProviderChangedListener> providerChangeListeners) {
- super(context, APPWIDGET_HOST_ID);
- mContext = context;
- mAppWidgetRemovedCallback = appWidgetRemovedCallback;
- mProviderChangeListeners = providerChangeListeners;
- }
-
- @Override
- protected void onProvidersChanged() {
- if (!mProviderChangeListeners.isEmpty()) {
- for (LauncherWidgetHolder.ProviderChangedListener callback :
- new ArrayList<>(mProviderChangeListeners)) {
- callback.notifyWidgetProvidersChanged();
- }
- }
+ LauncherAppWidgetHost(@NonNull Context context, int appWidgetId) {
+ super(context, appWidgetId);
}
/**
@@ -94,35 +61,6 @@ class LauncherAppWidgetHost extends AppWidgetHost {
}
/**
- * Called when the AppWidget provider for a AppWidget has been upgraded to a new apk.
- */
- @Override
- protected void onProviderChanged(int appWidgetId, @NonNull AppWidgetProviderInfo appWidget) {
- LauncherAppWidgetProviderInfo info = LauncherAppWidgetProviderInfo.fromProviderInfo(
- mContext, appWidget);
- super.onProviderChanged(appWidgetId, info);
- // The super method updates the dimensions of the providerInfo. Update the
- // launcher spans accordingly.
- info.initSpans(mContext, LauncherAppState.getIDP(mContext));
- }
-
- /**
- * Called on an appWidget is removed for a widgetId
- *
- * @param appWidgetId TODO: make this override when SDK is updated
- */
- @Override
- public void onAppWidgetRemoved(int appWidgetId) {
- if (mAppWidgetRemovedCallback == null) {
- return;
- }
- // Route the call via model thread, in case it comes while a loader-bind is in progress
- Executors.MODEL_EXECUTOR.execute(
- () -> Executors.MAIN_EXECUTOR.execute(
- () -> mAppWidgetRemovedCallback.accept(appWidgetId)));
- }
-
- /**
* The same as super.clearViews(), except with the scope exposed
*/
@Override
diff --git a/src/com/android/launcher3/widget/LauncherWidgetHolder.java b/src/com/android/launcher3/widget/LauncherWidgetHolder.java
index 78197e2dd0..642f35abcd 100644
--- a/src/com/android/launcher3/widget/LauncherWidgetHolder.java
+++ b/src/com/android/launcher3/widget/LauncherWidgetHolder.java
@@ -20,10 +20,9 @@ import static android.app.Activity.RESULT_CANCELED;
import static com.android.launcher3.BuildConfig.WIDGETS_ENABLED;
import static com.android.launcher3.Flags.enableWorkspaceInflation;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
-import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import static com.android.launcher3.widget.LauncherAppWidgetProviderInfo.fromProviderInfo;
+import static com.android.launcher3.widget.ListenableAppWidgetHost.getWidgetHolderExecutor;
-import android.appwidget.AppWidgetHost;
import android.appwidget.AppWidgetHostView;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProviderInfo;
@@ -32,6 +31,7 @@ import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.Looper;
+import android.util.Log;
import android.util.SparseArray;
import android.widget.Toast;
@@ -43,18 +43,23 @@ import androidx.annotation.WorkerThread;
import com.android.launcher3.BaseActivity;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
+import com.android.launcher3.dagger.LauncherComponentProvider;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.shared.TestProtocol;
-import com.android.launcher3.util.LooperExecutor;
-import com.android.launcher3.util.ResourceBasedOverride;
import com.android.launcher3.util.SafeCloseable;
import com.android.launcher3.views.ActivityContext;
+import com.android.launcher3.widget.ListenableAppWidgetHost.ProviderChangedListener;
import com.android.launcher3.widget.custom.CustomWidgetManager;
+import dagger.assisted.Assisted;
+import dagger.assisted.AssistedFactory;
+import dagger.assisted.AssistedInject;
+
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
import java.util.function.IntConsumer;
/**
@@ -62,51 +67,57 @@ import java.util.function.IntConsumer;
* background.
*/
public class LauncherWidgetHolder {
+
+ private static final String TAG = "LauncherWidgetHolder";
+
public static final int APPWIDGET_HOST_ID = 1024;
protected static final int FLAG_LISTENING = 1;
protected static final int FLAG_STATE_IS_NORMAL = 1 << 1;
protected static final int FLAG_ACTIVITY_STARTED = 1 << 2;
protected static final int FLAG_ACTIVITY_RESUMED = 1 << 3;
+
private static final int FLAGS_SHOULD_LISTEN =
FLAG_STATE_IS_NORMAL | FLAG_ACTIVITY_STARTED | FLAG_ACTIVITY_RESUMED;
+ // TODO(b/191735836): Replace with ActivityOptions.KEY_SPLASH_SCREEN_STYLE when un-hidden
+ private static final String KEY_SPLASH_SCREEN_STYLE = "android.activity.splashScreenStyle";
+ // TODO(b/191735836): Replace with SplashScreen.SPLASH_SCREEN_STYLE_EMPTY when un-hidden
+ private static final int SPLASH_SCREEN_STYLE_EMPTY = 0;
+
@NonNull
protected final Context mContext;
@NonNull
- private final AppWidgetHost mWidgetHost;
+ protected final ListenableAppWidgetHost mWidgetHost;
@NonNull
protected final SparseArray<LauncherAppWidgetHostView> mViews = new SparseArray<>();
- protected final List<ProviderChangedListener> mProviderChangedListeners = new ArrayList<>();
+
+ /** package visibility */
+ final List<ProviderChangedListener> mProviderChangedListeners = new ArrayList<>();
protected AtomicInteger mFlags = new AtomicInteger(FLAG_STATE_IS_NORMAL);
- // TODO(b/191735836): Replace with ActivityOptions.KEY_SPLASH_SCREEN_STYLE when un-hidden
- private static final String KEY_SPLASH_SCREEN_STYLE = "android.activity.splashScreenStyle";
- // TODO(b/191735836): Replace with SplashScreen.SPLASH_SCREEN_STYLE_EMPTY when un-hidden
- private static final int SPLASH_SCREEN_STYLE_EMPTY = 0;
+ @Nullable
+ private Consumer<LauncherAppWidgetHostView> mOnViewCreationCallback;
- protected LauncherWidgetHolder(@NonNull Context context,
- @Nullable IntConsumer appWidgetRemovedCallback) {
- mContext = context;
- mWidgetHost = createHost(context, appWidgetRemovedCallback);
- }
+ /** package visibility */
+ @Nullable IntConsumer mAppWidgetRemovedCallback;
- protected AppWidgetHost createHost(
- Context context, @Nullable IntConsumer appWidgetRemovedCallback) {
- return new LauncherAppWidgetHost(
- context, appWidgetRemovedCallback, mProviderChangedListeners);
+ @AssistedInject
+ protected LauncherWidgetHolder(@Assisted("UI_CONTEXT") @NonNull Context context) {
+ this(context, new LauncherAppWidgetHost(context, APPWIDGET_HOST_ID));
}
- protected LooperExecutor getWidgetHolderExecutor() {
- return UI_HELPER_EXECUTOR;
+ protected LauncherWidgetHolder(
+ @NonNull Context context, @NonNull ListenableAppWidgetHost appWidgetHost) {
+ mContext = context;
+ mWidgetHost = appWidgetHost;
+ MAIN_EXECUTOR.execute(() -> mWidgetHost.getHolders().add(this));
}
- /**
- * Starts listening to the widget updates from the server side
- */
+ /** Starts listening to the widget updates from the server side */
public void startListening() {
if (!WIDGETS_ENABLED) {
return;
@@ -127,13 +138,11 @@ public class LauncherWidgetHolder {
// TODO: Investigate why widgetHost.startListening() always return non-empty updates
setListeningFlag(true);
- MAIN_EXECUTOR.execute(() -> updateDeferredView());
+ MAIN_EXECUTOR.execute(this::updateDeferredView);
});
}
- /**
- * Update any views which have been deferred because the host was not listening.
- */
+ /** Update any views which have been deferred because the host was not listening */
protected void updateDeferredView() {
// Update any views which have been deferred because the host was not listening.
// We go in reverse order and inflate any deferred or cached widget
@@ -180,7 +189,14 @@ public class LauncherWidgetHolder {
* Called when the launcher is destroyed
*/
public void destroy() {
- // No-op
+ try {
+ MAIN_EXECUTOR.submit(() -> {
+ clearViews();
+ mWidgetHost.getHolders().remove(this);
+ }).get();
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to remove self from holder list", e);
+ }
}
/**
@@ -198,8 +214,7 @@ public class LauncherWidgetHolder {
* Add a listener that is triggered when the providers of the widgets are changed
* @param listener The listener that notifies when the providers changed
*/
- public void addProviderChangeListener(
- @NonNull LauncherWidgetHolder.ProviderChangedListener listener) {
+ public void addProviderChangeListener(@NonNull ProviderChangedListener listener) {
MAIN_EXECUTOR.execute(() -> mProviderChangedListeners.add(listener));
}
@@ -207,12 +222,23 @@ public class LauncherWidgetHolder {
* Remove the specified listener from the host
* @param listener The listener that is to be removed from the host
*/
- public void removeProviderChangeListener(
- LauncherWidgetHolder.ProviderChangedListener listener) {
+ public void removeProviderChangeListener(ProviderChangedListener listener) {
MAIN_EXECUTOR.execute(() -> mProviderChangedListeners.remove(listener));
}
/**
+ * Sets a callbacks for whenever a widget view is created
+ */
+ public void setOnViewCreationCallback(@Nullable Consumer<LauncherAppWidgetHostView> callback) {
+ mOnViewCreationCallback = callback;
+ }
+
+ /** Sets a callback for listening app widget removals */
+ public void setAppWidgetRemovedCallback(@Nullable IntConsumer callback) {
+ mAppWidgetRemovedCallback = callback;
+ }
+
+ /**
* Starts the configuration activity for the widget
* @param activity The activity in which to start the configuration page
* @param widgetId The ID of the widget
@@ -284,9 +310,7 @@ public class LauncherWidgetHolder {
activity.startActivityForResult(intent, requestCode);
}
- /**
- * Stop the host from listening to the widget updates
- */
+ /** Stop the host from listening to the widget updates */
public void stopListening() {
if (!WIDGETS_ENABLED) {
return;
@@ -298,8 +322,8 @@ public class LauncherWidgetHolder {
}
/**
- * Update {@link FLAG_LISTENING} on {@link mFlags} after making binder calls from
- * {@link sWidgetHost}.
+ * Update {@link #FLAG_LISTENING} on {@link #mFlags} after making binder calls from
+ * {@link #mWidgetHost}.
*/
@WorkerThread
protected void setListeningFlag(final boolean isListening) {
@@ -350,6 +374,7 @@ public class LauncherWidgetHolder {
}
LauncherAppWidgetHostView view = createViewInternal(appWidgetId, appWidget);
+ if (mOnViewCreationCallback != null) mOnViewCreationCallback.accept(view);
// Do not update mViews on a background thread call, as the holder is not thread safe.
if (!enableWorkspaceInflation() || Looper.myLooper() == Looper.getMainLooper()) {
mViews.put(appWidgetId, view);
@@ -368,8 +393,8 @@ public class LauncherWidgetHolder {
// Binder can also inflate placeholder widgets in case of backup-restore. Skip
// attaching such widgets
- boolean isRealWidget = ((view instanceof PendingAppWidgetHostView pw)
- ? pw.isDeferredWidget() : true)
+ boolean isRealWidget = (!(view instanceof PendingAppWidgetHostView pw)
+ || pw.isDeferredWidget())
&& view.getAppWidgetInfo() != null;
if (isRealWidget && mViews.get(view.getAppWidgetId()) != view) {
view = recycleExistingView(view);
@@ -446,28 +471,13 @@ public class LauncherWidgetHolder {
}
}
- /**
- * Listener for getting notifications on provider changes.
- */
- public interface ProviderChangedListener {
- /**
- * Notify the listener that the providers have changed
- */
- void notifyWidgetProvidersChanged();
- }
-
- /**
- * Clears all the views from the host
- */
+ /** Clears all the views from the host */
public void clearViews() {
- LauncherAppWidgetHost tempHost = (LauncherAppWidgetHost) mWidgetHost;
- tempHost.clearViews();
+ ((LauncherAppWidgetHost) mWidgetHost).clearViews();
mViews.clear();
}
- /**
- * Clears all the internal widget views
- */
+ /** Clears all the internal widget views */
public void clearWidgetViews() {
clearViews();
}
@@ -514,32 +524,19 @@ public class LauncherWidgetHolder {
* Returns the new LauncherWidgetHolder instance
*/
public static LauncherWidgetHolder newInstance(Context context) {
- return HolderFactory.newFactory(context).newInstance(context, null);
+ return LauncherComponentProvider.get(context).getWidgetHolderFactory().newInstance(context);
}
- /**
- * A factory class that generates new instances of {@code LauncherWidgetHolder}
- */
- public static class HolderFactory implements ResourceBasedOverride {
-
- /**
- * @param context The context of the caller
- * @param appWidgetRemovedCallback The callback that is called when widgets are removed
- * @return A new instance of {@code LauncherWidgetHolder}
- */
- public LauncherWidgetHolder newInstance(@NonNull Context context,
- @Nullable IntConsumer appWidgetRemovedCallback) {
- return new LauncherWidgetHolder(context, appWidgetRemovedCallback);
- }
+ /** A factory that generates new instances of {@code LauncherWidgetHolder} */
+ public interface WidgetHolderFactory {
- /**
- * @param context The context of the caller
- * @return A new instance of factory class for widget holders. If not specified, returning
- * {@code HolderFactory} by default.
- */
- public static HolderFactory newFactory(Context context) {
- return Overrides.getObject(
- HolderFactory.class, context, R.string.widget_holder_factory_class);
- }
+ LauncherWidgetHolder newInstance(@NonNull Context context);
+ }
+
+ /** A factory that generates new instances of {@code LauncherWidgetHolder} */
+ @AssistedFactory
+ public interface WidgetHolderFactoryImpl extends WidgetHolderFactory {
+
+ LauncherWidgetHolder newInstance(@Assisted("UI_CONTEXT") @NonNull Context context);
}
}
diff --git a/src/com/android/launcher3/widget/ListenableAppWidgetHost.kt b/src/com/android/launcher3/widget/ListenableAppWidgetHost.kt
new file mode 100644
index 0000000000..58bf0aa869
--- /dev/null
+++ b/src/com/android/launcher3/widget/ListenableAppWidgetHost.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2025 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.widget
+
+import android.appwidget.AppWidgetHost
+import android.appwidget.AppWidgetProviderInfo
+import android.content.Context
+import com.android.launcher3.InvariantDeviceProfile
+import com.android.launcher3.util.Executors
+import com.android.launcher3.util.Executors.MAIN_EXECUTOR
+import com.android.launcher3.util.Executors.MODEL_EXECUTOR
+import com.android.launcher3.util.LooperExecutor
+
+open class ListenableAppWidgetHost(private val ctx: Context, hostId: Int) :
+ AppWidgetHost(ctx, hostId) {
+
+ protected val holders = mutableListOf<LauncherWidgetHolder>()
+
+ override fun onProvidersChanged() {
+ MAIN_EXECUTOR.execute {
+ holders.forEach { holder ->
+ // Listeners might remove themselves from the list during the iteration.
+ // Creating a copy of the list to avoid exceptions for concurrent modification.
+ holder.mProviderChangedListeners.toList().forEach {
+ it.notifyWidgetProvidersChanged()
+ }
+ }
+ }
+ }
+
+ override fun onAppWidgetRemoved(appWidgetId: Int) {
+ // Route the call via model thread, in case it comes while a loader-bind is in progress
+ MODEL_EXECUTOR.execute {
+ MAIN_EXECUTOR.execute {
+ holders.forEach { it.mAppWidgetRemovedCallback?.accept(appWidgetId) }
+ }
+ }
+ }
+
+ override fun onProviderChanged(appWidgetId: Int, appWidget: AppWidgetProviderInfo) {
+ val info = LauncherAppWidgetProviderInfo.fromProviderInfo(ctx, appWidget)
+ super.onProviderChanged(appWidgetId, info)
+ // The super method updates the dimensions of the providerInfo. Update the
+ // launcher spans accordingly.
+ info.initSpans(ctx, InvariantDeviceProfile.INSTANCE.get(ctx))
+ }
+
+ /** Listener for getting notifications on provider changes. */
+ fun interface ProviderChangedListener {
+ /** Notify the listener that the providers have changed */
+ fun notifyWidgetProvidersChanged()
+ }
+
+ companion object {
+
+ @JvmStatic val widgetHolderExecutor: LooperExecutor = Executors.UI_HELPER_EXECUTOR
+ }
+}
diff --git a/src/com/android/launcher3/widget/PendingAppWidgetHostView.java b/src/com/android/launcher3/widget/PendingAppWidgetHostView.java
index cd8e4571e5..1c29f8907f 100644
--- a/src/com/android/launcher3/widget/PendingAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/PendingAppWidgetHostView.java
@@ -66,7 +66,7 @@ import com.android.launcher3.model.data.PackageItemInfo;
import com.android.launcher3.util.RunnableList;
import com.android.launcher3.util.SafeCloseable;
import com.android.launcher3.util.Themes;
-import com.android.launcher3.widget.LauncherWidgetHolder.ProviderChangedListener;
+import com.android.launcher3.widget.ListenableAppWidgetHost.ProviderChangedListener;
import java.util.List;