diff options
Diffstat (limited to 'src')
35 files changed, 642 insertions, 524 deletions
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java index c85ca49c43..090208a33c 100644 --- a/src/com/android/launcher3/DeviceProfile.java +++ b/src/com/android/launcher3/DeviceProfile.java @@ -30,8 +30,8 @@ import static com.android.launcher3.testing.shared.ResourceUtils.INVALID_RESOURC import static com.android.launcher3.testing.shared.ResourceUtils.pxFromDp; import static com.android.launcher3.testing.shared.ResourceUtils.roundPxValueFromFloat; import static com.android.wm.shell.Flags.enableBubbleBar; -import static com.android.wm.shell.Flags.enableTinyTaskbar; import static com.android.wm.shell.Flags.enableBubbleBarOnPhones; +import static com.android.wm.shell.Flags.enableTinyTaskbar; import android.annotation.SuppressLint; import android.content.Context; @@ -508,9 +508,11 @@ public class DeviceProfile { bottomSheetOpenDuration = res.getInteger(R.integer.config_bottomSheetOpenDuration); bottomSheetCloseDuration = res.getInteger(R.integer.config_bottomSheetCloseDuration); - if (isTablet) { + if (shouldShowAllAppsOnSheet()) { bottomSheetWorkspaceScale = workspaceContentScale; - if (isMultiDisplay) { + if (Flags.allAppsBlur()) { + bottomSheetDepth = 2f; + } else if (isMultiDisplay) { // TODO(b/259893832): Revert to use maxWallpaperScale to calculate bottomSheetDepth // when screen recorder bug is fixed. if (enableScalingRevealHomeAnimation()) { @@ -1830,10 +1832,17 @@ public class DeviceProfile { workspacePageIndicatorHeight - mWorkspacePageIndicatorOverlapWorkspace; } int paddingTop = workspaceTopPadding + (mIsScalableGrid ? 0 : edgeMarginPx); - // On isFixedLandscapeMode on phones we already have padding because of the camera hole - int paddingSide = inv.isFixedLandscape ? 0 : desiredWorkspaceHorizontalMarginPx; + int paddingLeft = desiredWorkspaceHorizontalMarginPx; + int paddingRight = desiredWorkspaceHorizontalMarginPx; - padding.set(paddingSide, paddingTop, paddingSide, paddingBottom); + // In fixed Landscape we don't need padding on the side next to the cutout because + // the cutout is already adding padding to all of Launcher, we only need on the other + // side + if (inv.isFixedLandscape) { + paddingLeft = isSeascape() ? desiredWorkspaceHorizontalMarginPx : 0; + paddingRight = isSeascape() ? 0 : desiredWorkspaceHorizontalMarginPx; + } + padding.set(paddingLeft, paddingTop, paddingRight, paddingBottom); } insetPadding(workspacePadding, cellLayoutPaddingPx); } @@ -1931,7 +1940,24 @@ public class DeviceProfile { hotseatBarPadding.set(mHotseatBarWorkspaceSpacePx, paddingTop, mInsets.right + mHotseatBarEdgePaddingPx, paddingBottom); } - } else if (isTaskbarPresent || inv.isFixedLandscape) { + } else if (inv.isFixedLandscape) { + // Center the QSB vertically with hotseat + int hotseatBarBottomPadding = getHotseatBarBottomPadding(); + int hotseatPlusQSBWidth = getHotseatRequiredWidth(); + int qsbWidth = getAdditionalQsbSpace(); + int availableWidthPxForHotseat = availableWidthPx - Math.abs(workspacePadding.width()) + - Math.abs(cellLayoutPaddingPx.width()); + int remainingSpaceOnSide = (availableWidthPxForHotseat - hotseatPlusQSBWidth) / 2; + + hotseatBarPadding.set( + (remainingSpaceOnSide + qsbWidth) + mInsets.left + workspacePadding.left + + cellLayoutPaddingPx.left, + hotseatBarSizePx - hotseatBarBottomPadding - hotseatCellHeightPx, + remainingSpaceOnSide + mInsets.right + workspacePadding.right + + cellLayoutPaddingPx.right, + hotseatBarBottomPadding + ); + } else if (isTaskbarPresent) { // Center the QSB vertically with hotseat int hotseatBarBottomPadding = getHotseatBarBottomPadding(); int hotseatBarTopPadding = @@ -1950,11 +1976,6 @@ public class DeviceProfile { } startSpacing += getAdditionalQsbSpace(); - if (inv.isFixedLandscape) { - endSpacing += mInsets.right; - startSpacing += mInsets.left; - } - hotseatBarPadding.top = hotseatBarTopPadding; hotseatBarPadding.bottom = hotseatBarBottomPadding; boolean isRtl = Utilities.isRtl(context.getResources()); @@ -2164,7 +2185,8 @@ public class DeviceProfile { } public boolean isSeascape() { - return rotationHint == Surface.ROTATION_270 && isVerticalBarLayout(); + return rotationHint == Surface.ROTATION_270 + && (isVerticalBarLayout() || inv.isFixedLandscape); } public boolean shouldFadeAdjacentWorkspaceScreens() { diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index 289f175aa9..5c9392d69d 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -1472,8 +1472,7 @@ public class Launcher extends StatefulActivity<LauncherState> // Adding a shortcut to a Folder. FolderIcon folderIcon = findFolderIcon(container); if (folderIcon != null) { - FolderInfo folderInfo = (FolderInfo) folderIcon.getTag(); - folderInfo.add(info, args.rank, false); + folderIcon.getFolder().addFolderContent(info, args.rank, false); } else { Log.e(TAG, "Could not find folder with id " + container + " to add shortcut."); } @@ -1792,7 +1791,6 @@ public class Launcher extends StatefulActivity<LauncherState> SettingsCache.INSTANCE.get(this).unregister(TOUCHPAD_NATURAL_SCROLLING, mNaturalScrollingChangedListener); ScreenOnTracker.INSTANCE.get(this).removeListener(mScreenOnListener); - mWorkspace.removeFolderListeners(); PluginManagerWrapper.INSTANCE.get(this).removePluginListener(this); mModel.removeCallbacks(this); @@ -2053,9 +2051,10 @@ public class Launcher extends StatefulActivity<LauncherState> @Nullable final String reason) { if (itemInfo instanceof WorkspaceItemInfo) { View collectionIcon = mWorkspace.getViewByItemId(itemInfo.container); - if (collectionIcon instanceof FolderIcon) { + if (collectionIcon instanceof FolderIcon folderIcon) { // Remove the shortcut from the folder before removing it from launcher - ((FolderInfo) collectionIcon.getTag()).remove((WorkspaceItemInfo) itemInfo, true); + Folder folder = folderIcon.getFolder(); + folder.removeFolderContent(true, itemInfo); } else if (collectionIcon instanceof AppPairIcon appPairIcon) { removeItem(appPairIcon, appPairIcon.getInfo(), deleteFromDb, "removing app pair because one of its member apps was removed"); @@ -2066,9 +2065,6 @@ public class Launcher extends StatefulActivity<LauncherState> getModelWriter().deleteItemFromDatabase(itemInfo, reason); } } else if (itemInfo instanceof CollectionInfo ci) { - if (v instanceof FolderIcon) { - ((FolderIcon) v).removeListeners(); - } mWorkspace.removeWorkspaceItem(v); if (deleteFromDb) { getModelWriter().deleteCollectionAndContentsFromDatabase(ci); diff --git a/src/com/android/launcher3/LauncherModel.kt b/src/com/android/launcher3/LauncherModel.kt index 02d70ae386..557ad67386 100644 --- a/src/com/android/launcher3/LauncherModel.kt +++ b/src/com/android/launcher3/LauncherModel.kt @@ -86,12 +86,11 @@ constructor( private val loaderFactory: LoaderTaskFactory, private val binderFactory: BaseLauncherBinderFactory, private val spaceFinderFactory: Provider<WorkspaceItemSpaceFinder>, + val modelDbController: ModelDbController, ) { private val mCallbacksList = ArrayList<BgDataModel.Callbacks>(1) - val modelDbController = ModelDbController(context) - private val mLock = Any() private var mLoaderTask: LoaderTask? = null diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java index 03ecf14ef6..acb2b48aeb 100644 --- a/src/com/android/launcher3/LauncherProvider.java +++ b/src/com/android/launcher3/LauncherProvider.java @@ -26,13 +26,13 @@ import android.content.ContentUris; import android.content.ContentValues; import android.content.pm.PackageManager; import android.database.Cursor; -import android.database.sqlite.SQLiteQueryBuilder; import android.net.Uri; import android.os.Binder; import android.os.Bundle; import android.os.Process; import android.text.TextUtils; import android.util.Log; +import android.util.Pair; import com.android.launcher3.LauncherSettings.Favorites; import com.android.launcher3.model.ModelDbController; @@ -74,24 +74,20 @@ public class LauncherProvider extends ContentProvider { @Override public String getType(Uri uri) { - SqlArguments args = new SqlArguments(uri, null, null); - if (TextUtils.isEmpty(args.where)) { - return "vnd.android.cursor.dir/" + args.table; + if (TextUtils.isEmpty(parseUri(uri, null, null).first)) { + return "vnd.android.cursor.dir/" + Favorites.TABLE_NAME; } else { - return "vnd.android.cursor.item/" + args.table; + return "vnd.android.cursor.item/" + Favorites.TABLE_NAME; } } @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { - SqlArguments args = new SqlArguments(uri, selection, selectionArgs); - SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); - qb.setTables(args.table); - + Pair<String, String[]> args = parseUri(uri, selection, selectionArgs); Cursor[] result = new Cursor[1]; executeControllerTask(controller -> { - result[0] = controller.query(args.table, projection, args.where, args.args, sortOrder); + result[0] = controller.query(projection, args.first, args.second, sortOrder); return 0; }); return result[0]; @@ -108,7 +104,7 @@ public class LauncherProvider extends ContentProvider { // attempt allocate and bind the widget. Integer itemType = values.getAsInteger(Favorites.ITEM_TYPE); if (itemType != null - && itemType.intValue() == Favorites.ITEM_TYPE_APPWIDGET + && itemType == Favorites.ITEM_TYPE_APPWIDGET && !values.containsKey(Favorites.APPWIDGET_ID)) { ComponentName cn = ComponentName.unflattenFromString( @@ -135,8 +131,7 @@ public class LauncherProvider extends ContentProvider { } } - SqlArguments args = new SqlArguments(uri); - return controller.insert(args.table, values); + return controller.insert(values); }); return rowId < 0 ? null : ContentUris.withAppendedId(uri, rowId); @@ -144,14 +139,14 @@ public class LauncherProvider extends ContentProvider { @Override public int delete(Uri uri, String selection, String[] selectionArgs) { - SqlArguments args = new SqlArguments(uri, selection, selectionArgs); - return executeControllerTask(c -> c.delete(args.table, args.where, args.args)); + Pair<String, String[]> args = parseUri(uri, selection, selectionArgs); + return executeControllerTask(c -> c.delete(args.first, args.second)); } @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { - SqlArguments args = new SqlArguments(uri, selection, selectionArgs); - return executeControllerTask(c -> c.update(args.table, values, args.where, args.args)); + Pair<String, String[]> args = parseUri(uri, selection, selectionArgs); + return executeControllerTask(c -> c.update(values, args.first, args.second)); } @Override @@ -209,35 +204,24 @@ public class LauncherProvider extends ContentProvider { } } - static class SqlArguments { - public final String table; - public final String where; - public final String[] args; - - SqlArguments(Uri url, String where, String[] args) { - if (url.getPathSegments().size() == 1) { - this.table = url.getPathSegments().get(0); - this.where = where; - this.args = args; - } else if (url.getPathSegments().size() != 2) { - throw new IllegalArgumentException("Invalid URI: " + url); - } else if (!TextUtils.isEmpty(where)) { - throw new UnsupportedOperationException("WHERE clause not supported: " + url); - } else { - this.table = url.getPathSegments().get(0); - this.where = "_id=" + ContentUris.parseId(url); - this.args = null; + /** + * Parses the uri and returns the where and arg clause. + * + * Note: This should be called on the binder thread (before posting on any executor) so that + * any parsing error gets propagated to the caller. + */ + private static Pair<String, String[]> parseUri(Uri url, String where, String[] args) { + switch (url.getPathSegments().size()) { + case 1 -> { + return Pair.create(where, args); } - } - - SqlArguments(Uri url) { - if (url.getPathSegments().size() == 1) { - table = url.getPathSegments().get(0); - where = null; - args = null; - } else { - throw new IllegalArgumentException("Invalid URI: " + url); + case 2 -> { + if (!TextUtils.isEmpty(where)) { + throw new UnsupportedOperationException("WHERE clause not supported: " + url); + } + return Pair.create("_id=" + ContentUris.parseId(url), null); } + default -> throw new IllegalArgumentException("Invalid URI: " + url); } } } diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java index 6ed183ab23..59f84ab891 100644 --- a/src/com/android/launcher3/Workspace.java +++ b/src/com/android/launcher3/Workspace.java @@ -137,7 +137,6 @@ import java.util.Iterator; import java.util.List; import java.util.function.Consumer; import java.util.function.Predicate; -import java.util.stream.Collectors; /** * The workspace is a wide area with a wallpaper and a finite number of pages. @@ -659,7 +658,6 @@ public class Workspace<T extends View & PageIndicator> extends PagedView<T> } // Remove the pages and clear the screen models - removeFolderListeners(); removeAllViews(); mScreenOrder.clear(); mWorkspaceScreens.clear(); @@ -1914,7 +1912,7 @@ public class Workspace<T extends View & PageIndicator> extends PagedView<T> boolean aboveShortcut = Folder.willAccept(dropOverView.getTag()) && ((ItemInfo) dropOverView.getTag()).container != CONTAINER_HOTSEAT_PREDICTION; - boolean willBecomeShortcut = Folder.willAcceptItemType(info.itemType); + boolean willBecomeShortcut = FolderInfo.willAcceptItemType(info.itemType); return (aboveShortcut && willBecomeShortcut); } @@ -1994,8 +1992,8 @@ public class Workspace<T extends View & PageIndicator> extends PagedView<T> fi.performCreateAnimation(destInfo, v, sourceInfo, d, folderLocation, scale); } else { fi.prepareCreateAnimation(v); - fi.addItem(destInfo); - fi.addItem(sourceInfo); + fi.getFolder().addFolderContent(destInfo); + fi.getFolder().addFolderContent(sourceInfo); } return true; } @@ -3209,21 +3207,6 @@ public class Workspace<T extends View & PageIndicator> extends PagedView<T> }); } - /** - * Removes all folder listeners - */ - public void removeFolderListeners() { - mapOverItems(new ItemOperator() { - @Override - public boolean evaluate(ItemInfo info, View view) { - if (view instanceof FolderIcon) { - ((FolderIcon) view).removeListeners(); - } - return false; - } - }); - } - public boolean isDropEnabled() { return true; } @@ -3349,15 +3332,15 @@ public class Workspace<T extends View & PageIndicator> extends PagedView<T> if (child instanceof DropTarget) { mDragController.removeDropTarget((DropTarget) child); } - } else if (child instanceof FolderIcon) { + } else if (child instanceof FolderIcon folderIcon) { FolderInfo folderInfo = (FolderInfo) info; - List<ItemInfo> matches = folderInfo.getContents().stream() + ItemInfo[] matches = folderInfo.getContents().stream() .filter(matcher) - .collect(Collectors.toList()); - if (!matches.isEmpty()) { - folderInfo.removeAll(matches, false); - if (((FolderIcon) child).getFolder().isOpen()) { - ((FolderIcon) child).getFolder().close(false /* animate */); + .toArray(ItemInfo[]::new); + if (matches.length > 0) { + folderIcon.getFolder().removeFolderContent(false, matches); + if (folderIcon.getFolder().isOpen()) { + folderIcon.getFolder().close(false /* animate */); } } } else if (info instanceof AppPairInfo api) { diff --git a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java index cd91f8e00b..df34ccf090 100644 --- a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java +++ b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java @@ -519,7 +519,7 @@ public class LauncherAccessibilityDelegate extends BaseAccessibilityDelegate<Lau Folder folder = Folder.getOpen(mContext); folder.close(true); WorkspaceItemInfo info = (WorkspaceItemInfo) item; - folder.getInfo().remove(info, false); + folder.removeFolderContent(false, info); final int[] coordinates = new int[2]; final int screenId = findSpaceOnWorkspace(item, coordinates); diff --git a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java index fafa60bf6d..f60896eb6d 100644 --- a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java +++ b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java @@ -181,6 +181,7 @@ public class ActivityAllAppsContainerView<T extends Context & ActivityContext> private ScrimView mScrimView; private int mHeaderColor; private int mBottomSheetBackgroundColor; + private float mBottomSheetBackgroundAlpha = 1f; private int mTabsProtectionAlpha; @Nullable private AllAppsTransitionController mAllAppsTransitionController; @@ -311,7 +312,17 @@ public class ActivityAllAppsContainerView<T extends Context & ActivityContext> 0, 0 // Bottom left }; - mBottomSheetBackgroundColor = getContext().getColor(R.color.materialColorSurfaceDim); + if (Flags.allAppsBlur()) { + int resId = Utilities.isDarkTheme(getContext()) + ? android.R.color.system_accent1_800 : android.R.color.system_accent1_100; + int layerAbove = ColorUtils.setAlphaComponent(getResources().getColor(resId, null), + (int) (0.4f * 255)); + int layerBelow = ColorUtils.setAlphaComponent(Color.WHITE, (int) (0.1f * 255)); + mBottomSheetBackgroundColor = ColorUtils.compositeColors(layerAbove, layerBelow); + } else { + mBottomSheetBackgroundColor = getContext().getColor(R.color.materialColorSurfaceDim); + } + mBottomSheetBackgroundAlpha = Color.alpha(mBottomSheetBackgroundColor) / 255.0f; updateBackgroundVisibility(mActivityContext.getDeviceProfile()); mSearchUiManager.initializeSearch(this); } @@ -1152,7 +1163,7 @@ public class ActivityAllAppsContainerView<T extends Context & ActivityContext> if (!grid.isVerticalBarLayout() || FeatureFlags.enableResponsiveWorkspace()) { int topPadding = grid.allAppsPadding.top; - if (isSearchBarFloating() && !grid.isTablet) { + if (isSearchBarFloating() && !grid.shouldShowAllAppsOnSheet()) { topPadding += getResources().getDimensionPixelSize( R.dimen.all_apps_additional_top_padding_floating_search); } @@ -1401,7 +1412,7 @@ public class ActivityAllAppsContainerView<T extends Context & ActivityContext> // Draw full background panel for tablets. if (hasBottomSheet) { mHeaderPaint.setColor(mBottomSheetBackgroundColor); - mHeaderPaint.setAlpha(255); + mHeaderPaint.setAlpha((int) (mBottomSheetBackgroundAlpha * 255)); mTmpRectF.set( leftWithScale, @@ -1424,6 +1435,10 @@ public class ActivityAllAppsContainerView<T extends Context & ActivityContext> return; } + if (hasBottomSheet) { + mHeaderPaint.setAlpha((int) (mHeaderPaint.getAlpha() * mBottomSheetBackgroundAlpha)); + } + // Draw header on background panel final float headerBottomNoScale = getHeaderBottom() + getVisibleContainerView().getPaddingTop(); @@ -1455,7 +1470,11 @@ public class ActivityAllAppsContainerView<T extends Context & ActivityContext> mHeaderPaint.setColor(Color.BLUE); mHeaderPaint.setAlpha(255); } else { - mHeaderPaint.setAlpha((int) (getAlpha() * mTabsProtectionAlpha)); + float tabAlpha = getAlpha() * mTabsProtectionAlpha; + if (hasBottomSheet) { + tabAlpha *= mBottomSheetBackgroundAlpha; + } + mHeaderPaint.setAlpha((int) tabAlpha); } float left = 0f; float right = canvas.getWidth(); @@ -1507,7 +1526,7 @@ public class ActivityAllAppsContainerView<T extends Context & ActivityContext> public int getHeaderBottom() { int bottom = (int) getTranslationY() + mHeader.getClipTop(); if (isSearchBarFloating()) { - if (mActivityContext.getDeviceProfile().isTablet) { + if (mActivityContext.getDeviceProfile().shouldShowAllAppsOnSheet()) { return bottom + mBottomSheetBackground.getTop(); } return bottom; diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java index 4cc31d296e..350f763939 100644 --- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java +++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java @@ -290,7 +290,8 @@ public class AllAppsTransitionController private void onScaleProgressChanged() { final float scaleProgress = mAllAppScale.value; SCALE_PROPERTY.set(mLauncher.getAppsView(), scaleProgress); - if (!mLauncher.getAppsView().isSearching() || !mLauncher.getDeviceProfile().isTablet) { + if (!mLauncher.getAppsView().isSearching() + || !mLauncher.getDeviceProfile().shouldShowAllAppsOnSheet()) { mLauncher.getScrimView().setScrimHeaderScale(scaleProgress); } diff --git a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java index 870c891f47..f223eaa600 100644 --- a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java +++ b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java @@ -436,6 +436,8 @@ public class AlphabeticalAppsList<T extends Context & ActivityContext> implement if (currentItem.itemInfo != null && Objects.equals( currentItem.itemInfo.getTargetPackage(), PRIVATE_SPACE_PACKAGE)) { currentItem.itemInfo.bitmap.creationFlags |= FLAG_NO_BADGE; + currentItem.itemInfo.contentDescription = + mPrivateProviderManager.getPsAppContentDesc(); privateSpaceAppIndex = i; } } diff --git a/src/com/android/launcher3/allapps/PrivateProfileManager.java b/src/com/android/launcher3/allapps/PrivateProfileManager.java index 609edd2c99..1bc1b17d15 100644 --- a/src/com/android/launcher3/allapps/PrivateProfileManager.java +++ b/src/com/android/launcher3/allapps/PrivateProfileManager.java @@ -142,6 +142,7 @@ public class PrivateProfileManager extends UserProfileManager { private PrivateSpaceSettingsButton mPrivateSpaceSettingsButton; @Nullable private ConstraintLayout mFloatingMaskView; + private final String mPrivateSpaceAppContentDesc; private final String mLockedStateContentDesc; private final String mUnLockedStateContentDesc; @@ -157,6 +158,8 @@ public class PrivateProfileManager extends UserProfileManager { UI_HELPER_EXECUTOR.post(() -> initializeInBackgroundThread(appContext)); mPsHeaderHeight = mAllApps.getContext().getResources().getDimensionPixelSize( R.dimen.ps_header_height); + mPrivateSpaceAppContentDesc = mAllApps.getContext() + .getString(R.string.ps_app_content_description); mLockedStateContentDesc = mAllApps.getContext() .getString(R.string.ps_container_lock_button_content_description); mUnLockedStateContentDesc = mAllApps.getContext() @@ -929,6 +932,10 @@ public class PrivateProfileManager extends UserProfileManager { return mPsHeaderHeight; } + String getPsAppContentDesc() { + return mPrivateSpaceAppContentDesc; + } + boolean isPrivateSpaceItem(BaseAllAppsAdapter.AdapterItem item) { return getItemInfoMatcher().test(item.itemInfo) || item.decorationInfo != null || (item.itemInfo instanceof PrivateSpaceInstallAppButtonInfo); diff --git a/src/com/android/launcher3/dot/FolderDotInfo.java b/src/com/android/launcher3/dot/FolderDotInfo.java index 54800a07a8..cb91db7654 100644 --- a/src/com/android/launcher3/dot/FolderDotInfo.java +++ b/src/com/android/launcher3/dot/FolderDotInfo.java @@ -30,6 +30,10 @@ public class FolderDotInfo extends DotInfo { private int mNumNotifications; + public void reset() { + mNumNotifications = 0; + } + public void addDotInfo(DotInfo dotToAdd) { if (dotToAdd == null) { return; @@ -39,15 +43,6 @@ public class FolderDotInfo extends DotInfo { mNumNotifications, MIN_COUNT, DotInfo.MAX_COUNT); } - public void subtractDotInfo(DotInfo dotToSubtract) { - if (dotToSubtract == null) { - return; - } - mNumNotifications -= dotToSubtract.getNotificationKeys().size(); - mNumNotifications = Utilities.boundToRange( - mNumNotifications, MIN_COUNT, DotInfo.MAX_COUNT); - } - @Override public int getNotificationCount() { return mNumNotifications; diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java index 0ae95196fd..967af053fb 100644 --- a/src/com/android/launcher3/folder/Folder.java +++ b/src/com/android/launcher3/folder/Folder.java @@ -19,9 +19,6 @@ package com.android.launcher3.folder; import static android.text.TextUtils.isEmpty; import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY; -import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION; -import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR; -import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT; import static com.android.launcher3.LauncherState.EDIT_MODE; import static com.android.launcher3.LauncherState.NORMAL; import static com.android.launcher3.compat.AccessibilityManagerCompat.sendCustomAccessibilityEvent; @@ -29,6 +26,7 @@ import static com.android.launcher3.config.FeatureFlags.ALWAYS_USE_HARDWARE_OPTI import static com.android.launcher3.folder.FolderGridOrganizer.createFolderGridOrganizer; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_LABEL_UPDATED; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ITEM_DROP_COMPLETED; +import static com.android.launcher3.model.data.FolderInfo.willAcceptItemType; import static com.android.launcher3.testing.shared.TestProtocol.FOLDER_OPENED_MESSAGE; import static com.android.launcher3.util.window.RefreshRateTracker.getSingleFrameMs; @@ -95,7 +93,6 @@ import com.android.launcher3.logger.LauncherAtom.ToState; import com.android.launcher3.logging.StatsLogManager; import com.android.launcher3.logging.StatsLogManager.StatsLogger; import com.android.launcher3.model.data.FolderInfo; -import com.android.launcher3.model.data.FolderInfo.FolderListener; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.model.data.WorkspaceItemFactory; import com.android.launcher3.model.data.WorkspaceItemInfo; @@ -111,6 +108,7 @@ import com.android.launcher3.widget.PendingAddShortcutInfo; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; +import java.util.Arrays; import java.util.Comparator; import java.util.List; import java.util.Objects; @@ -122,7 +120,7 @@ import java.util.stream.Stream; * Represents a set of icons chosen by the user or generated by the system. */ public class Folder extends AbstractFloatingView implements ClipPathView, DragSource, - View.OnLongClickListener, DropTarget, FolderListener, TextView.OnEditorActionListener, + View.OnLongClickListener, DropTarget, TextView.OnEditorActionListener, View.OnFocusChangeListener, DragListener, ExtendedEditText.OnBackKeyListener, LauncherBindableItemsContainer { private static final String TAG = "Launcher.Folder"; @@ -179,15 +177,6 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo return o instanceof ItemInfo info && willAcceptItemType(info.itemType); } - /** - * Checks if {@code itemType} is a type that can be placed in folders. - */ - public static boolean willAcceptItemType(int itemType) { - return itemType == ITEM_TYPE_APPLICATION - || itemType == ITEM_TYPE_DEEP_SHORTCUT - || itemType == ITEM_TYPE_APP_PAIR; - } - private Alarm mReorderAlarm = new Alarm(Looper.getMainLooper()); private Alarm mOnExitAlarm = new Alarm(Looper.getMainLooper()); private Alarm mOnScrollHintAlarm = new Alarm(Looper.getMainLooper()); @@ -243,7 +232,10 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo private boolean mIsExternalDrag; private boolean mIsDragInProgress = false; private boolean mDeleteFolderOnDropCompleted = false; + private boolean mSuppressFolderDeletion = false; + private boolean mSuppressContentUpdate = false; + private boolean mItemAddedBackToSelfViaIcon = false; private boolean mIsEditingName = false; @@ -385,9 +377,8 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo // We do not want to get events for the item being removed, as they will get handled // when the drop completes - try (SuppressInfoChanges s = new SuppressInfoChanges()) { - mInfo.remove(dragObject.dragInfo, true); - } + executeWithContentUpdateSuppressed(() -> removeFolderContent(true, dragObject.dragInfo)); + mIsDragInProgress = true; mItemAddedBackToSelfViaIcon = false; } @@ -532,8 +523,17 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo lp.customPosition = true; setLayoutParams(lp); } + reapplyItemInfo(); + // In case any children didn't come across during loading, clean up the folder accordingly + mFolderIcon.post(() -> { + if (getItemCount() <= 1) { + replaceFolderWithFinalItem(); + } + }); + } + + public void reapplyItemInfo() { mItemsInvalidated = true; - mInfo.addListener(this); if (!isEmpty(mInfo.title)) { mFolderName.setText(mInfo.title); @@ -542,15 +542,8 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo mFolderName.setText(""); mFolderName.setHint(R.string.folder_hint_text); } - // In case any children didn't come across during loading, clean up the folder accordingly - mFolderIcon.post(() -> { - if (getItemCount() <= 1) { - replaceFolderWithFinalItem(); - } - }); } - /** * Show suggested folder title in FolderEditText if the first suggestion is non-empty, push * rest of the suggestions to InputMethodManager. @@ -680,7 +673,6 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo if (!shouldAnimateOpen(items)) { return; } - Folder openFolder = getOpen(mActivityContext); closeOpenFolder(openFolder); @@ -954,9 +946,7 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo @Override public boolean acceptDrop(DragObject d) { - final ItemInfo item = d.dragInfo; - final int itemType = item.itemType; - return Folder.willAcceptItemType(itemType); + return willAcceptItemType(d.dragInfo.itemType); } public void onDragEnter(DragObject d) { @@ -1121,9 +1111,8 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo mContent.arrangeChildren(views); mItemsInvalidated = true; - try (SuppressInfoChanges s = new SuppressInfoChanges()) { - mFolderIcon.onDrop(d, true /* itemReturnedOnFailedDrop */); - } + executeWithContentUpdateSuppressed( + () -> mFolderIcon.onDrop(d, true /* itemReturnedOnFailedDrop */)); } } @@ -1417,9 +1406,7 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo rearrangeChildren(); // Temporarily suppress the listener, as we did all the work already here. - try (SuppressInfoChanges s = new SuppressInfoChanges()) { - mInfo.add(si, mEmptyCellRank, false); - } + executeWithContentUpdateSuppressed(() -> addFolderContent(si, mEmptyCellRank, false)); // We only need to update the locations if it doesn't get handled in // #onDropCompleted. @@ -1465,37 +1452,66 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo } } - @Override - public void onAdd(ItemInfo item, int rank) { - FolderGridOrganizer verifier = createFolderGridOrganizer( - mActivityContext.getDeviceProfile()).setFolderInfo(mInfo); - verifier.updateRankAndPos(item, rank); - mLauncherDelegate.getModelWriter().addOrMoveItemInDatabase(item, mInfo.id, 0, item.cellX, - item.cellY); - updateItemLocationsInDatabaseBatch(false); + /** Add an app or shortcut */ + public void addFolderContent(ItemInfo item) { + addFolderContent(item, mInfo.getContents().size(), true); + } - if (mContent.areViewsBound()) { - mContent.createAndAddViewForRank(item, rank); + /** Add an app or shortcut for a specified rank */ + public void addFolderContent(ItemInfo item, int rank, boolean animate) { + if (!willAcceptItemType(item.itemType)) { + throw new RuntimeException("tried to add an illegal type into a folder"); } - mItemsInvalidated = true; + + rank = Utilities.boundToRange(rank, 0, mInfo.getContents().size()); + mInfo.getContents().add(rank, item); + + if (!mSuppressContentUpdate) { + FolderGridOrganizer verifier = createFolderGridOrganizer( + mActivityContext.getDeviceProfile()).setFolderInfo(mInfo); + verifier.updateRankAndPos(item, rank); + mLauncherDelegate.getModelWriter().addOrMoveItemInDatabase(item, mInfo.id, 0, + item.cellX, + item.cellY); + updateItemLocationsInDatabaseBatch(false); + + if (mContent.areViewsBound()) { + mContent.createAndAddViewForRank(item, rank); + } + mItemsInvalidated = true; + updateTextViewFocus(); + } + + mLauncherDelegate.getModelWriter().notifyItemModified(mInfo); + mFolderIcon.onItemsChanged(animate); } - @Override - public void onRemove(List<ItemInfo> items) { - mItemsInvalidated = true; - items.stream().map(this::getViewForInfo).forEach(mContent::removeItem); - if (mState == STATE_ANIMATING) { - mRearrangeOnClose = true; - } else { - rearrangeChildren(); + /** Remove all matching app or shortcut. Does not change the DB. */ + public void removeFolderContent(boolean animate, ItemInfo... items) { + List<ItemInfo> itemArray = Arrays.asList(items); + if (mInfo.getContents().removeAll(itemArray)) { + mLauncherDelegate.getModelWriter().notifyItemModified(mInfo); } - if (getItemCount() <= 1) { - if (mIsOpen) { - close(true); + + if (!mSuppressContentUpdate) { + mItemsInvalidated = true; + itemArray.forEach(item -> mContent.removeItem(getViewForInfo(item))); + if (mState == STATE_ANIMATING) { + mRearrangeOnClose = true; } else { - replaceFolderWithFinalItem(); + rearrangeChildren(); + } + if (getItemCount() <= 1) { + if (mIsOpen) { + close(true); + } else { + replaceFolderWithFinalItem(); + } } + updateTextViewFocus(); } + + mFolderIcon.onItemsChanged(animate); } @VisibleForTesting @@ -1503,16 +1519,6 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo return mContent.iterateOverItems((info, view) -> info == item); } - @Override - public void onItemsChanged(boolean animate) { - updateTextViewFocus(); - } - - @Override - public void onTitleChanged(CharSequence title) { - mFolderName.setText(title); - } - /** * Utility methods to iterate over items of the view */ @@ -1666,18 +1672,14 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo } }; - /** - * Temporary resource held while we don't want to handle info changes - */ - private class SuppressInfoChanges implements AutoCloseable { - - SuppressInfoChanges() { - mInfo.removeListener(Folder.this); - } - - @Override - public void close() { - mInfo.addListener(Folder.this); + /** Executes the task while suppressing the content update for the folder */ + private void executeWithContentUpdateSuppressed(Runnable task) { + if (mSuppressContentUpdate) { + task.run(); + } else { + mSuppressContentUpdate = true; + task.run(); + mSuppressContentUpdate = false; updateTextViewFocus(); } } diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java index 0ed87871cc..1cd9bcc965 100644 --- a/src/com/android/launcher3/folder/FolderIcon.java +++ b/src/com/android/launcher3/folder/FolderIcon.java @@ -23,6 +23,7 @@ import static com.android.launcher3.folder.PreviewItemManager.INITIAL_ITEM_ANIMA import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_AUTO_LABELED; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_AUTO_LABELING_SKIPPED_EMPTY_PRIMARY; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_AUTO_LABELING_SKIPPED_EMPTY_SUGGESTIONS; +import static com.android.launcher3.model.data.FolderInfo.willAcceptItemType; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; @@ -73,7 +74,6 @@ import com.android.launcher3.logging.InstanceId; import com.android.launcher3.logging.StatsLogManager; import com.android.launcher3.model.data.AppPairInfo; import com.android.launcher3.model.data.FolderInfo; -import com.android.launcher3.model.data.FolderInfo.FolderListener; import com.android.launcher3.model.data.FolderInfo.LabelState; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.model.data.WorkspaceItemFactory; @@ -92,7 +92,7 @@ import java.util.function.Predicate; /** * An icon that can appear on in the workspace representing an {@link Folder}. */ -public class FolderIcon extends FrameLayout implements FolderListener, FloatingIconViewCompanion, +public class FolderIcon extends FrameLayout implements FloatingIconViewCompanion, DraggableView, Reorderable { private final MultiTranslateDelegate mTranslateDelegate = new MultiTranslateDelegate(this); @@ -127,7 +127,7 @@ public class FolderIcon extends FrameLayout implements FolderListener, FloatingI private boolean mForceHideDot; @ViewDebug.ExportedProperty(category = "launcher", deepExport = true) - private FolderDotInfo mDotInfo = new FolderDotInfo(); + private final FolderDotInfo mDotInfo = new FolderDotInfo(); private DotRenderer mDotRenderer; @ViewDebug.ExportedProperty(category = "launcher", deepExport = true) private DotRenderer.DrawParams mDotParams; @@ -178,7 +178,6 @@ public class FolderIcon extends FrameLayout implements FolderListener, FloatingI folder.bind(folderInfo); icon.setFolder(folder); - folderInfo.addListener(icon); return icon; } @@ -217,13 +216,7 @@ public class FolderIcon extends FrameLayout implements FolderListener, FloatingI icon.mDotRenderer = grid.mDotRendererWorkSpace; icon.setContentDescription(icon.getAccessiblityTitle(folderInfo.title)); - - // Keep the notification dot up to date with the sum of all the content's dots. - FolderDotInfo folderDotInfo = new FolderDotInfo(); - for (ItemInfo si : folderInfo.getContents()) { - folderDotInfo.addDotInfo(activity.getDotInfoForItem(si)); - } - icon.setDotInfo(folderDotInfo); + icon.updateDotInfo(); icon.setAccessibilityDelegate(activity.getAccessibilityDelegate()); @@ -264,22 +257,13 @@ public class FolderIcon extends FrameLayout implements FolderListener, FloatingI } private boolean willAcceptItem(ItemInfo item) { - final int itemType = item.itemType; - return (Folder.willAcceptItemType(itemType) && item != mInfo && !mFolder.isOpen()); + return (willAcceptItemType(item.itemType) && item != mInfo && !mFolder.isOpen()); } public boolean acceptDrop(ItemInfo dragInfo) { return !mFolder.isDestroyed() && willAcceptItem(dragInfo); } - public void addItem(ItemInfo item) { - mInfo.add(item, true); - } - - public void removeItem(ItemInfo item, boolean animate) { - mInfo.remove(item, animate); - } - public void onDragEnter(ItemInfo dragInfo) { if (mFolder.isDestroyed() || !willAcceptItem(dragInfo)) return; CellLayoutLayoutParams lp = (CellLayoutLayoutParams) getLayoutParams(); @@ -308,9 +292,8 @@ public class FolderIcon extends FrameLayout implements FolderListener, FloatingI public void performCreateAnimation(final ItemInfo destInfo, final View destView, final ItemInfo srcInfo, final DragObject d, Rect dstRect, float scaleRelativeToDragLayer) { - final DragView srcView = d.dragView; prepareCreateAnimation(destView); - addItem(destInfo); + getFolder().addFolderContent(destInfo); // This will animate the first item from it's position as an icon into its // position as the first item in the preview mPreviewItemManager.createFirstItemAnimation(false /* reverse */, null) @@ -364,7 +347,7 @@ public class FolderIcon extends FrameLayout implements FolderListener, FloatingI boolean itemAdded = false; if (itemReturnedOnFailedDrop || index >= MAX_NUM_ITEMS_IN_PREVIEW) { List<ItemInfo> oldPreviewItems = new ArrayList<>(mCurrentPreviewItems); - mInfo.add(item, index, false); + getFolder().addFolderContent(item, index, false); mCurrentPreviewItems.clear(); mCurrentPreviewItems.addAll(getPreviewItemsOnPage(0)); @@ -380,12 +363,12 @@ public class FolderIcon extends FrameLayout implements FolderListener, FloatingI mPreviewItemManager.onDrop(oldPreviewItems, mCurrentPreviewItems, item); itemAdded = true; } else { - removeItem(item, false); + getFolder().removeFolderContent(false, item); } } if (!itemAdded) { - mInfo.add(item, index, true); + getFolder().addFolderContent(item, index, true); } int[] center = new int[2]; @@ -431,7 +414,7 @@ public class FolderIcon extends FrameLayout implements FolderListener, FloatingI }, DROP_IN_ANIMATION_DURATION); }); } else { - addItem(item); + getFolder().addFolderContent(item); } } @@ -500,9 +483,23 @@ public class FolderIcon extends FrameLayout implements FolderListener, FloatingI ); } - public void setDotInfo(FolderDotInfo dotInfo) { - updateDotScale(mDotInfo.hasDot(), dotInfo.hasDot()); - mDotInfo = dotInfo; + /** Keep the notification dot up to date with the sum of all the content's dots. */ + public void updateDotInfo() { + boolean hadDot = mDotInfo.hasDot(); + mDotInfo.reset(); + for (ItemInfo si : mInfo.getContents()) { + mDotInfo.addDotInfo(mActivity.getDotInfoForItem(si)); + } + boolean isDotted = mDotInfo.hasDot(); + float newDotScale = isDotted ? 1f : 0f; + // Animate when a dot is first added or when it is removed. + if ((hadDot ^ isDotted) && isShown()) { + animateDotScale(newDotScale); + } else { + cancelDotScaleAnim(); + mDotScale = newDotScale; + invalidate(); + } } public ClippedFolderIconLayoutRule getLayoutRule() { @@ -523,22 +520,6 @@ public class FolderIcon extends FrameLayout implements FolderListener, FloatingI } } - /** - * Sets mDotScale to 1 or 0, animating if wasDotted or isDotted is false - * (the dot is being added or removed). - */ - private void updateDotScale(boolean wasDotted, boolean isDotted) { - float newDotScale = isDotted ? 1f : 0f; - // Animate when a dot is first added or when it is removed. - if ((wasDotted ^ isDotted) && isShown()) { - animateDotScale(newDotScale); - } else { - cancelDotScaleAnim(); - mDotScale = newDotScale; - invalidate(); - } - } - private void cancelDotScaleAnim() { if (mDotScaleAnim != null) { mDotScaleAnim.cancel(); @@ -682,13 +663,6 @@ public class FolderIcon extends FrameLayout implements FolderListener, FloatingI return mPreviewItemManager.verifyDrawable(who) || super.verifyDrawable(who); } - @Override - public void onItemsChanged(boolean animate) { - updatePreviewItems(animate); - invalidate(); - requestLayout(); - } - private void updatePreviewItems(boolean animate) { mPreviewItemManager.updatePreviewItems(animate); mCurrentPreviewItems.clear(); @@ -702,31 +676,15 @@ public class FolderIcon extends FrameLayout implements FolderListener, FloatingI mPreviewItemManager.updatePreviewItems(itemCheck); } - @Override - public void onAdd(ItemInfo item, int rank) { - updatePreviewItems(false); - boolean wasDotted = mDotInfo.hasDot(); - mDotInfo.addDotInfo(mActivity.getDotInfoForItem(item)); - boolean isDotted = mDotInfo.hasDot(); - updateDotScale(wasDotted, isDotted); - setContentDescription(getAccessiblityTitle(mInfo.title)); - invalidate(); - requestLayout(); - } - - @Override - public void onRemove(List<ItemInfo> items) { + public void onItemsChanged(boolean animate) { updatePreviewItems(false); - boolean wasDotted = mDotInfo.hasDot(); - items.stream().map(mActivity::getDotInfoForItem).forEach(mDotInfo::subtractDotInfo); - boolean isDotted = mDotInfo.hasDot(); - updateDotScale(wasDotted, isDotted); + updateDotInfo(); setContentDescription(getAccessiblityTitle(mInfo.title)); + updatePreviewItems(animate); invalidate(); requestLayout(); } - @Override public void onTitleChanged(CharSequence title) { mFolderName.setText(title); setContentDescription(getAccessiblityTitle(title)); @@ -762,11 +720,6 @@ public class FolderIcon extends FrameLayout implements FolderListener, FloatingI mLongPressHelper.cancelLongPress(); } - public void removeListeners() { - mInfo.removeListener(this); - mInfo.removeListener(mFolder); - } - private boolean isInHotseat() { return mInfo.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT; } diff --git a/src/com/android/launcher3/folder/FolderPagedView.java b/src/com/android/launcher3/folder/FolderPagedView.java index bebe1a49ef..0963421f8a 100644 --- a/src/com/android/launcher3/folder/FolderPagedView.java +++ b/src/com/android/launcher3/folder/FolderPagedView.java @@ -47,6 +47,7 @@ import com.android.launcher3.keyboard.ViewGroupFocusHelper; import com.android.launcher3.model.data.AppPairInfo; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.model.data.WorkspaceItemInfo; +import com.android.launcher3.pageindicators.Direction; import com.android.launcher3.pageindicators.PageIndicatorDots; import com.android.launcher3.util.LauncherBindableItemsContainer.ItemOperator; import com.android.launcher3.util.Thunk; @@ -129,6 +130,8 @@ public class FolderPagedView extends PagedView<PageIndicatorDots> implements Cli public void setFolder(Folder folder) { mFolder = folder; mPageIndicator = folder.findViewById(R.id.folder_page_indicator); + mPageIndicator.setArrowClickListener(direction -> snapToPageImmediately( + (Direction.END == direction) ? mCurrentPage + 1 : mCurrentPage - 1)); initParentViews(folder); } diff --git a/src/com/android/launcher3/graphics/GridCustomizationsProxy.java b/src/com/android/launcher3/graphics/GridCustomizationsProxy.java index 70b9f46c1f..48519ce8bc 100644 --- a/src/com/android/launcher3/graphics/GridCustomizationsProxy.java +++ b/src/com/android/launcher3/graphics/GridCustomizationsProxy.java @@ -113,6 +113,7 @@ public class GridCustomizationsProxy implements ProxyProvider { private static final String KEY_SHAPE_OPTIONS = "/shape_options"; // default_grid is for setting grid and shape to system settings private static final String KEY_DEFAULT_GRID = "/default_grid"; + private static final String SET_SHAPE = "/shape"; private static final String METHOD_GET_PREVIEW = "get_preview"; @@ -130,6 +131,7 @@ public class GridCustomizationsProxy implements ProxyProvider { private static final int MESSAGE_ID_UPDATE_SHAPE = 2586; private static final int MESSAGE_ID_UPDATE_GRID = 7414; private static final int MESSAGE_ID_UPDATE_COLOR = 856; + private static final int MESSAGE_ID_UPDATE_ICON_THEMED = 311; // Set of all active previews used to track duplicate memory allocations private final Set<PreviewLifecycleObserver> mActivePreviews = @@ -264,6 +266,12 @@ public class GridCustomizationsProxy implements ProxyProvider { mContext.getContentResolver().notifyChange(uri, null); return 1; } + case SET_SHAPE: + if (Flags.newCustomizationPickerUi()) { + mPrefs.put(PREF_ICON_SHAPE, + requireNonNullElse(values.getAsString(KEY_SHAPE_KEY), "")); + } + return 1; case ICON_THEMED: case SET_ICON_THEMED: { mThemeManager.setMonoThemeEnabled(values.getAsBoolean(BOOLEAN_VALUE)); @@ -384,6 +392,12 @@ public class GridCustomizationsProxy implements ProxyProvider { renderer.previewColor(message.getData()); } break; + case MESSAGE_ID_UPDATE_ICON_THEMED: + if (Flags.newCustomizationPickerUi()) { + Boolean iconThemed = message.getData().getBoolean(BOOLEAN_VALUE); + // TODO Update icon themed in the preview + } + break; default: // Unknown command, destroy lifecycle Log.d(TAG, "Unknown preview command: " + message.what + ", destroying preview"); diff --git a/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java b/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java index 5a9b9c20f9..457d12e8ca 100644 --- a/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java +++ b/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java @@ -21,7 +21,6 @@ import static android.content.res.Configuration.UI_MODE_NIGHT_YES; import static android.view.Display.DEFAULT_DISPLAY; import static com.android.launcher3.LauncherPrefs.GRID_NAME; -import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME; import static com.android.launcher3.graphics.ThemeManager.PREF_ICON_SHAPE; import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; @@ -179,7 +178,7 @@ public class PreviewSurfaceRenderer { ModelDbController mainController = LauncherAppState.getInstance(mContext).getModel().getModelDbController(); - try (Cursor c = mainController.query(TABLE_NAME, + try (Cursor c = mainController.query( new String[] { LauncherSettings.Favorites.APPWIDGET_ID, LauncherSettings.Favorites.SPANX, diff --git a/src/com/android/launcher3/graphics/ShapeDelegate.kt b/src/com/android/launcher3/graphics/ShapeDelegate.kt index 7c042929e2..01bfe3018c 100644 --- a/src/com/android/launcher3/graphics/ShapeDelegate.kt +++ b/src/com/android/launcher3/graphics/ShapeDelegate.kt @@ -18,6 +18,7 @@ package com.android.launcher3.graphics import android.animation.Animator import android.animation.AnimatorListenerAdapter import android.animation.ValueAnimator +import android.animation.ValueAnimator.AnimatorUpdateListener import android.graphics.Canvas import android.graphics.Color import android.graphics.Matrix @@ -41,7 +42,6 @@ import androidx.graphics.shapes.SvgPathParser import androidx.graphics.shapes.rectangle import androidx.graphics.shapes.toPath import androidx.graphics.shapes.transformed -import com.android.launcher3.anim.RoundedRectRevealOutlineProvider import com.android.launcher3.icons.GraphicsUtils import com.android.launcher3.views.ClipPathView @@ -127,16 +127,20 @@ interface ShapeDelegate { endRadius: Float, isReversed: Boolean, ): ValueAnimator where T : View, T : ClipPathView { - return object : - RoundedRectRevealOutlineProvider( - (startRect.width() / 2f) * radiusRatio, - endRadius, - startRect, - endRect, - ) { - override fun shouldRemoveElevationDuringAnimation() = true - } - .createRevealAnimator(target, isReversed) + val startRadius = (startRect.width() / 2f) * radiusRatio + return ClipAnimBuilder(target) { progress, path -> + val radius = (1 - progress) * startRadius + progress * endRadius + path.addRoundRect( + (1 - progress) * startRect.left + progress * endRect.left, + (1 - progress) * startRect.top + progress * endRect.top, + (1 - progress) * startRect.right + progress * endRect.right, + (1 - progress) * startRect.bottom + progress * endRect.bottom, + radius, + radius, + Path.Direction.CW, + ) + } + .toAnim(isReversed) } override fun equals(other: Any?) = @@ -220,38 +224,44 @@ interface ShapeDelegate { ), ) - val va = - if (isReversed) ValueAnimator.ofFloat(1f, 0f) else ValueAnimator.ofFloat(0f, 1f) - va.addListener( - object : AnimatorListenerAdapter() { - private var oldOutlineProvider: ViewOutlineProvider? = null - - override fun onAnimationStart(animation: Animator) { - target.apply { - oldOutlineProvider = outlineProvider - outlineProvider = null - translationZ = -target.elevation - } - } + return ClipAnimBuilder(target, morph::toPath).toAnim(isReversed) + } + } - override fun onAnimationEnd(animation: Animator) { - target.apply { - translationZ = 0f - setClipPath(null) - outlineProvider = oldOutlineProvider - } - } - } - ) + private class ClipAnimBuilder<T>(val target: T, val pathProvider: (Float, Path) -> Unit) : + AnimatorListenerAdapter(), AnimatorUpdateListener where T : View, T : ClipPathView { - val path = Path() - va.addUpdateListener { anim: ValueAnimator -> - path.reset() - morph.toPath(anim.animatedValue as Float, path) - target.setClipPath(path) + private var oldOutlineProvider: ViewOutlineProvider? = null + val path = Path() + + override fun onAnimationStart(animation: Animator) { + target.apply { + oldOutlineProvider = outlineProvider + outlineProvider = null + translationZ = -target.elevation + } + } + + override fun onAnimationEnd(animation: Animator) { + target.apply { + translationZ = 0f + setClipPath(null) + outlineProvider = oldOutlineProvider } - return va } + + override fun onAnimationUpdate(anim: ValueAnimator) { + path.reset() + pathProvider.invoke(anim.animatedValue as Float, path) + target.setClipPath(path) + } + + fun toAnim(isReversed: Boolean) = + (if (isReversed) ValueAnimator.ofFloat(1f, 0f) else ValueAnimator.ofFloat(0f, 1f)) + .also { + it.addListener(this) + it.addUpdateListener(this) + } } companion object { diff --git a/src/com/android/launcher3/model/LoaderCursor.java b/src/com/android/launcher3/model/LoaderCursor.java index 77d0d7656b..efe61572fe 100644 --- a/src/com/android/launcher3/model/LoaderCursor.java +++ b/src/com/android/launcher3/model/LoaderCursor.java @@ -21,7 +21,6 @@ import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION; import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR; import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT; -import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME; import static com.android.launcher3.Utilities.SHOULD_SHOW_FIRST_PAGE_WIDGET; import static com.android.launcher3.icons.cache.CacheLookupFlag.DEFAULT_LOOKUP_FLAG; import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_ARCHIVED; @@ -466,7 +465,7 @@ public class LoaderCursor extends CursorWrapper { public boolean commitDeleted() { if (mItemsToRemove.size() > 0) { // Remove dead items - mModel.getModelDbController().delete(TABLE_NAME, + mModel.getModelDbController().delete( Utilities.createDbSelectionQuery(Favorites._ID, mItemsToRemove), null); return true; } @@ -492,7 +491,7 @@ public class LoaderCursor extends CursorWrapper { // Update restored items that no longer require special handling ContentValues values = new ContentValues(); values.put(Favorites.RESTORED, 0); - mModel.getModelDbController().update(TABLE_NAME, values, + mModel.getModelDbController().update(values, Utilities.createDbSelectionQuery(Favorites._ID, mRestoredRows), null); } if (mRestoreEventLogger != null) { diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java index 73af6a221a..78e5d898cd 100644 --- a/src/com/android/launcher3/model/LoaderTask.java +++ b/src/com/android/launcher3/model/LoaderTask.java @@ -23,7 +23,6 @@ import static com.android.launcher3.Flags.enableSmartspaceRemovalToggle; import static com.android.launcher3.LauncherPrefs.IS_FIRST_LOAD_AFTER_RESTORE; import static com.android.launcher3.LauncherPrefs.SHOULD_SHOW_SMARTSPACE; import static com.android.launcher3.LauncherSettings.Favorites.DESKTOP_ICON_FLAG; -import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME; import static com.android.launcher3.icons.CacheableShortcutInfo.convertShortcutsToCacheableShortcuts; import static com.android.launcher3.icons.cache.CacheLookupFlag.DEFAULT_LOOKUP_FLAG; import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_HAS_SHORTCUT_PERMISSION; @@ -485,7 +484,7 @@ public class LoaderTask implements Runnable { mShortcutKeyToPinnedShortcuts = new HashMap<>(); final LoaderCursor c = mLoaderCursorFactory.createLoaderCursor( - dbController.query(TABLE_NAME, null, selection, null, null), + dbController.query(null, selection, null, null), mUserManagerState, mIsRestoreFromBackup ? restoreEventLogger : null); final Bundle extras = c.getExtras(); diff --git a/src/com/android/launcher3/model/ModelDbController.java b/src/com/android/launcher3/model/ModelDbController.java index feae632bc5..64b9c1c9cd 100644 --- a/src/com/android/launcher3/model/ModelDbController.java +++ b/src/com/android/launcher3/model/ModelDbController.java @@ -73,6 +73,8 @@ import com.android.launcher3.LauncherSettings.Favorites; import com.android.launcher3.Utilities; import com.android.launcher3.backuprestore.LauncherRestoreEventLogger; import com.android.launcher3.backuprestore.LauncherRestoreEventLogger.RestoreError; +import com.android.launcher3.dagger.ApplicationContext; +import com.android.launcher3.dagger.LauncherAppSingleton; import com.android.launcher3.logging.FileLog; import com.android.launcher3.pm.UserCache; import com.android.launcher3.provider.LauncherDbUtils; @@ -91,10 +93,13 @@ import java.io.StringReader; import java.util.List; import java.util.stream.Collectors; +import javax.inject.Inject; + /** * Utility class which maintains an instance of Launcher database and provides utility methods * around it. */ +@LauncherAppSingleton public class ModelDbController { private static final String TAG = "ModelDbController"; @@ -105,17 +110,25 @@ public class ModelDbController { protected DatabaseHelper mOpenHelper; private final Context mContext; - - public ModelDbController(Context context) { + private final InvariantDeviceProfile mIdp; + private final LauncherPrefs mPrefs; + private final UserCache mUserCache; + + @Inject + ModelDbController( + @ApplicationContext Context context, + InvariantDeviceProfile idp, + LauncherPrefs prefs, + UserCache userCache) { mContext = context; + mIdp = idp; + mPrefs = prefs; + mUserCache = userCache; } private void printDBs(String prefix) { try { - File directory = new File( - mContext.getDatabasePath(InvariantDeviceProfile.INSTANCE.get(mContext).dbFile) - .getParent() - ); + File directory = new File(mContext.getDatabasePath(mIdp.dbFile).getParent()); if (directory.exists()) { for (File file : directory.listFiles()) { Log.d("b/353505773", prefix + "Database file: " + file.getName()); @@ -130,9 +143,9 @@ public class ModelDbController { private synchronized void createDbIfNotExists() { if (mOpenHelper == null) { - String dbFile = LauncherPrefs.get(mContext).get(DB_FILE); + String dbFile = mPrefs.get(DB_FILE); if (dbFile.isEmpty()) { - dbFile = InvariantDeviceProfile.INSTANCE.get(mContext).dbFile; + dbFile = mIdp.dbFile; } mOpenHelper = createDatabaseHelper(false /* forMigration */, dbFile); printDBs("before: "); @@ -144,7 +157,7 @@ public class ModelDbController { protected DatabaseHelper createDatabaseHelper(boolean forMigration, String dbFile) { // Set the flag for empty DB Runnable onEmptyDbCreateCallback = forMigration ? () -> { } - : () -> LauncherPrefs.get(mContext).putSync(getEmptyDbCreatedKey(dbFile).to(true)); + : () -> mPrefs.putSync(getEmptyDbCreatedKey(dbFile).to(true)); DatabaseHelper databaseHelper = new DatabaseHelper(mContext, dbFile, this::getSerialNumberForUser, onEmptyDbCreateCallback); @@ -169,12 +182,12 @@ public class ModelDbController { * Refer {@link SQLiteDatabase#query} */ @WorkerThread - public Cursor query(String table, String[] projection, String selection, + public Cursor query(String[] projection, String selection, String[] selectionArgs, String sortOrder) { createDbIfNotExists(); SQLiteDatabase db = mOpenHelper.getWritableDatabase(); Cursor result = db.query( - table, projection, selection, selectionArgs, null, null, sortOrder); + TABLE_NAME, projection, selection, selectionArgs, null, null, sortOrder); final Bundle extra = new Bundle(); extra.putString(EXTRA_DB_NAME, mOpenHelper.getDatabaseName()); @@ -186,12 +199,12 @@ public class ModelDbController { * Refer {@link SQLiteDatabase#insert(String, String, ContentValues)} */ @WorkerThread - public int insert(String table, ContentValues initialValues) { + public int insert(ContentValues initialValues) { createDbIfNotExists(); SQLiteDatabase db = mOpenHelper.getWritableDatabase(); addModifiedTime(initialValues); - int rowId = mOpenHelper.dbInsertAndCheck(db, table, initialValues); + int rowId = mOpenHelper.dbInsertAndCheck(db, TABLE_NAME, initialValues); if (rowId >= 0) { onAddOrDeleteOp(db); } @@ -202,11 +215,11 @@ public class ModelDbController { * Refer {@link SQLiteDatabase#delete(String, String, String[])} */ @WorkerThread - public int delete(String table, String selection, String[] selectionArgs) { + public int delete(String selection, String[] selectionArgs) { createDbIfNotExists(); SQLiteDatabase db = mOpenHelper.getWritableDatabase(); - int count = db.delete(table, selection, selectionArgs); + int count = db.delete(TABLE_NAME, selection, selectionArgs); if (count > 0) { onAddOrDeleteOp(db); } @@ -217,14 +230,12 @@ public class ModelDbController { * Refer {@link SQLiteDatabase#update(String, ContentValues, String, String[])} */ @WorkerThread - public int update(String table, ContentValues values, - String selection, String[] selectionArgs) { + public int update(ContentValues values, String selection, String[] selectionArgs) { createDbIfNotExists(); addModifiedTime(values); SQLiteDatabase db = mOpenHelper.getWritableDatabase(); - int count = db.update(table, values, selection, selectionArgs); - return count; + return db.update(TABLE_NAME, values, selection, selectionArgs); } /** @@ -261,7 +272,7 @@ public class ModelDbController { public void createEmptyDB() { createDbIfNotExists(); mOpenHelper.createEmptyDB(mOpenHelper.getWritableDatabase()); - LauncherPrefs.get(mContext).putSync(getEmptyDbCreatedKey().to(true)); + mPrefs.putSync(getEmptyDbCreatedKey().to(true)); } /** @@ -292,7 +303,6 @@ public class ModelDbController { mOpenHelper.getReadableDatabase(), Favorites.HYBRID_HOTSEAT_BACKUP_TABLE); } - /** * Resets the launcher DB if we should reset it. */ @@ -302,11 +312,10 @@ public class ModelDbController { } FileLog.d(TAG, "resetLauncherDb: Migration failed: resetting launcher database"); createEmptyDB(); - LauncherPrefs.get(mContext).putSync( - getEmptyDbCreatedKey(mOpenHelper.getDatabaseName()).to(true)); + mPrefs.putSync(getEmptyDbCreatedKey(mOpenHelper.getDatabaseName()).to(true)); // Write the grid state to avoid another migration - new DeviceGridState(LauncherAppState.getIDP(mContext)).writeToPrefs(mContext); + new DeviceGridState(mIdp).writeToPrefs(mContext); } /** @@ -326,7 +335,7 @@ public class ModelDbController { } private boolean isThereExistingDb() { - if (LauncherPrefs.get(mContext).get(getEmptyDbCreatedKey())) { + if (mPrefs.get(getEmptyDbCreatedKey())) { // If we already have a new DB, ignore migration FileLog.d(TAG, "isThereExistingDb: new DB already created, skipping migration"); return true; @@ -335,8 +344,7 @@ public class ModelDbController { } private boolean isGridMigrationNecessary() { - InvariantDeviceProfile idp = LauncherAppState.getIDP(mContext); - if (GridSizeMigrationDBController.needsToMigrate(mContext, idp)) { + if (GridSizeMigrationDBController.needsToMigrate(mContext, mIdp)) { return true; } FileLog.d(TAG, "isGridMigrationNecessary: no grid migration needed"); @@ -344,8 +352,7 @@ public class ModelDbController { } private boolean isCurrentDbSameAsTarget() { - InvariantDeviceProfile idp = LauncherAppState.getIDP(mContext); - String targetDbName = new DeviceGridState(idp).getDbFile(); + String targetDbName = new DeviceGridState(mIdp).getDbFile(); if (TextUtils.equals(targetDbName, mOpenHelper.getDatabaseName())) { FileLog.e(TAG, "isCurrentDbSameAsTarget: target db is same as current" + " current db: " + mOpenHelper.getDatabaseName() @@ -367,7 +374,6 @@ public class ModelDbController { return; } - InvariantDeviceProfile idp = LauncherAppState.getIDP(mContext); DatabaseHelper oldHelper = mOpenHelper; // We save the existing db's before creating the destination db helper so we know what logic @@ -376,12 +382,12 @@ public class ModelDbController { .filter(dbName -> mContext.getDatabasePath(dbName).exists()) .collect(Collectors.toList()); - mOpenHelper = createDatabaseHelper(true, new DeviceGridState(idp).getDbFile()); + mOpenHelper = createDatabaseHelper(true, new DeviceGridState(mIdp).getDbFile()); try { // This is the current grid we have, given by the mContext DeviceGridState srcDeviceState = new DeviceGridState(mContext); // This is the state we want to migrate to that is given by the idp - DeviceGridState destDeviceState = new DeviceGridState(idp); + DeviceGridState destDeviceState = new DeviceGridState(mIdp); boolean isDestNewDb = !existingDBs.contains(destDeviceState.getDbFile()); GridSizeMigrationLogic gridSizeMigrationLogic = new GridSizeMigrationLogic(); @@ -404,10 +410,10 @@ public class ModelDbController { ModelDelegate modelDelegate) { if (!migrateGridIfNeeded(modelDelegate)) { if (restoreEventLogger != null) { - if (LauncherPrefs.get(mContext).get(NO_DB_FILES_RESTORED)) { + if (mPrefs.get(NO_DB_FILES_RESTORED)) { restoreEventLogger.logLauncherItemsRestoreFailed(DATA_TYPE_DB_FILE, 1, RestoreError.DATABASE_FILE_NOT_RESTORED); - LauncherPrefs.get(mContext).put(NO_DB_FILES_RESTORED, false); + mPrefs.put(NO_DB_FILES_RESTORED, false); FileLog.d(TAG, "There is no data to migrate: resetting launcher database"); } else { restoreEventLogger.logLauncherItemsRestored(DATA_TYPE_DB_FILE, 1); @@ -416,11 +422,10 @@ public class ModelDbController { } FileLog.d(TAG, "tryMigrateDB: Migration failed: resetting launcher database"); createEmptyDB(); - LauncherPrefs.get(mContext).putSync( - getEmptyDbCreatedKey(mOpenHelper.getDatabaseName()).to(true)); + mPrefs.putSync(getEmptyDbCreatedKey(mOpenHelper.getDatabaseName()).to(true)); // Write the grid state to avoid another migration - new DeviceGridState(LauncherAppState.getIDP(mContext)).writeToPrefs(mContext); + new DeviceGridState(mIdp).writeToPrefs(mContext); } else if (restoreEventLogger != null) { restoreEventLogger.logLauncherItemsRestored(DATA_TYPE_DB_FILE, 1); } @@ -434,17 +439,16 @@ public class ModelDbController { */ private boolean migrateGridIfNeeded(ModelDelegate modelDelegate) { createDbIfNotExists(); - if (LauncherPrefs.get(mContext).get(getEmptyDbCreatedKey())) { + if (mPrefs.get(getEmptyDbCreatedKey())) { // If we have already create a new DB, ignore migration FileLog.d(TAG, "migrateGridIfNeeded: new DB already created, skipping migration"); return false; } - InvariantDeviceProfile idp = LauncherAppState.getIDP(mContext); - if (!GridSizeMigrationDBController.needsToMigrate(mContext, idp)) { + if (!GridSizeMigrationDBController.needsToMigrate(mContext, mIdp)) { FileLog.d(TAG, "migrateGridIfNeeded: no grid migration needed"); return true; } - String targetDbName = new DeviceGridState(idp).getDbFile(); + String targetDbName = new DeviceGridState(mIdp).getDbFile(); if (TextUtils.equals(targetDbName, mOpenHelper.getDatabaseName())) { FileLog.e(TAG, "migrateGridIfNeeded: target db is same as current" + " current db: " + mOpenHelper.getDatabaseName() @@ -462,7 +466,7 @@ public class ModelDbController { // This is the current grid we have, given by the mContext DeviceGridState srcDeviceState = new DeviceGridState(mContext); // This is the state we want to migrate to that is given by the idp - DeviceGridState destDeviceState = new DeviceGridState(idp); + DeviceGridState destDeviceState = new DeviceGridState(mIdp); boolean isDestNewDb = !existingDBs.contains(destDeviceState.getDbFile()); return GridSizeMigrationDBController.migrateGridIfNeeded(mContext, srcDeviceState, destDeviceState, mOpenHelper, oldHelper.getWritableDatabase(), isDestNewDb, @@ -611,7 +615,7 @@ public class ModelDbController { } private void clearFlagEmptyDbCreated() { - LauncherPrefs.get(mContext).removeSync(getEmptyDbCreatedKey()); + mPrefs.removeSync(getEmptyDbCreatedKey()); } /** @@ -625,7 +629,7 @@ public class ModelDbController { public synchronized void loadDefaultFavoritesIfNecessary() { createDbIfNotExists(); - if (LauncherPrefs.get(mContext).get(getEmptyDbCreatedKey())) { + if (mPrefs.get(getEmptyDbCreatedKey())) { Log.d(TAG, "loading default workspace"); LauncherWidgetHolder widgetHolder = mOpenHelper.newLauncherWidgetHolder(); @@ -737,10 +741,9 @@ public class ModelDbController { } private DefaultLayoutParser getDefaultLayoutParser(LauncherWidgetHolder widgetHolder) { - InvariantDeviceProfile idp = LauncherAppState.getIDP(mContext); - int defaultLayout = idp.demoModeLayoutId != 0 + int defaultLayout = mIdp.demoModeLayoutId != 0 && mContext.getSystemService(UserManager.class).isDemoUser() - ? idp.demoModeLayoutId : idp.defaultLayoutId; + ? mIdp.demoModeLayoutId : mIdp.defaultLayoutId; return new DefaultLayoutParser(mContext, widgetHolder, mOpenHelper, mContext.getResources(), defaultLayout); @@ -766,6 +769,6 @@ public class ModelDbController { * Returns the serial number for the provided user */ public long getSerialNumberForUser(UserHandle user) { - return UserCache.INSTANCE.get(mContext).getSerialNumberForUser(user); + return mUserCache.getSerialNumberForUser(user); } } diff --git a/src/com/android/launcher3/model/ModelWriter.java b/src/com/android/launcher3/model/ModelWriter.java index 0332775224..2650e03c63 100644 --- a/src/com/android/launcher3/model/ModelWriter.java +++ b/src/com/android/launcher3/model/ModelWriter.java @@ -16,7 +16,6 @@ package com.android.launcher3.model; -import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME; import static com.android.launcher3.provider.LauncherDbUtils.itemIdMatch; import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; @@ -229,7 +228,7 @@ public class ModelWriter { }).executeOnModelThread(); } - private void notifyItemModified(ItemInfo item) { + public void notifyItemModified(ItemInfo item) { notifyOtherCallbacks(c -> c.bindItemsModified(Collections.singletonList(item))); } @@ -253,7 +252,7 @@ public class ModelWriter { item.onAddToDatabase(writer); writer.put(Favorites._ID, item.id); - mModel.getModelDbController().insert(Favorites.TABLE_NAME, writer.getValues(mContext)); + mModel.getModelDbController().insert(writer.getValues(mContext)); synchronized (mBgDataModel) { checkItemInfoLocked(item.id, item, stackTrace); mBgDataModel.addItem(mContext, item, true); @@ -292,7 +291,7 @@ public class ModelWriter { notifyDelete(items); enqueueDeleteRunnable(newModelTask(() -> { for (ItemInfo item : items) { - mModel.getModelDbController().delete(TABLE_NAME, itemIdMatch(item.id), null); + mModel.getModelDbController().delete(itemIdMatch(item.id), null); mBgDataModel.removeItem(mContext, item); verifier.verifyModel(); } @@ -307,12 +306,12 @@ public class ModelWriter { notifyDelete(Collections.singleton(info)); enqueueDeleteRunnable(newModelTask(() -> { - mModel.getModelDbController().delete(Favorites.TABLE_NAME, + mModel.getModelDbController().delete( Favorites.CONTAINER + "=" + info.id, null); mBgDataModel.removeItem(mContext, info.getContents()); info.getContents().clear(); - mModel.getModelDbController().delete(Favorites.TABLE_NAME, + mModel.getModelDbController().delete( Favorites._ID + "=" + info.id, null); mBgDataModel.removeItem(mContext, info); verifier.verifyModel(); @@ -411,7 +410,7 @@ public class ModelWriter { @Override public void runImpl() { mModel.getModelDbController().update( - TABLE_NAME, mWriter.get().getValues(mContext), itemIdMatch(mItemId), null); + mWriter.get().getValues(mContext), itemIdMatch(mItemId), null); updateItemArrays(mItem, mItemId); } } @@ -433,7 +432,7 @@ public class ModelWriter { ItemInfo item = mItems.get(i); final int itemId = item.id; mModel.getModelDbController().update( - TABLE_NAME, mValues.get(i), itemIdMatch(itemId), null); + mValues.get(i), itemIdMatch(itemId), null); updateItemArrays(item, itemId); } t.commit(); diff --git a/src/com/android/launcher3/model/data/FolderInfo.java b/src/com/android/launcher3/model/data/FolderInfo.java index 9656ac10b2..4c792a7672 100644 --- a/src/com/android/launcher3/model/data/FolderInfo.java +++ b/src/com/android/launcher3/model/data/FolderInfo.java @@ -20,6 +20,9 @@ import static android.text.TextUtils.isEmpty; import static androidx.core.util.Preconditions.checkNotNull; +import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION; +import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR; +import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT; import static com.android.launcher3.logger.LauncherAtom.Attribute.EMPTY_LABEL; import static com.android.launcher3.logger.LauncherAtom.Attribute.MANUAL_LABEL; import static com.android.launcher3.logger.LauncherAtom.Attribute.SUGGESTED_LABEL; @@ -30,8 +33,6 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.launcher3.LauncherSettings; -import com.android.launcher3.Utilities; -import com.android.launcher3.folder.Folder; import com.android.launcher3.folder.FolderNameInfos; import com.android.launcher3.logger.LauncherAtom; import com.android.launcher3.logger.LauncherAtom.Attribute; @@ -42,8 +43,6 @@ import com.android.launcher3.model.ModelWriter; import com.android.launcher3.util.ContentWriter; import java.util.ArrayList; -import java.util.Collections; -import java.util.List; import java.util.OptionalInt; import java.util.stream.IntStream; @@ -52,18 +51,6 @@ import java.util.stream.IntStream; */ public class FolderInfo extends CollectionInfo { - public static final int NO_FLAGS = 0x00000000; - - /** - * The folder is locked in sorted mode - */ - public static final int FLAG_ITEMS_SORTED = 0x00000001; - - /** - * It is a work folder - */ - public static final int FLAG_WORK_FOLDER = 0x00000002; - /** * The multi-page animation has run for this folder */ @@ -95,8 +82,6 @@ public class FolderInfo extends CollectionInfo { } } - public static final String EXTRA_FOLDER_SUGGESTIONS = "suggest"; - public int options; public FolderNameInfos suggestedFolderNames; @@ -106,61 +91,16 @@ public class FolderInfo extends CollectionInfo { */ private final ArrayList<ItemInfo> contents = new ArrayList<>(); - private ArrayList<FolderListener> mListeners = new ArrayList<>(); - public FolderInfo() { itemType = LauncherSettings.Favorites.ITEM_TYPE_FOLDER; } - /** Adds a app or shortcut to the contents ArrayList without animation. */ @Override public void add(@NonNull ItemInfo item) { - add(item, false /* animate */); - } - - /** - * Add an app or shortcut - * - * @param item - */ - public void add(ItemInfo item, boolean animate) { - add(item, getContents().size(), animate); - } - - /** - * Add an app or shortcut for a specified rank. - */ - public void add(ItemInfo item, int rank, boolean animate) { - if (!Folder.willAccept(item)) { + if (!willAcceptItemType(item.itemType)) { throw new RuntimeException("tried to add an illegal type into a folder"); } - - rank = Utilities.boundToRange(rank, 0, getContents().size()); - getContents().add(rank, item); - for (int i = 0; i < mListeners.size(); i++) { - mListeners.get(i).onAdd(item, rank); - } - itemsChanged(animate); - } - - /** - * Remove an app or shortcut. Does not change the DB. - * - * @param item - */ - public void remove(ItemInfo item, boolean animate) { - removeAll(Collections.singletonList(item), animate); - } - - /** - * Remove all matching app or shortcut. Does not change the DB. - */ - public void removeAll(List<ItemInfo> items, boolean animate) { - contents.removeAll(items); - for (int i = 0; i < mListeners.size(); i++) { - mListeners.get(i).onRemove(items); - } - itemsChanged(animate); + getContents().add(item); } /** @@ -197,28 +137,6 @@ public class FolderInfo extends CollectionInfo { writer.put(LauncherSettings.Favorites.OPTIONS, options); } - public void addListener(FolderListener listener) { - mListeners.add(listener); - } - - public void removeListener(FolderListener listener) { - mListeners.remove(listener); - } - - public void itemsChanged(boolean animate) { - for (int i = 0; i < mListeners.size(); i++) { - mListeners.get(i).onItemsChanged(animate); - } - } - - public interface FolderListener { - void onAdd(ItemInfo item, int rank); - void onRemove(List<ItemInfo> item); - void onItemsChanged(boolean animate); - void onTitleChanged(CharSequence title); - - } - public boolean hasOption(int optionFlag) { return (options & optionFlag) != 0; } @@ -261,7 +179,6 @@ public class FolderInfo extends CollectionInfo { .build(); } - @Override public void setTitle(@Nullable CharSequence title, ModelWriter modelWriter) { // Updating label from null to empty is considered as false touch. // Retaining null title(ie., UNLABELED state) allows auto-labeling when new items added. @@ -289,10 +206,6 @@ public class FolderInfo extends CollectionInfo { if (modelWriter != null) { modelWriter.updateItemInDatabase(this); } - - for (int i = 0; i < mListeners.size(); i++) { - mListeners.get(i).onTitleChanged(title); - } } /** @@ -401,4 +314,13 @@ public class FolderInfo extends CollectionInfo { } return LauncherAtom.ToState.TO_STATE_UNSPECIFIED; } + + /** + * Checks if {@code itemType} is a type that can be placed in folders. + */ + public static boolean willAcceptItemType(int itemType) { + return itemType == ITEM_TYPE_APPLICATION + || itemType == ITEM_TYPE_DEEP_SHORTCUT + || itemType == ITEM_TYPE_APP_PAIR; + } } diff --git a/src/com/android/launcher3/model/data/ItemInfo.java b/src/com/android/launcher3/model/data/ItemInfo.java index 588e75959e..ad7696cd15 100644 --- a/src/com/android/launcher3/model/data/ItemInfo.java +++ b/src/com/android/launcher3/model/data/ItemInfo.java @@ -61,7 +61,6 @@ import com.android.launcher3.logger.LauncherAtom.ShortcutsContainer; import com.android.launcher3.logger.LauncherAtom.TaskSwitcherContainer; import com.android.launcher3.logger.LauncherAtom.WallpapersContainer; import com.android.launcher3.logger.LauncherAtomExtensions.ExtendedContainers; -import com.android.launcher3.model.ModelWriter; import com.android.launcher3.pm.UserCache; import com.android.launcher3.util.ComponentKey; import com.android.launcher3.util.ContentWriter; @@ -536,14 +535,6 @@ public class ItemInfo { } /** - * Sets the title of the item and writes to DB model if needed. - */ - public void setTitle(@Nullable final CharSequence title, - @Nullable final ModelWriter modelWriter) { - this.title = title; - } - - /** * Returns a string ID that is stable for a user session, but may not be persisted */ @Nullable diff --git a/src/com/android/launcher3/pageindicators/PageIndicator.java b/src/com/android/launcher3/pageindicators/PageIndicator.java index 0640bf3672..a6f76c4d44 100644 --- a/src/com/android/launcher3/pageindicators/PageIndicator.java +++ b/src/com/android/launcher3/pageindicators/PageIndicator.java @@ -15,6 +15,8 @@ */ package com.android.launcher3.pageindicators; +import java.util.function.Consumer; + /** * Base class for a page indicator. */ @@ -27,6 +29,14 @@ public interface PageIndicator { void setMarkersCount(int numMarkers); /** + * This is only going to be used by the FolderPagedView's PageIndicator. A refactor is planned + * to separate the two purposes of this class, but in the meantime, this indicator will serve to + * let the folder snap to the page of its click, and also tell the PageIndicator not to draw + * arrows if the click listener is null (at least until after this is refactored). + */ + void setArrowClickListener(Consumer<Direction> listener); + + /** * Sets a flag indicating whether to pause scroll. * <p>Should be set to {@code true} while the screen is binding or new data is being applied, * and to {@code false} once done. This prevents animation conflicts due to scrolling during diff --git a/src/com/android/launcher3/pageindicators/PageIndicatorArrowClickListener.kt b/src/com/android/launcher3/pageindicators/PageIndicatorArrowClickListener.kt new file mode 100644 index 0000000000..970d210ad9 --- /dev/null +++ b/src/com/android/launcher3/pageindicators/PageIndicatorArrowClickListener.kt @@ -0,0 +1,26 @@ +/* + * 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.pageindicators + +interface PageIndicatorArrowClickListener { + fun onArrowClick(direction: Direction) +} + +enum class Direction { + END, + START, +} diff --git a/src/com/android/launcher3/pageindicators/PageIndicatorDots.java b/src/com/android/launcher3/pageindicators/PageIndicatorDots.java index a9a26a1631..384f87623a 100644 --- a/src/com/android/launcher3/pageindicators/PageIndicatorDots.java +++ b/src/com/android/launcher3/pageindicators/PageIndicatorDots.java @@ -32,11 +32,13 @@ import android.graphics.Paint; import android.graphics.Paint.Style; import android.graphics.Rect; import android.graphics.RectF; +import android.graphics.drawable.VectorDrawable; import android.os.Handler; import android.os.Looper; import android.util.AttributeSet; import android.util.FloatProperty; import android.util.IntProperty; +import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; import android.view.ViewOutlineProvider; @@ -51,6 +53,8 @@ import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.util.Themes; +import java.util.function.Consumer; + /** * {@link PageIndicator} which shows dots per page. The active page is shown with the current * accent color. @@ -71,6 +75,12 @@ public class PageIndicatorDots extends View implements Insettable, PageIndicator private static final int ENTER_ANIMATION_STAGGERED_DELAY = 150; private static final int ENTER_ANIMATION_DURATION = 400; + private static final int LARGE_HEIGHT_MULTIPLIER = 12; + private static final int SMALL_HEIGHT_MULTIPLIER = 4; + private static final int LARGE_WIDTH_MULTIPLIER = 5; + private static final int SMALL_WIDTH_MULTIPLIER = 3; + private static final float ARROW_TOUCH_BOX_FACTOR = 5f; + private static final int PAGE_INDICATOR_ALPHA = 255; private static final int DOT_ALPHA = 128; private static final float DOT_ALPHA_FRACTION = 0.5f; @@ -78,12 +88,14 @@ public class PageIndicatorDots extends View implements Insettable, PageIndicator private static final int VISIBLE_ALPHA = 255; private static final int INVISIBLE_ALPHA = 0; private Paint mPaginationPaint; + private Consumer<Direction> mOnArrowClickListener; // This value approximately overshoots to 1.5 times the original size. private static final float ENTER_ANIMATION_OVERSHOOT_TENSION = 4.9f; // This is used to optimize the onDraw method by not constructing a new RectF each draw. private static final RectF sTempRect = new RectF(); + private static final RectF sLastActiveRect = new RectF(); private static final FloatProperty<PageIndicatorDots> CURRENT_POSITION = new FloatProperty<PageIndicatorDots>("current_position") { @@ -102,23 +114,27 @@ public class PageIndicatorDots extends View implements Insettable, PageIndicator private static final IntProperty<PageIndicatorDots> PAGINATION_ALPHA = new IntProperty<PageIndicatorDots>("pagination_alpha") { - @Override - public Integer get(PageIndicatorDots obj) { - return obj.mPaginationPaint.getAlpha(); - } + @Override + public Integer get(PageIndicatorDots obj) { + return obj.mPaginationPaint.getAlpha(); + } - @Override - public void setValue(PageIndicatorDots obj, int alpha) { - obj.mPaginationPaint.setAlpha(alpha); - obj.invalidate(); - } - }; + @Override + public void setValue(PageIndicatorDots obj, int alpha) { + obj.mPaginationPaint.setAlpha(alpha); + obj.invalidate(); + } + }; private final Handler mDelayedPaginationFadeHandler = new Handler(Looper.getMainLooper()); private final float mDotRadius; private final float mGapWidth; private final float mCircleGap; private final boolean mIsRtl; + private final VectorDrawable mArrowRight; + private final VectorDrawable mArrowLeft; + private final Rect mArrowRightBounds = new Rect(); + private final Rect mArrowLeftBounds = new Rect(); private int mNumPages; private int mActivePage; @@ -170,6 +186,8 @@ public class PageIndicatorDots extends View implements Insettable, PageIndicator : DOT_GAP_FACTOR * mDotRadius; setOutlineProvider(new MyOutlineProver()); mIsRtl = Utilities.isRtl(getResources()); + mArrowRight = (VectorDrawable) getResources().getDrawable(R.drawable.ic_chevron_end); + mArrowLeft = (VectorDrawable) getResources().getDrawable(R.drawable.ic_chevron_start); } @Override @@ -408,6 +426,11 @@ public class PageIndicatorDots extends View implements Insettable, PageIndicator } @Override + public void setArrowClickListener(Consumer<Direction> listener) { + mOnArrowClickListener = listener; + } + + @Override public void setPauseScroll(boolean pause, boolean isTwoPanels) { mIsTwoPanels = isTwoPanels; @@ -422,11 +445,16 @@ public class PageIndicatorDots extends View implements Insettable, PageIndicator @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // TODO(b/394355070): Verify Folder Entry Animation works correctly with visual updates - // Add extra spacing of mDotRadius on all sides so than entry animation could be run. + // Add extra spacing of mDotRadius on all sides so than entry animation could be run + // and so the hitboxes of arrows can be clicked easier. int width = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY ? - MeasureSpec.getSize(widthMeasureSpec) : (int) ((mNumPages * 3 + 2) * mDotRadius); + MeasureSpec.getSize(widthMeasureSpec) + : (int) ((mNumPages * ((enableLauncherVisualRefresh()) + ? LARGE_WIDTH_MULTIPLIER : SMALL_WIDTH_MULTIPLIER) + 2) * mDotRadius); int height = MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY - ? MeasureSpec.getSize(heightMeasureSpec) : (int) (4 * mDotRadius); + ? MeasureSpec.getSize(heightMeasureSpec) + : (int) (((enableLauncherVisualRefresh()) + ? LARGE_HEIGHT_MULTIPLIER : SMALL_HEIGHT_MULTIPLIER) * mDotRadius); setMeasuredDimension(width, height); } @@ -446,18 +474,51 @@ public class PageIndicatorDots extends View implements Insettable, PageIndicator float y = getHeight() / 2; if (mEntryAnimationRadiusFactors != null) { - // During entry animation, only draw the circles - // TODO(b/394355070): Verify Folder Entry Animation works correctly - visual updates + if (enableLauncherVisualRefresh()) { + x -= mDotRadius; + if (mIsRtl) { + x = getWidth() - x; + circleGap = -circleGap; + } + sTempRect.top = y - mDotRadius; + sTempRect.bottom = y + mDotRadius; - if (mIsRtl) { - x = getWidth() - x; - circleGap = -circleGap; - } - for (int i = 0; i < mEntryAnimationRadiusFactors.length; i++) { - mPaginationPaint.setAlpha(i == mActivePage ? PAGE_INDICATOR_ALPHA : DOT_ALPHA); - canvas.drawCircle(x, y, mDotRadius * mEntryAnimationRadiusFactors[i], - mPaginationPaint); - x += circleGap; + for (int i = 0; i < mEntryAnimationRadiusFactors.length; i++) { + if (i == mActivePage) { + if (mIsRtl) { + sTempRect.left = x - (mDotRadius * 3); + sTempRect.right = x + mDotRadius; + x += circleGap - (mDotRadius * 2); + } else { + sTempRect.left = x - mDotRadius; + sTempRect.right = x + (mDotRadius * 3); + x += circleGap + (mDotRadius * 2); + } + scale(sTempRect, mEntryAnimationRadiusFactors[i]); + float scaledRadius = mDotRadius * mEntryAnimationRadiusFactors[i]; + mPaginationPaint.setAlpha(PAGE_INDICATOR_ALPHA); + canvas.drawRoundRect(sTempRect, scaledRadius, scaledRadius, + mPaginationPaint); + } else { + mPaginationPaint.setAlpha(DOT_ALPHA); + canvas.drawCircle(x, y, mDotRadius * mEntryAnimationRadiusFactors[i], + mPaginationPaint); + x += circleGap; + } + } + } else { + // During entry animation, only draw the circles + + if (mIsRtl) { + x = getWidth() - x; + circleGap = -circleGap; + } + for (int i = 0; i < mEntryAnimationRadiusFactors.length; i++) { + mPaginationPaint.setAlpha(i == mActivePage ? PAGE_INDICATOR_ALPHA : DOT_ALPHA); + canvas.drawCircle(x, y, mDotRadius * mEntryAnimationRadiusFactors[i], + mPaginationPaint); + x += circleGap; + } } } else { // Save the current alpha value, so we can reset to it again after drawing the dots @@ -471,12 +532,31 @@ public class PageIndicatorDots extends View implements Insettable, PageIndicator sTempRect.bottom = y + mDotRadius; sTempRect.left = x - diameter; - float posDif = Math.abs(mLastPosition - mCurrentPosition); + float currentPosition = mCurrentPosition; + float lastPosition = mLastPosition; + + if (mIsRtl) { + currentPosition = mNumPages - currentPosition - 1; + lastPosition = mNumPages - lastPosition - 1; + } + float posDif = Math.abs(lastPosition - currentPosition); float boundedPosition = (posDif > 1) - ? Math.round(mCurrentPosition) - : mCurrentPosition; + ? Math.round(currentPosition) + : currentPosition; float bounceProgress = (posDif > 1) ? posDif - 1 : 0; - float bounceAdjustment = Math.abs(mCurrentPosition - boundedPosition) * diameter; + float bounceAdjustment = Math.abs(currentPosition - boundedPosition) * diameter; + + if (mOnArrowClickListener != null && boundedPosition >= 1) { + // Here we draw the Left Arrow + mArrowLeft.setAlpha(alpha); + int size = (int) (mGapWidth * 4); + mArrowLeftBounds.left = (int) (sTempRect.left - mGapWidth - size); + mArrowLeftBounds.top = (int) (y - size / 2); + mArrowLeftBounds.right = (int) (sTempRect.left - mGapWidth); + mArrowLeftBounds.bottom = (int) (y + size / 2); + mArrowLeft.setBounds(mArrowLeftBounds); + mArrowLeft.draw(canvas); + } // Here we draw the dots, one at a time from the left-most dot to the right-most dot // 1.0 => 000000 000000111111 000000 @@ -498,10 +578,10 @@ public class PageIndicatorDots extends View implements Insettable, PageIndicator // While the animation is shifting the active pagination dots size from // the previously active one, to the newly active dot, there is no bounce // adjustment. The bounce happens in the "Overshoot" phase of the animation. - // mLastPosition is used to determine when the currentPosition is just + // lastPosition is used to determine when the currentPosition is just // leaving the page, or if it is in the overshoot phase. if (boundedPosition == i && bounceProgress != 0) { - if (mLastPosition < mCurrentPosition) { + if (lastPosition < currentPosition) { sTempRect.left -= bounceAdjustment; } else { sTempRect.right += bounceAdjustment; @@ -510,19 +590,34 @@ public class PageIndicatorDots extends View implements Insettable, PageIndicator } else { sTempRect.right = sTempRect.left + diameter; - if (mLastPosition == i && bounceProgress != 0) { - if (mLastPosition > mCurrentPosition) { + if (lastPosition == i && bounceProgress != 0) { + if (lastPosition > currentPosition) { sTempRect.left += bounceAdjustment; } else { sTempRect.right -= bounceAdjustment; } } } + if (Math.round(mCurrentPosition) == i) { + sLastActiveRect.set(sTempRect); + } canvas.drawRoundRect(sTempRect, mDotRadius, mDotRadius, mPaginationPaint); // TODO(b/394355070) Verify RTL experience works correctly with visual updates sTempRect.left = sTempRect.right + mGapWidth; } + + if (mOnArrowClickListener != null && boundedPosition <= mNumPages - 2) { + // Here we draw the Right Arrow + mArrowRight.setAlpha(alpha); + int size = (int) (mGapWidth * 4); + mArrowRightBounds.left = (int) sTempRect.left; + mArrowRightBounds.top = (int) (y - size / 2); + mArrowRightBounds.right = (int) (int) (sTempRect.left + size); + mArrowRightBounds.bottom = (int) (y + size / 2); + mArrowRight.setBounds(mArrowRightBounds); + mArrowRight.draw(canvas); + } } else { // Here we draw the dots mPaginationPaint.setAlpha((int) (alpha * DOT_ALPHA_FRACTION)); @@ -541,6 +636,38 @@ public class PageIndicatorDots extends View implements Insettable, PageIndicator } } + @Override + public boolean onTouchEvent(MotionEvent ev) { + if (mOnArrowClickListener == null) { + // No - Op. Don't care about touch events + } else if ((mIsRtl && withinExpandedBounds(mArrowRightBounds, ev)) + || (!mIsRtl && withinExpandedBounds(mArrowLeftBounds, ev))) { + mOnArrowClickListener.accept(Direction.START); + } else if ((mIsRtl && withinExpandedBounds(mArrowLeftBounds, ev)) + || (!mIsRtl && withinExpandedBounds(mArrowRightBounds, ev))) { + mOnArrowClickListener.accept(Direction.END); + } + return super.onTouchEvent(ev); + } + + // For larger Touch box + private boolean withinExpandedBounds(Rect rect, MotionEvent ev) { + RectF scaledRect = new RectF(rect); + scale(scaledRect, ARROW_TOUCH_BOX_FACTOR); + return scaledRect.contains(ev.getX(), ev.getY()); + } + + private static void scale(RectF rect, float factor) { + float horizontalAdjustment = rect.width() * (factor - 1) / 2; + float verticalAdjustment = rect.height() * (factor - 1) / 2; + + rect.top -= verticalAdjustment; + rect.bottom += verticalAdjustment; + + rect.left -= horizontalAdjustment; + rect.right += horizontalAdjustment; + } + private RectF getActiveRect() { float startCircle = (int) mCurrentPosition; float delta = mCurrentPosition - startCircle; @@ -593,8 +720,8 @@ public class PageIndicatorDots extends View implements Insettable, PageIndicator @Override public void getOutline(View view, Outline outline) { if (mEntryAnimationRadiusFactors == null) { - // TODO(b/394355070): Verify Outline works correctly with visual updates - RectF activeRect = getActiveRect(); + RectF activeRect = enableLauncherVisualRefresh() + ? sLastActiveRect : getActiveRect(); outline.setRoundRect( (int) activeRect.left, (int) activeRect.top, diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java index aad1400865..39f68bfe70 100644 --- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java +++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java @@ -16,9 +16,12 @@ package com.android.launcher3.popup; +import static android.multiuser.Flags.enableMovingContentIntoPrivateSpace; + import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_SHORTCUTS; import static com.android.launcher3.Utilities.squaredHypot; 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.util.Executors.MODEL_EXECUTOR; @@ -65,6 +68,7 @@ import com.android.launcher3.views.ActivityContext; import com.android.launcher3.views.BaseDragLayer; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.Optional; @@ -207,7 +211,10 @@ public class PopupContainerWithArrow<T extends Context & ActivityContext> container = (PopupContainerWithArrow) launcher.getLayoutInflater().inflate( R.layout.popup_container, launcher.getDragLayer(), false); container.configureForLauncher(launcher, item); - container.populateAndShowRows(icon, deepShortcutCount, systemShortcuts); + boolean shouldHideSystemShortcuts = enableMovingContentIntoPrivateSpace() + && Objects.equals(item.getTargetPackage(), PRIVATE_SPACE_PACKAGE); + container.populateAndShowRows(icon, deepShortcutCount, + shouldHideSystemShortcuts ? Collections.emptyList() : systemShortcuts); launcher.refreshAndBindWidgetsForPackageUser(PackageUserKey.fromItemInfo(item)); container.requestFocus(); return container; diff --git a/src/com/android/launcher3/popup/PopupDataProvider.java b/src/com/android/launcher3/popup/PopupDataProvider.java index 5c1a7553a5..95110329d3 100644 --- a/src/com/android/launcher3/popup/PopupDataProvider.java +++ b/src/com/android/launcher3/popup/PopupDataProvider.java @@ -26,7 +26,6 @@ import androidx.annotation.Nullable; import com.android.launcher3.BubbleTextView; import com.android.launcher3.allapps.ActivityAllAppsContainerView; import com.android.launcher3.dot.DotInfo; -import com.android.launcher3.dot.FolderDotInfo; import com.android.launcher3.folder.Folder; import com.android.launcher3.folder.FolderIcon; import com.android.launcher3.model.data.FolderInfo; @@ -76,11 +75,7 @@ public class PopupDataProvider implements NotificationListener.NotificationsChan ((BubbleTextView) v).applyDotState(info, true /* animate */); } else if (v instanceof FolderIcon icon && info instanceof FolderInfo fi && fi.anyMatch(matcher)) { - FolderDotInfo folderDotInfo = new FolderDotInfo(); - for (ItemInfo si : fi.getContents()) { - folderDotInfo.addDotInfo(getDotInfoForItem(si)); - } - icon.setDotInfo(folderDotInfo); + icon.updateDotInfo(); } // process all the shortcuts diff --git a/src/com/android/launcher3/provider/RestoreDbTask.java b/src/com/android/launcher3/provider/RestoreDbTask.java index 23941bb1e6..f6ee26baa6 100644 --- a/src/com/android/launcher3/provider/RestoreDbTask.java +++ b/src/com/android/launcher3/provider/RestoreDbTask.java @@ -80,6 +80,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Map; +import java.util.function.Supplier; import java.util.stream.Collectors; /** @@ -206,7 +207,8 @@ public class RestoreDbTask { LauncherRestoreEventLogger restoreEventLogger = LauncherRestoreEventLogger.Companion.newInstance(context); task.sanitizeDB(context, controller, db, backupManager, restoreEventLogger); - task.restoreAppWidgetIdsIfExists(context, controller, restoreEventLogger); + task.restoreAppWidgetIdsIfExists(context, controller, restoreEventLogger, + () -> new AppWidgetHost(context, APPWIDGET_HOST_ID)); t.commit(); return true; } catch (Exception e) { @@ -438,14 +440,13 @@ public class RestoreDbTask { @WorkerThread @VisibleForTesting void restoreAppWidgetIdsIfExists(Context context, ModelDbController controller, - LauncherRestoreEventLogger restoreEventLogger) { + LauncherRestoreEventLogger restoreEventLogger, Supplier<AppWidgetHost> hostSupplier) { LauncherPrefs lp = LauncherPrefs.get(context); if (lp.has(APP_WIDGET_IDS, OLD_APP_WIDGET_IDS)) { - AppWidgetHost host = new AppWidgetHost(context, APPWIDGET_HOST_ID); restoreAppWidgetIds(context, controller, restoreEventLogger, IntArray.fromConcatString(lp.get(OLD_APP_WIDGET_IDS)).toArray(), IntArray.fromConcatString(lp.get(APP_WIDGET_IDS)).toArray(), - host); + hostSupplier.get()); } else { FileLog.d(TAG, "Did not receive new app widget id map during Launcher restore"); } diff --git a/src/com/android/launcher3/touch/AllAppsSwipeController.java b/src/com/android/launcher3/touch/AllAppsSwipeController.java index 2cc4909712..f86fd6ac33 100644 --- a/src/com/android/launcher3/touch/AllAppsSwipeController.java +++ b/src/com/android/launcher3/touch/AllAppsSwipeController.java @@ -22,6 +22,7 @@ import static com.android.app.animation.Interpolators.EMPHASIZED_DECELERATE; import static com.android.app.animation.Interpolators.FINAL_FRAME; import static com.android.app.animation.Interpolators.INSTANT; import static com.android.app.animation.Interpolators.LINEAR; +import static com.android.app.animation.Interpolators.clampToProgress; import static com.android.launcher3.LauncherState.ALL_APPS; import static com.android.launcher3.LauncherState.NORMAL; import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_FADE; @@ -39,6 +40,7 @@ import android.view.animation.Interpolator; import com.android.app.animation.Interpolators; import com.android.launcher3.AbstractFloatingView; +import com.android.launcher3.Flags; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherState; import com.android.launcher3.states.StateAnimationConfig; @@ -53,10 +55,10 @@ public class AllAppsSwipeController extends AbstractStateChangeTouchController { private static final float ALL_APPS_SCRIM_VISIBLE_THRESHOLD = 0.1f; private static final float ALL_APPS_STAGGERED_FADE_THRESHOLD = 0.5f; - public static final Interpolator ALL_APPS_SCRIM_RESPONDER = + private static final Interpolator ALL_APPS_SCRIM_RESPONDER = Interpolators.clampToProgress( LINEAR, ALL_APPS_SCRIM_VISIBLE_THRESHOLD, ALL_APPS_STAGGERED_FADE_THRESHOLD); - public static final Interpolator ALL_APPS_CLAMPING_RESPONDER = + private static final Interpolator ALL_APPS_CLAMPING_RESPONDER = Interpolators.clampToProgress( LINEAR, 1 - ALL_APPS_CONTENT_FADE_MAX_CLAMPING_THRESHOLD, @@ -207,7 +209,16 @@ public class AllAppsSwipeController extends AbstractStateChangeTouchController { } config.setInterpolator(ANIM_WORKSPACE_SCALE, DECELERATED_EASE); config.setInterpolator(ANIM_DEPTH, DECELERATED_EASE); - if (launcher.getDeviceProfile().isPhone) { + if (Flags.allAppsBlur()) { + if (!config.isUserControlled()) { + config.setInterpolator(ANIM_DEPTH, EMPHASIZED_DECELERATE); + } + config.setInterpolator(ANIM_WORKSPACE_FADE, + clampToProgress(LINEAR, 1 - ALL_APPS_SCRIM_VISIBLE_THRESHOLD, 1)); + config.setInterpolator(ANIM_HOTSEAT_FADE, + clampToProgress(LINEAR, 1 - ALL_APPS_SCRIM_VISIBLE_THRESHOLD, 1)); + } else if (launcher.getDeviceProfile().isPhone) { + // On phones without blur, reveal the workspace and hotseat when leaving All Apps. config.setInterpolator(ANIM_WORKSPACE_FADE, INSTANT); config.setInterpolator(ANIM_HOTSEAT_FADE, INSTANT); config.animFlags |= StateAnimationConfig.SKIP_DEPTH_CONTROLLER; @@ -253,7 +264,14 @@ public class AllAppsSwipeController extends AbstractStateChangeTouchController { } config.setInterpolator(ANIM_WORKSPACE_SCALE, DECELERATED_EASE); config.setInterpolator(ANIM_DEPTH, DECELERATED_EASE); - if (launcher.getDeviceProfile().isPhone) { + if (Flags.allAppsBlur()) { + config.setInterpolator(ANIM_DEPTH, LINEAR); + config.setInterpolator(ANIM_WORKSPACE_FADE, + clampToProgress(LINEAR, 0, ALL_APPS_SCRIM_VISIBLE_THRESHOLD)); + config.setInterpolator(ANIM_HOTSEAT_FADE, + clampToProgress(LINEAR, 0, ALL_APPS_SCRIM_VISIBLE_THRESHOLD)); + } else if (launcher.getDeviceProfile().isPhone) { + // On phones without blur, hide the workspace and hotseat when entering All Apps. config.setInterpolator(ANIM_WORKSPACE_FADE, FINAL_FRAME); config.setInterpolator(ANIM_HOTSEAT_FADE, FINAL_FRAME); config.animFlags |= StateAnimationConfig.SKIP_DEPTH_CONTROLLER; diff --git a/src/com/android/launcher3/touch/WorkspaceTouchListener.java b/src/com/android/launcher3/touch/WorkspaceTouchListener.java index d72e6f9e39..576f17616b 100644 --- a/src/com/android/launcher3/touch/WorkspaceTouchListener.java +++ b/src/com/android/launcher3/touch/WorkspaceTouchListener.java @@ -131,7 +131,7 @@ public class WorkspaceTouchListener extends GestureDetector.SimpleOnGestureListe } boolean isInAllAppsBottomSheet = mLauncher.isInState(ALL_APPS) - && mLauncher.getDeviceProfile().isTablet; + && mLauncher.getDeviceProfile().shouldShowAllAppsOnSheet(); final boolean result; if (mLongPressState == STATE_COMPLETED) { diff --git a/src/com/android/launcher3/util/ContentWriter.java b/src/com/android/launcher3/util/ContentWriter.java index 9910dc2e70..c2c1fee561 100644 --- a/src/com/android/launcher3/util/ContentWriter.java +++ b/src/com/android/launcher3/util/ContentWriter.java @@ -23,7 +23,6 @@ import android.os.UserHandle; import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherSettings; -import com.android.launcher3.LauncherSettings.Favorites; import com.android.launcher3.icons.BitmapInfo; import com.android.launcher3.icons.GraphicsUtils; import com.android.launcher3.model.ModelDbController; @@ -107,7 +106,7 @@ public class ContentWriter { public int commit() { if (mCommitParams != null) { return mCommitParams.mDbController.update( - Favorites.TABLE_NAME, getValues(mContext), + getValues(mContext), mCommitParams.mWhere, mCommitParams.mSelectionArgs); } return 0; diff --git a/src/com/android/launcher3/util/SimpleBroadcastReceiver.java b/src/com/android/launcher3/util/SimpleBroadcastReceiver.java index 7a40abe62b..53e0bce182 100644 --- a/src/com/android/launcher3/util/SimpleBroadcastReceiver.java +++ b/src/com/android/launcher3/util/SimpleBroadcastReceiver.java @@ -123,6 +123,32 @@ public class SimpleBroadcastReceiver extends BroadcastReceiver { } } + /** + * Same as {@link #register(Runnable, int, String...)} above but with additional permission + * params utilizine the original {@link Context}. + */ + @AnyThread + public void register(@Nullable Runnable completionCallback, + String broadcastPermission, int flags, String... actions) { + if (Looper.myLooper() == mHandler.getLooper()) { + registerInternal(mContext, completionCallback, broadcastPermission, flags, actions); + } else { + mHandler.post(() -> registerInternal(mContext, completionCallback, broadcastPermission, + flags, actions)); + } + } + + /** Register broadcast receiver with permission and run completion callback if passed. */ + @AnyThread + private void registerInternal( + @NonNull Context context, @Nullable Runnable completionCallback, + String broadcastPermission, int flags, String... actions) { + context.registerReceiver(this, getFilter(actions), broadcastPermission, null, flags); + if (completionCallback != null) { + completionCallback.run(); + } + } + /** Same as {@link #register(Runnable, String...)} above but with pkg name. */ @AnyThread public void registerPkgActions(@Nullable String pkg, String... actions) { diff --git a/src/com/android/launcher3/views/ScrimView.java b/src/com/android/launcher3/views/ScrimView.java index ce58de1ee5..ec71f3118a 100644 --- a/src/com/android/launcher3/views/ScrimView.java +++ b/src/com/android/launcher3/views/ScrimView.java @@ -54,8 +54,7 @@ public class ScrimView extends View implements Insettable { } @Override - public void setInsets(Rect insets) { - } + public void setInsets(Rect insets) {} @Override public boolean hasOverlappingRendering() { diff --git a/src/com/android/launcher3/workprofile/PersonalWorkSlidingTabStrip.java b/src/com/android/launcher3/workprofile/PersonalWorkSlidingTabStrip.java index e94f3a065d..185c3ee9ed 100644 --- a/src/com/android/launcher3/workprofile/PersonalWorkSlidingTabStrip.java +++ b/src/com/android/launcher3/workprofile/PersonalWorkSlidingTabStrip.java @@ -26,9 +26,12 @@ import androidx.annotation.Nullable; import com.android.launcher3.DeviceProfile; import com.android.launcher3.R; +import com.android.launcher3.pageindicators.Direction; import com.android.launcher3.pageindicators.PageIndicator; import com.android.launcher3.views.ActivityContext; +import java.util.function.Consumer; + /** * Supports two indicator colors, dedicated for personal and work tabs. */ @@ -78,6 +81,11 @@ public class PersonalWorkSlidingTabStrip extends LinearLayout implements PageInd } @Override + public void setArrowClickListener(Consumer<Direction> listener) { + // No-Op. All Apps doesn't need accessibility arrows for single click navigation. + } + + @Override public boolean hasOverlappingRendering() { return false; } |