diff options
Diffstat (limited to 'src')
54 files changed, 977 insertions, 514 deletions
diff --git a/src/com/android/launcher3/AppWidgetResizeFrame.java b/src/com/android/launcher3/AppWidgetResizeFrame.java index b51e850c04..213d88f852 100644 --- a/src/com/android/launcher3/AppWidgetResizeFrame.java +++ b/src/com/android/launcher3/AppWidgetResizeFrame.java @@ -216,6 +216,13 @@ public class AppWidgetResizeFrame extends AbstractFloatingView implements View.O AppWidgetResizeFrame frame = (AppWidgetResizeFrame) launcher.getLayoutInflater() .inflate(R.layout.app_widget_resize_frame, dl, false); frame.setupForWidget(widget, cellLayout, dl); + // Save widget item info as tag on resize frame; so that, the accessibility delegate can + // attach actions that typically happen on widget (e.g. resize, move) also on the resize + // frame. + frame.setTag(widget.getTag()); + frame.setAccessibilityDelegate(launcher.getAccessibilityDelegate()); + frame.setContentDescription(launcher.asContext().getString(R.string.widget_frame_name, + widget.getContentDescription())); ((DragLayer.LayoutParams) frame.getLayoutParams()).customPosition = true; dl.addView(frame); @@ -235,6 +242,13 @@ public class AppWidgetResizeFrame extends AbstractFloatingView implements View.O } } + /** + * Retrieves the view where accessibility actions happen. + */ + public View getViewForAccessibility() { + return mWidgetView; + } + private void setupForWidget(LauncherAppWidgetHostView widgetView, CellLayout cellLayout, DragLayer dragLayer) { mCellLayout = cellLayout; diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java index 783e82cae9..30e3a2b577 100644 --- a/src/com/android/launcher3/BubbleTextView.java +++ b/src/com/android/launcher3/BubbleTextView.java @@ -26,6 +26,7 @@ import static com.android.launcher3.BubbleTextView.RunningAppState.NOT_RUNNING; import static com.android.launcher3.BubbleTextView.RunningAppState.MINIMIZED; import static com.android.launcher3.Flags.enableContrastTiles; import static com.android.launcher3.Flags.enableCursorHoverStates; +import static com.android.launcher3.allapps.AlphabeticalAppsList.PRIVATE_SPACE_PACKAGE; import static com.android.launcher3.graphics.PreloadIconDrawable.newPendingIcon; import static com.android.launcher3.icons.BitmapInfo.FLAG_NO_BADGE; import static com.android.launcher3.icons.BitmapInfo.FLAG_SKIP_USER_BADGE; @@ -104,6 +105,7 @@ import com.android.launcher3.views.FloatingIconViewCompanion; import java.text.NumberFormat; import java.util.HashMap; import java.util.Locale; +import java.util.Objects; /** * TextView that draws a bubble behind the text. We cannot use a LineBackgroundSpan @@ -589,7 +591,9 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, } private void setNonPendingIcon(ItemInfoWithIcon info) { - int flags = shouldUseTheme() ? FLAG_THEMED : info.bitmap.creationFlags; + // Set nonPendingIcon acts as a restart which should refresh the flag state when applicable. + int flags = Objects.equals(info.getTargetPackage(), PRIVATE_SPACE_PACKAGE) + ? info.bitmap.creationFlags : shouldUseTheme() ? FLAG_THEMED : 0; // Remove badge on icons smaller than 48dp. if (mHideBadge || mDisplay == DISPLAY_SEARCH_RESULT_SMALL) { flags |= FLAG_NO_BADGE; diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java index c85ca49c43..9c4d5ba8b5 100644 --- a/src/com/android/launcher3/DeviceProfile.java +++ b/src/com/android/launcher3/DeviceProfile.java @@ -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()) { diff --git a/src/com/android/launcher3/LauncherModel.kt b/src/com/android/launcher3/LauncherModel.kt index add0ad843c..02d70ae386 100644 --- a/src/com/android/launcher3/LauncherModel.kt +++ b/src/com/android/launcher3/LauncherModel.kt @@ -28,11 +28,12 @@ import com.android.launcher3.dagger.LauncherAppSingleton import com.android.launcher3.icons.IconCache import com.android.launcher3.model.AddWorkspaceItemsTask import com.android.launcher3.model.AllAppsList -import com.android.launcher3.model.BaseLauncherBinder +import com.android.launcher3.model.BaseLauncherBinder.BaseLauncherBinderFactory import com.android.launcher3.model.BgDataModel import com.android.launcher3.model.CacheDataUpdatedTask import com.android.launcher3.model.ItemInstallQueue import com.android.launcher3.model.LoaderTask +import com.android.launcher3.model.LoaderTask.LoaderTaskFactory import com.android.launcher3.model.ModelDbController import com.android.launcher3.model.ModelDelegate import com.android.launcher3.model.ModelInitializer @@ -43,6 +44,8 @@ import com.android.launcher3.model.PackageUpdatedTask import com.android.launcher3.model.ReloadStringCacheTask import com.android.launcher3.model.ShortcutsChangedTask import com.android.launcher3.model.UserLockStateChangedTask +import com.android.launcher3.model.UserManagerState +import com.android.launcher3.model.WorkspaceItemSpaceFinder import com.android.launcher3.model.data.ItemInfo import com.android.launcher3.model.data.WorkspaceItemInfo import com.android.launcher3.pm.UserCache @@ -70,28 +73,23 @@ class LauncherModel @Inject constructor( @ApplicationContext private val context: Context, - private val appProvider: Provider<LauncherAppState>, + private val taskControllerProvider: Provider<ModelTaskController>, private val iconCache: IconCache, private val prefs: LauncherPrefs, private val installQueue: ItemInstallQueue, - appFilter: AppFilter, @Named("ICONS_DB") dbFileName: String?, initializer: ModelInitializer, lifecycle: DaggerSingletonTracker, val modelDelegate: ModelDelegate, + private val mBgAllAppsList: AllAppsList, + private val mBgDataModel: BgDataModel, + private val loaderFactory: LoaderTaskFactory, + private val binderFactory: BaseLauncherBinderFactory, + private val spaceFinderFactory: Provider<WorkspaceItemSpaceFinder>, ) { private val mCallbacksList = ArrayList<BgDataModel.Callbacks>(1) - // < only access in worker thread > - private val mBgAllAppsList = AllAppsList(iconCache, appFilter) - - /** - * All the static data should be accessed on the background thread, A lock should be acquired on - * this object when accessing any data from this model. - */ - private val mBgDataModel = BgDataModel() - val modelDbController = ModelDbController(context) private val mLock = Any() @@ -139,7 +137,7 @@ constructor( /** Adds the provided items to the workspace. */ fun addAndBindAddedWorkspaceItems(itemList: List<Pair<ItemInfo?, Any?>?>) { callbacks.forEach { it.preAddApps() } - enqueueModelUpdateTask(AddWorkspaceItemsTask(itemList)) + enqueueModelUpdateTask(AddWorkspaceItemsTask(itemList, spaceFinderFactory.get())) } fun getWriter( @@ -299,13 +297,7 @@ constructor( // Clear any pending bind-runnables from the synchronized load process. callbacksList.forEach { MAIN_EXECUTOR.execute(it::clearPendingBinds) } - val launcherBinder = - BaseLauncherBinder( - appProvider.get(), - mBgDataModel, - mBgAllAppsList, - callbacksList, - ) + val launcherBinder = binderFactory.createBinder(callbacksList) if (bindDirectly) { // Divide the set of loaded items into those that we are binding synchronously, // and everything else that is to be bound normally (asynchronously). @@ -317,14 +309,7 @@ constructor( launcherBinder.bindWidgets() return true } else { - val task = - LoaderTask( - appProvider.get(), - mBgAllAppsList, - mBgDataModel, - this.modelDelegate, - launcherBinder, - ) + val task = loaderFactory.newLoaderTask(launcherBinder, UserManagerState()) mLoaderTask = task // Always post the loader task, instead of running directly @@ -425,7 +410,7 @@ constructor( /** Called when the labels for the widgets has updated in the icon cache. */ fun onWidgetLabelsUpdated(updatedPackages: HashSet<String?>, user: UserHandle) { enqueueModelUpdateTask { taskController, dataModel, _ -> - dataModel.widgetsModel.onPackageIconsUpdated(updatedPackages, user, appProvider.get()) + dataModel.widgetsModel.onPackageIconsUpdated(updatedPackages, user) taskController.bindUpdatedWidgets(dataModel) } } @@ -439,17 +424,7 @@ constructor( // Loader has not yet run. return@execute } - task.execute( - ModelTaskController( - appProvider.get(), - mBgDataModel, - mBgAllAppsList, - this, - MAIN_EXECUTOR, - ), - mBgDataModel, - mBgAllAppsList, - ) + task.execute(taskControllerProvider.get(), mBgDataModel, mBgAllAppsList) } } @@ -476,7 +451,7 @@ constructor( fun refreshAndBindWidgetsAndShortcuts(packageUser: PackageUserKey?) { enqueueModelUpdateTask { taskController, dataModel, _ -> - dataModel.widgetsModel.update(taskController.app, packageUser) + dataModel.widgetsModel.update(packageUser) taskController.bindUpdatedWidgets(dataModel) } } diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java index 9a9bc1d97e..d6ae3a6335 100644 --- a/src/com/android/launcher3/Utilities.java +++ b/src/com/android/launcher3/Utilities.java @@ -100,6 +100,7 @@ import java.lang.reflect.Method; import java.util.Collections; import java.util.List; import java.util.Locale; +import java.util.Objects; import java.util.function.Predicate; /** @@ -276,8 +277,12 @@ public final class Utilities { */ public static void mapCoordInSelfToDescendant(View descendant, View root, float[] coord) { sMatrix.reset(); + //TODO(b/307488755) when implemented this check should be removed + if (!Objects.equals(descendant.getWindowId(), root.getWindowId())) { + return; + } View v = descendant; - while(v != root) { + while (v != root) { sMatrix.postTranslate(-v.getScrollX(), -v.getScrollY()); sMatrix.postConcat(v.getMatrix()); sMatrix.postTranslate(v.getLeft(), v.getTop()); diff --git a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java index 78b53a96bd..cd91f8e00b 100644 --- a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java +++ b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java @@ -25,6 +25,7 @@ import android.view.accessibility.AccessibilityEvent; import androidx.annotation.Nullable; import com.android.launcher3.AbstractFloatingView; +import com.android.launcher3.AppWidgetResizeFrame; import com.android.launcher3.BubbleTextView; import com.android.launcher3.ButtonDropTarget; import com.android.launcher3.CellLayout; @@ -82,6 +83,7 @@ public class LauncherAccessibilityDelegate extends BaseAccessibilityDelegate<Lau protected static final int MOVE_TO_WORKSPACE = R.id.action_move_to_workspace; protected static final int RESIZE = R.id.action_resize; public static final int DEEP_SHORTCUTS = R.id.action_deep_shortcuts; + public static final int CLOSE = R.id.action_close; public LauncherAccessibilityDelegate(Launcher launcher) { super(launcher); @@ -104,6 +106,8 @@ public class LauncherAccessibilityDelegate extends BaseAccessibilityDelegate<Lau RESIZE, R.string.action_resize, KeyEvent.KEYCODE_R)); mActions.put(DEEP_SHORTCUTS, new LauncherAction(DEEP_SHORTCUTS, R.string.action_deep_shortcut, KeyEvent.KEYCODE_S)); + mActions.put(CLOSE, new LauncherAction(CLOSE, + R.string.action_close, KeyEvent.KEYCODE_X)); } private static boolean isNotInShortcutMenu(@Nullable View view) { @@ -137,6 +141,10 @@ public class LauncherAccessibilityDelegate extends BaseAccessibilityDelegate<Lau } } + if (host instanceof AppWidgetResizeFrame) { + out.add(mActions.get(CLOSE)); + } + if (supportAddToWorkSpace(item)) { out.add(mActions.get(ADD_TO_WORKSPACE)); } @@ -183,22 +191,28 @@ public class LauncherAccessibilityDelegate extends BaseAccessibilityDelegate<Lau } return dragCondition != null; } else if (action == MOVE) { - return beginAccessibleDrag(host, item, fromKeyboard); + final View itemView = (host instanceof AppWidgetResizeFrame) + ? ((AppWidgetResizeFrame) host).getViewForAccessibility() + : host; + return beginAccessibleDrag(itemView, item, fromKeyboard); } else if (action == ADD_TO_WORKSPACE) { return addToWorkspace(item, true /*accessibility*/, null /*finishCallback*/); } else if (action == MOVE_TO_WORKSPACE) { return moveToWorkspace(item); } else if (action == RESIZE) { + final View itemView = (host instanceof AppWidgetResizeFrame) + ? ((AppWidgetResizeFrame) host).getViewForAccessibility() + : host; final LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) item; - List<OptionItem> actions = getSupportedResizeActions(host, info); + List<OptionItem> actions = getSupportedResizeActions(itemView, info); Rect pos = new Rect(); - mContext.getDragLayer().getDescendantRectRelativeToSelf(host, pos); + mContext.getDragLayer().getDescendantRectRelativeToSelf(itemView, pos); ArrowPopup popup = OptionsPopupView.show(mContext, new RectF(pos), actions, false); popup.requestFocus(); popup.addOnCloseCallback(() -> { - host.requestFocus(); - host.sendAccessibilityEvent(TYPE_VIEW_FOCUSED); - host.performAccessibilityAction(ACTION_ACCESSIBILITY_FOCUS, null); + itemView.requestFocus(); + itemView.sendAccessibilityEvent(TYPE_VIEW_FOCUSED); + itemView.performAccessibilityAction(ACTION_ACCESSIBILITY_FOCUS, null); AbstractFloatingView.closeOpenViews(mContext, /* animate= */ false, AbstractFloatingView.TYPE_WIDGET_RESIZE_FRAME); }); @@ -208,6 +222,11 @@ public class LauncherAccessibilityDelegate extends BaseAccessibilityDelegate<Lau : (host instanceof BubbleTextHolder ? ((BubbleTextHolder) host).getBubbleText() : null); return btv != null && PopupContainerWithArrow.showForIcon(btv) != null; + } else if (action == CLOSE) { + if (host instanceof AppWidgetResizeFrame) { + AbstractFloatingView.closeOpenViews(mContext, /* animate= */ false, + AbstractFloatingView.TYPE_WIDGET_RESIZE_FRAME); + } } else { for (ButtonDropTarget dropTarget : mContext.getDropTargetBar().getDropTargets()) { if (dropTarget.supportsAccessibilityDrop(item, host) @@ -222,6 +241,10 @@ public class LauncherAccessibilityDelegate extends BaseAccessibilityDelegate<Lau private List<OptionItem> getSupportedResizeActions(View host, LauncherAppWidgetInfo info) { List<OptionItem> actions = new ArrayList<>(); + if (host instanceof AppWidgetResizeFrame) { + return getSupportedResizeActions( + ((AppWidgetResizeFrame) host).getViewForAccessibility(), info); + } AppWidgetProviderInfo providerInfo = ((LauncherAppWidgetHostView) host).getAppWidgetInfo(); if (providerInfo == null) { return actions; 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 5f632fe25d..870c891f47 100644 --- a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java +++ b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java @@ -64,7 +64,7 @@ public class AlphabeticalAppsList<T extends Context & ActivityContext> implement AllAppsStore.OnUpdateListener { public static final String TAG = "AlphabeticalAppsList"; - private static final String PRIVATE_SPACE_PACKAGE = "com.android.privatespace"; + public static final String PRIVATE_SPACE_PACKAGE = "com.android.privatespace"; private final WorkProfileManager mWorkProviderManager; diff --git a/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java b/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java index 06643d3cbf..c49909772e 100644 --- a/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java +++ b/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java @@ -28,6 +28,7 @@ import com.android.launcher3.graphics.GridCustomizationsProxy; import com.android.launcher3.graphics.ThemeManager; import com.android.launcher3.icons.LauncherIcons.IconPool; import com.android.launcher3.model.ItemInstallQueue; +import com.android.launcher3.model.LoaderCursor.LoaderCursorFactory; import com.android.launcher3.model.WidgetsFilterDataProvider; import com.android.launcher3.pm.InstallSessionHelper; import com.android.launcher3.pm.UserCache; @@ -87,6 +88,8 @@ public interface LauncherBaseAppComponent { GridCustomizationsProxy getGridCustomizationsProxy(); WidgetsFilterDataProvider getWidgetsFilterDataProvider(); + LoaderCursorFactory getLoaderCursorFactory(); + /** Builder for LauncherBaseAppComponent. */ interface Builder { @BindsInstance Builder appContext(@ApplicationContext Context context); diff --git a/src/com/android/launcher3/debug/TestEventEmitter.java b/src/com/android/launcher3/debug/TestEventEmitter.java index ed3b4bbac9..db69bc2130 100644 --- a/src/com/android/launcher3/debug/TestEventEmitter.java +++ b/src/com/android/launcher3/debug/TestEventEmitter.java @@ -34,7 +34,9 @@ public class TestEventEmitter { RESIZE_FRAME_SHOWING("RESIZE_FRAME_SHOWING"), WORKSPACE_FINISH_LOADING("WORKSPACE_FINISH_LOADING"), SPRING_LOADED_STATE_STARTED("SPRING_LOADED_STATE_STARTED"), - SPRING_LOADED_STATE_COMPLETED("SPRING_LOADED_STATE_COMPLETED"); + SPRING_LOADED_STATE_COMPLETED("SPRING_LOADED_STATE_COMPLETED"), + + LAUNCHER_STATE_COMPLETED("LAUNCHER_STATE_COMPLETED"); TestEvent(String event) { } diff --git a/src/com/android/launcher3/dragndrop/DragController.java b/src/com/android/launcher3/dragndrop/DragController.java index 613b4308eb..284faba9da 100644 --- a/src/com/android/launcher3/dragndrop/DragController.java +++ b/src/com/android/launcher3/dragndrop/DragController.java @@ -16,6 +16,7 @@ package com.android.launcher3.dragndrop; +import static com.android.launcher3.Flags.removeAppsRefreshOnRightClick; import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_NOT_PINNABLE; import android.graphics.Point; @@ -521,17 +522,21 @@ public abstract class DragController<T extends ActivityContext> mDragObject.dragComplete = true; if (mIsInPreDrag) { - if (dropTarget != null) { - dropTarget.onDragExit(mDragObject); + if (removeAppsRefreshOnRightClick()) { + mDragObject.cancelled = true; + } else { + if (dropTarget != null) { + dropTarget.onDragExit(mDragObject); + } + return; } - return; } // Drop onto the target. boolean accepted = false; if (dropTarget != null) { dropTarget.onDragExit(mDragObject); - if (dropTarget.acceptDrop(mDragObject)) { + if (!mIsInPreDrag && dropTarget.acceptDrop(mDragObject)) { if (flingAnimation != null) { flingAnimation.run(); } else { @@ -540,7 +545,7 @@ public abstract class DragController<T extends ActivityContext> accepted = true; } } - final View dropTargetAsView = dropTarget instanceof View ? (View) dropTarget : null; + final View dropTargetAsView = dropTarget.getDropView(); dispatchDropComplete(dropTargetAsView, accepted); } diff --git a/src/com/android/launcher3/dragndrop/LauncherDragController.java b/src/com/android/launcher3/dragndrop/LauncherDragController.java index 4aa3673586..dd433c02d3 100644 --- a/src/com/android/launcher3/dragndrop/LauncherDragController.java +++ b/src/com/android/launcher3/dragndrop/LauncherDragController.java @@ -15,7 +15,10 @@ */ package com.android.launcher3.dragndrop; +import static android.view.View.VISIBLE; + import static com.android.launcher3.AbstractFloatingView.TYPE_DISCOVERY_BOUNCE; +import static com.android.launcher3.Flags.removeAppsRefreshOnRightClick; import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY; import static com.android.launcher3.LauncherState.EDIT_MODE; import static com.android.launcher3.LauncherState.NORMAL; @@ -25,6 +28,7 @@ import android.content.res.Resources; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.view.HapticFeedbackConstants; +import android.view.MotionEvent; import android.view.View; import androidx.annotation.Nullable; @@ -33,10 +37,13 @@ import androidx.annotation.VisibleForTesting; import com.android.launcher3.AbstractFloatingView; import com.android.launcher3.DragSource; import com.android.launcher3.DropTarget; +import com.android.launcher3.DropTarget.DragObject; import com.android.launcher3.Launcher; import com.android.launcher3.R; import com.android.launcher3.accessibility.DragViewStateAnnouncer; +import com.android.launcher3.dragndrop.DragOptions.PreDragCondition; import com.android.launcher3.model.data.ItemInfo; +import com.android.launcher3.util.TouchUtil; import com.android.launcher3.widget.util.WidgetDragScaleUtils; /** @@ -47,6 +54,9 @@ public class LauncherDragController extends DragController<Launcher> { private static final boolean PROFILE_DRAWING_DURING_DRAG = false; private final FlingToDeleteHelper mFlingToDeleteHelper; + /** Whether or not the drag operation is triggered by mouse right click. */ + private boolean mIsInMouseRightClick = false; + public LauncherDragController(Launcher launcher) { super(launcher); mFlingToDeleteHelper = new FlingToDeleteHelper(launcher); @@ -69,6 +79,28 @@ public class LauncherDragController extends DragController<Launcher> { android.os.Debug.startMethodTracing("Launcher"); } + if (removeAppsRefreshOnRightClick() && mIsInMouseRightClick + && options.preDragCondition == null + && originalView instanceof View v) { + options.preDragCondition = new PreDragCondition() { + + @Override + public boolean shouldStartDrag(double distanceDragged) { + return false; + } + + @Override + public void onPreDragStart(DragObject dragObject) { + // Set it to visible so the text of FolderIcon would not flash (avoid it from + // being invisible and then visible) + v.setVisibility(VISIBLE); + } + + @Override + public void onPreDragEnd(DragObject dragObject, boolean dragStarted) { } + }; + } + mActivity.hideKeyboard(); AbstractFloatingView.closeOpenViews(mActivity, false, TYPE_DISCOVERY_BOUNCE); @@ -191,7 +223,7 @@ public class LauncherDragController extends DragController<Launcher> { @Override protected void exitDrag() { - if (!mActivity.isInState(EDIT_MODE)) { + if (!mIsInPreDrag && !mActivity.isInState(EDIT_MODE)) { mActivity.getStateManager().goToState(NORMAL, SPRING_LOADED_EXIT_DELAY); } } @@ -218,4 +250,13 @@ public class LauncherDragController extends DragController<Launcher> { dropCoordinates); return mActivity.getWorkspace(); } + + /** + * Intercepts touch events from a drag source view. + */ + @Override + public boolean onControllerInterceptTouchEvent(MotionEvent ev) { + mIsInMouseRightClick = TouchUtil.isMouseRightClickDownOrMove(ev); + return super.onControllerInterceptTouchEvent(ev); + } } diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java index 28032569b2..0ae95196fd 100644 --- a/src/com/android/launcher3/folder/Folder.java +++ b/src/com/android/launcher3/folder/Folder.java @@ -102,7 +102,6 @@ import com.android.launcher3.model.data.WorkspaceItemInfo; import com.android.launcher3.pageindicators.PageIndicatorDots; import com.android.launcher3.util.Executors; import com.android.launcher3.util.LauncherBindableItemsContainer; -import com.android.launcher3.util.LauncherBindableItemsContainer.ItemOperator; import com.android.launcher3.util.Thunk; import com.android.launcher3.views.ActivityContext; import com.android.launcher3.views.BaseDragLayer; @@ -1116,13 +1115,15 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo View icon = (mCurrentDragView != null && mCurrentDragView.getTag() == info) ? mCurrentDragView : mContent.createNewView(info); ArrayList<View> views = getIconsInReadingOrder(); - info.rank = Utilities.boundToRange(info.rank, 0, views.size()); - views.add(info.rank, icon); - mContent.arrangeChildren(views); - mItemsInvalidated = true; - - try (SuppressInfoChanges s = new SuppressInfoChanges()) { - mFolderIcon.onDrop(d, true /* itemReturnedOnFailedDrop */); + if (!views.contains(icon)) { + info.rank = Utilities.boundToRange(info.rank, 0, views.size()); + views.add(info.rank, icon); + mContent.arrangeChildren(views); + mItemsInvalidated = true; + + try (SuppressInfoChanges s = new SuppressInfoChanges()) { + mFolderIcon.onDrop(d, true /* itemReturnedOnFailedDrop */); + } } } 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/LauncherPreviewRenderer.java b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java index b80238cb1e..e4e4b900fa 100644 --- a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java +++ b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java @@ -80,8 +80,10 @@ import com.android.launcher3.dagger.LauncherAppComponent; import com.android.launcher3.dagger.LauncherAppModule; import com.android.launcher3.dagger.LauncherAppSingleton; import com.android.launcher3.folder.FolderIcon; +import com.android.launcher3.model.BaseLauncherBinder.BaseLauncherBinderFactory; import com.android.launcher3.model.BgDataModel; import com.android.launcher3.model.BgDataModel.FixedContainerItems; +import com.android.launcher3.model.LoaderTask.LoaderTaskFactory; import com.android.launcher3.model.data.AppPairInfo; import com.android.launcher3.model.data.CollectionInfo; import com.android.launcher3.model.data.FolderInfo; @@ -605,6 +607,10 @@ public class LauncherPreviewRenderer extends BaseContext @Component(modules = LauncherAppModule.class) public interface PreviewAppComponent extends LauncherAppComponent { + LoaderTaskFactory getLoaderTaskFactory(); + BaseLauncherBinderFactory getBaseLauncherBinderFactory(); + BgDataModel getDataModel(); + /** Builder for NexusLauncherAppComponent. */ @Component.Builder interface Builder extends LauncherAppComponent.Builder { diff --git a/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java b/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java index d425f03712..5a9b9c20f9 100644 --- a/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java +++ b/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java @@ -58,19 +58,21 @@ import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherPrefs; import com.android.launcher3.LauncherSettings; import com.android.launcher3.Workspace; +import com.android.launcher3.dagger.LauncherComponentProvider; +import com.android.launcher3.graphics.LauncherPreviewRenderer.PreviewAppComponent; import com.android.launcher3.graphics.LauncherPreviewRenderer.PreviewContext; -import com.android.launcher3.model.BaseLauncherBinder; import com.android.launcher3.model.BgDataModel; import com.android.launcher3.model.BgDataModel.Callbacks; import com.android.launcher3.model.LoaderTask; import com.android.launcher3.model.ModelDbController; +import com.android.launcher3.model.UserManagerState; import com.android.launcher3.util.ComponentKey; import com.android.launcher3.util.RunnableList; import com.android.launcher3.util.Themes; import com.android.launcher3.widget.LocalColorExtractor; import com.android.systemui.shared.Flags; -import java.util.ArrayList; +import java.util.HashMap; import java.util.Map; import java.util.concurrent.TimeUnit; @@ -338,38 +340,32 @@ public class PreviewSurfaceRenderer { // Start the migration PreviewContext previewContext = new PreviewContext(inflationContext, mGridName, mShapeKey); + PreviewAppComponent appComponent = + (PreviewAppComponent) LauncherComponentProvider.get(previewContext); + + LoaderTask task = appComponent.getLoaderTaskFactory().newLoaderTask( + appComponent.getBaseLauncherBinderFactory().createBinder(new Callbacks[0]), + new UserManagerState()); + + InvariantDeviceProfile idp = appComponent.getIDP(); + DeviceProfile deviceProfile = idp.getDeviceProfile(previewContext); + String query = + LauncherSettings.Favorites.SCREEN + " = " + Workspace.FIRST_SCREEN_ID + + " or " + LauncherSettings.Favorites.CONTAINER + " = " + + LauncherSettings.Favorites.CONTAINER_HOTSEAT; + if (deviceProfile.isTwoPanels) { + query += " or " + LauncherSettings.Favorites.SCREEN + " = " + + Workspace.SECOND_SCREEN_ID; + } - BgDataModel bgModel = new BgDataModel(); - new LoaderTask( - LauncherAppState.getInstance(previewContext), - /* bgAllAppsList= */ null, - bgModel, - LauncherAppState.getInstance(previewContext).getModel().getModelDelegate(), - new BaseLauncherBinder(LauncherAppState.getInstance(previewContext), bgModel, - /* bgAllAppsList= */ null, new Callbacks[0])) { - - @Override - public void run() { - InvariantDeviceProfile idp = LauncherAppState.getIDP(previewContext); - DeviceProfile deviceProfile = idp.getDeviceProfile(previewContext); - String query = - LauncherSettings.Favorites.SCREEN + " = " + Workspace.FIRST_SCREEN_ID - + " or " + LauncherSettings.Favorites.CONTAINER + " = " - + LauncherSettings.Favorites.CONTAINER_HOTSEAT; - if (deviceProfile.isTwoPanels) { - query += " or " + LauncherSettings.Favorites.SCREEN + " = " - + Workspace.SECOND_SCREEN_ID; - } - loadWorkspace(new ArrayList<>(), query, null, null); - - final SparseArray<Size> spanInfo = getLoadedLauncherWidgetInfo(); - MAIN_EXECUTOR.execute(() -> { - renderView(previewContext, mBgDataModel, mWidgetProvidersMap, spanInfo, - idp); - mLifeCycleTracker.add(previewContext::onDestroy); - }); - } - }.run(); + Map<ComponentKey, AppWidgetProviderInfo> widgetProviderInfoMap = new HashMap<>(); + task.loadWorkspaceForPreview(query, widgetProviderInfoMap); + final SparseArray<Size> spanInfo = getLoadedLauncherWidgetInfo(); + MAIN_EXECUTOR.execute(() -> { + renderView(previewContext, appComponent.getDataModel(), widgetProviderInfoMap, + spanInfo, idp); + mLifeCycleTracker.add(previewContext::onDestroy); + }); } else { LauncherAppState.getInstance(inflationContext).getModel().loadAsync(dataModel -> { if (dataModel != null) { @@ -420,7 +416,6 @@ public class PreviewSurfaceRenderer { view.setTranslationY((mHeight - scale * view.getHeight()) / 2); } - if (!Flags.newCustomizationPickerUi()) { view.setAlpha(mSkipAnimations ? 1 : 0); view.animate().alpha(1) @@ -477,5 +472,4 @@ public class PreviewSurfaceRenderer { MAIN_EXECUTOR.execute(mLifecycleTracker::executeAllAndDestroy); } } - } 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/logging/StatsLogManager.java b/src/com/android/launcher3/logging/StatsLogManager.java index 74d50988f1..4715132357 100644 --- a/src/com/android/launcher3/logging/StatsLogManager.java +++ b/src/com/android/launcher3/logging/StatsLogManager.java @@ -166,6 +166,9 @@ public class StatsLogManager implements ResourceBasedOverride { @UiEvent(doc = "User tapped or long pressed on settings icon inside launcher settings.") LAUNCHER_SETTINGS_BUTTON_TAP_OR_LONGPRESS(463), + @UiEvent(doc = "User tapped or long pressed on apps icon inside launcher settings.") + LAUNCHER_ALL_APPS_TAP_OR_LONGPRESS(2204), + @UiEvent(doc = "User tapped or long pressed on widget tray icon inside launcher settings.") LAUNCHER_WIDGETSTRAY_BUTTON_TAP_OR_LONGPRESS(464), diff --git a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java index ddbbdc722a..dfba4bb192 100644 --- a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java +++ b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java @@ -66,13 +66,6 @@ public class AddWorkspaceItemsTask implements ModelUpdateTask { /** * @param itemList items to add on the workspace - */ - public AddWorkspaceItemsTask(@NonNull final List<Pair<ItemInfo, Object>> itemList) { - this(itemList, new WorkspaceItemSpaceFinder()); - } - - /** - * @param itemList items to add on the workspace * @param itemSpaceFinder inject WorkspaceItemSpaceFinder dependency for testing */ public AddWorkspaceItemsTask(@NonNull final List<Pair<ItemInfo, Object>> itemList, @@ -91,7 +84,7 @@ public class AddWorkspaceItemsTask implements ModelUpdateTask { final ArrayList<ItemInfo> addedItemsFinal = new ArrayList<>(); final IntArray addedWorkspaceScreensFinal = new IntArray(); - final Context context = taskController.getApp().getContext(); + final Context context = taskController.getContext(); synchronized (dataModel) { IntArray workspaceScreens = dataModel.collectWorkspaceScreens(); @@ -133,7 +126,7 @@ public class AddWorkspaceItemsTask implements ModelUpdateTask { for (ItemInfo item : filteredItems) { // Find appropriate space for the item. - int[] coords = mItemSpaceFinder.findSpaceForItem(taskController.getApp(), dataModel, + int[] coords = mItemSpaceFinder.findSpaceForItem( workspaceScreens, addedWorkspaceScreensFinal, item.spanX, item.spanY); int screenId = coords[0]; @@ -192,7 +185,7 @@ public class AddWorkspaceItemsTask implements ModelUpdateTask { continue; } - IconCache cache = taskController.getApp().getIconCache(); + IconCache cache = taskController.getIconCache(); WorkspaceItemInfo wii = (WorkspaceItemInfo) itemInfo; wii.title = ""; wii.bitmap = cache.getDefaultIcon(item.user); diff --git a/src/com/android/launcher3/model/AllAppsList.java b/src/com/android/launcher3/model/AllAppsList.java index 98f9afdaaa..2311239025 100644 --- a/src/com/android/launcher3/model/AllAppsList.java +++ b/src/com/android/launcher3/model/AllAppsList.java @@ -34,6 +34,7 @@ import androidx.annotation.Nullable; import com.android.launcher3.AppFilter; import com.android.launcher3.compat.AlphabeticIndexCompat; +import com.android.launcher3.dagger.LauncherAppSingleton; import com.android.launcher3.icons.IconCache; import com.android.launcher3.model.BgDataModel.Callbacks; import com.android.launcher3.model.data.AppInfo; @@ -53,11 +54,13 @@ import java.util.List; import java.util.function.Consumer; import java.util.function.Predicate; +import javax.inject.Inject; + /** * Stores the list of all applications for the all apps view. */ -@SuppressWarnings("NewApi") +@LauncherAppSingleton public class AllAppsList { private static final String TAG = "AllAppsList"; @@ -70,10 +73,10 @@ public class AllAppsList { public final ArrayList<AppInfo> data = new ArrayList<>(DEFAULT_APPLICATIONS_NUMBER); @NonNull - private IconCache mIconCache; + private final IconCache mIconCache; @NonNull - private AppFilter mAppFilter; + private final AppFilter mAppFilter; private boolean mDataChanged = false; private Consumer<AppInfo> mRemoveListener = NO_OP_CONSUMER; @@ -92,6 +95,7 @@ public class AllAppsList { /** * Boring constructor. */ + @Inject public AllAppsList(IconCache iconCache, AppFilter appFilter) { mIconCache = iconCache; mAppFilter = appFilter; diff --git a/src/com/android/launcher3/model/BaseLauncherBinder.java b/src/com/android/launcher3/model/BaseLauncherBinder.java index 262bf678c7..c4bbae416b 100644 --- a/src/com/android/launcher3/model/BaseLauncherBinder.java +++ b/src/com/android/launcher3/model/BaseLauncherBinder.java @@ -26,6 +26,7 @@ import static com.android.launcher3.model.ModelUtils.currentScreenContentFilter; import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; +import android.content.Context; import android.os.Trace; import android.util.Log; import android.util.Pair; @@ -34,11 +35,12 @@ import android.view.View; import androidx.annotation.NonNull; import com.android.launcher3.InvariantDeviceProfile; -import com.android.launcher3.LauncherAppState; +import com.android.launcher3.LauncherModel; import com.android.launcher3.LauncherModel.CallbackTask; import com.android.launcher3.LauncherSettings; import com.android.launcher3.celllayout.CellPosMapper; import com.android.launcher3.config.FeatureFlags; +import com.android.launcher3.dagger.ApplicationContext; import com.android.launcher3.model.BgDataModel.Callbacks; import com.android.launcher3.model.BgDataModel.FixedContainerItems; import com.android.launcher3.model.data.AppInfo; @@ -55,6 +57,10 @@ import com.android.launcher3.util.RunnableList; import com.android.launcher3.widget.model.WidgetsListBaseEntriesBuilder; import com.android.launcher3.widget.model.WidgetsListBaseEntry; +import dagger.assisted.Assisted; +import dagger.assisted.AssistedFactory; +import dagger.assisted.AssistedInject; + import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -76,7 +82,9 @@ public class BaseLauncherBinder { protected final LooperExecutor mUiExecutor; - protected final LauncherAppState mApp; + private final Context mContext; + private final InvariantDeviceProfile mIDP; + private final LauncherModel mModel; protected final BgDataModel mBgDataModel; private final AllAppsList mBgAllAppsList; @@ -84,10 +92,18 @@ public class BaseLauncherBinder { private int mMyBindingId; - public BaseLauncherBinder(LauncherAppState app, BgDataModel dataModel, - AllAppsList allAppsList, Callbacks[] callbacksList) { + @AssistedInject + public BaseLauncherBinder( + @ApplicationContext Context context, + InvariantDeviceProfile idp, + LauncherModel model, + BgDataModel dataModel, + AllAppsList allAppsList, + @Assisted Callbacks[] callbacksList) { mUiExecutor = MAIN_EXECUTOR; - mApp = app; + mContext = context; + mIDP = idp; + mModel = model; mBgDataModel = dataModel; mBgAllAppsList = allAppsList; mCallbacksList = callbacksList; @@ -110,15 +126,14 @@ public class BaseLauncherBinder { mBgDataModel.extraItems.forEach(extraItems::add); if (incrementBindId) { mBgDataModel.lastBindId++; - mBgDataModel.lastLoadId = mApp.getModel().getLastLoadId(); + mBgDataModel.lastLoadId = mModel.getLastLoadId(); } mMyBindingId = mBgDataModel.lastBindId; workspaceItemCount = mBgDataModel.itemsIdMap.size(); } for (Callbacks cb : mCallbacksList) { - new UnifiedWorkspaceBinder(cb, mUiExecutor, mApp, mBgDataModel, mMyBindingId, - itemsIdMap, extraItems, orderedScreenIds) + new UnifiedWorkspaceBinder(cb, itemsIdMap, extraItems, orderedScreenIds) .bind(isBindSync, workspaceItemCount); } } finally { @@ -162,9 +177,8 @@ public class BaseLauncherBinder { if (!WIDGETS_ENABLED) { return; } - List<WidgetsListBaseEntry> widgets = new WidgetsListBaseEntriesBuilder(mApp.getContext()) + List<WidgetsListBaseEntry> widgets = new WidgetsListBaseEntriesBuilder(mContext) .build(mBgDataModel.widgetsModel.getWidgetsByPackageItemForPicker()); - executeCallbacksTask(c -> c.bindAllWidgets(widgets), mUiExecutor); } @@ -181,10 +195,9 @@ public class BaseLauncherBinder { /** * Sorts the set of items by hotseat, workspace (spatially from top to bottom, left to right) */ - protected void sortWorkspaceItemsSpatially(InvariantDeviceProfile profile, - ArrayList<ItemInfo> workspaceItems) { - final int screenCols = profile.numColumns; - final int screenCellCount = profile.numColumns * profile.numRows; + protected void sortWorkspaceItemsSpatially(ArrayList<ItemInfo> workspaceItems) { + final int screenCols = mIDP.numColumns; + final int screenCellCount = mIDP.numColumns * mIDP.numRows; Collections.sort(workspaceItems, (lhs, rhs) -> { if (lhs.container == rhs.container) { // Within containers, order by their spatial position in that container @@ -240,30 +253,18 @@ public class BaseLauncherBinder { private class UnifiedWorkspaceBinder { - private final Executor mUiExecutor; private final Callbacks mCallbacks; - private final LauncherAppState mApp; - private final BgDataModel mBgDataModel; - - private final int mMyBindingId; private final IntSparseArrayMap<ItemInfo> mItemIdMap; private final IntArray mOrderedScreenIds; private final ArrayList<FixedContainerItems> mExtraItems; - UnifiedWorkspaceBinder(Callbacks callbacks, - Executor uiExecutor, - LauncherAppState app, - BgDataModel bgDataModel, - int myBindingId, + UnifiedWorkspaceBinder( + Callbacks callbacks, IntSparseArrayMap<ItemInfo> itemIdMap, ArrayList<FixedContainerItems> extraItems, IntArray orderedScreenIds) { mCallbacks = callbacks; - mUiExecutor = uiExecutor; - mApp = app; - mBgDataModel = bgDataModel; - mMyBindingId = myBindingId; mItemIdMap = itemIdMap; mExtraItems = extraItems; mOrderedScreenIds = orderedScreenIds; @@ -289,9 +290,8 @@ public class BaseLauncherBinder { (WIDGET_FILTER.test(item) ? otherAppWidgets : otherWorkspaceItems).add(item); } }); - final InvariantDeviceProfile idp = mApp.getInvariantDeviceProfile(); - sortWorkspaceItemsSpatially(idp, currentWorkspaceItems); - sortWorkspaceItemsSpatially(idp, otherWorkspaceItems); + sortWorkspaceItemsSpatially(currentWorkspaceItems); + sortWorkspaceItemsSpatially(otherWorkspaceItems); // Tell the workspace that we're about to start binding items executeCallbacksTask(c -> { @@ -352,7 +352,7 @@ public class BaseLauncherBinder { executeCallbacksTask(c -> c.bindStringCache(cacheClone), pendingExecutor); executeCallbacksTask(c -> c.finishBindingItems(currentScreenIds), pendingExecutor); - pendingExecutor.execute(() -> ItemInstallQueue.INSTANCE.get(mApp.getContext()) + pendingExecutor.execute(() -> ItemInstallQueue.INSTANCE.get(mContext) .resumeModelPush(FLAG_LOADER_RUNNING)); } @@ -367,8 +367,8 @@ public class BaseLauncherBinder { return; } - ModelWriter writer = mApp.getModel() - .getWriter(false /* verifyChanges */, CellPosMapper.DEFAULT, null); + ModelWriter writer = mModel.getWriter( + false /* verifyChanges */, CellPosMapper.DEFAULT, null); List<Pair<ItemInfo, View>> bindItems = items.stream() .map(i -> Pair.create(i, inflater.inflateItem(i, writer, null))) .collect(Collectors.toList()); @@ -398,4 +398,9 @@ public class BaseLauncherBinder { }); } } + + @AssistedFactory + public interface BaseLauncherBinderFactory { + BaseLauncherBinder createBinder(Callbacks[] callbacks); + } } diff --git a/src/com/android/launcher3/model/BgDataModel.java b/src/com/android/launcher3/model/BgDataModel.java index d9eccaf4f3..ea9f36c419 100644 --- a/src/com/android/launcher3/model/BgDataModel.java +++ b/src/com/android/launcher3/model/BgDataModel.java @@ -46,6 +46,7 @@ import androidx.annotation.Nullable; import com.android.launcher3.BuildConfig; import com.android.launcher3.Workspace; import com.android.launcher3.config.FeatureFlags; +import com.android.launcher3.dagger.LauncherAppSingleton; import com.android.launcher3.logging.FileLog; import com.android.launcher3.model.data.AppInfo; import com.android.launcher3.model.data.CollectionInfo; @@ -79,9 +80,15 @@ import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.stream.Stream; +import javax.inject.Inject; + /** * All the data stored in-memory and managed by the LauncherModel + * + * All the static data should be accessed on the background thread, A lock should be acquired on + * this object when accessing any data from this model. */ +@LauncherAppSingleton public class BgDataModel { private static final String TAG = "BgDataModel"; @@ -105,7 +112,7 @@ public class BgDataModel { /** * Entire list of widgets. */ - public final WidgetsModel widgetsModel = new WidgetsModel(); + public final WidgetsModel widgetsModel; /** * Cache for strings used in launcher @@ -124,6 +131,11 @@ public class BgDataModel { public boolean isFirstPagePinnedItemEnabled = QSB_ON_FIRST_SCREEN && !enableSmartspaceRemovalToggle(); + @Inject + public BgDataModel(WidgetsModel widgetsModel) { + this.widgetsModel = widgetsModel; + } + /** * Clears all the data */ diff --git a/src/com/android/launcher3/model/CacheDataUpdatedTask.java b/src/com/android/launcher3/model/CacheDataUpdatedTask.java index 48934e2c11..f740b49694 100644 --- a/src/com/android/launcher3/model/CacheDataUpdatedTask.java +++ b/src/com/android/launcher3/model/CacheDataUpdatedTask.java @@ -59,7 +59,7 @@ public class CacheDataUpdatedTask implements ModelUpdateTask { @Override public void execute(@NonNull ModelTaskController taskController, @NonNull BgDataModel dataModel, @NonNull AllAppsList apps) { - IconCache iconCache = taskController.getApp().getIconCache(); + IconCache iconCache = taskController.getIconCache(); ArrayList<ItemInfo> updatedItems = new ArrayList<>(); synchronized (dataModel) { diff --git a/src/com/android/launcher3/model/FirstScreenBroadcast.java b/src/com/android/launcher3/model/FirstScreenBroadcast.java index f443f8142e..829e0fe08b 100644 --- a/src/com/android/launcher3/model/FirstScreenBroadcast.java +++ b/src/com/android/launcher3/model/FirstScreenBroadcast.java @@ -45,9 +45,9 @@ import com.android.launcher3.util.PackageUserKey; import java.util.ArrayList; import java.util.Collections; -import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.stream.Collectors; @@ -76,9 +76,9 @@ public class FirstScreenBroadcast { private static final String VERIFICATION_TOKEN_EXTRA = "verificationToken"; - private final HashMap<PackageUserKey, SessionInfo> mSessionInfoForPackage; + private final Map<PackageUserKey, SessionInfo> mSessionInfoForPackage; - public FirstScreenBroadcast(HashMap<PackageUserKey, SessionInfo> sessionInfoForPackage) { + public FirstScreenBroadcast(Map<PackageUserKey, SessionInfo> sessionInfoForPackage) { mSessionInfoForPackage = sessionInfoForPackage; } diff --git a/src/com/android/launcher3/model/LoaderCursor.java b/src/com/android/launcher3/model/LoaderCursor.java index bd8c36b576..77d0d7656b 100644 --- a/src/com/android/launcher3/model/LoaderCursor.java +++ b/src/com/android/launcher3/model/LoaderCursor.java @@ -45,13 +45,14 @@ import androidx.annotation.VisibleForTesting; import com.android.launcher3.Flags; import com.android.launcher3.InvariantDeviceProfile; -import com.android.launcher3.LauncherAppState; +import com.android.launcher3.LauncherModel; import com.android.launcher3.LauncherSettings.Favorites; import com.android.launcher3.Utilities; import com.android.launcher3.Workspace; import com.android.launcher3.backuprestore.LauncherRestoreEventLogger; import com.android.launcher3.backuprestore.LauncherRestoreEventLogger.RestoreError; import com.android.launcher3.config.FeatureFlags; +import com.android.launcher3.dagger.ApplicationContext; import com.android.launcher3.icons.IconCache; import com.android.launcher3.logging.FileLog; import com.android.launcher3.model.data.AppInfo; @@ -70,6 +71,10 @@ import com.android.launcher3.util.IntSparseArrayMap; import com.android.launcher3.util.PackageManagerHelper; import com.android.launcher3.util.UserIconInfo; +import dagger.assisted.Assisted; +import dagger.assisted.AssistedFactory; +import dagger.assisted.AssistedInject; + import java.net.URISyntaxException; import java.security.InvalidParameterException; @@ -82,8 +87,8 @@ public class LoaderCursor extends CursorWrapper { private final LongSparseArray<UserHandle> allUsers; - private final LauncherAppState mApp; private final Context mContext; + private final LauncherModel mModel; private final PackageManagerHelper mPmHelper; private final IconCache mIconCache; private final InvariantDeviceProfile mIDP; @@ -130,17 +135,24 @@ public class LoaderCursor extends CursorWrapper { public int itemType; public int restoreFlag; - public LoaderCursor(Cursor cursor, LauncherAppState app, UserManagerState userManagerState, - PackageManagerHelper pmHelper, - @Nullable LauncherRestoreEventLogger restoreEventLogger) { + @AssistedInject + public LoaderCursor( + @Assisted Cursor cursor, + @Assisted UserManagerState userManagerState, + @Assisted @Nullable LauncherRestoreEventLogger restoreEventLogger, + @ApplicationContext Context context, + IconCache iconCache, + InvariantDeviceProfile idp, + LauncherModel model, + PackageManagerHelper pmHelper) { super(cursor); + mContext = context; + mIconCache = iconCache; + mIDP = idp; + mModel = model; + mPmHelper = pmHelper; - mApp = app; allUsers = userManagerState.allUsers; - mContext = app.getContext(); - mIconCache = app.getIconCache(); - mPmHelper = pmHelper; - mIDP = app.getInvariantDeviceProfile(); mRestoreEventLogger = restoreEventLogger; // Init column indices @@ -432,7 +444,7 @@ public class LoaderCursor extends CursorWrapper { */ public ContentWriter updater() { return new ContentWriter(mContext, new ContentWriter.CommitParams( - mApp.getModel().getModelDbController(), + mModel.getModelDbController(), BaseColumns._ID + "= ?", new String[]{Integer.toString(id)})); } @@ -454,7 +466,7 @@ public class LoaderCursor extends CursorWrapper { public boolean commitDeleted() { if (mItemsToRemove.size() > 0) { // Remove dead items - mApp.getModel().getModelDbController().delete(TABLE_NAME, + mModel.getModelDbController().delete(TABLE_NAME, Utilities.createDbSelectionQuery(Favorites._ID, mItemsToRemove), null); return true; } @@ -480,7 +492,7 @@ public class LoaderCursor extends CursorWrapper { // Update restored items that no longer require special handling ContentValues values = new ContentValues(); values.put(Favorites.RESTORED, 0); - mApp.getModel().getModelDbController().update(TABLE_NAME, values, + mModel.getModelDbController().update(TABLE_NAME, values, Utilities.createDbSelectionQuery(Favorites._ID, mRestoredRows), null); } if (mRestoreEventLogger != null) { @@ -645,4 +657,12 @@ public class LoaderCursor extends CursorWrapper { return false; } } + + @AssistedFactory + public interface LoaderCursorFactory { + + LoaderCursor createLoaderCursor(Cursor cursor, + UserManagerState userManagerState, + @Nullable LauncherRestoreEventLogger restoreEventLogger); + } } diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java index fb1ebaf3c4..73af6a221a 100644 --- a/src/com/android/launcher3/model/LoaderTask.java +++ b/src/com/android/launcher3/model/LoaderTask.java @@ -53,7 +53,6 @@ import android.os.Trace; import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; -import android.util.ArrayMap; import android.util.Log; import android.util.LongSparseArray; @@ -63,13 +62,14 @@ import androidx.annotation.VisibleForTesting; import androidx.annotation.WorkerThread; import com.android.launcher3.Flags; -import com.android.launcher3.LauncherAppState; +import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.LauncherModel; import com.android.launcher3.LauncherPrefs; import com.android.launcher3.LauncherSettings.Favorites; import com.android.launcher3.Utilities; import com.android.launcher3.backuprestore.LauncherRestoreEventLogger; import com.android.launcher3.config.FeatureFlags; +import com.android.launcher3.dagger.ApplicationContext; import com.android.launcher3.folder.Folder; import com.android.launcher3.folder.FolderGridOrganizer; import com.android.launcher3.folder.FolderNameInfos; @@ -82,6 +82,7 @@ import com.android.launcher3.icons.cache.CachedObjectCachingLogic; import com.android.launcher3.icons.cache.IconCacheUpdateHandler; import com.android.launcher3.icons.cache.LauncherActivityCachingLogic; import com.android.launcher3.logging.FileLog; +import com.android.launcher3.model.LoaderCursor.LoaderCursorFactory; import com.android.launcher3.model.data.AppInfo; import com.android.launcher3.model.data.AppPairInfo; import com.android.launcher3.model.data.FolderInfo; @@ -106,6 +107,10 @@ import com.android.launcher3.util.PackageUserKey; import com.android.launcher3.util.TraceHelper; import com.android.launcher3.widget.WidgetInflater; +import dagger.assisted.Assisted; +import dagger.assisted.AssistedFactory; +import dagger.assisted.AssistedInject; + import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; @@ -117,6 +122,8 @@ import java.util.Optional; import java.util.Set; import java.util.concurrent.CancellationException; +import javax.inject.Named; + /** * Runnable for the thread that loads the contents of the launcher: * - workspace icons @@ -131,10 +138,14 @@ public class LoaderTask implements Runnable { private static final boolean DEBUG = true; - @NonNull - protected final LauncherAppState mApp; + private final Context mContext; + private final LauncherModel mModel; + private final InvariantDeviceProfile mIDP; + private final boolean mIsSafeModeEnabled; private final AllAppsList mBgAllAppsList; protected final BgDataModel mBgDataModel; + private final LoaderCursorFactory mLoaderCursorFactory; + private final ModelDelegate mModelDelegate; private boolean mIsRestoreFromBackup; @@ -152,7 +163,6 @@ public class LoaderTask implements Runnable { private final IconCache mIconCache; private final UserManagerState mUserManagerState; - protected final Map<ComponentKey, AppWidgetProviderInfo> mWidgetProvidersMap = new ArrayMap<>(); private Map<ShortcutKey, ShortcutInfo> mShortcutKeyToPinnedShortcuts; private HashMap<PackageUserKey, SessionInfo> mInstallingPkgsCached; @@ -164,26 +174,36 @@ public class LoaderTask implements Runnable { private boolean mItemsDeleted = false; private String mDbName; - public LoaderTask(@NonNull LauncherAppState app, AllAppsList bgAllAppsList, BgDataModel bgModel, - ModelDelegate modelDelegate, @NonNull BaseLauncherBinder launcherBinder) { - this(app, bgAllAppsList, bgModel, modelDelegate, launcherBinder, new UserManagerState()); - } - - @VisibleForTesting - LoaderTask(@NonNull LauncherAppState app, AllAppsList bgAllAppsList, BgDataModel bgModel, - ModelDelegate modelDelegate, @NonNull BaseLauncherBinder launcherBinder, - UserManagerState userManagerState) { - mApp = app; + @AssistedInject + LoaderTask( + @ApplicationContext Context context, + InvariantDeviceProfile idp, + LauncherModel model, + UserCache userCache, + PackageManagerHelper pmHelper, + InstallSessionHelper sessionHelper, + IconCache iconCache, + AllAppsList bgAllAppsList, + BgDataModel bgModel, + LoaderCursorFactory loaderCursorFactory, + @Named("SAFE_MODE") boolean isSafeModeEnabled, + @Assisted @NonNull BaseLauncherBinder launcherBinder, + @Assisted UserManagerState userManagerState) { + mContext = context; + mIDP = idp; + mModel = model; + mIsSafeModeEnabled = isSafeModeEnabled; mBgAllAppsList = bgAllAppsList; mBgDataModel = bgModel; - mModelDelegate = modelDelegate; + mModelDelegate = model.getModelDelegate(); mLauncherBinder = launcherBinder; - mLauncherApps = mApp.getContext().getSystemService(LauncherApps.class); - mUserManager = mApp.getContext().getSystemService(UserManager.class); - mUserCache = UserCache.INSTANCE.get(mApp.getContext()); - mPmHelper = PackageManagerHelper.INSTANCE.get(mApp.getContext()); - mSessionHelper = InstallSessionHelper.INSTANCE.get(mApp.getContext()); - mIconCache = mApp.getIconCache(); + mLoaderCursorFactory = loaderCursorFactory; + mLauncherApps = mContext.getSystemService(LauncherApps.class); + mUserManager = mContext.getSystemService(UserManager.class); + mUserCache = userCache; + mPmHelper = pmHelper; + mSessionHelper = sessionHelper; + mIconCache = iconCache; mUserManagerState = userManagerState; mInstallingPkgsCached = null; } @@ -215,7 +235,7 @@ public class LoaderTask implements Runnable { .filter(currentScreenContentFilter(firstScreens)) .toList(); final int disableArchivingLauncherBroadcast = Settings.Secure.getInt( - mApp.getContext().getContentResolver(), + mContext.getContentResolver(), "disable_launcher_broadcast_installed_apps", /* default */ 0); boolean shouldAttachArchivingExtras = mIsRestoreFromBackup @@ -230,10 +250,10 @@ public class LoaderTask implements Runnable { mBgDataModel.itemsIdMap.stream().filter(WIDGET_FILTER).toList() ); logASplit("Sending first screen broadcast with additional archiving Extras"); - FirstScreenBroadcastHelper.sendBroadcastsForModels(mApp.getContext(), broadcastModels); + FirstScreenBroadcastHelper.sendBroadcastsForModels(mContext, broadcastModels); } else { logASplit("Sending first screen broadcast"); - mFirstScreenBroadcast.sendBroadcasts(mApp.getContext(), firstScreenItems); + mFirstScreenBroadcast.sendBroadcasts(mContext, firstScreenItems); } } @@ -249,16 +269,14 @@ public class LoaderTask implements Runnable { MODEL_EXECUTOR.elevatePriority(CALLER_LOADER_TASK); LoaderMemoryLogger memoryLogger = new LoaderMemoryLogger(); mIsRestoreFromBackup = - LauncherPrefs.get(mApp.getContext()).get(IS_FIRST_LOAD_AFTER_RESTORE); + LauncherPrefs.get(mContext).get(IS_FIRST_LOAD_AFTER_RESTORE); LauncherRestoreEventLogger restoreEventLogger = null; if (enableLauncherBrMetricsFixed()) { - restoreEventLogger = LauncherRestoreEventLogger.Companion - .newInstance(mApp.getContext()); + restoreEventLogger = LauncherRestoreEventLogger.Companion.newInstance(mContext); } - try (LauncherModel.LoaderTransaction transaction = mApp.getModel().beginLoader(this)) { - + try (LauncherModel.LoaderTransaction transaction = mModel.beginLoader(this)) { List<CacheableShortcutInfo> allShortcuts = new ArrayList<>(); - loadWorkspace(allShortcuts, "", memoryLogger, restoreEventLogger); + loadWorkspace(allShortcuts, "", new HashMap<>(), memoryLogger, restoreEventLogger); // Sanitize data re-syncs widgets/shortcuts based on the workspace loaded from db. // sanitizeData should not be invoked if the workspace is loaded from a db different @@ -267,7 +285,7 @@ public class LoaderTask implements Runnable { // TODO(b/384731096): Write Unit Test to make sure sanitizeWidgetsShortcutsAndPackages // actually re-pins shortcuts that are in model but not in ShortcutManager, if possible // after a simulated restore. - if (Objects.equals(mApp.getInvariantDeviceProfile().dbFile, mDbName)) { + if (Objects.equals(mIDP.dbFile, mDbName)) { verifyNotStopped(); sanitizeFolders(mItemsDeleted); sanitizeAppPairs(); @@ -307,13 +325,13 @@ public class LoaderTask implements Runnable { setIgnorePackages(updateHandler); updateHandler.updateIcons(allActivityList, LauncherActivityCachingLogic.INSTANCE, - mApp.getModel()::onPackageIconsUpdated); + mModel::onPackageIconsUpdated); logASplit("update AllApps icon cache finished"); verifyNotStopped(); logASplit("saving all shortcuts in icon cache"); updateHandler.updateIcons(allShortcuts, CacheableShortcutCachingLogic.INSTANCE, - mApp.getModel()::onPackageIconsUpdated); + mModel::onPackageIconsUpdated); // Take a break waitForIdle(); @@ -342,14 +360,14 @@ public class LoaderTask implements Runnable { // fourth step WidgetsModel widgetsModel = mBgDataModel.widgetsModel; - List<CachedObject> allWidgetsList = widgetsModel.update(mApp, /*packageUser=*/null); + List<CachedObject> allWidgetsList = widgetsModel.update(/*packageUser=*/null); logASplit("load widgets finished"); verifyNotStopped(); mLauncherBinder.bindWidgets(); logASplit("bindWidgets finished"); verifyNotStopped(); - LauncherPrefs prefs = LauncherPrefs.get(mApp.getContext()); + LauncherPrefs prefs = LauncherPrefs.get(mContext); if (enableSmartspaceAsAWidget() && prefs.get(SHOULD_SHOW_SMARTSPACE)) { mLauncherBinder.bindSmartspaceWidget(); @@ -366,7 +384,7 @@ public class LoaderTask implements Runnable { logASplit("saving all widgets in icon cache"); updateHandler.updateIcons(allWidgetsList, CachedObjectCachingLogic.INSTANCE, - mApp.getModel()::onWidgetLabelsUpdated); + mModel::onWidgetLabelsUpdated); // fifth step loadFolderNames(); @@ -380,7 +398,7 @@ public class LoaderTask implements Runnable { memoryLogger.clearLogs(); if (mIsRestoreFromBackup) { mIsRestoreFromBackup = false; - LauncherPrefs.get(mApp.getContext()).putSync(IS_FIRST_LOAD_AFTER_RESTORE.to(false)); + LauncherPrefs.get(mContext).putSync(IS_FIRST_LOAD_AFTER_RESTORE.to(false)); if (restoreEventLogger != null) { restoreEventLogger.reportLauncherRestoreResults(); } @@ -402,35 +420,42 @@ public class LoaderTask implements Runnable { this.notify(); } - protected void loadWorkspace( + public void loadWorkspaceForPreview(String selection, + Map<ComponentKey, AppWidgetProviderInfo> widgetProviderInfoMap) { + loadWorkspace(new ArrayList<>(), selection, widgetProviderInfoMap, null, null); + } + + private void loadWorkspace( List<CacheableShortcutInfo> allDeepShortcuts, String selection, + Map<ComponentKey, AppWidgetProviderInfo> widgetProviderInfoMap, @Nullable LoaderMemoryLogger memoryLogger, @Nullable LauncherRestoreEventLogger restoreEventLogger ) { Trace.beginSection("LoadWorkspace"); try { - loadWorkspaceImpl(allDeepShortcuts, selection, memoryLogger, restoreEventLogger); + loadWorkspaceImpl(allDeepShortcuts, selection, widgetProviderInfoMap, + memoryLogger, restoreEventLogger); } finally { Trace.endSection(); } logASplit("loadWorkspace finished"); mBgDataModel.isFirstPagePinnedItemEnabled = FeatureFlags.QSB_ON_FIRST_SCREEN - && (!enableSmartspaceRemovalToggle() || LauncherPrefs.getPrefs( - mApp.getContext()).getBoolean(SMARTSPACE_ON_HOME_SCREEN, true)); + && (!enableSmartspaceRemovalToggle() + || LauncherPrefs.getPrefs(mContext).getBoolean(SMARTSPACE_ON_HOME_SCREEN, true)); } private void loadWorkspaceImpl( List<CacheableShortcutInfo> allDeepShortcuts, String selection, + Map<ComponentKey, AppWidgetProviderInfo> widgetProviderInfoMap, @Nullable LoaderMemoryLogger memoryLogger, @Nullable LauncherRestoreEventLogger restoreEventLogger) { - final Context context = mApp.getContext(); final boolean isSdCardReady = Utilities.isBootCompleted(); - final WidgetInflater widgetInflater = new WidgetInflater(context); + final WidgetInflater widgetInflater = new WidgetInflater(mContext, mIsSafeModeEnabled); - ModelDbController dbController = mApp.getModel().getModelDbController(); + ModelDbController dbController = mModel.getModelDbController(); if (Flags.gridMigrationRefactor()) { try { dbController.attemptMigrateDb(restoreEventLogger, mModelDelegate); @@ -452,28 +477,29 @@ public class LoaderTask implements Runnable { if (Flags.enableSupportForArchiving()) { mInstallingPkgsCached = installingPkgs; } - installingPkgs.forEach(mApp.getIconCache()::updateSessionCache); + installingPkgs.forEach(mIconCache::updateSessionCache); FileLog.d(TAG, "loadWorkspace: Packages with active install/update sessions: " + installingPkgs.keySet().stream().map(info -> info.mPackageName).toList()); mFirstScreenBroadcast = new FirstScreenBroadcast(installingPkgs); mShortcutKeyToPinnedShortcuts = new HashMap<>(); - final LoaderCursor c = new LoaderCursor( + final LoaderCursor c = mLoaderCursorFactory.createLoaderCursor( dbController.query(TABLE_NAME, null, selection, null, null), - mApp, mUserManagerState, mPmHelper, + mUserManagerState, mIsRestoreFromBackup ? restoreEventLogger : null); final Bundle extras = c.getExtras(); mDbName = extras == null ? null : extras.getString(ModelDbController.EXTRA_DB_NAME); try { final LongSparseArray<Boolean> unlockedUsers = new LongSparseArray<>(); - queryPinnedShortcutsForUnlockedUsers(context, unlockedUsers); + queryPinnedShortcutsForUnlockedUsers(mContext, unlockedUsers); mWorkspaceIconRequestInfos = new ArrayList<>(); WorkspaceItemProcessor itemProcessor = new WorkspaceItemProcessor(c, memoryLogger, mUserCache, mUserManagerState, mLauncherApps, mPendingPackages, - mShortcutKeyToPinnedShortcuts, mApp, mBgDataModel, - mWidgetProvidersMap, installingPkgs, isSdCardReady, + mShortcutKeyToPinnedShortcuts, mContext, mIDP, mIconCache, + mIsSafeModeEnabled, mBgDataModel, + widgetProviderInfoMap, installingPkgs, isSdCardReady, widgetInflater, mPmHelper, mWorkspaceIconRequestInfos, unlockedUsers, allDeepShortcuts); @@ -577,7 +603,7 @@ public class LoaderTask implements Runnable { @WorkerThread private void processFolderItems() { // Sort the folder items, update ranks, and make sure all preview items are high res. - List<FolderGridOrganizer> verifiers = mApp.getInvariantDeviceProfile().supportedProfiles + List<FolderGridOrganizer> verifiers = mIDP.supportedProfiles .stream().map(FolderGridOrganizer::createFolderGridOrganizer).toList(); for (ItemInfo itemInfo : mBgDataModel.itemsIdMap) { if (!(itemInfo instanceof FolderInfo folder)) { @@ -617,7 +643,7 @@ public class LoaderTask implements Runnable { if (mIconCache.isDefaultIcon(wai.bitmap, wai.user)) { logASplit("tryLoadWorkspaceIconsInBulk: default icon found for " + wai.getTargetComponent() + ", will attempt to load from iconBlob"); - iconRequestInfo.loadIconFromDbBlob(mApp.getContext()); + iconRequestInfo.loadIconFromDbBlob(mContext); } } } finally { @@ -649,7 +675,7 @@ public class LoaderTask implements Runnable { private void sanitizeFolders(boolean itemsDeleted) { if (itemsDeleted) { // Remove any empty folder - IntArray deletedFolderIds = mApp.getModel().getModelDbController().deleteEmptyFolders(); + IntArray deletedFolderIds = mModel.getModelDbController().deleteEmptyFolders(); synchronized (mBgDataModel) { for (int folderId : deletedFolderIds) { mBgDataModel.itemsIdMap.remove(folderId); @@ -660,8 +686,8 @@ public class LoaderTask implements Runnable { /** Cleans up app pairs if they don't have the right number of member apps (2). */ private void sanitizeAppPairs() { - IntArray deletedAppPairIds = mApp.getModel().getModelDbController().deleteBadAppPairs(); - IntArray deletedAppIds = mApp.getModel().getModelDbController().deleteUnparentedApps(); + IntArray deletedAppPairIds = mModel.getModelDbController().deleteBadAppPairs(); + IntArray deletedAppIds = mModel.getModelDbController().deleteUnparentedApps(); IntArray deleted = new IntArray(); deleted.addAll(deletedAppPairIds); @@ -675,17 +701,15 @@ public class LoaderTask implements Runnable { } private void sanitizeWidgetsShortcutsAndPackages() { - Context context = mApp.getContext(); - // Remove any ghost widgets - mApp.getModel().getModelDbController().removeGhostWidgets(); + mModel.getModelDbController().removeGhostWidgets(); // Update pinned state of model shortcuts - mBgDataModel.updateShortcutPinnedState(context); + mBgDataModel.updateShortcutPinnedState(mContext); if (!Utilities.isBootCompleted() && !mPendingPackages.isEmpty()) { - context.registerReceiver( - new SdCardAvailableReceiver(mApp, mPendingPackages), + mContext.registerReceiver( + new SdCardAvailableReceiver(mContext, mModel, mPendingPackages), new IntentFilter(Intent.ACTION_BOOT_COMPLETED), null, MODEL_EXECUTOR.getHandler()); @@ -722,7 +746,7 @@ public class LoaderTask implements Runnable { for (int i = 0; i < apps.size(); i++) { LauncherActivityInfo app = apps.get(i); AppInfo appInfo = new AppInfo(app, mUserCache.getUserInfo(user), - ApiWrapper.INSTANCE.get(mApp.getContext()), mPmHelper, quietMode); + ApiWrapper.INSTANCE.get(mContext), mPmHelper, quietMode); if (Flags.enableSupportForArchiving() && app.getApplicationInfo().isArchived) { // For archived apps, include progress info in case there is a pending // install session post restart of device. @@ -751,7 +775,7 @@ public class LoaderTask implements Runnable { for (PackageInstaller.SessionInfo info : mSessionHelper.getAllVerifiedSessions()) { AppInfo promiseAppInfo = mBgAllAppsList.addPromiseApp( - mApp.getContext(), + mContext, PackageInstallInfo.fromInstallingState(info), false); @@ -776,7 +800,7 @@ public class LoaderTask implements Runnable { + appInfo.getTargetComponent() + ", will attempt to load from iconBlob: " + Arrays.toString(iconRequestInfo.iconBlob)); - iconRequestInfo.loadIconFromDbBlob(mApp.getContext()); + iconRequestInfo.loadIconFromDbBlob(mContext); } } } @@ -794,9 +818,9 @@ public class LoaderTask implements Runnable { mUserManagerState.isAnyProfileQuietModeEnabled()); } mBgAllAppsList.setFlags(FLAG_HAS_SHORTCUT_PERMISSION, - hasShortcutsPermission(mApp.getContext())); + hasShortcutsPermission(mContext)); mBgAllAppsList.setFlags(FLAG_QUIET_MODE_CHANGE_PERMISSION, - mApp.getContext().checkSelfPermission("android.permission.MODIFY_QUIET_MODE") + mContext.checkSelfPermission("android.permission.MODIFY_QUIET_MODE") == PackageManager.PERMISSION_GRANTED); mBgAllAppsList.getAndResetChangeFlag(); @@ -827,7 +851,7 @@ public class LoaderTask implements Runnable { workspaceIconRequest.get().iconBlob, false /* useLowResIcon= */ ); - if (!iconRequestInfo.loadIconFromDbBlob(mApp.getContext())) { + if (!iconRequestInfo.loadIconFromDbBlob(mContext)) { Log.d(TAG, "AppInfo Icon failed to load from blob, using cache."); mIconCache.getTitleAndIcon( appInfo, @@ -853,7 +877,7 @@ public class LoaderTask implements Runnable { if (mBgAllAppsList.hasShortcutHostPermission()) { for (UserHandle user : mUserCache.getUserProfiles()) { if (mUserManager.isUserUnlocked(user)) { - List<ShortcutInfo> shortcuts = new ShortcutRequest(mApp.getContext(), user) + List<ShortcutInfo> shortcuts = new ShortcutRequest(mContext, user) .query(ShortcutRequest.ALL); allShortcuts.addAll(shortcuts); mBgDataModel.updateDeepShortcutCounts(null, user, shortcuts); @@ -864,7 +888,7 @@ public class LoaderTask implements Runnable { } private void loadFolderNames() { - FolderNameProvider provider = FolderNameProvider.newInstance(mApp.getContext(), + FolderNameProvider provider = FolderNameProvider.newInstance(mContext, mBgAllAppsList.data, FolderNameProvider.getCollectionForSuggestions(mBgDataModel)); synchronized (mBgDataModel) { @@ -874,7 +898,7 @@ public class LoaderTask implements Runnable { .forEach(info -> { FolderInfo fi = (FolderInfo) info; FolderNameInfos suggestionInfos = new FolderNameInfos(); - provider.getSuggestedFolderName(mApp.getContext(), fi.getAppContents(), + provider.getSuggestedFolderName(mContext, fi.getAppContents(), suggestionInfos); fi.suggestedFolderNames = suggestionInfos; }); @@ -891,4 +915,10 @@ public class LoaderTask implements Runnable { Log.d(TAG, label); } } + + @AssistedFactory + public interface LoaderTaskFactory { + + LoaderTask newLoaderTask(BaseLauncherBinder binder, UserManagerState userState); + } } diff --git a/src/com/android/launcher3/model/ModelLauncherCallbacks.kt b/src/com/android/launcher3/model/ModelLauncherCallbacks.kt index 7ba2dad553..8b6c369e66 100644 --- a/src/com/android/launcher3/model/ModelLauncherCallbacks.kt +++ b/src/com/android/launcher3/model/ModelLauncherCallbacks.kt @@ -114,7 +114,7 @@ class ModelLauncherCallbacks(private var taskExecutor: Consumer<ModelUpdateTask> override fun onUpdateSessionDisplay(key: PackageUserKey, info: SessionInfo) { /** Updates the icons and label of all pending icons for the provided package name. */ taskExecutor.accept { controller, _, _ -> - controller.app.iconCache.updateSessionCache(key, info) + controller.iconCache.updateSessionCache(key, info) } taskExecutor.accept( CacheDataUpdatedTask( @@ -128,7 +128,7 @@ class ModelLauncherCallbacks(private var taskExecutor: Consumer<ModelUpdateTask> override fun onInstallSessionCreated(sessionInfo: PackageInstallInfo) { if (FeatureFlags.PROMISE_APPS_IN_ALL_APPS.get()) { taskExecutor.accept { taskController, _, apps -> - apps.addPromiseApp(taskController.app.context, sessionInfo) + apps.addPromiseApp(taskController.context, sessionInfo) taskController.bindApplicationsIfNeeded() } } diff --git a/src/com/android/launcher3/model/ModelTaskController.kt b/src/com/android/launcher3/model/ModelTaskController.kt index 5566482738..f17ca32cce 100644 --- a/src/com/android/launcher3/model/ModelTaskController.kt +++ b/src/com/android/launcher3/model/ModelTaskController.kt @@ -16,27 +16,34 @@ package com.android.launcher3.model -import com.android.launcher3.LauncherAppState +import android.content.Context import com.android.launcher3.LauncherModel import com.android.launcher3.LauncherModel.CallbackTask import com.android.launcher3.celllayout.CellPosMapper +import com.android.launcher3.dagger.ApplicationContext +import com.android.launcher3.icons.IconCache import com.android.launcher3.model.BgDataModel.FixedContainerItems import com.android.launcher3.model.data.ItemInfo +import com.android.launcher3.util.Executors.MAIN_EXECUTOR import com.android.launcher3.util.PackageUserKey import com.android.launcher3.widget.model.WidgetsListBaseEntriesBuilder import java.util.Objects -import java.util.concurrent.Executor import java.util.function.Predicate +import javax.inject.Inject /** Class with utility methods and properties for running a LauncherModel Task */ -class ModelTaskController( - val app: LauncherAppState, +class ModelTaskController +@Inject +constructor( + @ApplicationContext val context: Context, + val iconCache: IconCache, val dataModel: BgDataModel, val allAppsList: AllAppsList, - private val model: LauncherModel, - private val uiExecutor: Executor, + val model: LauncherModel, ) { + private val uiExecutor = MAIN_EXECUTOR + /** Schedules a {@param task} to be executed on the current callbacks. */ fun scheduleCallbackTask(task: CallbackTask) { for (cb in model.callbacks) { @@ -78,7 +85,7 @@ class ModelTaskController( fun bindUpdatedWidgets(dataModel: BgDataModel) { val allWidgets = - WidgetsListBaseEntriesBuilder(app.context) + WidgetsListBaseEntriesBuilder(context) .build(dataModel.widgetsModel.widgetsByPackageItemForPicker) scheduleCallbackTask { it.bindAllWidgets(allWidgets) } } diff --git a/src/com/android/launcher3/model/PackageInstallStateChangedTask.java b/src/com/android/launcher3/model/PackageInstallStateChangedTask.java index a2160422b2..a3561eda4d 100644 --- a/src/com/android/launcher3/model/PackageInstallStateChangedTask.java +++ b/src/com/android/launcher3/model/PackageInstallStateChangedTask.java @@ -52,11 +52,11 @@ public class PackageInstallStateChangedTask implements ModelUpdateTask { try { // For instant apps we do not get package-add. Use setting events to update // any pinned icons. - Context context = taskController.getApp().getContext(); + Context context = taskController.getContext(); ApplicationInfo ai = context .getPackageManager().getApplicationInfo(mInstallInfo.packageName, 0); if (InstantAppResolver.newInstance(context).isInstantApp(ai)) { - taskController.getApp().getModel().newModelCallbacks() + taskController.getModel().newModelCallbacks() .onPackageAdded(ai.packageName, mInstallInfo.user); } } catch (PackageManager.NameNotFoundException e) { diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java index 3cdb2500e1..04f3faa194 100644 --- a/src/com/android/launcher3/model/PackageUpdatedTask.java +++ b/src/com/android/launcher3/model/PackageUpdatedTask.java @@ -38,7 +38,6 @@ import android.util.Log; import androidx.annotation.NonNull; import com.android.launcher3.Flags; -import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherModel.ModelUpdateTask; import com.android.launcher3.LauncherSettings.Favorites; import com.android.launcher3.config.FeatureFlags; @@ -106,9 +105,8 @@ public class PackageUpdatedTask implements ModelUpdateTask { @Override public void execute(@NonNull ModelTaskController taskController, @NonNull BgDataModel dataModel, @NonNull AllAppsList appsList) { - final LauncherAppState app = taskController.getApp(); - final Context context = app.getContext(); - final IconCache iconCache = app.getIconCache(); + final Context context = taskController.getContext(); + final IconCache iconCache = taskController.getIconCache(); final String[] packages = mPackages; final int packageCount = packages.length; @@ -433,7 +431,7 @@ public class PackageUpdatedTask implements ModelUpdateTask { // Load widgets for the new package. Changes due to app updates are handled through // AppWidgetHost events, this is just to initialize the long-press options. for (int i = 0; i < packageCount; i++) { - dataModel.widgetsModel.update(app, new PackageUserKey(packages[i], mUser)); + dataModel.widgetsModel.update(new PackageUserKey(packages[i], mUser)); } taskController.bindUpdatedWidgets(dataModel); } diff --git a/src/com/android/launcher3/model/SdCardAvailableReceiver.java b/src/com/android/launcher3/model/SdCardAvailableReceiver.java index 9e3f0e1aa2..9ae8092345 100644 --- a/src/com/android/launcher3/model/SdCardAvailableReceiver.java +++ b/src/com/android/launcher3/model/SdCardAvailableReceiver.java @@ -22,7 +22,6 @@ import android.content.Intent; import android.content.pm.LauncherApps; import android.os.UserHandle; -import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherModel; import com.android.launcher3.util.ApplicationInfoWrapper; import com.android.launcher3.util.PackageUserKey; @@ -43,9 +42,10 @@ public class SdCardAvailableReceiver extends BroadcastReceiver { private final Context mContext; private final Set<PackageUserKey> mPackages; - public SdCardAvailableReceiver(LauncherAppState app, Set<PackageUserKey> packages) { - mModel = app.getModel(); - mContext = app.getContext(); + public SdCardAvailableReceiver( + Context context, LauncherModel model, Set<PackageUserKey> packages) { + mContext = context; + mModel = model; mPackages = packages; } diff --git a/src/com/android/launcher3/model/SessionFailureTask.kt b/src/com/android/launcher3/model/SessionFailureTask.kt index 8baf568ace..6ed5178ed7 100644 --- a/src/com/android/launcher3/model/SessionFailureTask.kt +++ b/src/com/android/launcher3/model/SessionFailureTask.kt @@ -33,9 +33,9 @@ class SessionFailureTask(val packageName: String, val user: UserHandle) : ModelU dataModel: BgDataModel, apps: AllAppsList, ) { - val iconCache = taskController.app.iconCache + val iconCache = taskController.iconCache val isAppArchived = - ApplicationInfoWrapper(taskController.app.context, packageName, user).isArchived() + ApplicationInfoWrapper(taskController.context, packageName, user).isArchived() synchronized(dataModel) { if (isAppArchived) { val updatedItems = mutableListOf<WorkspaceItemInfo>() diff --git a/src/com/android/launcher3/model/ShortcutsChangedTask.kt b/src/com/android/launcher3/model/ShortcutsChangedTask.kt index 56e9e43d3a..d6759e2d21 100644 --- a/src/com/android/launcher3/model/ShortcutsChangedTask.kt +++ b/src/com/android/launcher3/model/ShortcutsChangedTask.kt @@ -40,8 +40,7 @@ class ShortcutsChangedTask( dataModel: BgDataModel, apps: AllAppsList, ) { - val app = taskController.app - val context = app.context + val context = taskController.context // Find WorkspaceItemInfo's that have changed on the workspace. val matchingWorkspaceItems = ArrayList<WorkspaceItemInfo>() @@ -88,7 +87,7 @@ class ShortcutsChangedTask( .filter { itemInfo: WorkspaceItemInfo -> shortcutId == itemInfo.deepShortcutId } .forEach { workspaceItemInfo: WorkspaceItemInfo -> workspaceItemInfo.updateFromDeepShortcutInfo(fullDetails, context) - app.iconCache.getShortcutIcon( + taskController.iconCache.getShortcutIcon( workspaceItemInfo, CacheableShortcutInfo(fullDetails, infoWrapper), ) diff --git a/src/com/android/launcher3/model/UserLockStateChangedTask.java b/src/com/android/launcher3/model/UserLockStateChangedTask.java index 3dc5ff3b57..4d28ccb42d 100644 --- a/src/com/android/launcher3/model/UserLockStateChangedTask.java +++ b/src/com/android/launcher3/model/UserLockStateChangedTask.java @@ -23,7 +23,6 @@ import android.os.UserHandle; import androidx.annotation.NonNull; -import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherModel.ModelUpdateTask; import com.android.launcher3.LauncherSettings; import com.android.launcher3.model.data.WorkspaceItemInfo; @@ -55,8 +54,7 @@ public class UserLockStateChangedTask implements ModelUpdateTask { @Override public void execute(@NonNull ModelTaskController taskController, @NonNull BgDataModel dataModel, @NonNull AllAppsList apps) { - LauncherAppState app = taskController.getApp(); - Context context = app.getContext(); + Context context = taskController.getContext(); HashMap<ShortcutKey, ShortcutInfo> pinnedShortcuts = new HashMap<>(); if (mIsUserUnlocked) { @@ -92,7 +90,7 @@ public class UserLockStateChangedTask implements ModelUpdateTask { } si.runtimeStatusFlags &= ~FLAG_DISABLED_LOCKED_USER; si.updateFromDeepShortcutInfo(shortcut, context); - app.getIconCache().getShortcutIcon(si, shortcut); + taskController.getIconCache().getShortcutIcon(si, shortcut); } else { si.runtimeStatusFlags |= FLAG_DISABLED_LOCKED_USER; } diff --git a/src/com/android/launcher3/model/WidgetsModel.java b/src/com/android/launcher3/model/WidgetsModel.java index 52b142db67..f2144c0e3a 100644 --- a/src/com/android/launcher3/model/WidgetsModel.java +++ b/src/com/android/launcher3/model/WidgetsModel.java @@ -27,6 +27,7 @@ import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.LauncherAppState; import com.android.launcher3.Utilities; import com.android.launcher3.config.FeatureFlags; +import com.android.launcher3.dagger.ApplicationContext; import com.android.launcher3.icons.IconCache; import com.android.launcher3.icons.cache.CachedObject; import com.android.launcher3.model.data.PackageItemInfo; @@ -54,6 +55,8 @@ import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Collectors; +import javax.inject.Inject; + /** * Widgets data model that is used by the adapters of the widget views and controllers. * @@ -68,6 +71,29 @@ public class WidgetsModel { private final Map<PackageItemInfo, List<WidgetItem>> mWidgetsByPackageItem = new HashMap<>(); @Nullable private WidgetValidityCheckForPicker mWidgetValidityCheckForPicker = null; + private final Context mContext; + private final InvariantDeviceProfile mIdp; + private final IconCache mIconCache; + private final AppFilter mAppFilter; + + @Inject + public WidgetsModel( + @ApplicationContext Context context, + InvariantDeviceProfile idp, + IconCache iconCache, + AppFilter appFilter) { + mContext = context; + mIdp = idp; + mIconCache = iconCache; + mAppFilter = appFilter; + } + + public WidgetsModel(Context context) { + this(context, + LauncherAppState.getIDP(context), + LauncherAppState.getInstance(context).getIconCache(), new AppFilter(context)); + } + /** * Returns all widgets keyed by their component key. */ @@ -128,36 +154,33 @@ public class WidgetsModel { * @param packageUser If null, all widgets and shortcuts are updated and returned, otherwise * only widgets and shortcuts associated with the package/user are. */ - public List<CachedObject> update( - LauncherAppState app, @Nullable PackageUserKey packageUser) { + public List<CachedObject> update(@Nullable PackageUserKey packageUser) { if (!WIDGETS_ENABLED) { return new ArrayList<>(); } Preconditions.assertWorkerThread(); - Context context = app.getContext(); final ArrayList<WidgetItem> widgetsAndShortcuts = new ArrayList<>(); List<CachedObject> updatedItems = new ArrayList<>(); try { - InvariantDeviceProfile idp = app.getInvariantDeviceProfile(); // Widgets - WidgetManagerHelper widgetManager = new WidgetManagerHelper(context); + WidgetManagerHelper widgetManager = new WidgetManagerHelper(mContext); for (AppWidgetProviderInfo widgetInfo : widgetManager.getAllProviders(packageUser)) { LauncherAppWidgetProviderInfo launcherWidgetInfo = - LauncherAppWidgetProviderInfo.fromProviderInfo(context, widgetInfo); + LauncherAppWidgetProviderInfo.fromProviderInfo(mContext, widgetInfo); widgetsAndShortcuts.add(new WidgetItem( - launcherWidgetInfo, idp, app.getIconCache(), app.getContext())); + launcherWidgetInfo, mIdp, mIconCache, mContext)); updatedItems.add(launcherWidgetInfo); } // Shortcuts for (ShortcutConfigActivityInfo info : - queryList(context, packageUser)) { - widgetsAndShortcuts.add(new WidgetItem(info, app.getIconCache())); + queryList(mContext, packageUser)) { + widgetsAndShortcuts.add(new WidgetItem(info, mIconCache)); updatedItems.add(info); } - setWidgetsAndShortcuts(widgetsAndShortcuts, app, packageUser); + setWidgetsAndShortcuts(widgetsAndShortcuts, packageUser); } catch (Exception e) { if (!FeatureFlags.IS_STUDIO_BUILD && Utilities.isBinderSizeError(e)) { // the returned value may be incomplete and will not be refreshed until the next @@ -172,14 +195,14 @@ public class WidgetsModel { return updatedItems; } - private synchronized void setWidgetsAndShortcuts(ArrayList<WidgetItem> rawWidgetsShortcuts, - LauncherAppState app, @Nullable PackageUserKey packageUser) { + private synchronized void setWidgetsAndShortcuts( + ArrayList<WidgetItem> rawWidgetsShortcuts, @Nullable PackageUserKey packageUser) { if (DEBUG) { Log.d(TAG, "addWidgetsAndShortcuts, widgetsShortcuts#=" + rawWidgetsShortcuts.size()); } // Refresh the validity checker with latest app state. - mWidgetValidityCheckForPicker = new WidgetValidityCheckForPicker(app); + mWidgetValidityCheckForPicker = new WidgetValidityCheckForPicker(mIdp, mAppFilter); // Temporary cache for {@link PackageItemInfos} to avoid having to go through // {@link mPackageItemInfos} to locate the key to be used for {@link #mWidgetsList} @@ -196,19 +219,17 @@ public class WidgetsModel { // add and update. mWidgetsByPackageItem.putAll(rawWidgetsShortcuts.stream() .filter(new WidgetFlagCheck()) - .flatMap(widgetItem -> getPackageUserKeys(app.getContext(), widgetItem).stream() + .flatMap(widgetItem -> getPackageUserKeys(mContext, widgetItem).stream() .map(key -> new Pair<>(packageItemInfoCache.getOrCreate(key), widgetItem))) .collect(groupingBy(pair -> pair.first, mapping(pair -> pair.second, toList())))); // Update each package entry - IconCache iconCache = app.getIconCache(); for (PackageItemInfo p : packageItemInfoCache.values()) { - iconCache.getTitleAndIconForApp(p, DEFAULT_LOOKUP_FLAG.withUseLowRes()); + mIconCache.getTitleAndIconForApp(p, DEFAULT_LOOKUP_FLAG.withUseLowRes()); } } - public void onPackageIconsUpdated(Set<String> packageNames, UserHandle user, - LauncherAppState app) { + public void onPackageIconsUpdated(Set<String> packageNames, UserHandle user) { if (!WIDGETS_ENABLED) { return; } @@ -220,11 +241,10 @@ public class WidgetsModel { WidgetItem item = items.get(i); if (item.user.equals(user)) { if (item.activityInfo != null) { - items.set(i, new WidgetItem(item.activityInfo, app.getIconCache())); + items.set(i, new WidgetItem(item.activityInfo, mIconCache)); } else { - items.set(i, new WidgetItem(item.widgetInfo, - app.getInvariantDeviceProfile(), app.getIconCache(), - app.getContext())); + items.set(i, new WidgetItem( + item.widgetInfo, mIdp, mIconCache, mContext)); } } } @@ -277,9 +297,9 @@ public class WidgetsModel { private final InvariantDeviceProfile mIdp; private final AppFilter mAppFilter; - WidgetValidityCheckForPicker(LauncherAppState app) { - mIdp = app.getInvariantDeviceProfile(); - mAppFilter = new AppFilter(app.getContext()); + WidgetValidityCheckForPicker(InvariantDeviceProfile idp, AppFilter appFilter) { + mIdp = idp; + mAppFilter = appFilter; } @Override diff --git a/src/com/android/launcher3/model/WorkspaceItemProcessor.kt b/src/com/android/launcher3/model/WorkspaceItemProcessor.kt index 99f2837dd0..7b8f21866a 100644 --- a/src/com/android/launcher3/model/WorkspaceItemProcessor.kt +++ b/src/com/android/launcher3/model/WorkspaceItemProcessor.kt @@ -18,6 +18,7 @@ package com.android.launcher3.model import android.annotation.SuppressLint import android.appwidget.AppWidgetProviderInfo import android.content.ComponentName +import android.content.Context import android.content.Intent import android.content.pm.LauncherApps import android.content.pm.LauncherApps.ShortcutQuery @@ -29,10 +30,10 @@ import android.util.Log import android.util.LongSparseArray import com.android.launcher3.Flags import com.android.launcher3.InvariantDeviceProfile -import com.android.launcher3.LauncherAppState import com.android.launcher3.LauncherSettings.Favorites import com.android.launcher3.backuprestore.LauncherRestoreEventLogger.RestoreError import com.android.launcher3.icons.CacheableShortcutInfo +import com.android.launcher3.icons.IconCache import com.android.launcher3.icons.cache.CacheLookupFlag.Companion.DEFAULT_LOOKUP_FLAG import com.android.launcher3.logging.FileLog import com.android.launcher3.model.data.AppInfo @@ -70,7 +71,10 @@ class WorkspaceItemProcessor( private val launcherApps: LauncherApps, private val pendingPackages: MutableSet<PackageUserKey>, private val shortcutKeyToPinnedShortcuts: Map<ShortcutKey, ShortcutInfo>, - private val app: LauncherAppState, + private val context: Context, + private val idp: InvariantDeviceProfile, + private val iconCache: IconCache, + private val isSafeMode: Boolean, private val bgDataModel: BgDataModel, private val widgetProvidersMap: MutableMap<ComponentKey, AppWidgetProviderInfo?>, private val installingPkgs: HashMap<PackageUserKey, PackageInstaller.SessionInfo>, @@ -82,9 +86,7 @@ class WorkspaceItemProcessor( private val allDeepShortcuts: MutableList<CacheableShortcutInfo>, ) { - private val isSafeMode = app.isSafeModeEnabled private val tempPackageKey = PackageUserKey(null, null) - private val iconCache = app.iconCache /** * This is the entry point for processing 1 workspace item. This method is like the midfielder @@ -159,7 +161,7 @@ class WorkspaceItemProcessor( ) return } - val appInfoWrapper = ApplicationInfoWrapper(app.context, targetPkg, c.user) + val appInfoWrapper = ApplicationInfoWrapper(context, targetPkg, c.user) var validTarget = launcherApps.isPackageEnabled(targetPkg, c.user) // If it's a deep shortcut, we'll use pinned shortcuts to restore it @@ -306,7 +308,7 @@ class WorkspaceItemProcessor( ) return } - info = WorkspaceItemInfo(pinnedShortcut, app.context) + info = WorkspaceItemInfo(pinnedShortcut, context) // If the pinned deep shortcut is no longer published, // use the last saved icon instead of the default. val csi = CacheableShortcutInfo(pinnedShortcut, appInfoWrapper) @@ -369,7 +371,7 @@ class WorkspaceItemProcessor( info, activityInfo, userCache.getUserInfo(c.user), - ApiWrapper.INSTANCE[app.context], + ApiWrapper.INSTANCE[context], pmHelper, ) } @@ -529,7 +531,7 @@ class WorkspaceItemProcessor( (si == null) && (lapi == null) && !(Flags.enableSupportForArchiving() && - ApplicationInfoWrapper(app.context, component.packageName, c.user) + ApplicationInfoWrapper(context, component.packageName, c.user) .isArchived()) ) { // Restore never started @@ -553,7 +555,7 @@ class WorkspaceItemProcessor( if (si == null) 0 else (si.getProgress() * 100).toInt() appWidgetInfo.pendingItemInfo = WidgetsModel.newPendingItemInfo( - app.context, + context, appWidgetInfo.providerName, appWidgetInfo.user, ) @@ -563,7 +565,7 @@ class WorkspaceItemProcessor( WidgetSizes.updateWidgetSizeRangesAsync( appWidgetInfo.appWidgetId, lapi, - app.context, + context, appWidgetInfo.spanX, appWidgetInfo.spanY, ) @@ -586,7 +588,7 @@ class WorkspaceItemProcessor( " appWidgetId: ${c.appWidgetId}," + " component=${component}", ) - logWidgetInfo(app.invariantDeviceProfile, lapi) + logWidgetInfo(idp, lapi) } } c.checkAndAddItem(appWidgetInfo, bgDataModel, memoryLogger) diff --git a/src/com/android/launcher3/model/WorkspaceItemSpaceFinder.java b/src/com/android/launcher3/model/WorkspaceItemSpaceFinder.java index 1a6d1786e6..17f1615d8d 100644 --- a/src/com/android/launcher3/model/WorkspaceItemSpaceFinder.java +++ b/src/com/android/launcher3/model/WorkspaceItemSpaceFinder.java @@ -21,7 +21,7 @@ import static com.android.launcher3.WorkspaceLayoutManager.FIRST_SCREEN_ID; import android.util.LongSparseArray; import com.android.launcher3.InvariantDeviceProfile; -import com.android.launcher3.LauncherAppState; +import com.android.launcher3.LauncherModel; import com.android.launcher3.LauncherSettings; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.model.data.ItemInfo; @@ -31,23 +31,37 @@ import com.android.launcher3.util.IntSet; import java.util.ArrayList; +import javax.inject.Inject; + /** * Utility class to help find space for new workspace items */ public class WorkspaceItemSpaceFinder { + private BgDataModel mDataModel; + private InvariantDeviceProfile mIDP; + private LauncherModel mModel; + + @Inject + WorkspaceItemSpaceFinder( + BgDataModel dataModel, InvariantDeviceProfile idp, LauncherModel model) { + mDataModel = dataModel; + mIDP = idp; + mModel = model; + } + /** * Find a position on the screen for the given size or adds a new screen. * * @return screenId and the coordinates for the item in an int array of size 3. */ - public int[] findSpaceForItem(LauncherAppState app, BgDataModel dataModel, + public int[] findSpaceForItem( IntArray workspaceScreens, IntArray addedWorkspaceScreensFinal, int spanX, int spanY) { LongSparseArray<ArrayList<ItemInfo>> screenItems = new LongSparseArray<>(); // Use sBgItemsIdMap as all the items are already loaded. - synchronized (dataModel) { - for (ItemInfo info : dataModel.itemsIdMap) { + synchronized (mDataModel) { + for (ItemInfo info : mDataModel.itemsIdMap) { if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) { ArrayList<ItemInfo> items = screenItems.get(info.screenId); if (items == null) { @@ -75,7 +89,7 @@ public class WorkspaceItemSpaceFinder { for (int screen = 0; screen < screenCount; screen++) { screenId = workspaceScreens.get(screen); if (!screensToExclude.contains(screenId) && findNextAvailableIconSpaceInScreen( - app, screenItems.get(screenId), coordinates, spanX, spanY)) { + screenItems.get(screenId), coordinates, spanX, spanY)) { // We found a space for it found = true; break; @@ -84,7 +98,7 @@ public class WorkspaceItemSpaceFinder { if (!found) { // Still no position found. Add a new screen to the end. - screenId = app.getModel().getModelDbController().getNewScreenId(); + screenId = mModel.getModelDbController().getNewScreenId(); // Save the screen id for binding in the workspace workspaceScreens.add(screenId); @@ -92,7 +106,7 @@ public class WorkspaceItemSpaceFinder { // If we still can't find an empty space, then God help us all!!! if (!findNextAvailableIconSpaceInScreen( - app, screenItems.get(screenId), coordinates, spanX, spanY)) { + screenItems.get(screenId), coordinates, spanX, spanY)) { throw new RuntimeException("Can't find space to add the item"); } } @@ -100,11 +114,8 @@ public class WorkspaceItemSpaceFinder { } private boolean findNextAvailableIconSpaceInScreen( - LauncherAppState app, ArrayList<ItemInfo> occupiedPos, - int[] xy, int spanX, int spanY) { - InvariantDeviceProfile profile = app.getInvariantDeviceProfile(); - - GridOccupancy occupied = new GridOccupancy(profile.numColumns, profile.numRows); + ArrayList<ItemInfo> occupiedPos, int[] xy, int spanX, int spanY) { + GridOccupancy occupied = new GridOccupancy(mIDP.numColumns, mIDP.numRows); if (occupiedPos != null) { for (ItemInfo r : occupiedPos) { occupied.markCells(r, true); 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 37f51899aa..211263835c 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,9 +53,14 @@ 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. + * <p> + * TODO(b/402258632): Split PageIndicatorDots into 2 different classes: FolderPageIndicator & + * WorkspacePageIndicator. A lot of the functionality in this class is only used by one UI purpose. */ public class PageIndicatorDots extends View implements Insettable, PageIndicator { @@ -68,6 +75,11 @@ 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 int PAGE_INDICATOR_ALPHA = 255; private static final int DOT_ALPHA = 128; private static final float DOT_ALPHA_FRACTION = 0.5f; @@ -75,12 +87,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") { @@ -99,23 +113,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 mArrowEnd; + private final VectorDrawable mArrowStart; + private final Rect mArrowEndBounds = new Rect(); + private final Rect mArrowStartBounds = new Rect(); private int mNumPages; private int mActivePage; @@ -167,6 +185,8 @@ public class PageIndicatorDots extends View implements Insettable, PageIndicator : DOT_GAP_FACTOR * mDotRadius; setOutlineProvider(new MyOutlineProver()); mIsRtl = Utilities.isRtl(getResources()); + mArrowEnd = (VectorDrawable) getResources().getDrawable(R.drawable.ic_chevron_end); + mArrowStart = (VectorDrawable) getResources().getDrawable(R.drawable.ic_chevron_start); } @Override @@ -405,6 +425,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; @@ -419,11 +444,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); } @@ -468,12 +498,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 + mArrowStart.setAlpha(alpha); + int size = (int) (mGapWidth * 4); + mArrowStartBounds.left = (int) (sTempRect.left - mGapWidth - size); + mArrowStartBounds.top = (int) (y - size / 2); + mArrowStartBounds.right = (int) (sTempRect.left - mGapWidth); + mArrowStartBounds.bottom = (int) (y + size / 2); + mArrowStart.setBounds(mArrowStartBounds); + mArrowStart.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 @@ -495,10 +544,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; @@ -507,19 +556,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 + mArrowEnd.setAlpha(alpha); + int size = (int) (mGapWidth * 4); + mArrowEndBounds.left = (int) sTempRect.left; + mArrowEndBounds.top = (int) (y - size / 2); + mArrowEndBounds.right = (int) (int) (sTempRect.left + size); + mArrowEndBounds.bottom = (int) (y + size / 2); + mArrowEnd.setBounds(mArrowEndBounds); + mArrowEnd.draw(canvas); + } } else { // Here we draw the dots mPaginationPaint.setAlpha((int) (alpha * DOT_ALPHA_FRACTION)); @@ -538,6 +602,34 @@ 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 (withinExpandedBounds(mArrowStartBounds, ev)) { + mOnArrowClickListener.accept(Direction.START); + } else if (withinExpandedBounds(mArrowEndBounds, ev)) { + mOnArrowClickListener.accept(Direction.END); + } + return super.onTouchEvent(ev); + } + + // For larger Touch box + private boolean withinExpandedBounds(Rect rect, MotionEvent ev) { + Rect scaledRect = new Rect(); + scaledRect.set(rect); + + float verticalAdjustment = (scaledRect.bottom - scaledRect.top) * 2; + scaledRect.top -= verticalAdjustment; + scaledRect.bottom += verticalAdjustment; + + float horizontalAdjustment = (scaledRect.right - scaledRect.left) * 2; + scaledRect.left -= horizontalAdjustment; + scaledRect.right += horizontalAdjustment; + + return scaledRect.contains((int) ev.getX(), (int) ev.getY()); + } + private RectF getActiveRect() { float startCircle = (int) mCurrentPosition; float delta = mCurrentPosition - startCircle; @@ -590,8 +682,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/provider/LauncherDbUtils.kt b/src/com/android/launcher3/provider/LauncherDbUtils.kt index c92328d8a3..36413715e0 100644 --- a/src/com/android/launcher3/provider/LauncherDbUtils.kt +++ b/src/com/android/launcher3/provider/LauncherDbUtils.kt @@ -26,19 +26,17 @@ import android.os.PersistableBundle import android.os.Process import android.os.UserManager import android.text.TextUtils -import com.android.launcher3.LauncherAppState import com.android.launcher3.LauncherSettings import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP import com.android.launcher3.Utilities +import com.android.launcher3.dagger.LauncherComponentProvider.appComponent import com.android.launcher3.icons.IconCache -import com.android.launcher3.model.LoaderCursor import com.android.launcher3.model.UserManagerState import com.android.launcher3.pm.PinRequestHelper import com.android.launcher3.pm.UserCache import com.android.launcher3.shortcuts.ShortcutKey import com.android.launcher3.util.IntArray import com.android.launcher3.util.IntSet -import com.android.launcher3.util.PackageManagerHelper /** A set of utility methods for Launcher DB used for DB updates and migration. */ object LauncherDbUtils { @@ -155,12 +153,11 @@ object LauncherDbUtils { null, null, ) - val pmHelper = PackageManagerHelper.INSTANCE[context] val ums = UserManagerState() ums.run { init(UserCache.INSTANCE[context], context.getSystemService(UserManager::class.java)) } - val lc = LoaderCursor(c, LauncherAppState.getInstance(context), ums, pmHelper, null) + val lc = context.appComponent.loaderCursorFactory.createLoaderCursor(c, ums, null) val deletedShortcuts = IntSet() while (lc.moveToNext()) { 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/ItemInflater.kt b/src/com/android/launcher3/util/ItemInflater.kt index ebf4656c61..acb3c4ee12 100644 --- a/src/com/android/launcher3/util/ItemInflater.kt +++ b/src/com/android/launcher3/util/ItemInflater.kt @@ -24,6 +24,7 @@ import android.view.View.OnClickListener import android.view.View.OnFocusChangeListener import android.view.ViewGroup import com.android.launcher3.BubbleTextView +import com.android.launcher3.LauncherAppState import com.android.launcher3.LauncherSettings.Favorites import com.android.launcher3.R import com.android.launcher3.apppairs.AppPairIcon @@ -46,10 +47,11 @@ class ItemInflater<T>( private val widgetHolder: LauncherWidgetHolder, private val clickListener: OnClickListener, private val focusListener: OnFocusChangeListener, - private val defaultParent: ViewGroup + private val defaultParent: ViewGroup, ) where T : Context, T : ActivityContext { - private val widgetInflater = WidgetInflater(context) + private val widgetInflater = + WidgetInflater(context, LauncherAppState.getInstance(context).isSafeModeEnabled) @JvmOverloads fun inflateItem(item: ItemInfo, writer: ModelWriter, nullableParent: ViewGroup? = null): View? { @@ -75,7 +77,7 @@ class ItemInflater<T>( R.layout.folder_icon, context, parent, - item as FolderInfo + item as FolderInfo, ) Favorites.ITEM_TYPE_APP_PAIR -> return AppPairIcon.inflateIcon( @@ -83,7 +85,7 @@ class ItemInflater<T>( context, parent, item as AppPairInfo, - BubbleTextView.DISPLAY_WORKSPACE + BubbleTextView.DISPLAY_WORKSPACE, ) Favorites.ITEM_TYPE_APPWIDGET, Favorites.ITEM_TYPE_CUSTOM_APPWIDGET -> 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/util/window/WindowManagerProxy.java b/src/com/android/launcher3/util/window/WindowManagerProxy.java index 11f0bc21b9..68e032426b 100644 --- a/src/com/android/launcher3/util/window/WindowManagerProxy.java +++ b/src/com/android/launcher3/util/window/WindowManagerProxy.java @@ -520,6 +520,33 @@ public class WindowManagerProxy { */ default void onCanCreateDesksChanged(boolean canCreateDesks) { } + + /** + * Called when a new desk is added. + * + * @param displayId The ID of the display on which the desk was added. + * @param deskId The ID of the newly added desk. + */ + default void onDeskAdded(int displayId, int deskId) {} + + /** + * Called when an existing desk is removed. + * + * @param displayId The ID of the display on which the desk was removed. + * @param deskId The ID of the desk that was removed. + */ + default void onDeskRemoved(int displayId, int deskId) {} + + /** + * Called when the active desk changes. + * + * @param displayId The ID of the display on which the desk activation change is happening. + * @param newActiveDesk The ID of the new active desk or -1 if no desk is active anymore + * (i.e. exit desktop mode). + * @param oldActiveDesk The ID of the desk that was previously active, or -1 if no desk was + * active before. + */ + default void onActiveDeskChanged(int displayId, int newActiveDesk, int oldActiveDesk) {} } } diff --git a/src/com/android/launcher3/views/OptionsPopupView.java b/src/com/android/launcher3/views/OptionsPopupView.java index 82cc40d93b..8c01c5999f 100644 --- a/src/com/android/launcher3/views/OptionsPopupView.java +++ b/src/com/android/launcher3/views/OptionsPopupView.java @@ -19,6 +19,7 @@ import static com.android.launcher3.BuildConfig.WIDGETS_ENABLED; import static com.android.launcher3.LauncherState.EDIT_MODE; import static com.android.launcher3.config.FeatureFlags.MULTI_SELECT_EDIT_MODE; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.IGNORE; +import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALL_APPS_TAP_OR_LONGPRESS; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SETTINGS_BUTTON_TAP_OR_LONGPRESS; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_WIDGETSTRAY_BUTTON_TAP_OR_LONGPRESS; @@ -219,6 +220,11 @@ public class OptionsPopupView<T extends Context & ActivityContext> extends Arrow OptionsPopupView::enterHomeGardening)); } options.add(new OptionItem(launcher, + R.string.all_apps_home_screen, + R.drawable.ic_apps, + LAUNCHER_ALL_APPS_TAP_OR_LONGPRESS, + OptionsPopupView::enterAllApps)); + options.add(new OptionItem(launcher, R.string.settings_button_text, R.drawable.ic_setting, LAUNCHER_SETTINGS_BUTTON_TAP_OR_LONGPRESS, @@ -226,6 +232,20 @@ public class OptionsPopupView<T extends Context & ActivityContext> extends Arrow return options; } + /** + * Used by the options to open All Apps, uses an intent as to not tie the implementation of + * opening All Apps with OptionsPopup, instead it uses the public API to open All Apps. + */ + public static boolean enterAllApps(View view) { + Launcher launcher = Launcher.getLauncher(view.getContext()); + launcher.startActivity( + new Intent(Intent.ACTION_ALL_APPS) + .setComponent(launcher.getComponentName()) + .setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP) + ); + return true; + } + private static boolean enterHomeGardening(View view) { Launcher launcher = Launcher.getLauncher(view.getContext()); launcher.getStateManager().goToState(EDIT_MODE); 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/widget/WidgetInflater.kt b/src/com/android/launcher3/widget/WidgetInflater.kt index d6cadc73a8..03f680abb4 100644 --- a/src/com/android/launcher3/widget/WidgetInflater.kt +++ b/src/com/android/launcher3/widget/WidgetInflater.kt @@ -19,14 +19,21 @@ package com.android.launcher3.widget import android.content.Context import com.android.launcher3.BuildConfig import com.android.launcher3.Launcher -import com.android.launcher3.LauncherAppState import com.android.launcher3.backuprestore.LauncherRestoreEventLogger.RestoreError +import com.android.launcher3.dagger.ApplicationContext import com.android.launcher3.logging.FileLog import com.android.launcher3.model.data.LauncherAppWidgetInfo import com.android.launcher3.qsb.QsbContainerView +import javax.inject.Inject +import javax.inject.Named /** Utility class for handling widget inflation taking into account all the restore state updates */ -class WidgetInflater(private val context: Context) { +class WidgetInflater +@Inject +constructor( + @ApplicationContext private val context: Context, + @Named("SAFE_MODE") private val isSafeModeEnabled: Boolean, +) { private val widgetHelper = WidgetManagerHelper(context) @@ -41,9 +48,8 @@ class WidgetInflater(private val context: Context) { ) } } - if (LauncherAppState.INSTANCE.get(context).isSafeModeEnabled) { - return InflationResult(TYPE_PENDING) - } + if (isSafeModeEnabled) return InflationResult(TYPE_PENDING) + val appWidgetInfo: LauncherAppWidgetProviderInfo? var removalReason = "" @RestoreError var logReason = RestoreError.OTHER_WIDGET_INFLATION_FAIL diff --git a/src/com/android/launcher3/widget/picker/WidgetRecommendationsView.java b/src/com/android/launcher3/widget/picker/WidgetRecommendationsView.java index 4ccf16bb4e..ec916228db 100644 --- a/src/com/android/launcher3/widget/picker/WidgetRecommendationsView.java +++ b/src/com/android/launcher3/widget/picker/WidgetRecommendationsView.java @@ -16,6 +16,7 @@ package com.android.launcher3.widget.picker; +import static com.android.launcher3.widget.picker.WidgetRecommendationCategory.DEFAULT_WIDGET_RECOMMENDATION_CATEGORY; import static com.android.launcher3.widget.util.WidgetsTableUtils.groupWidgetItemsUsingRowPxWithReordering; import android.content.ComponentName; @@ -55,6 +56,7 @@ import java.util.stream.Collectors; public final class WidgetRecommendationsView extends PagedView<PageIndicatorDots> { private @Px float mAvailableHeight = Float.MAX_VALUE; private @Px float mAvailableWidth = 0; + private int mLastUiMode = -1; private static final String INITIALLY_DISPLAYED_WIDGETS_STATE_KEY = "widgetRecommendationsView:mDisplayedWidgets"; private static final int MAX_CATEGORIES = 3; @@ -151,41 +153,7 @@ public final class WidgetRecommendationsView extends PagedView<PageIndicatorDots mPageSwitchListeners.add(pageChangeListener); } - /** - * Displays all the provided recommendations in a single table if they fit. - * - * @param recommendedWidgets list of widgets to be displayed in recommendation section. - * @param deviceProfile the current {@link DeviceProfile} - * @param availableHeight height in px that can be used to display the recommendations; - * recommendations that don't fit in this height won't be shown - * @param availableWidth width in px that the recommendations should display in - * @param cellPadding padding in px that should be applied to each widget in the - * recommendations - * @return number of recommendations that could fit in the available space. - */ - public int setRecommendations( - List<WidgetItem> recommendedWidgets, DeviceProfile deviceProfile, - final @Px float availableHeight, final @Px int availableWidth, - final @Px int cellPadding) { - this.mAvailableHeight = availableHeight; - this.mAvailableWidth = availableWidth; - clear(); - - Set<ComponentName> displayedWidgets = maybeDisplayInTable(recommendedWidgets, - deviceProfile, - availableWidth, cellPadding); - - if (mDisplayedWidgets.isEmpty()) { - // Save the widgets shown for the first time user opened the picker; so that, they can - // be maintained across orientation changes. - mDisplayedWidgets = displayedWidgets; - } - - updateTitleAndIndicator(/* requestedPage= */ 0); - return displayedWidgets.size(); - } - - private boolean shouldShowFullPageView( + private boolean shouldShowSinglePageView( Map<WidgetRecommendationCategory, List<WidgetItem>> recommendations) { if (mShowFullPageViewIfLowDensity) { boolean hasLessCategories = recommendations.size() < MAX_CATEGORIES; @@ -213,63 +181,82 @@ public final class WidgetRecommendationsView extends PagedView<PageIndicatorDots * @param cellPadding padding in px that should be applied to each widget in the * recommendations * @param requestedPage page number to display initially. + * @param forceUpdate whether to re-render even if available space didn't change * @return number of recommendations that could fit in the available space. */ public int setRecommendations( Map<WidgetRecommendationCategory, List<WidgetItem>> recommendations, DeviceProfile deviceProfile, final @Px float availableHeight, - final @Px int availableWidth, final @Px int cellPadding, final int requestedPage) { - if (shouldShowFullPageView(recommendations)) { - // Show all widgets in single page with unlimited available height. - return setRecommendations( - recommendations.values().stream().flatMap(Collection::stream) - .collect(Collectors.toList()), - deviceProfile, /*availableHeight=*/ Float.MAX_VALUE, availableWidth, - cellPadding); + final @Px int availableWidth, final @Px int cellPadding, final int requestedPage, + final boolean forceUpdate) { + if (forceUpdate || shouldUpdate(availableWidth, availableHeight)) { + Context context = getContext(); + this.mAvailableHeight = availableHeight; + this.mAvailableWidth = availableWidth; + this.mLastUiMode = context.getResources().getConfiguration().uiMode; + + final Map<WidgetRecommendationCategory, List<WidgetItem>> mappedRecommendations; + if (shouldShowSinglePageView(recommendations)) { // map to single category. + mappedRecommendations = Map.of(DEFAULT_WIDGET_RECOMMENDATION_CATEGORY, + recommendations.values().stream().flatMap( + Collection::stream).toList()); + } else { + mappedRecommendations = recommendations; + } - } - this.mAvailableHeight = availableHeight; - this.mAvailableWidth = availableWidth; - Context context = getContext(); - // For purpose of recommendations section, we don't want paging dots to be halved in two - // pane display, so, we always provide isTwoPanels = "false". - mPageIndicator.setPauseScroll(/*pause=*/true, /*isTwoPanels=*/ false); - clear(); - - int displayedCategories = 0; - Set<ComponentName> allDisplayedWidgets = new HashSet<>(); - - // Render top MAX_CATEGORIES in separate tables. Each table becomes a page. - for (Map.Entry<WidgetRecommendationCategory, List<WidgetItem>> entry : - new TreeMap<>(recommendations).entrySet()) { - // If none of the recommendations for the category could fit in the mAvailableHeight, we - // don't want to add that category; and we look for the next one. - Set<ComponentName> displayedWidgetsForCategory = maybeDisplayInTable(entry.getValue(), - deviceProfile, - availableWidth, cellPadding); - if (!displayedWidgetsForCategory.isEmpty()) { - mCategoryTitles.add( - context.getResources().getString(entry.getKey().categoryTitleRes)); - displayedCategories++; - allDisplayedWidgets.addAll(displayedWidgetsForCategory); + // For purpose of recommendations section, we don't want paging dots to be halved in two + // pane display, so, we always provide isTwoPanels = "false". + mPageIndicator.setPauseScroll(/*pause=*/true, /*isTwoPanels=*/ false); + clear(); + + int displayedCategories = 0; + Set<ComponentName> allDisplayedWidgets = new HashSet<>(); + + // Render top MAX_CATEGORIES in separate tables. Each table becomes a page. + for (Map.Entry<WidgetRecommendationCategory, List<WidgetItem>> entry : + new TreeMap<>(mappedRecommendations).entrySet()) { + // If none of the recommendations for the category could fit in the + // mAvailableHeight, we don't want to add that category; and we look for the next + // one. + Set<ComponentName> displayedWidgetsForCategory = maybeDisplayInTable( + entry.getValue(), + deviceProfile, + availableWidth, cellPadding); + if (!displayedWidgetsForCategory.isEmpty()) { + mCategoryTitles.add( + context.getResources().getString(entry.getKey().categoryTitleRes)); + displayedCategories++; + allDisplayedWidgets.addAll(displayedWidgetsForCategory); + } + + if (displayedCategories == MAX_CATEGORIES) { + break; + } } - if (displayedCategories == MAX_CATEGORIES) { - break; + if (mDisplayedWidgets.isEmpty()) { + // Save the widgets shown for the first time user opened the picker; so that, + // they can + // be maintained across orientation changes. + mDisplayedWidgets = allDisplayedWidgets; } - } - if (mDisplayedWidgets.isEmpty()) { - // Save the widgets shown for the first time user opened the picker; so that, they can - // be maintained across orientation changes. - mDisplayedWidgets = allDisplayedWidgets; + updateTitleAndIndicator(requestedPage); + // For purpose of recommendations section, we don't want paging dots to be halved in two + // pane display, so, we always provide isTwoPanels = "false". + mPageIndicator.setPauseScroll(/*pause=*/false, /*isTwoPanels=*/false); + return allDisplayedWidgets.size(); + } else { + return mDisplayedWidgets.size(); } + } - updateTitleAndIndicator(requestedPage); - // For purpose of recommendations section, we don't want paging dots to be halved in two - // pane display, so, we always provide isTwoPanels = "false". - mPageIndicator.setPauseScroll(/*pause=*/false, /*isTwoPanels=*/false); - return allDisplayedWidgets.size(); + /** + * Returns if we should re-render the views. + */ + private boolean shouldUpdate(int availableWidth, float availableHeight) { + return this.mAvailableWidth != availableWidth || this.mAvailableHeight != availableHeight + || getContext().getResources().getConfiguration().uiMode != this.mLastUiMode; } private void clear() { diff --git a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java index b0abf230b5..44c0ebde4b 100644 --- a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java +++ b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java @@ -628,10 +628,12 @@ public class WidgetsFullSheet extends BaseWidgetSheet if (mIsInSearchMode) { return; } + boolean forceUpdate = false; // We avoid applying new recommendations when some are already displayed. if (mRecommendedWidgetsMap.isEmpty()) { mRecommendedWidgetsMap = mActivityContext.getWidgetPickerDataProvider().get().getRecommendations(); + forceUpdate = true; } mRecommendedWidgetsCount = mWidgetRecommendationsView.setRecommendations( mRecommendedWidgetsMap, @@ -639,7 +641,8 @@ public class WidgetsFullSheet extends BaseWidgetSheet /* availableHeight= */ getMaxAvailableHeightForRecommendations(), /* availableWidth= */ mMaxSpanPerRow, /* cellPadding= */ mWidgetCellHorizontalPadding, - /* requestedPage= */ mRecommendationsCurrentPage + /* requestedPage= */ mRecommendationsCurrentPage, + /* forceUpdate= */ forceUpdate ); mWidgetRecommendationsContainer.setVisibility( diff --git a/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinder.java b/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinder.java index 679b0f566b..fc99fccf11 100644 --- a/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinder.java +++ b/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinder.java @@ -112,19 +112,46 @@ public final class WidgetsListTableViewHolderBinder // Bind the widget items. for (int i = 0; i < widgetItemsTable.size(); i++) { List<WidgetItem> widgetItemsPerRow = widgetItemsTable.get(i); - for (int j = 0; j < widgetItemsPerRow.size(); j++) { - WidgetTableRow row = (WidgetTableRow) table.getChildAt(i); + WidgetTableRow row = (WidgetTableRow) table.getChildAt(i); + + if (areRowItemsUnchanged(row, widgetItemsPerRow)) { // Just show widgets in row as is row.setVisibility(View.VISIBLE); - WidgetCell widget = (WidgetCell) row.getChildAt(j); - widget.clear(); - widget.addPreviewReadyListener(row); - WidgetItem widgetItem = widgetItemsPerRow.get(j); - widget.setVisibility(View.VISIBLE); + for (int j = 0; j < widgetItemsPerRow.size(); j++) { + WidgetCell widget = (WidgetCell) row.getChildAt(j); + widget.setVisibility(View.VISIBLE); + } + } else { + for (int j = 0; j < widgetItemsPerRow.size(); j++) { + row.setVisibility(View.VISIBLE); + WidgetCell widget = (WidgetCell) row.getChildAt(j); + widget.clear(); + WidgetItem widgetItem = widgetItemsPerRow.get(j); + widget.addPreviewReadyListener(row); + widget.setVisibility(View.VISIBLE); + + widget.applyFromCellItem(widgetItem); + widget.requestLayout(); + } + } + } + } + + private boolean areRowItemsUnchanged(WidgetTableRow row, List<WidgetItem> widgetItemsPerRow) { + // NOTE: on rotation or fold / unfold, we bind different view holders + // so, we don't any special handling for that case. + if (row.getChildCount() != widgetItemsPerRow.size()) { // Items not equal + return false; + } - widget.applyFromCellItem(widgetItem); - widget.requestLayout(); + for (int j = 0; j < widgetItemsPerRow.size(); j++) { + WidgetCell widgetCell = (WidgetCell) row.getChildAt(j); + WidgetItem widgetItem = widgetItemsPerRow.get(j); + if (widgetCell.getWidgetItem() == null + || !widgetCell.getWidgetItem().equals(widgetItem)) { + return false; // Items at given position in row aren't same. } } + return true; } /** @@ -151,26 +178,31 @@ public final class WidgetsListTableViewHolderBinder tableRow.setGravity(Gravity.TOP); table.addView(tableRow); } - // Pass resize delay to let the "move" and "change" animations run before resizing the - // row. - tableRow.setupRow(widgetItems.size(), - /*resizeDelayMs=*/ WIDGET_LIST_ITEM_APPEARANCE_START_DELAY); - if (tableRow.getChildCount() > widgetItems.size()) { - for (int j = widgetItems.size(); j < tableRow.getChildCount(); j++) { - tableRow.getChildAt(j).setVisibility(View.GONE); - } - } else { - for (int j = tableRow.getChildCount(); j < widgetItems.size(); j++) { - WidgetCell widget = (WidgetCell) mLayoutInflater.inflate( - R.layout.widget_cell, tableRow, false); - // set up touch. - widget.setOnClickListener(mIconClickListener); - widget.addPreviewReadyListener(tableRow); - View preview = widget.findViewById(R.id.widget_preview_container); - preview.setOnClickListener(mIconClickListener); - preview.setOnLongClickListener(mIconLongClickListener); - widget.setAnimatePreview(false); - tableRow.addView(widget); + + // If the row items are unchanged, we don't need to re-setup the row or the items; + // we can just show the row as is. + if (!areRowItemsUnchanged(tableRow, widgetItems)) { + // Pass resize delay to let the "move" and "change" animations run before resizing + // the row. + tableRow.setupRow(widgetItems.size(), + /*resizeDelayMs=*/ WIDGET_LIST_ITEM_APPEARANCE_START_DELAY); + if (tableRow.getChildCount() > widgetItems.size()) { + for (int j = widgetItems.size(); j < tableRow.getChildCount(); j++) { + tableRow.getChildAt(j).setVisibility(View.GONE); + } + } else { + for (int j = tableRow.getChildCount(); j < widgetItems.size(); j++) { + WidgetCell widget = (WidgetCell) mLayoutInflater.inflate( + R.layout.widget_cell, tableRow, false); + // set up touch. + widget.setOnClickListener(mIconClickListener); + widget.addPreviewReadyListener(tableRow); + View preview = widget.findViewById(R.id.widget_preview_container); + preview.setOnClickListener(mIconClickListener); + preview.setOnLongClickListener(mIconLongClickListener); + widget.setAnimatePreview(false); + tableRow.addView(widget); + } } } } 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; } |