diff options
Diffstat (limited to 'src')
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; |