Merge "Change the misused logic" into main
diff --git a/go/quickstep/src/com/android/launcher3/util/MainThreadInitializedObject.java b/go/quickstep/src/com/android/launcher3/util/MainThreadInitializedObject.java
new file mode 100644
index 0000000..e1f3508
--- /dev/null
+++ b/go/quickstep/src/com/android/launcher3/util/MainThreadInitializedObject.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2018 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.util;
+
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+
+import android.content.Context;
+import android.os.Looper;
+
+import java.util.concurrent.ExecutionException;
+
+/**
+ * Utility class for defining singletons which are initiated on main thread.
+ */
+public class MainThreadInitializedObject<T extends SafeCloseable> {
+
+ private final ObjectProvider<T> mProvider;
+ private T mValue;
+
+ public MainThreadInitializedObject(ObjectProvider<T> provider) {
+ mProvider = provider;
+ }
+
+ public T get(Context context) {
+ Context app = context.getApplicationContext();
+ if (app instanceof ObjectSandbox sc) {
+ return sc.getObject(this);
+ }
+
+ if (mValue == null) {
+ if (Looper.myLooper() == Looper.getMainLooper()) {
+ mValue = TraceHelper.allowIpcs("main.thread.object", () -> mProvider.get(app));
+ } else {
+ try {
+ return MAIN_EXECUTOR.submit(() -> get(context)).get();
+ } catch (InterruptedException | ExecutionException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+ return mValue;
+ }
+
+ public interface ObjectProvider<T> {
+
+ T get(Context context);
+ }
+
+ /** Sandbox for isolating {@link MainThreadInitializedObject} instances from Launcher. */
+ public interface ObjectSandbox {
+
+ /**
+ * Find a cached object from mObjectMap if we have already created one. If not, generate
+ * an object using the provider.
+ */
+ <T extends SafeCloseable> T getObject(MainThreadInitializedObject<T> object);
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index 6241111..ade75eb 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -97,12 +97,12 @@
import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.R;
+import com.android.launcher3.allapps.ActivityAllAppsContainerView;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.apppairs.AppPairIcon;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.desktop.DesktopAppLaunchTransition;
import com.android.launcher3.desktop.DesktopAppLaunchTransition.AppLaunchType;
-import com.android.launcher3.dot.DotInfo;
import com.android.launcher3.folder.Folder;
import com.android.launcher3.folder.FolderIcon;
import com.android.launcher3.logger.LauncherAtom;
@@ -149,6 +149,7 @@
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.Executors;
+import com.android.launcher3.util.LauncherBindableItemsContainer;
import com.android.launcher3.util.MultiPropertyFactory;
import com.android.launcher3.util.NavigationMode;
import com.android.launcher3.util.RunnableList;
@@ -877,17 +878,23 @@
}
}
- @Override
- public DotInfo getDotInfoForItem(ItemInfo info) {
- return getPopupDataProvider().getDotInfoForItem(info);
- }
-
@NonNull
@Override
public PopupDataProvider getPopupDataProvider() {
return mControllers.taskbarPopupController.getPopupDataProvider();
}
+ @NonNull
+ @Override
+ public LauncherBindableItemsContainer getContent() {
+ return mControllers.taskbarViewController.getContent();
+ }
+
+ @Override
+ public ActivityAllAppsContainerView<?> getAppsView() {
+ return mControllers.taskbarAllAppsController.getAppsView();
+ }
+
@Override
public View.AccessibilityDelegate getAccessibilityDelegate() {
return mAccessibilityDelegate;
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java
index 5d8b821..a9ee584 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java
@@ -35,11 +35,7 @@
import com.android.launcher3.Flags;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.R;
-import com.android.launcher3.dot.FolderDotInfo;
-import com.android.launcher3.folder.Folder;
-import com.android.launcher3.folder.FolderIcon;
import com.android.launcher3.model.data.AppInfo;
-import com.android.launcher3.model.data.FolderInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.notification.NotificationListener;
@@ -49,8 +45,6 @@
import com.android.launcher3.shortcuts.DeepShortcutView;
import com.android.launcher3.splitscreen.SplitShortcut;
import com.android.launcher3.util.ComponentKey;
-import com.android.launcher3.util.LauncherBindableItemsContainer;
-import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.util.ShortcutUtil;
import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
import com.android.launcher3.views.ActivityContext;
@@ -65,7 +59,6 @@
import java.util.HashMap;
import java.util.List;
import java.util.Objects;
-import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -93,7 +86,7 @@
public TaskbarPopupController(TaskbarActivityContext context) {
mContext = context;
- mPopupDataProvider = new PopupDataProvider(this::updateNotificationDots);
+ mPopupDataProvider = new PopupDataProvider(mContext);
}
public void init(TaskbarControllers controllers) {
@@ -132,39 +125,6 @@
mAllowInitialSplitSelection = allowInitialSplitSelection;
}
- private void updateNotificationDots(Predicate<PackageUserKey> updatedDots) {
- final PackageUserKey packageUserKey = new PackageUserKey(null, null);
- Predicate<ItemInfo> matcher = info -> !packageUserKey.updateFromItemInfo(info)
- || updatedDots.test(packageUserKey);
-
- LauncherBindableItemsContainer.ItemOperator op = (info, v) -> {
- if (info instanceof WorkspaceItemInfo && v instanceof BubbleTextView) {
- if (matcher.test(info)) {
- ((BubbleTextView) v).applyDotState(info, true /* animate */);
- }
- } else if (info instanceof FolderInfo && v instanceof FolderIcon) {
- FolderInfo fi = (FolderInfo) info;
- if (fi.anyMatch(matcher)) {
- FolderDotInfo folderDotInfo = new FolderDotInfo();
- for (ItemInfo si : fi.getContents()) {
- folderDotInfo.addDotInfo(mPopupDataProvider.getDotInfoForItem(si));
- }
- ((FolderIcon) v).setDotInfo(folderDotInfo);
- }
- }
-
- // process all the shortcuts
- return false;
- };
-
- mControllers.taskbarViewController.mapOverItems(op);
- Folder folder = Folder.getOpen(mContext);
- if (folder != null) {
- folder.iterateOverItems(op);
- }
- mControllers.taskbarAllAppsController.updateNotificationDots(updatedDots);
- }
-
/**
* Shows the notifications and deep shortcuts associated with a Taskbar {@param icon}.
* @return the container if shown or null.
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
index c92f20b..de8e286 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
@@ -62,7 +62,6 @@
import com.android.launcher3.taskbar.customization.TaskbarDividerContainer;
import com.android.launcher3.uioverrides.PredictedAppIcon;
import com.android.launcher3.util.DisplayController;
-import com.android.launcher3.util.LauncherBindableItemsContainer;
import com.android.launcher3.util.Themes;
import com.android.launcher3.views.ActivityContext;
import com.android.quickstep.util.GroupTask;
@@ -1098,20 +1097,6 @@
}
/**
- * Maps {@code op} over all the child views.
- */
- public void mapOverItems(LauncherBindableItemsContainer.ItemOperator op) {
- // map over all the shortcuts on the taskbar
- for (int i = 0; i < getChildCount(); i++) {
- View item = getChildAt(i);
- // TODO(b/344657629): Support GroupTask as well for notification dots/popup
- if (item.getTag() instanceof ItemInfo itemInfo && op.evaluate(itemInfo, item)) {
- return;
- }
- }
- }
-
- /**
* Finds the first icon to match one of the given matchers, from highest to lowest priority.
*
* @return The first match, or All Apps button if no match was found.
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
index 384468c..6ae13d4 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
@@ -83,14 +83,16 @@
import com.android.launcher3.model.data.TaskItemInfo;
import com.android.launcher3.taskbar.bubbles.BubbleBarController;
import com.android.launcher3.taskbar.bubbles.BubbleControllers;
+import com.android.launcher3.taskbar.customization.TaskbarAllAppsButtonContainer;
+import com.android.launcher3.taskbar.customization.TaskbarDividerContainer;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.ItemInfoMatcher;
import com.android.launcher3.util.LauncherBindableItemsContainer;
-import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext;
import com.android.launcher3.util.MultiPropertyFactory;
import com.android.launcher3.util.MultiPropertyFactory.MultiProperty;
import com.android.launcher3.util.MultiTranslateDelegate;
import com.android.launcher3.util.MultiValueAlpha;
+import com.android.launcher3.util.SandboxContext;
import com.android.quickstep.util.GroupTask;
import com.android.quickstep.util.SingleTask;
import com.android.systemui.shared.recents.model.Task;
@@ -735,10 +737,21 @@
for (View iconView : getIconViews()) {
if (iconView instanceof BubbleTextView btv) {
btv.updateRunningState(getRunningAppState(btv));
+ if (shouldUpdateIconContentDescription(btv)) {
+ btv.setContentDescription(
+ btv.getContentDescription() + " " + btv.getIconStateDescription());
+ }
}
}
}
+ private boolean shouldUpdateIconContentDescription(BubbleTextView btv) {
+ boolean isInDesktopMode = mControllers.taskbarDesktopModeController.isInDesktopMode();
+ boolean isAllAppsButton = btv instanceof TaskbarAllAppsButtonContainer;
+ boolean isDividerButton = btv instanceof TaskbarDividerContainer;
+ return isInDesktopMode && !isAllAppsButton && !isDividerButton;
+ }
+
/**
* @return A set of Task ids of running apps that are pinned in the taskbar.
*/
@@ -1167,11 +1180,8 @@
mTaskbarNavButtonTranslationY.updateValue(-deviceProfile.getTaskbarOffsetY());
}
- /**
- * Maps the given operator to all the top-level children of TaskbarView.
- */
- public void mapOverItems(LauncherBindableItemsContainer.ItemOperator op) {
- mTaskbarView.mapOverItems(op);
+ public LauncherBindableItemsContainer getContent() {
+ return mModelCallbacks;
}
/**
diff --git a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsController.java b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsController.java
index ddbf3b7..6c55b28 100644
--- a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsController.java
@@ -35,7 +35,6 @@
import java.util.Collections;
import java.util.List;
import java.util.Map;
-import java.util.function.Predicate;
/**
* Handles the all apps overlay window initialization, updates, and its data.
* <p>
@@ -120,13 +119,6 @@
mZeroStateSearchSuggestions = zeroStateSearchSuggestions;
}
- /** Updates the current notification dots. */
- public void updateNotificationDots(Predicate<PackageUserKey> updatedDots) {
- if (mAppsView != null) {
- mAppsView.getAppsStore().updateNotificationDots(updatedDots);
- }
- }
-
/** Toggles visibility of {@link TaskbarAllAppsContainerView} in the overlay window. */
public void toggle() {
toggle(false);
@@ -218,6 +210,11 @@
mAppsView = null;
}
+ @Nullable
+ public TaskbarAllAppsContainerView getAppsView() {
+ return mAppsView;
+ }
+
@VisibleForTesting
public int getTaskbarAllAppsTopPadding() {
// Allow null-pointer since this should only be null if the apps view is not showing.
diff --git a/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayContext.java b/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayContext.java
index 0fce237..636d89b 100644
--- a/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayContext.java
@@ -18,12 +18,11 @@
import android.content.Context;
import android.view.View;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.R;
-import com.android.launcher3.dot.DotInfo;
-import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.popup.PopupDataProvider;
import com.android.launcher3.taskbar.BaseTaskbarContext;
import com.android.launcher3.taskbar.TaskbarActivityContext;
@@ -125,6 +124,7 @@
return mDragController::startDragOnLongClick;
}
+ @NonNull
@Override
public PopupDataProvider getPopupDataProvider() {
return mTaskbarContext.getPopupDataProvider();
@@ -136,11 +136,6 @@
}
@Override
- public DotInfo getDotInfoForItem(ItemInfo info) {
- return mTaskbarContext.getDotInfoForItem(info);
- }
-
- @Override
public void onDragStart() {}
@Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.kt b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.kt
index b230fa6..79328df 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.kt
+++ b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.kt
@@ -36,8 +36,9 @@
import com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_X
import com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_Y
import com.android.launcher3.states.StateAnimationConfig.SKIP_OVERVIEW
-import com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VALUE
import com.android.quickstep.util.AnimUtils
+import com.android.quickstep.views.AddDesktopButton
+import com.android.quickstep.views.ClearAllButton
import com.android.quickstep.views.RecentsView
import com.android.quickstep.views.RecentsView.ADJACENT_PAGE_HORIZONTAL_OFFSET
import com.android.quickstep.views.RecentsView.CONTENT_ALPHA
@@ -287,8 +288,8 @@
val clearAllButtonAlpha =
if (state.areElementsVisible(launcher, LauncherState.CLEAR_ALL_BUTTON)) 1f else 0f
propertySetter.setFloat(
- recentsView.clearAllButton.visibilityAlphaProperty,
- MULTI_PROPERTY_VALUE,
+ recentsView.clearAllButton,
+ ClearAllButton.VISIBILITY_ALPHA,
clearAllButtonAlpha,
LINEAR,
)
@@ -302,8 +303,8 @@
)
recentsView.addDeskButton?.let {
propertySetter.setFloat(
- it.visibilityAlphaProperty,
- MULTI_PROPERTY_VALUE,
+ it,
+ AddDesktopButton.VISIBILITY_ALPHA,
if (state.areElementsVisible(launcher, LauncherState.ADD_DESK_BUTTON)) 1f else 0f,
LINEAR,
)
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewDismissTouchController.kt b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewDismissTouchController.kt
index 88b7155..454a307 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewDismissTouchController.kt
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewDismissTouchController.kt
@@ -36,7 +36,6 @@
import com.android.quickstep.views.TaskView
import com.google.android.msdl.data.model.MSDLToken
import kotlin.math.abs
-import kotlin.math.sign
/** Touch controller for handling task view card dismiss swipes */
class TaskViewDismissTouchController<CONTAINER>(
@@ -53,6 +52,8 @@
recentsView.pagedOrientationHandler.upDownSwipeDirection,
)
private val isRtl = isRtl(container.resources)
+ private val upDirection: Int = recentsView.pagedOrientationHandler.getUpDirection(isRtl)
+
private val tempTaskThumbnailBounds = Rect()
private var taskBeingDragged: TaskView? = null
@@ -96,7 +97,11 @@
}
onControllerTouchEvent(ev)
- return detector.isDraggingState && detector.wasInitialTouchPositive()
+ val upDirectionIsPositive = upDirection == SingleAxisSwipeDetector.DIRECTION_POSITIVE
+ val wasInitialTouchUp =
+ (upDirectionIsPositive && detector.wasInitialTouchPositive()) ||
+ (!upDirectionIsPositive && !detector.wasInitialTouchPositive())
+ return detector.isDraggingState && wasInitialTouchUp
}
override fun onControllerTouchEvent(ev: MotionEvent?): Boolean = detector.onTouchEvent(ev)
@@ -107,25 +112,27 @@
if (!canInterceptTouch(ev)) {
return false
}
-
taskBeingDragged =
recentsView.taskViews
.firstOrNull {
recentsView.isTaskViewVisible(it) && container.dragLayer.isEventOverView(it, ev)
}
?.also {
+ val secondaryLayerDimension =
+ recentsView.pagedOrientationHandler.getSecondaryDimension(
+ container.dragLayer
+ )
// Dismiss length as bottom of task so it is fully off screen when dismissed.
it.getThumbnailBounds(tempTaskThumbnailBounds, relativeToDragLayer = true)
- dismissLength = tempTaskThumbnailBounds.bottom
+ dismissLength =
+ recentsView.pagedOrientationHandler.getTaskDismissLength(
+ secondaryLayerDimension,
+ tempTaskThumbnailBounds,
+ )
verticalFactor =
- recentsView.pagedOrientationHandler.secondaryTranslationDirectionFactor
+ recentsView.pagedOrientationHandler.getTaskDismissVerticalDirection()
}
-
- detector.setDetectableScrollConditions(
- recentsView.pagedOrientationHandler.getUpDirection(isRtl),
- /* ignoreSlop = */ false,
- )
-
+ detector.setDetectableScrollConditions(upDirection, /* ignoreSlop= */ false)
return true
}
@@ -148,8 +155,8 @@
boundToRange(abs(currentDisplacement), 0f, dismissLength.toFloat())
// When swiping below origin, allow slight undershoot to simulate resisting the movement.
val totalDisplacement =
- if (isDisplacementPositiveDirection(currentDisplacement))
- boundedDisplacement * sign(currentDisplacement)
+ if (recentsView.pagedOrientationHandler.isGoingUp(currentDisplacement, isRtl))
+ boundedDisplacement * verticalFactor
else
mapToRange(
boundedDisplacement,
@@ -158,7 +165,7 @@
0f,
container.resources.getDimension(R.dimen.task_dismiss_max_undershoot),
DECELERATE,
- )
+ ) * -verticalFactor
taskBeingDragged.secondaryDismissTranslationProperty.setValue(
taskBeingDragged,
totalDisplacement,
@@ -207,8 +214,9 @@
}
val isBeyondDismissThreshold =
abs(currentDisplacement) > abs(DISMISS_THRESHOLD_FRACTION * dismissLength)
- val isFlingingTowardsDismiss = detector.isFling(velocity) && velocity < 0
- val isFlingingTowardsRestState = detector.isFling(velocity) && velocity > 0
+ val velocityIsGoingUp = recentsView.pagedOrientationHandler.isGoingUp(velocity, isRtl)
+ val isFlingingTowardsDismiss = detector.isFling(velocity) && velocityIsGoingUp
+ val isFlingingTowardsRestState = detector.isFling(velocity) && !velocityIsGoingUp
val isDismissing =
isFlingingTowardsDismiss || (isBeyondDismissThreshold && !isFlingingTowardsRestState)
springAnimation =
@@ -232,10 +240,6 @@
}
}
- // Returns if the current task being dragged is towards "positive" (e.g. dismissal).
- private fun isDisplacementPositiveDirection(displacement: Float): Boolean =
- sign(displacement) == sign(verticalFactor.toFloat())
-
private fun clearState() {
detector.finishedScrolling()
detector.setDetectableScrollConditions(0, false)
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewLaunchTouchController.kt b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewLaunchTouchController.kt
index c740dad..8ee552d 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewLaunchTouchController.kt
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewLaunchTouchController.kt
@@ -53,6 +53,7 @@
recentsView.pagedOrientationHandler.upDownSwipeDirection,
)
private val isRtl = isRtl(container.resources)
+ private val downDirection = recentsView.pagedOrientationHandler.getDownDirection(isRtl)
private var taskBeingDragged: TaskView? = null
private var launchEndDisplacement: Float = 0f
@@ -104,7 +105,11 @@
}
}
onControllerTouchEvent(ev)
- return detector.isDraggingState && !detector.wasInitialTouchPositive()
+ val downDirectionIsNegative = downDirection == SingleAxisSwipeDetector.DIRECTION_NEGATIVE
+ val wasInitialTouchDown =
+ (downDirectionIsNegative && !detector.wasInitialTouchPositive()) ||
+ (!downDirectionIsNegative && detector.wasInitialTouchPositive())
+ return detector.isDraggingState && wasInitialTouchDown
}
override fun onControllerTouchEvent(ev: MotionEvent) = detector.onTouchEvent(ev)
@@ -120,15 +125,12 @@
}
?.also {
verticalFactor =
- recentsView.pagedOrientationHandler.secondaryTranslationDirectionFactor
+ recentsView.pagedOrientationHandler.getTaskDragDisplacementFactor(isRtl)
}
if (!canTaskLaunchTaskView(taskBeingDragged)) {
return false
}
- detector.setDetectableScrollConditions(
- recentsView.pagedOrientationHandler.getDownDirection(isRtl),
- /* ignoreSlop = */ false,
- )
+ detector.setDetectableScrollConditions(downDirection, /* ignoreSlop= */ false)
return true
}
@@ -143,7 +145,10 @@
recentsView.createTaskLaunchAnimation(taskBeingDragged, maxDuration, ZOOM_IN)
// Since the thumbnail is what is filling the screen, based the end displacement on it.
taskBeingDragged.getThumbnailBounds(tempRect, /* relativeToDragLayer= */ true)
- launchEndDisplacement = (secondaryLayerDimension - tempRect.bottom).toFloat()
+ launchEndDisplacement =
+ recentsView.pagedOrientationHandler
+ .getTaskLaunchLength(secondaryLayerDimension, tempRect)
+ .toFloat() * verticalFactor
playbackController =
pendingAnimation.createPlaybackController()?.apply {
taskViewRecentsTouchContext.onUserControlledAnimationCreated(this)
@@ -163,8 +168,9 @@
val isBeyondLaunchThreshold =
abs(playbackController.progressFraction) > abs(LAUNCH_THRESHOLD_FRACTION)
- val isFlingingTowardsLaunch = detector.isFling(velocity) && velocity > 0
- val isFlingingTowardsRestState = detector.isFling(velocity) && velocity < 0
+ val velocityIsNegative = !recentsView.pagedOrientationHandler.isGoingUp(velocity, isRtl)
+ val isFlingingTowardsLaunch = detector.isFling(velocity) && velocityIsNegative
+ val isFlingingTowardsRestState = detector.isFling(velocity) && !velocityIsNegative
val isLaunching =
isFlingingTowardsLaunch || (isBeyondLaunchThreshold && !isFlingingTowardsRestState)
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
index 51f2a33..2631efe 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
@@ -26,7 +26,6 @@
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_Y;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_SCRIM_FADE;
import static com.android.launcher3.states.StateAnimationConfig.SKIP_OVERVIEW;
-import static com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VALUE;
import static com.android.quickstep.fallback.RecentsState.OVERVIEW_SPLIT_SELECT;
import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_HORIZONTAL_OFFSET;
import static com.android.quickstep.views.RecentsView.DESKTOP_CAROUSEL_DETACH_PROGRESS;
@@ -52,6 +51,8 @@
import com.android.launcher3.anim.PropertySetter;
import com.android.launcher3.statemanager.StateManager.StateHandler;
import com.android.launcher3.states.StateAnimationConfig;
+import com.android.quickstep.views.AddDesktopButton;
+import com.android.quickstep.views.ClearAllButton;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.RecentsViewContainer;
@@ -97,12 +98,12 @@
private void setProperties(RecentsState state, StateAnimationConfig config,
PropertySetter setter) {
float clearAllButtonAlpha = state.hasClearAllButton() ? 1 : 0;
- setter.setFloat(mRecentsView.getClearAllButton().visibilityAlphaProperty,
- MULTI_PROPERTY_VALUE, clearAllButtonAlpha, LINEAR);
+ setter.setFloat(mRecentsView.getClearAllButton(),
+ ClearAllButton.VISIBILITY_ALPHA, clearAllButtonAlpha, LINEAR);
if (mRecentsView.getAddDeskButton() != null) {
float addDeskButtonAlpha = state.hasAddDeskButton() ? 1 : 0;
- setter.setFloat(mRecentsView.getAddDeskButton().getVisibilityAlphaProperty(),
- MULTI_PROPERTY_VALUE, addDeskButtonAlpha, LINEAR);
+ setter.setFloat(mRecentsView.getAddDeskButton(), AddDesktopButton.VISIBILITY_ALPHA,
+ addDeskButtonAlpha, LINEAR);
}
float overviewButtonAlpha = state.hasOverviewActions() ? 1 : 0;
setter.setFloat(mRecentsViewContainer.getActionsView().getVisibilityAlpha(),
diff --git a/quickstep/src/com/android/quickstep/orientation/LandscapePagedViewHandler.kt b/quickstep/src/com/android/quickstep/orientation/LandscapePagedViewHandler.kt
index ee9b3e8..e3c9b2b 100644
--- a/quickstep/src/com/android/quickstep/orientation/LandscapePagedViewHandler.kt
+++ b/quickstep/src/com/android/quickstep/orientation/LandscapePagedViewHandler.kt
@@ -311,6 +311,14 @@
override fun getTaskDragDisplacementFactor(isRtl: Boolean): Int = if (isRtl) 1 else -1
+ override fun getTaskDismissVerticalDirection(): Int = 1
+
+ override fun getTaskDismissLength(secondaryDimension: Int, taskThumbnailBounds: Rect): Int =
+ secondaryDimension - taskThumbnailBounds.left
+
+ override fun getTaskLaunchLength(secondaryDimension: Int, taskThumbnailBounds: Rect): Int =
+ taskThumbnailBounds.left
+
/* -------------------- */
override fun getChildBounds(
diff --git a/quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.kt b/quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.kt
index f2e53fc..1883649 100644
--- a/quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.kt
+++ b/quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.kt
@@ -284,6 +284,14 @@
// Ignore rtl since it only affects X value displacement, Y displacement doesn't change
override fun getTaskDragDisplacementFactor(isRtl: Boolean): Int = 1
+ override fun getTaskDismissVerticalDirection(): Int = -1
+
+ override fun getTaskDismissLength(secondaryDimension: Int, taskThumbnailBounds: Rect): Int =
+ taskThumbnailBounds.bottom
+
+ override fun getTaskLaunchLength(secondaryDimension: Int, taskThumbnailBounds: Rect): Int =
+ secondaryDimension - taskThumbnailBounds.bottom
+
/* -------------------- */
override fun getDistanceToBottomOfRect(dp: DeviceProfile, rect: Rect): Int =
diff --git a/quickstep/src/com/android/quickstep/orientation/RecentsPagedOrientationHandler.kt b/quickstep/src/com/android/quickstep/orientation/RecentsPagedOrientationHandler.kt
index 9b3c467..a7bc93b 100644
--- a/quickstep/src/com/android/quickstep/orientation/RecentsPagedOrientationHandler.kt
+++ b/quickstep/src/com/android/quickstep/orientation/RecentsPagedOrientationHandler.kt
@@ -82,13 +82,13 @@
fun getSplitTranslationDirectionFactor(
@StagePosition stagePosition: Int,
- deviceProfile: DeviceProfile
+ deviceProfile: DeviceProfile,
): Int
fun <T> getSplitSelectTaskOffset(
primary: FloatProperty<T>,
secondary: FloatProperty<T>,
- deviceProfile: DeviceProfile
+ deviceProfile: DeviceProfile,
): Pair<FloatProperty<T>, FloatProperty<T>>
fun getDistanceToBottomOfRect(dp: DeviceProfile, rect: Rect): Int
@@ -101,7 +101,7 @@
placeholderInset: Int,
dp: DeviceProfile,
@StagePosition stagePosition: Int,
- out: Rect
+ out: Rect,
)
/**
@@ -128,7 +128,7 @@
drawableWidth: Int,
drawableHeight: Int,
dp: DeviceProfile,
- @StagePosition stagePosition: Int
+ @StagePosition stagePosition: Int,
)
/**
@@ -143,7 +143,7 @@
out: View,
dp: DeviceProfile,
splitInstructionsHeight: Int,
- splitInstructionsWidth: Int
+ splitInstructionsWidth: Int,
)
/**
@@ -159,7 +159,7 @@
dp: DeviceProfile,
@StagePosition stagePosition: Int,
out1: Rect,
- out2: Rect
+ out2: Rect,
)
fun getDefaultSplitPosition(deviceProfile: DeviceProfile): Int
@@ -174,7 +174,7 @@
dp: DeviceProfile,
outRect: Rect,
splitInfo: SplitConfigurationOptions.SplitBounds,
- @StagePosition desiredStagePosition: Int
+ @StagePosition desiredStagePosition: Int,
)
fun measureGroupedTaskViewThumbnailBounds(
@@ -185,7 +185,7 @@
splitBoundsConfig: SplitConfigurationOptions.SplitBounds,
dp: DeviceProfile,
isRtl: Boolean,
- inSplitSelection: Boolean
+ inSplitSelection: Boolean,
)
/**
@@ -198,7 +198,7 @@
dp: DeviceProfile,
splitBoundsConfig: SplitConfigurationOptions.SplitBounds,
parentWidth: Int,
- parentHeight: Int
+ parentHeight: Int,
): Pair<Point, Point>
// Overview TaskMenuView methods
@@ -208,7 +208,7 @@
taskIconMargin: Int,
taskIconHeight: Int,
thumbnailTopMargin: Int,
- isRtl: Boolean
+ isRtl: Boolean,
)
/**
@@ -216,14 +216,14 @@
*/
fun setIconAppChipChildrenParams(
iconParams: FrameLayout.LayoutParams,
- chipChildMarginStart: Int
+ chipChildMarginStart: Int,
)
fun setIconAppChipMenuParams(
iconAppChipView: IconAppChipView,
iconMenuParams: FrameLayout.LayoutParams,
iconMenuMargin: Int,
- thumbnailTopMargin: Int
+ thumbnailTopMargin: Int,
)
fun setSplitIconParams(
@@ -252,7 +252,7 @@
thumbnailView: View,
deviceProfile: DeviceProfile,
taskInsetMargin: Float,
- taskViewIcon: View
+ taskViewIcon: View,
): Float
fun getTaskMenuY(
@@ -261,20 +261,20 @@
stagePosition: Int,
taskMenuView: View,
taskInsetMargin: Float,
- taskViewIcon: View
+ taskViewIcon: View,
): Float
fun getTaskMenuWidth(
thumbnailView: View,
deviceProfile: DeviceProfile,
- @StagePosition stagePosition: Int
+ @StagePosition stagePosition: Int,
): Int
fun getTaskMenuHeight(
taskInsetMargin: Float,
deviceProfile: DeviceProfile,
taskMenuX: Float,
- taskMenuY: Float
+ taskMenuY: Float,
): Int
/**
@@ -285,7 +285,7 @@
deviceProfile: DeviceProfile,
taskMenuLayout: LinearLayout,
dividerSpacing: Int,
- dividerDrawable: ShapeDrawable
+ dividerDrawable: ShapeDrawable,
)
/**
@@ -295,7 +295,7 @@
fun setLayoutParamsForTaskMenuOptionItem(
lp: LinearLayout.LayoutParams,
viewGroup: LinearLayout,
- deviceProfile: DeviceProfile
+ deviceProfile: DeviceProfile,
)
/** Layout a Digital Wellbeing Banner on its parent. TaskView. */
@@ -306,7 +306,7 @@
deviceProfile: DeviceProfile,
snapshotViewWidth: Int,
snapshotViewHeight: Int,
- banner: View
+ banner: View,
)
/**
@@ -322,7 +322,7 @@
deviceProfile: DeviceProfile,
thumbnailViews: Array<View>,
desiredTaskId: Int,
- banner: View
+ banner: View,
): Pair<Float, Float>
// The following are only used by TaskViewTouchHandler.
@@ -342,6 +342,15 @@
/** @return Either 1 or -1, a factor to multiply by so the animation goes the correct way. */
fun getTaskDragDisplacementFactor(isRtl: Boolean): Int
+ /** @return Either 1 or -1, the direction sign towards task dismiss. */
+ fun getTaskDismissVerticalDirection(): Int
+
+ /** @return the length to drag a task off screen for dismiss. */
+ fun getTaskDismissLength(secondaryDimension: Int, taskThumbnailBounds: Rect): Int
+
+ /** @return the length to drag a task to full screen for launch. */
+ fun getTaskLaunchLength(secondaryDimension: Int, taskThumbnailBounds: Rect): Int
+
/**
* Maps the velocity from the coordinate plane of the foreground app to that of Launcher's
* (which now will always be portrait)
@@ -371,7 +380,7 @@
floatingTask: View,
onScreenRect: RectF,
@StagePosition stagePosition: Int,
- dp: DeviceProfile
+ dp: DeviceProfile,
): Float
/**
diff --git a/quickstep/src/com/android/quickstep/orientation/SeascapePagedViewHandler.kt b/quickstep/src/com/android/quickstep/orientation/SeascapePagedViewHandler.kt
index 7fea55d..1f9f752 100644
--- a/quickstep/src/com/android/quickstep/orientation/SeascapePagedViewHandler.kt
+++ b/quickstep/src/com/android/quickstep/orientation/SeascapePagedViewHandler.kt
@@ -344,6 +344,14 @@
override fun getTaskDragDisplacementFactor(isRtl: Boolean): Int = if (isRtl) -1 else 1
+ override fun getTaskDismissVerticalDirection(): Int = -1
+
+ override fun getTaskDismissLength(secondaryDimension: Int, taskThumbnailBounds: Rect): Int =
+ taskThumbnailBounds.right
+
+ override fun getTaskLaunchLength(secondaryDimension: Int, taskThumbnailBounds: Rect): Int =
+ secondaryDimension - taskThumbnailBounds.right
+
/* -------------------- */
override fun getSplitIconsPosition(
diff --git a/quickstep/src/com/android/quickstep/views/AddDesktopButton.kt b/quickstep/src/com/android/quickstep/views/AddDesktopButton.kt
index 244c3b2..b83acf0 100644
--- a/quickstep/src/com/android/quickstep/views/AddDesktopButton.kt
+++ b/quickstep/src/com/android/quickstep/views/AddDesktopButton.kt
@@ -20,10 +20,12 @@
import android.graphics.Canvas
import android.graphics.Rect
import android.util.AttributeSet
-import android.view.View
+import android.util.FloatProperty
import android.widget.ImageButton
import com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X
import com.android.launcher3.R
+import com.android.launcher3.util.KFloatProperty
+import com.android.launcher3.util.MultiPropertyDelegate
import com.android.launcher3.util.MultiPropertyFactory
import com.android.launcher3.util.MultiValueAlpha
import com.android.quickstep.util.BorderAnimator
@@ -36,44 +38,17 @@
class AddDesktopButton @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
ImageButton(context, attrs) {
- private enum class Alpha {
- CONTENT,
- VISIBILITY,
- }
-
private val addDeskButtonAlpha = MultiValueAlpha(this, Alpha.entries.size)
-
- var contentAlpha
- set(value) {
- addDeskButtonAlpha.get(Alpha.CONTENT.ordinal).value = value
- }
- get() = addDeskButtonAlpha.get(Alpha.CONTENT.ordinal).value
-
- val visibilityAlphaProperty: MultiPropertyFactory<View>.MultiProperty
- get() = addDeskButtonAlpha.get(Alpha.VISIBILITY.ordinal)
-
- private enum class TranslationX {
- GRID,
- OFFSET,
- }
+ var contentAlpha by MultiPropertyDelegate(addDeskButtonAlpha, Alpha.CONTENT)
+ var visibilityAlpha by MultiPropertyDelegate(addDeskButtonAlpha, Alpha.VISIBILITY)
private val multiTranslationX =
MultiPropertyFactory(this, VIEW_TRANSLATE_X, TranslationX.entries.size) { a: Float, b: Float
->
a + b
}
-
- var gridTranslationX
- get() = multiTranslationX[TranslationX.GRID.ordinal].value
- set(value) {
- multiTranslationX[TranslationX.GRID.ordinal].value = value
- }
-
- var offsetTranslationX
- get() = multiTranslationX[TranslationX.OFFSET.ordinal].value
- set(value) {
- multiTranslationX[TranslationX.OFFSET.ordinal].value = value
- }
+ var gridTranslationX by MultiPropertyDelegate(multiTranslationX, TranslationX.GRID)
+ var offsetTranslationX by MultiPropertyDelegate(multiTranslationX, TranslationX.OFFSET)
private val focusBorderAnimator: BorderAnimator =
createSimpleBorderAnimator(
@@ -109,7 +84,7 @@
}
}
- protected fun getScrollAdjustment(showAsGrid: Boolean): Int =
+ fun getScrollAdjustment(showAsGrid: Boolean): Int =
if (showAsGrid) gridTranslationX.toInt() else 0
private fun getBorderBounds(bounds: Rect) {
@@ -123,4 +98,20 @@
focusBorderAnimator.drawBorder(canvas)
super.draw(canvas)
}
+
+ companion object {
+ private enum class Alpha {
+ CONTENT,
+ VISIBILITY,
+ }
+
+ private enum class TranslationX {
+ GRID,
+ OFFSET,
+ }
+
+ @JvmField
+ val VISIBILITY_ALPHA: FloatProperty<AddDesktopButton> =
+ KFloatProperty(AddDesktopButton::visibilityAlpha)
+ }
}
diff --git a/quickstep/src/com/android/quickstep/views/ClearAllButton.kt b/quickstep/src/com/android/quickstep/views/ClearAllButton.kt
index 6420ece..69c85ee 100644
--- a/quickstep/src/com/android/quickstep/views/ClearAllButton.kt
+++ b/quickstep/src/com/android/quickstep/views/ClearAllButton.kt
@@ -19,11 +19,12 @@
import android.graphics.Canvas
import android.graphics.Rect
import android.util.AttributeSet
-import android.view.View
+import android.util.FloatProperty
import android.widget.Button
import com.android.launcher3.Flags.enableFocusOutline
import com.android.launcher3.R
-import com.android.launcher3.util.MultiPropertyFactory
+import com.android.launcher3.util.KFloatProperty
+import com.android.launcher3.util.MultiPropertyDelegate
import com.android.launcher3.util.MultiValueAlpha
import com.android.quickstep.util.BorderAnimator
import com.android.quickstep.util.BorderAnimator.Companion.createSimpleBorderAnimator
@@ -33,13 +34,6 @@
class ClearAllButton @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
Button(context, attrs) {
- private enum class Alpha {
- SCROLL,
- CONTENT,
- VISIBILITY,
- DISMISS,
- }
-
private val clearAllButtonAlpha =
object : MultiValueAlpha(this, Alpha.entries.size) {
override fun apply(value: Float) {
@@ -47,32 +41,10 @@
isClickable = value >= 1f
}
}
-
- var scrollAlpha
- set(value) {
- clearAllButtonAlpha.get(Alpha.SCROLL.ordinal).value = value
- }
- get() = clearAllButtonAlpha.get(Alpha.SCROLL.ordinal).value
-
- var contentAlpha
- set(value) {
- clearAllButtonAlpha.get(Alpha.CONTENT.ordinal).value = value
- }
- get() = clearAllButtonAlpha.get(Alpha.CONTENT.ordinal).value
-
- @JvmField
- val visibilityAlphaProperty: MultiPropertyFactory<View>.MultiProperty =
- clearAllButtonAlpha.get(Alpha.VISIBILITY.ordinal)
-
- var dismissAlpha
- set(value) {
- dismissAlphaProperty.value = value
- }
- get() = dismissAlphaProperty.value
-
- @JvmField
- val dismissAlphaProperty: MultiPropertyFactory<View>.MultiProperty =
- clearAllButtonAlpha.get(Alpha.DISMISS.ordinal)
+ var scrollAlpha by MultiPropertyDelegate(clearAllButtonAlpha, Alpha.SCROLL)
+ var contentAlpha by MultiPropertyDelegate(clearAllButtonAlpha, Alpha.CONTENT)
+ var visibilityAlpha by MultiPropertyDelegate(clearAllButtonAlpha, Alpha.VISIBILITY)
+ var dismissAlpha by MultiPropertyDelegate(clearAllButtonAlpha, Alpha.DISMISS)
var fullscreenProgress = 1f
set(value) {
@@ -257,4 +229,21 @@
if (fullscreenProgress > 0) endTranslation else 0f
private fun getGridTrans(endTranslation: Float) = if (gridProgress > 0) endTranslation else 0f
+
+ companion object {
+ private enum class Alpha {
+ SCROLL,
+ CONTENT,
+ VISIBILITY,
+ DISMISS,
+ }
+
+ @JvmField
+ val VISIBILITY_ALPHA: FloatProperty<ClearAllButton> =
+ KFloatProperty(ClearAllButton::visibilityAlpha)
+
+ @JvmField
+ val DISMISS_ALPHA: FloatProperty<ClearAllButton> =
+ KFloatProperty(ClearAllButton::dismissAlpha)
+ }
}
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index e186838..44bf82c 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -66,6 +66,7 @@
import static com.android.quickstep.BaseContainerInterface.getTaskDimension;
import static com.android.quickstep.TaskUtils.checkCurrentOrManagedUserId;
import static com.android.quickstep.util.LogUtils.splitFailureMessage;
+import static com.android.quickstep.views.ClearAllButton.DISMISS_ALPHA;
import static com.android.quickstep.views.OverviewActionsView.HIDDEN_ACTIONS_IN_MENU;
import static com.android.quickstep.views.OverviewActionsView.HIDDEN_DESKTOP;
import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NON_ZERO_ROTATION;
@@ -73,6 +74,7 @@
import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NO_TASKS;
import static com.android.quickstep.views.OverviewActionsView.HIDDEN_SPLIT_SELECT_ACTIVE;
import static com.android.quickstep.views.RecentsViewUtils.DESK_EXPLODE_PROGRESS;
+import static com.android.quickstep.views.TaskView.SPLIT_ALPHA;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -3974,8 +3976,7 @@
// Change alpha of clear all if translating grid to hide it
if (isClearAllHidden) {
- anim.setFloat(mClearAllButton.dismissAlphaProperty, MULTI_PROPERTY_VALUE, 0,
- LINEAR);
+ anim.setFloat(mClearAllButton, DISMISS_ALPHA, 0, LINEAR);
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
@@ -5354,8 +5355,7 @@
clampToProgress(timings.getDesktopTaskScaleInterpolator(), 0f,
timings.getDesktopFadeSplitAnimationEndOffset()));
}
- builder.addFloat(taskView.getSplitAlphaProperty(),
- MULTI_PROPERTY_VALUE, 1f, 0f,
+ builder.addFloat(taskView, SPLIT_ALPHA, 1f, 0f,
clampToProgress(deskTopFadeInterPolator, 0f,
timings.getDesktopFadeSplitAnimationEndOffset()));
}
diff --git a/quickstep/src/com/android/quickstep/views/RecentsViewUtils.kt b/quickstep/src/com/android/quickstep/views/RecentsViewUtils.kt
index 67318ac..31ae890 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsViewUtils.kt
+++ b/quickstep/src/com/android/quickstep/views/RecentsViewUtils.kt
@@ -28,6 +28,7 @@
import com.android.quickstep.views.RecentsView.RUNNING_TASK_ATTACH_ALPHA
import com.android.systemui.shared.recents.model.ThumbnailData
import java.util.function.BiConsumer
+import kotlin.reflect.KMutableProperty1
/**
* Helper class for [RecentsView]. This util class contains refactored and extracted functions from
@@ -305,16 +306,19 @@
}
companion object {
- @JvmField
- val DESK_EXPLODE_PROGRESS =
- object : FloatProperty<RecentsView<*, *>>("deskExplodeProgress") {
- override fun setValue(recentsView: RecentsView<*, *>, value: Float) {
- recentsView.mUtils.deskExplodeProgress = value
- }
+ class RecentsViewFloatProperty(
+ private val utilsProperty: KMutableProperty1<RecentsViewUtils, Float>
+ ) : FloatProperty<RecentsView<*, *>>(utilsProperty.name) {
+ override fun get(recentsView: RecentsView<*, *>): Float =
+ utilsProperty.get(recentsView.mUtils)
- override fun get(recentsView: RecentsView<*, *>) =
- recentsView.mUtils.deskExplodeProgress
+ override fun setValue(recentsView: RecentsView<*, *>, value: Float) {
+ utilsProperty.set(recentsView.mUtils, value)
}
+ }
+
+ @JvmField
+ val DESK_EXPLODE_PROGRESS = RecentsViewFloatProperty(RecentsViewUtils::deskExplodeProgress)
val TEMP_RECT = Rect()
}
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.kt b/quickstep/src/com/android/quickstep/views/TaskView.kt
index ad60329..276318c 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskView.kt
@@ -61,8 +61,9 @@
import com.android.launcher3.testing.shared.TestProtocol
import com.android.launcher3.util.CancellableTask
import com.android.launcher3.util.Executors
+import com.android.launcher3.util.KFloatProperty
+import com.android.launcher3.util.MultiPropertyDelegate
import com.android.launcher3.util.MultiPropertyFactory
-import com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VALUE
import com.android.launcher3.util.MultiValueAlpha
import com.android.launcher3.util.RunnableList
import com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_UNDEFINED
@@ -440,28 +441,10 @@
applyTranslationX()
}
- private val taskViewAlpha = MultiValueAlpha(this, NUM_ALPHA_CHANNELS)
-
- protected var stableAlpha
- set(value) {
- taskViewAlpha.get(ALPHA_INDEX_STABLE).value = value
- }
- get() = taskViewAlpha.get(ALPHA_INDEX_STABLE).value
-
- var attachAlpha
- set(value) {
- taskViewAlpha.get(ALPHA_INDEX_ATTACH).value = value
- }
- get() = taskViewAlpha.get(ALPHA_INDEX_ATTACH).value
-
- var splitAlpha
- set(value) {
- splitAlphaProperty.value = value
- }
- get() = splitAlphaProperty.value
-
- val splitAlphaProperty: MultiPropertyFactory<View>.MultiProperty
- get() = taskViewAlpha.get(ALPHA_INDEX_SPLIT)
+ private val taskViewAlpha = MultiValueAlpha(this, Alpha.entries.size)
+ protected var stableAlpha by MultiPropertyDelegate(taskViewAlpha, Alpha.STABLE)
+ var attachAlpha by MultiPropertyDelegate(taskViewAlpha, Alpha.ATTACH)
+ var splitAlpha by MultiPropertyDelegate(taskViewAlpha, Alpha.SPLIT)
protected var shouldShowScreenshot = false
get() = !isRunningTask || field
@@ -515,16 +498,16 @@
MultiPropertyFactory(
this,
SETTLED_PROGRESS,
- SETTLED_PROGRESS_INDEX_COUNT,
+ SettledProgress.entries.size,
{ x: Float, y: Float -> x * y },
1f,
)
- private val settledProgressFullscreen =
- settledProgressPropertyFactory.get(SETTLED_PROGRESS_INDEX_FULLSCREEN)
- private val settledProgressGesture =
- settledProgressPropertyFactory.get(SETTLED_PROGRESS_INDEX_GESTURE)
- private val settledProgressDismiss =
- settledProgressPropertyFactory.get(SETTLED_PROGRESS_INDEX_DISMISS)
+ private var settledProgressFullscreen by
+ MultiPropertyDelegate(settledProgressPropertyFactory, SettledProgress.Fullscreen)
+ private var settledProgressGesture by
+ MultiPropertyDelegate(settledProgressPropertyFactory, SettledProgress.Gesture)
+ private var settledProgressDismiss by
+ MultiPropertyDelegate(settledProgressPropertyFactory, SettledProgress.Dismiss)
private var viewModel: TaskViewModel? = null
private val dispatcherProvider: DispatcherProvider by RecentsDependencies.inject()
@@ -536,7 +519,7 @@
* interpolator.
*/
fun getDismissIconFadeInAnimator(): ObjectAnimator =
- ObjectAnimator.ofFloat(settledProgressDismiss, MULTI_PROPERTY_VALUE, 1f).apply {
+ ObjectAnimator.ofFloat(this, SETTLED_PROGRESS_DISMISS, 1f).apply {
duration = FADE_IN_ICON_DURATION
interpolator = FADE_IN_ICON_INTERPOLATOR
}
@@ -548,8 +531,7 @@
*/
fun getDismissIconFadeOutAnimator(): ObjectAnimator =
AnimatedFloat { v ->
- settledProgressDismiss.value =
- SETTLED_PROGRESS_FAST_OUT_INTERPOLATOR.getInterpolation(v)
+ settledProgressDismiss = SETTLED_PROGRESS_FAST_OUT_INTERPOLATOR.getInterpolation(v)
}
.animateToValue(1f, 0f)
@@ -1603,7 +1585,7 @@
fun startIconFadeInOnGestureComplete() {
iconFadeInOnGestureCompleteAnimator?.cancel()
iconFadeInOnGestureCompleteAnimator =
- ObjectAnimator.ofFloat(settledProgressGesture, MULTI_PROPERTY_VALUE, 1f).apply {
+ ObjectAnimator.ofFloat(this, SETTLED_PROGRESS_GESTURE, 1f).apply {
duration = FADE_IN_ICON_DURATION
interpolator = Interpolators.LINEAR
addListener(
@@ -1619,7 +1601,7 @@
fun setIconVisibleForGesture(isVisible: Boolean) {
iconFadeInOnGestureCompleteAnimator?.cancel()
- settledProgressGesture.value = if (isVisible) 1f else 0f
+ settledProgressGesture = if (isVisible) 1f else 0f
}
/** Set a color tint on the snapshot and supporting views. */
@@ -1708,7 +1690,7 @@
it.iconView.setVisibility(if (fullscreenProgress < 1) VISIBLE else INVISIBLE)
it.overlay.setFullscreenProgress(fullscreenProgress)
}
- settledProgressFullscreen.value =
+ settledProgressFullscreen =
SETTLED_PROGRESS_FAST_OUT_INTERPOLATOR.getInterpolation(1 - fullscreenProgress)
updateFullscreenParams()
}
@@ -1766,7 +1748,7 @@
dismissScale = 1f
translationZ = 0f
setIconVisibleForGesture(true)
- settledProgressDismiss.value = 1f
+ settledProgressDismiss = 1f
setColorTint(0f, 0)
}
@@ -1788,23 +1770,25 @@
companion object {
private const val TAG = "TaskView"
+
+ private enum class Alpha {
+ STABLE,
+ ATTACH,
+ SPLIT,
+ }
+
+ private enum class SettledProgress {
+ Fullscreen,
+ Gesture,
+ Dismiss,
+ }
+
const val FLAG_UPDATE_ICON = 1
const val FLAG_UPDATE_THUMBNAIL = FLAG_UPDATE_ICON shl 1
const val FLAG_UPDATE_CORNER_RADIUS = FLAG_UPDATE_THUMBNAIL shl 1
const val FLAG_UPDATE_ALL =
(FLAG_UPDATE_ICON or FLAG_UPDATE_THUMBNAIL or FLAG_UPDATE_CORNER_RADIUS)
- const val SETTLED_PROGRESS_INDEX_FULLSCREEN = 0
- const val SETTLED_PROGRESS_INDEX_GESTURE = 1
- const val SETTLED_PROGRESS_INDEX_DISMISS = 2
- const val SETTLED_PROGRESS_INDEX_COUNT = 3
-
- private const val ALPHA_INDEX_STABLE = 0
- private const val ALPHA_INDEX_ATTACH = 1
- private const val ALPHA_INDEX_SPLIT = 2
-
- private const val NUM_ALPHA_CHANNELS = 3
-
/** The maximum amount that a task view can be scrimmed, dimmed or tinted. */
const val MAX_PAGE_SCRIM_ALPHA = 0.4f
const val FADE_IN_ICON_DURATION: Long = 120
@@ -1821,104 +1805,45 @@
private val SYSTEM_GESTURE_EXCLUSION_RECT = listOf(Rect())
private val SETTLED_PROGRESS: FloatProperty<TaskView> =
- object : FloatProperty<TaskView>("settleTransition") {
- override fun setValue(taskView: TaskView, v: Float) {
- taskView.settledProgress = v
- }
+ KFloatProperty(TaskView::settledProgress)
- override fun get(taskView: TaskView) = taskView.settledProgress
- }
+ private val SETTLED_PROGRESS_GESTURE: FloatProperty<TaskView> =
+ KFloatProperty(TaskView::settledProgressGesture)
+
+ private val SETTLED_PROGRESS_DISMISS: FloatProperty<TaskView> =
+ KFloatProperty(TaskView::settledProgressDismiss)
private val SPLIT_SELECT_TRANSLATION_X: FloatProperty<TaskView> =
- object : FloatProperty<TaskView>("splitSelectTranslationX") {
- override fun setValue(taskView: TaskView, v: Float) {
- taskView.splitSelectTranslationX = v
- }
-
- override fun get(taskView: TaskView) = taskView.splitSelectTranslationX
- }
+ KFloatProperty(TaskView::splitSelectTranslationX)
private val SPLIT_SELECT_TRANSLATION_Y: FloatProperty<TaskView> =
- object : FloatProperty<TaskView>("splitSelectTranslationY") {
- override fun setValue(taskView: TaskView, v: Float) {
- taskView.splitSelectTranslationY = v
- }
-
- override fun get(taskView: TaskView) = taskView.splitSelectTranslationY
- }
+ KFloatProperty(TaskView::splitSelectTranslationY)
private val DISMISS_TRANSLATION_X: FloatProperty<TaskView> =
- object : FloatProperty<TaskView>("dismissTranslationX") {
- override fun setValue(taskView: TaskView, v: Float) {
- taskView.dismissTranslationX = v
- }
-
- override fun get(taskView: TaskView) = taskView.dismissTranslationX
- }
+ KFloatProperty(TaskView::dismissTranslationX)
private val DISMISS_TRANSLATION_Y: FloatProperty<TaskView> =
- object : FloatProperty<TaskView>("dismissTranslationY") {
- override fun setValue(taskView: TaskView, v: Float) {
- taskView.dismissTranslationY = v
- }
-
- override fun get(taskView: TaskView) = taskView.dismissTranslationY
- }
+ KFloatProperty(TaskView::dismissTranslationY)
private val TASK_OFFSET_TRANSLATION_X: FloatProperty<TaskView> =
- object : FloatProperty<TaskView>("taskOffsetTranslationX") {
- override fun setValue(taskView: TaskView, v: Float) {
- taskView.taskOffsetTranslationX = v
- }
-
- override fun get(taskView: TaskView) = taskView.taskOffsetTranslationX
- }
+ KFloatProperty(TaskView::taskOffsetTranslationX)
private val TASK_OFFSET_TRANSLATION_Y: FloatProperty<TaskView> =
- object : FloatProperty<TaskView>("taskOffsetTranslationY") {
- override fun setValue(taskView: TaskView, v: Float) {
- taskView.taskOffsetTranslationY = v
- }
-
- override fun get(taskView: TaskView) = taskView.taskOffsetTranslationY
- }
+ KFloatProperty(TaskView::taskOffsetTranslationY)
private val TASK_RESISTANCE_TRANSLATION_X: FloatProperty<TaskView> =
- object : FloatProperty<TaskView>("taskResistanceTranslationX") {
- override fun setValue(taskView: TaskView, v: Float) {
- taskView.taskResistanceTranslationX = v
- }
-
- override fun get(taskView: TaskView) = taskView.taskResistanceTranslationX
- }
+ KFloatProperty(TaskView::taskResistanceTranslationX)
private val TASK_RESISTANCE_TRANSLATION_Y: FloatProperty<TaskView> =
- object : FloatProperty<TaskView>("taskResistanceTranslationY") {
- override fun setValue(taskView: TaskView, v: Float) {
- taskView.taskResistanceTranslationY = v
- }
-
- override fun get(taskView: TaskView) = taskView.taskResistanceTranslationY
- }
+ KFloatProperty(TaskView::taskResistanceTranslationY)
@JvmField
val GRID_END_TRANSLATION_X: FloatProperty<TaskView> =
- object : FloatProperty<TaskView>("gridEndTranslationX") {
- override fun setValue(taskView: TaskView, v: Float) {
- taskView.gridEndTranslationX = v
- }
-
- override fun get(taskView: TaskView) = taskView.gridEndTranslationX
- }
+ KFloatProperty(TaskView::gridEndTranslationX)
@JvmField
- val DISMISS_SCALE: FloatProperty<TaskView> =
- object : FloatProperty<TaskView>("dismissScale") {
- override fun setValue(taskView: TaskView, v: Float) {
- taskView.dismissScale = v
- }
+ val DISMISS_SCALE: FloatProperty<TaskView> = KFloatProperty(TaskView::dismissScale)
- override fun get(taskView: TaskView) = taskView.dismissScale
- }
+ @JvmField val SPLIT_ALPHA: FloatProperty<TaskView> = KFloatProperty(TaskView::splitAlpha)
}
}
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/model/AppEventProducerTest.java b/quickstep/tests/multivalentTests/src/com/android/launcher3/model/AppEventProducerTest.java
index 06a939a..91f9e53 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/model/AppEventProducerTest.java
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/model/AppEventProducerTest.java
@@ -41,7 +41,7 @@
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.pm.UserCache;
import com.android.launcher3.util.AllModulesForTest;
-import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext;
+import com.android.launcher3.util.SandboxContext;
import com.android.launcher3.util.UserIconInfo;
import com.android.systemui.shared.system.SysUiStatsLog;
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/model/data/TaskViewItemInfoTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/model/data/TaskViewItemInfoTest.kt
index adfbca5..8d20ba8 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/model/data/TaskViewItemInfoTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/model/data/TaskViewItemInfoTest.kt
@@ -28,7 +28,7 @@
import com.android.launcher3.model.data.TaskViewItemInfo.Companion.createTaskViewAtom
import com.android.launcher3.pm.UserCache
import com.android.launcher3.util.AllModulesForTest
-import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext
+import com.android.launcher3.util.SandboxContext
import com.android.launcher3.util.SplitConfigurationOptions
import com.android.launcher3.util.TransformingTouchDelegate
import com.android.launcher3.util.UserIconInfo
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarModeRule.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarModeRule.kt
index 3cf912c..f225807 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarModeRule.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarModeRule.kt
@@ -20,7 +20,6 @@
import com.android.launcher3.taskbar.rules.TaskbarModeRule.Mode
import com.android.launcher3.taskbar.rules.TaskbarModeRule.TaskbarMode
import com.android.launcher3.util.DisplayController
-import com.android.launcher3.util.MainThreadInitializedObject
import com.android.launcher3.util.NavigationMode
import org.junit.rules.TestRule
import org.junit.runner.Description
@@ -31,8 +30,8 @@
/**
* Allows tests to specify which Taskbar [Mode] to run under.
*
- * [context] should match the test's target context, so that [MainThreadInitializedObject] instances
- * are properly sandboxed.
+ * [context] should match the test's target context, so that Dagger singleton instances are properly
+ * sandboxed.
*
* Annotate tests with [TaskbarMode] to set a mode. If the annotation is omitted for any tests, this
* rule is a no-op.
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarWindowSandboxContext.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarWindowSandboxContext.kt
index e6806b7..6d53e8e 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarWindowSandboxContext.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarWindowSandboxContext.kt
@@ -30,7 +30,6 @@
import com.android.launcher3.util.DaggerSingletonTracker
import com.android.launcher3.util.DisplayController
import com.android.launcher3.util.FakePrefsModule
-import com.android.launcher3.util.MainThreadInitializedObject.ObjectSandbox
import com.android.launcher3.util.SandboxApplication
import com.android.launcher3.util.SettingsCache
import com.android.launcher3.util.SettingsCacheSandbox
@@ -58,7 +57,7 @@
private val base: SandboxApplication,
val virtualDisplay: VirtualDisplay,
private val params: SandboxParams,
-) : ContextWrapper(base), ObjectSandbox by base, TestRule {
+) : ContextWrapper(base), TestRule {
val settingsCacheSandbox = SettingsCacheSandbox()
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumerTest.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumerTest.java
index 99a34ea..b3056f5 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumerTest.java
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumerTest.java
@@ -55,7 +55,7 @@
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.util.AllModulesForTest;
import com.android.launcher3.util.DisplayController;
-import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext;
+import com.android.launcher3.util.SandboxContext;
import com.android.quickstep.DeviceConfigWrapper;
import com.android.quickstep.GestureState;
import com.android.quickstep.InputConsumer;
diff --git a/res/values/strings.xml b/res/values/strings.xml
index a02516a..56befd6 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -293,6 +293,9 @@
<!-- Description for a new page on homescreen[CHAR_LIMIT=none] -->
<string name="workspace_new_page">New home screen page</string>
+ <string name="app_running_state_description">Active</string>
+ <string name="app_minimized_state_description">Minimized</string>
+
<!-- Folder accessibility -->
<!-- The format string for when a folder is opened, speaks the dimensions -->
<string name="folder_opened">Folder opened, <xliff:g id="width" example="5">%1$d</xliff:g> by <xliff:g id="height" example="3">%2$d</xliff:g></string>
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index bd42b2b..730ad78 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -20,6 +20,9 @@
import static android.graphics.fonts.FontStyle.FONT_WEIGHT_NORMAL;
import static android.text.Layout.Alignment.ALIGN_NORMAL;
+import static com.android.launcher3.BubbleTextView.RunningAppState.RUNNING;
+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.graphics.PreloadIconDrawable.newPendingIcon;
@@ -208,6 +211,9 @@
private final int mRunningAppIndicatorColor;
private final int mMinimizedAppIndicatorColor;
+ private final String mMinimizedStateDescription;
+ private final String mRunningStateDescription;
+
/**
* Various options for the running state of an app.
*/
@@ -240,6 +246,9 @@
super(context, attrs, defStyle);
mActivity = ActivityContext.lookupContext(context);
FastBitmapDrawable.setFlagHoverEnabled(enableCursorHoverStates());
+ mMinimizedStateDescription = getContext().getString(
+ R.string.app_minimized_state_description);
+ mRunningStateDescription = getContext().getString(R.string.app_running_state_description);
TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.BubbleTextView, defStyle, 0);
@@ -432,6 +441,19 @@
invalidate();
}
+ /**
+ * Returns state description of this icon.
+ */
+ public String getIconStateDescription() {
+ if (mRunningAppState == MINIMIZED) {
+ return mMinimizedStateDescription;
+ } else if (mRunningAppState == RUNNING) {
+ return mRunningStateDescription;
+ } else {
+ return "";
+ }
+ }
+
protected void setItemInfo(ItemInfoWithIcon itemInfo) {
setTag(itemInfo);
}
@@ -768,13 +790,13 @@
/** Draws a line under the app icon if this is representing a running app in Desktop Mode. */
protected void drawRunningAppIndicatorIfNecessary(Canvas canvas) {
- if (mRunningAppState == RunningAppState.NOT_RUNNING || mDisplay != DISPLAY_TASKBAR) {
+ if (mRunningAppState == NOT_RUNNING || mDisplay != DISPLAY_TASKBAR) {
return;
}
getIconBounds(mRunningAppIconBounds);
Utilities.scaleRectAboutCenter(mRunningAppIconBounds, ICON_VISIBLE_AREA_FACTOR);
- final boolean isMinimized = mRunningAppState == RunningAppState.MINIMIZED;
+ final boolean isMinimized = mRunningAppState == MINIMIZED;
final int indicatorTop = mRunningAppIconBounds.bottom + mRunningAppIndicatorTopMargin;
final int indicatorWidth =
isMinimized ? mMinimizedAppIndicatorWidth : mRunningAppIndicatorWidth;
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 84de1c2..728bc34 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -185,7 +185,6 @@
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.debug.TestEventEmitter;
import com.android.launcher3.debug.TestEventEmitter.TestEvent;
-import com.android.launcher3.dot.DotInfo;
import com.android.launcher3.dragndrop.DragController;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.dragndrop.DragView;
@@ -237,6 +236,7 @@
import com.android.launcher3.util.IntSet;
import com.android.launcher3.util.ItemInflater;
import com.android.launcher3.util.KeyboardShortcutsDelegate;
+import com.android.launcher3.util.LauncherBindableItemsContainer;
import com.android.launcher3.util.LockedUserState;
import com.android.launcher3.util.MSDLPlayerWrapper;
import com.android.launcher3.util.PackageUserKey;
@@ -544,7 +544,7 @@
mItemInflater = new ItemInflater<>(this, mAppWidgetHolder, getItemOnClickListener(),
mFocusHandler, new CellLayout(mWorkspace.getContext(), mWorkspace));
- mPopupDataProvider = new PopupDataProvider(this::updateNotificationDots);
+ mPopupDataProvider = new PopupDataProvider(this);
mWidgetPickerDataProvider = new WidgetPickerDataProvider();
PillColorProvider.getInstance(mWorkspace.getContext()).registerObserver();
@@ -1598,11 +1598,6 @@
private final ScreenOnListener mScreenOnListener = this::onScreenOnChanged;
- private void updateNotificationDots(Predicate<PackageUserKey> updatedDots) {
- mWorkspace.updateNotificationDots(updatedDots);
- mAppsView.getAppsStore().updateNotificationDots(updatedDots);
- }
-
@Override
public void onAttachedToWindow() {
super.onAttachedToWindow();
@@ -3022,11 +3017,6 @@
return mWidgetPickerDataProvider;
}
- @Override
- public DotInfo getDotInfoForItem(ItemInfo info) {
- return mPopupDataProvider.getDotInfoForItem(info);
- }
-
@NonNull
public LauncherOverlayManager getOverlayManager() {
return mOverlayManager;
@@ -3041,6 +3031,12 @@
return mDragLayer;
}
+ @NonNull
+ @Override
+ public LauncherBindableItemsContainer getContent() {
+ return mWorkspace;
+ }
+
@Override
public ActivityAllAppsContainerView<Launcher> getAppsView() {
return mAppsView;
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 94ff441..5595828 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -83,7 +83,6 @@
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.debug.TestEventEmitter;
import com.android.launcher3.debug.TestEventEmitter.TestEvent;
-import com.android.launcher3.dot.FolderDotInfo;
import com.android.launcher3.dragndrop.DragController;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.dragndrop.DragOptions;
@@ -119,7 +118,6 @@
import com.android.launcher3.util.LauncherBindableItemsContainer;
import com.android.launcher3.util.MSDLPlayerWrapper;
import com.android.launcher3.util.OverlayEdgeEffect;
-import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.util.RunnableList;
import com.android.launcher3.util.Thunk;
import com.android.launcher3.util.WallpaperOffsetInterpolator;
@@ -3419,38 +3417,6 @@
return null;
}
- public void updateNotificationDots(Predicate<PackageUserKey> updatedDots) {
- final PackageUserKey packageUserKey = new PackageUserKey(null, null);
- Predicate<ItemInfo> matcher = info -> !packageUserKey.updateFromItemInfo(info)
- || updatedDots.test(packageUserKey);
-
- ItemOperator op = (info, v) -> {
- if (info instanceof WorkspaceItemInfo && v instanceof BubbleTextView) {
- if (matcher.test(info)) {
- ((BubbleTextView) v).applyDotState(info, true /* animate */);
- }
- } else if (info instanceof FolderInfo && v instanceof FolderIcon) {
- FolderInfo fi = (FolderInfo) info;
- if (fi.anyMatch(matcher)) {
- FolderDotInfo folderDotInfo = new FolderDotInfo();
- for (ItemInfo si : fi.getContents()) {
- folderDotInfo.addDotInfo(mLauncher.getDotInfoForItem(si));
- }
- ((FolderIcon) v).setDotInfo(folderDotInfo);
- }
- }
-
- // process all the shortcuts
- return false;
- };
-
- mapOverItems(op);
- Folder folder = Folder.getOpen(mLauncher);
- if (folder != null) {
- folder.iterateOverItems(op);
- }
- }
-
/**
* Remove workspace icons & widget information related to items in matcher.
*
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index fb48a4d..0ce7249 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -65,6 +65,7 @@
import android.widget.TextView;
import androidx.annotation.IntDef;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.core.content.res.ResourcesCompat;
@@ -261,7 +262,7 @@
@Nullable
private KeyboardInsetAnimationCallback mKeyboardInsetAnimationCallback;
- private GradientDrawable mBackground;
+ private final @NonNull GradientDrawable mBackground;
/**
* Used to inflate the Workspace from XML.
@@ -283,6 +284,10 @@
// click).
setFocusableInTouchMode(true);
+ mBackground = (GradientDrawable) Objects.requireNonNull(
+ ResourcesCompat.getDrawable(getResources(),
+ R.drawable.round_rect_folder, getContext().getTheme()));
+ mBackground.setCallback(this);
}
@Override
@@ -296,9 +301,6 @@
final DeviceProfile dp = mActivityContext.getDeviceProfile();
final int paddingLeftRight = dp.folderContentPaddingLeftRight;
- mBackground = (GradientDrawable) ResourcesCompat.getDrawable(getResources(),
- R.drawable.round_rect_folder, getContext().getTheme());
-
mContent = findViewById(R.id.folder_content);
mContent.setPadding(paddingLeftRight, dp.folderContentPaddingTop, paddingLeftRight, 0);
mContent.setFolder(this);
@@ -345,6 +347,11 @@
return true;
}
+ @Override
+ protected boolean verifyDrawable(@NonNull Drawable who) {
+ return super.verifyDrawable(who) || (who == mBackground);
+ }
+
void callBeginDragShared(View v, DragOptions options) {
mLauncherDelegate.beginDragShared(v, this, options);
}
diff --git a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
index 740b87b..3836f7d 100644
--- a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
+++ b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
@@ -94,7 +94,7 @@
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.IntSet;
-import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext;
+import com.android.launcher3.util.SandboxContext;
import com.android.launcher3.util.WindowBounds;
import com.android.launcher3.util.window.WindowManagerProxy;
import com.android.launcher3.views.ActivityContext;
diff --git a/src/com/android/launcher3/popup/PopupDataProvider.java b/src/com/android/launcher3/popup/PopupDataProvider.java
index 8a5e388..318b3ce 100644
--- a/src/com/android/launcher3/popup/PopupDataProvider.java
+++ b/src/com/android/launcher3/popup/PopupDataProvider.java
@@ -23,20 +23,27 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.allapps.ActivityAllAppsContainerView;
import com.android.launcher3.dot.DotInfo;
+import com.android.launcher3.dot.FolderDotInfo;
+import com.android.launcher3.folder.Folder;
+import com.android.launcher3.folder.FolderIcon;
+import com.android.launcher3.model.data.FolderInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.notification.NotificationKeyData;
import com.android.launcher3.notification.NotificationListener;
import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.LauncherBindableItemsContainer.ItemOperator;
import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.util.ShortcutUtil;
+import com.android.launcher3.views.ActivityContext;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import java.util.function.Consumer;
import java.util.function.Predicate;
/**
@@ -47,19 +54,49 @@
private static final boolean LOGD = false;
private static final String TAG = "PopupDataProvider";
- private final Consumer<Predicate<PackageUserKey>> mNotificationDotsChangeListener;
+ private final ActivityContext mContext;
+
+ /** Maps packages to their DotInfo's . */
+ private final Map<PackageUserKey, DotInfo> mPackageUserToDotInfos = new HashMap<>();
/** Maps launcher activity components to a count of how many shortcuts they have. */
private HashMap<ComponentKey, Integer> mDeepShortcutMap = new HashMap<>();
- /** Maps packages to their DotInfo's . */
- private Map<PackageUserKey, DotInfo> mPackageUserToDotInfos = new HashMap<>();
- public PopupDataProvider(Consumer<Predicate<PackageUserKey>> notificationDotsChangeListener) {
- mNotificationDotsChangeListener = notificationDotsChangeListener;
+ public PopupDataProvider(ActivityContext context) {
+ mContext = context;
}
private void updateNotificationDots(Predicate<PackageUserKey> updatedDots) {
- mNotificationDotsChangeListener.accept(updatedDots);
+ final PackageUserKey packageUserKey = new PackageUserKey(null, null);
+ Predicate<ItemInfo> matcher = info -> !packageUserKey.updateFromItemInfo(info)
+ || updatedDots.test(packageUserKey);
+
+ ItemOperator op = (info, v) -> {
+ if (v instanceof BubbleTextView && info != null && matcher.test(info)) {
+ ((BubbleTextView) v).applyDotState(info, true /* animate */);
+ } else if (v instanceof FolderIcon icon
+ && info instanceof FolderInfo fi && fi.anyMatch(matcher)) {
+ FolderDotInfo folderDotInfo = new FolderDotInfo();
+ for (ItemInfo si : fi.getContents()) {
+ folderDotInfo.addDotInfo(getDotInfoForItem(si));
+ }
+ icon.setDotInfo(folderDotInfo);
+ }
+
+ // process all the shortcuts
+ return false;
+ };
+
+ mContext.getContent().mapOverItems(op);
+ Folder folder = Folder.getOpen(mContext);
+ if (folder != null) {
+ folder.iterateOverItems(op);
+ }
+
+ ActivityAllAppsContainerView<?> appsView = mContext.getAppsView();
+ if (appsView != null) {
+ appsView.getAppsStore().updateNotificationDots(updatedDots);
+ }
}
@Override
diff --git a/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
index 2a21085..fd8b0e7 100644
--- a/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
+++ b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
@@ -29,6 +29,7 @@
import android.view.ViewAnimationUtils;
import android.view.inputmethod.InputMethodManager;
+import androidx.annotation.NonNull;
import androidx.annotation.UiThread;
import com.android.launcher3.AbstractFloatingView;
@@ -129,8 +130,7 @@
}
mDragController.addDragListener(this);
- mPopupDataProvider = new PopupDataProvider(
- mAppsView.getAppsStore()::updateNotificationDots);
+ mPopupDataProvider = new PopupDataProvider(this);
mModel.addCallbacksAndLoad(this);
}
@@ -293,6 +293,8 @@
mStringCache = cache;
}
+ @Override
+ @NonNull
public PopupDataProvider getPopupDataProvider() {
return mPopupDataProvider;
}
diff --git a/src/com/android/launcher3/util/DaggerSingletonObject.java b/src/com/android/launcher3/util/DaggerSingletonObject.java
index a245761..a4bd30a 100644
--- a/src/com/android/launcher3/util/DaggerSingletonObject.java
+++ b/src/com/android/launcher3/util/DaggerSingletonObject.java
@@ -24,8 +24,7 @@
import java.util.function.Function;
/**
- * A class to provide DaggerSingleton objects in a traditional way for
- * {@link MainThreadInitializedObject}.
+ * A class to provide DaggerSingleton objects in a traditional way.
* We should delete this class at the end and use @Inject to get dagger provided singletons.
*/
diff --git a/src/com/android/launcher3/util/DaggerSingletonTracker.java b/src/com/android/launcher3/util/DaggerSingletonTracker.java
index b7a88db..34b3760 100644
--- a/src/com/android/launcher3/util/DaggerSingletonTracker.java
+++ b/src/com/android/launcher3/util/DaggerSingletonTracker.java
@@ -45,7 +45,7 @@
* Adds the SafeCloseable Singletons to the mLauncherAppSingletons list.
* This helps to track the singletons and close them appropriately.
* See {@link DaggerSingletonTracker#close()} and
- * {@link MainThreadInitializedObject.SandboxContext#onDestroy()}
+ * {@link SandboxContext#onDestroy()}
*/
public void addCloseable(SafeCloseable closeable) {
MAIN_EXECUTOR.execute(() -> {
diff --git a/src/com/android/launcher3/util/KFloatProperty.kt b/src/com/android/launcher3/util/KFloatProperty.kt
new file mode 100644
index 0000000..5579241
--- /dev/null
+++ b/src/com/android/launcher3/util/KFloatProperty.kt
@@ -0,0 +1,30 @@
+/*
+ * 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.util
+
+import android.util.FloatProperty
+import kotlin.reflect.KMutableProperty1
+
+/** Maps any Kotlin mutable property (var) to [FloatProperty]. */
+class KFloatProperty<T>(private val kProperty: KMutableProperty1<T, Float>) :
+ FloatProperty<T>(kProperty.name) {
+ override fun get(target: T) = kProperty.get(target)
+
+ override fun setValue(target: T, value: Float) {
+ kProperty.set(target, value)
+ }
+}
diff --git a/src/com/android/launcher3/util/LauncherBindableItemsContainer.java b/src/com/android/launcher3/util/LauncherBindableItemsContainer.java
deleted file mode 100644
index 20e3eaf..0000000
--- a/src/com/android/launcher3/util/LauncherBindableItemsContainer.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Copyright (C) 2021 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.util;
-
-import android.view.View;
-
-import com.android.launcher3.BubbleTextView;
-import com.android.launcher3.apppairs.AppPairIcon;
-import com.android.launcher3.folder.Folder;
-import com.android.launcher3.folder.FolderIcon;
-import com.android.launcher3.model.data.AppPairInfo;
-import com.android.launcher3.model.data.FolderInfo;
-import com.android.launcher3.model.data.ItemInfo;
-import com.android.launcher3.model.data.WorkspaceItemInfo;
-import com.android.launcher3.views.ActivityContext;
-import com.android.launcher3.widget.PendingAppWidgetHostView;
-
-import java.util.Set;
-
-/**
- * Interface representing a container which can bind Launcher items with some utility methods
- */
-public interface LauncherBindableItemsContainer {
-
- /**
- * Called to update workspace items as a result of
- * {@link com.android.launcher3.model.BgDataModel.Callbacks#bindItemsUpdated(Set)}
- */
- default void updateContainerItems(Set<ItemInfo> updates, ActivityContext context) {
- ItemOperator op = (info, v) -> {
- if (v instanceof BubbleTextView shortcut
- && info instanceof WorkspaceItemInfo wii
- && updates.contains(info)) {
- shortcut.applyFromWorkspaceItem(wii);
- } else if (info instanceof FolderInfo && v instanceof FolderIcon folderIcon) {
- folderIcon.updatePreviewItems(updates::contains);
- } else if (info instanceof AppPairInfo && v instanceof AppPairIcon appPairIcon) {
- appPairIcon.maybeRedrawForWorkspaceUpdate(updates::contains);
- } else if (v instanceof PendingAppWidgetHostView pendingView
- && updates.contains(info)) {
- pendingView.applyState();
- pendingView.postProviderAvailabilityCheck();
- }
-
- // Iterate all items
- return false;
- };
-
- mapOverItems(op);
- Folder openFolder = Folder.getOpen(context);
- if (openFolder != null) {
- openFolder.iterateOverItems(op);
- }
- }
-
- /**
- * Map the operator over the shortcuts and widgets.
- *
- * @param op the operator to map over the shortcuts
- */
- void mapOverItems(ItemOperator op);
-
- interface ItemOperator {
- /**
- * Process the next itemInfo, possibly with side-effect on the next item.
- *
- * @param info info for the shortcut
- * @param view view for the shortcut
- * @return true if done, false to continue the map
- */
- boolean evaluate(ItemInfo info, View view);
- }
-}
diff --git a/src/com/android/launcher3/util/LauncherBindableItemsContainer.kt b/src/com/android/launcher3/util/LauncherBindableItemsContainer.kt
new file mode 100644
index 0000000..1661796
--- /dev/null
+++ b/src/com/android/launcher3/util/LauncherBindableItemsContainer.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2021 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.util
+
+import android.view.View
+import com.android.launcher3.BubbleTextView
+import com.android.launcher3.apppairs.AppPairIcon
+import com.android.launcher3.folder.Folder
+import com.android.launcher3.folder.FolderIcon
+import com.android.launcher3.model.data.AppPairInfo
+import com.android.launcher3.model.data.FolderInfo
+import com.android.launcher3.model.data.ItemInfo
+import com.android.launcher3.model.data.WorkspaceItemInfo
+import com.android.launcher3.views.ActivityContext
+import com.android.launcher3.widget.PendingAppWidgetHostView
+
+/** Interface representing a container which can bind Launcher items with some utility methods */
+interface LauncherBindableItemsContainer {
+
+ /**
+ * Called to update workspace items as a result of {@link
+ * com.android.launcher3.model.BgDataModel.Callbacks#bindItemsUpdated(Set)}
+ */
+ fun updateContainerItems(updates: Set<ItemInfo>, context: ActivityContext) {
+ val op = ItemOperator { info, v ->
+ when {
+ v is BubbleTextView && info is WorkspaceItemInfo && updates.contains(info) ->
+ v.applyFromWorkspaceItem(info)
+ v is FolderIcon && info is FolderInfo -> v.updatePreviewItems(updates::contains)
+ v is AppPairIcon && info is AppPairInfo ->
+ v.maybeRedrawForWorkspaceUpdate(updates::contains)
+ v is PendingAppWidgetHostView && updates.contains(info) -> {
+ v.applyState()
+ v.postProviderAvailabilityCheck()
+ }
+ }
+
+ // Iterate all items
+ false
+ }
+
+ mapOverItems(op)
+ Folder.getOpen(context)?.iterateOverItems(op)
+ }
+
+ /** Map the [op] over the shortcuts and widgets. */
+ fun mapOverItems(op: ItemOperator)
+
+ fun interface ItemOperator {
+
+ /**
+ * Process the next itemInfo, possibly with side-effect on the next item.
+ *
+ * @param info info for the shortcut
+ * @param view view for the shortcut
+ * @return true if done, false to continue the map
+ */
+ fun evaluate(info: ItemInfo?, view: View): Boolean
+ }
+}
diff --git a/src/com/android/launcher3/util/MainThreadInitializedObject.java b/src/com/android/launcher3/util/MainThreadInitializedObject.java
deleted file mode 100644
index 356a551..0000000
--- a/src/com/android/launcher3/util/MainThreadInitializedObject.java
+++ /dev/null
@@ -1,214 +0,0 @@
-/*
- * Copyright (C) 2018 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.util;
-
-import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
-
-import android.content.Context;
-import android.os.Looper;
-import android.util.Log;
-
-import androidx.annotation.UiThread;
-import androidx.annotation.VisibleForTesting;
-
-import com.android.launcher3.LauncherApplication;
-import com.android.launcher3.util.ResourceBasedOverride.Overrides;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.concurrent.ExecutionException;
-import java.util.function.Consumer;
-
-/**
- * Utility class for defining singletons which are initiated on main thread.
- *
- * TODO(b/361850561): Do not delete MainThreadInitializedObject until we find a way to
- * unregister and understand how singleton objects are destroyed in dagger graph.
- */
-public class MainThreadInitializedObject<T extends SafeCloseable> {
-
- private final ObjectProvider<T> mProvider;
- private T mValue;
-
- public MainThreadInitializedObject(ObjectProvider<T> provider) {
- mProvider = provider;
- }
-
- public T get(Context context) {
- Context app = context.getApplicationContext();
- if (app instanceof ObjectSandbox sc) {
- return sc.getObject(this);
- }
-
- if (mValue == null) {
- if (Looper.myLooper() == Looper.getMainLooper()) {
- mValue = TraceHelper.allowIpcs("main.thread.object", () -> mProvider.get(app));
- } else {
- try {
- return MAIN_EXECUTOR.submit(() -> get(context)).get();
- } catch (InterruptedException|ExecutionException e) {
- throw new RuntimeException(e);
- }
- }
- }
- return mValue;
- }
-
- /**
- * Executes the callback is the value is already created
- * @return true if the callback was executed, false otherwise
- */
- public boolean executeIfCreated(Consumer<T> callback) {
- T v = mValue;
- if (v != null) {
- callback.accept(v);
- return true;
- } else {
- return false;
- }
- }
-
- @VisibleForTesting
- public void initializeForTesting(T value) {
- mValue = value;
- }
-
- /**
- * Initializes a provider based on resource overrides
- */
- public static <T extends ResourceBasedOverride & SafeCloseable> MainThreadInitializedObject<T>
- forOverride(Class<T> clazz, int resourceId) {
- return new MainThreadInitializedObject<>(c -> Overrides.getObject(clazz, c, resourceId));
- }
-
- public interface ObjectProvider<T> {
-
- T get(Context context);
- }
-
- /** Sandbox for isolating {@link MainThreadInitializedObject} instances from Launcher. */
- public interface ObjectSandbox {
-
- /**
- * Find a cached object from mObjectMap if we have already created one. If not, generate
- * an object using the provider.
- */
- <T extends SafeCloseable> T getObject(MainThreadInitializedObject<T> object);
-
-
- /**
- * Put a value into cache, can be used to put mocked MainThreadInitializedObject
- * instances.
- */
- <T extends SafeCloseable> void putObject(MainThreadInitializedObject<T> object, T value);
-
- /**
- * Returns whether this sandbox should cleanup all objects when its destroyed or leave it
- * to the GC.
- * These objects can have listeners attached to the system server and mey not be able to get
- * GCed themselves when running on a device.
- * Some environments like Robolectric tear down the whole system at the end of the test,
- * so manual cleanup may not be required.
- */
- default boolean shouldCleanUpOnDestroy() {
- return true;
- }
-
- @UiThread
- default <T extends SafeCloseable> T createObject(MainThreadInitializedObject<T> object) {
- return object.mProvider.get((Context) this);
- }
- }
-
- /**
- * Abstract Context which allows custom implementations for
- * {@link MainThreadInitializedObject} providers
- */
- public static class SandboxContext extends LauncherApplication implements ObjectSandbox {
-
- private static final String TAG = "SandboxContext";
-
- private final Map<MainThreadInitializedObject, Object> mObjectMap = new HashMap<>();
- private final ArrayList<SafeCloseable> mOrderedObjects = new ArrayList<>();
-
- private final Object mDestroyLock = new Object();
- private boolean mDestroyed = false;
-
- public SandboxContext(Context base) {
- attachBaseContext(base);
- }
-
- @Override
- public Context getApplicationContext() {
- return this;
- }
-
- @Override
- public boolean shouldCleanUpOnDestroy() {
- return (getBaseContext().getApplicationContext() instanceof ObjectSandbox os)
- ? os.shouldCleanUpOnDestroy() : true;
- }
-
- public void onDestroy() {
- if (shouldCleanUpOnDestroy()) {
- cleanUpObjects();
- }
- }
-
- protected void cleanUpObjects() {
- getAppComponent().getDaggerSingletonTracker().close();
- synchronized (mDestroyLock) {
- // Destroy in reverse order
- for (int i = mOrderedObjects.size() - 1; i >= 0; i--) {
- mOrderedObjects.get(i).close();
- }
- mDestroyed = true;
- }
- }
-
- @Override
- public <T extends SafeCloseable> T getObject(MainThreadInitializedObject<T> object) {
- synchronized (mDestroyLock) {
- if (mDestroyed) {
- Log.e(TAG, "Static object access with a destroyed context");
- }
- T t = (T) mObjectMap.get(object);
- if (t != null) {
- return t;
- }
- if (Looper.myLooper() == Looper.getMainLooper()) {
- t = createObject(object);
- mObjectMap.put(object, t);
- mOrderedObjects.add(t);
- return t;
- }
- }
-
- try {
- return MAIN_EXECUTOR.submit(() -> getObject(object)).get();
- } catch (InterruptedException | ExecutionException e) {
- throw new RuntimeException(e);
- }
- }
-
- @Override
- public <T extends SafeCloseable> void putObject(
- MainThreadInitializedObject<T> object, T value) {
- mObjectMap.put(object, value);
- }
- }
-}
diff --git a/src/com/android/launcher3/util/MultiPropertyDelegate.kt b/src/com/android/launcher3/util/MultiPropertyDelegate.kt
new file mode 100644
index 0000000..837a586
--- /dev/null
+++ b/src/com/android/launcher3/util/MultiPropertyDelegate.kt
@@ -0,0 +1,30 @@
+/*
+ * 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.util
+
+import kotlin.reflect.KProperty
+
+/** Delegate Kotlin mutable property (var) to a property in [MultiPropertyFactory] */
+class MultiPropertyDelegate(private val property: MultiPropertyFactory<*>.MultiProperty) {
+ constructor(factory: MultiPropertyFactory<*>, enum: Enum<*>) : this(factory[enum.ordinal])
+
+ operator fun getValue(thisRef: Any?, kProperty: KProperty<*>): Float = property.value
+
+ operator fun setValue(thisRef: Any?, kProperty: KProperty<*>, value: Float) {
+ property.value = value
+ }
+}
diff --git a/src/com/android/launcher3/util/SandboxContext.kt b/src/com/android/launcher3/util/SandboxContext.kt
new file mode 100644
index 0000000..c6224e2
--- /dev/null
+++ b/src/com/android/launcher3/util/SandboxContext.kt
@@ -0,0 +1,56 @@
+/*
+ * 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.util
+
+import android.content.Context
+import com.android.launcher3.LauncherApplication
+
+/** Abstract Context which allows custom implementations for dagger components. */
+open class SandboxContext(base: Context?) : LauncherApplication() {
+ init {
+ base?.let { attachBaseContext(it) }
+ }
+
+ override fun getApplicationContext(): Context {
+ return this
+ }
+
+ /**
+ * Returns whether this sandbox should cleanup all objects when its destroyed or leave it to the
+ * GC. These objects can have listeners attached to the system server and mey not be able to get
+ * GCed themselves when running on a device. Some environments like Robolectric tear down the
+ * whole system at the end of the test, so manual cleanup may not be required.
+ */
+ open fun shouldCleanUpOnDestroy(): Boolean {
+ return (getBaseContext().getApplicationContext() as? SandboxContext)
+ ?.shouldCleanUpOnDestroy() ?: true
+ }
+
+ fun onDestroy() {
+ if (shouldCleanUpOnDestroy()) {
+ cleanUpObjects()
+ }
+ }
+
+ open protected fun cleanUpObjects() {
+ appComponent.daggerSingletonTracker.close()
+ }
+
+ companion object {
+ private const val TAG = "SandboxContext"
+ }
+}
diff --git a/src/com/android/launcher3/views/ActivityContext.java b/src/com/android/launcher3/views/ActivityContext.java
index 5a04539..30af586 100644
--- a/src/com/android/launcher3/views/ActivityContext.java
+++ b/src/com/android/launcher3/views/ActivityContext.java
@@ -80,6 +80,7 @@
import com.android.launcher3.popup.PopupDataProvider;
import com.android.launcher3.util.ActivityOptionsWrapper;
import com.android.launcher3.util.ApplicationInfoWrapper;
+import com.android.launcher3.util.LauncherBindableItemsContainer;
import com.android.launcher3.util.Preconditions;
import com.android.launcher3.util.RunnableList;
import com.android.launcher3.util.SplitConfigurationOptions;
@@ -101,10 +102,6 @@
return false;
}
- default DotInfo getDotInfoForItem(ItemInfo info) {
- return null;
- }
-
default AccessibilityDelegate getAccessibilityDelegate() {
return null;
}
@@ -194,6 +191,14 @@
}
/**
+ * Returns the primary content of this context
+ */
+ @NonNull
+ default LauncherBindableItemsContainer getContent() {
+ return op -> { };
+ }
+
+ /**
* The all apps container, if it exists in this context.
*/
default ActivityAllAppsContainerView<?> getAppsView() {
@@ -281,9 +286,13 @@
return v -> false;
}
- @Nullable
+ @NonNull
default PopupDataProvider getPopupDataProvider() {
- return null;
+ return new PopupDataProvider(this);
+ }
+
+ default DotInfo getDotInfoForItem(ItemInfo info) {
+ return getPopupDataProvider().getDotInfoForItem(info);
}
/**
diff --git a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
index b07d807..7a44c6a 100644
--- a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
@@ -79,7 +79,8 @@
private Runnable mAutoAdvanceRunnable;
private long mDeferUpdatesUntilMillis = 0;
- RemoteViews mLastRemoteViews;
+ private RemoteViews mLastRemoteViews;
+ private boolean mReapplyOnResumeUpdates = false;
private boolean mTrackingWidgetUpdate = false;
@@ -138,11 +139,11 @@
TRACE_METHOD_NAME + getAppWidgetInfo().provider, getAppWidgetId());
mTrackingWidgetUpdate = false;
}
- if (isDeferringUpdates()) {
- mLastRemoteViews = remoteViews;
+ mLastRemoteViews = remoteViews;
+ mReapplyOnResumeUpdates = isDeferringUpdates();
+ if (mReapplyOnResumeUpdates) {
return;
}
- mLastRemoteViews = null;
super.updateAppWidget(remoteViews);
@@ -150,6 +151,18 @@
checkIfAutoAdvance();
}
+ @Override
+ public void onViewAdded(View child) {
+ super.onViewAdded(child);
+ mReapplyOnResumeUpdates |= isDeferringUpdates();
+ }
+
+ @Override
+ public void onViewRemoved(View child) {
+ super.onViewRemoved(child);
+ mReapplyOnResumeUpdates |= isDeferringUpdates();
+ }
+
private boolean checkScrollableRecursively(ViewGroup viewGroup) {
if (viewGroup instanceof AdapterView) {
return true;
@@ -204,18 +217,16 @@
* {@link #updateAppWidget} and apply any deferred updates.
*/
public void endDeferringUpdates() {
- RemoteViews remoteViews;
mDeferUpdatesUntilMillis = 0;
- remoteViews = mLastRemoteViews;
-
- if (remoteViews != null) {
- updateAppWidget(remoteViews);
+ if (mReapplyOnResumeUpdates) {
+ updateAppWidget(mLastRemoteViews);
}
}
+ @Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
- BaseDragLayer dragLayer = mActivityContext.getDragLayer();
+ BaseDragLayer<?> dragLayer = mActivityContext.getDragLayer();
if (mIsScrollable) {
dragLayer.requestDisallowInterceptTouchEvent(true);
}
@@ -225,6 +236,7 @@
return mLongPressHelper.hasPerformedLongPress();
}
+ @Override
public boolean onTouchEvent(MotionEvent ev) {
mLongPressHelper.onTouchEvent(ev);
// We want to keep receiving though events to be able to cancel long press on ACTION_UP
diff --git a/tests/multivalentTests/src/com/android/launcher3/AbstractDeviceProfileTest.kt b/tests/multivalentTests/src/com/android/launcher3/AbstractDeviceProfileTest.kt
index 3658989..ad6afcf 100644
--- a/tests/multivalentTests/src/com/android/launcher3/AbstractDeviceProfileTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/AbstractDeviceProfileTest.kt
@@ -35,8 +35,8 @@
import com.android.launcher3.util.AllModulesMinusWMProxy
import com.android.launcher3.util.DisplayController
import com.android.launcher3.util.FakePrefsModule
-import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext
import com.android.launcher3.util.NavigationMode
+import com.android.launcher3.util.SandboxContext
import com.android.launcher3.util.WindowBounds
import com.android.launcher3.util.rule.TestStabilityRule
import com.android.launcher3.util.rule.setFlags
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/LauncherModelHelper.java b/tests/multivalentTests/src/com/android/launcher3/util/LauncherModelHelper.java
index cee5559..4458e8f 100644
--- a/tests/multivalentTests/src/com/android/launcher3/util/LauncherModelHelper.java
+++ b/tests/multivalentTests/src/com/android/launcher3/util/LauncherModelHelper.java
@@ -56,7 +56,6 @@
import com.android.launcher3.model.BgDataModel.Callbacks;
import com.android.launcher3.model.ModelDbController;
import com.android.launcher3.testing.TestInformationProvider;
-import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/ModelTestExtensions.kt b/tests/multivalentTests/src/com/android/launcher3/util/ModelTestExtensions.kt
index 547d05e..ceefb0d 100644
--- a/tests/multivalentTests/src/com/android/launcher3/util/ModelTestExtensions.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/util/ModelTestExtensions.kt
@@ -1,6 +1,7 @@
package com.android.launcher3.util
import android.content.ContentValues
+import android.os.Process
import com.android.launcher3.Flags
import com.android.launcher3.LauncherModel
import com.android.launcher3.LauncherSettings.Favorites
@@ -66,7 +67,7 @@
spanX: Int = 1,
spanY: Int = 1,
id: Int = 0,
- profileId: Int = 0,
+ profileId: Int = Process.myUserHandle().identifier,
tableName: String = Favorites.TABLE_NAME,
appWidgetId: Int = -1,
appWidgetSource: Int = -1,
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/SandboxApplication.kt b/tests/multivalentTests/src/com/android/launcher3/util/SandboxApplication.kt
index 0da8891..2fa4cad 100644
--- a/tests/multivalentTests/src/com/android/launcher3/util/SandboxApplication.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/util/SandboxApplication.kt
@@ -27,7 +27,6 @@
import android.view.Display
import androidx.test.core.app.ApplicationProvider
import com.android.launcher3.util.LauncherModelHelper.SandboxModelContext
-import com.android.launcher3.util.MainThreadInitializedObject.ObjectSandbox
import org.junit.Rule
import org.junit.rules.ExternalResource
import org.junit.rules.TestRule
@@ -69,7 +68,7 @@
// Defer to the true application to decide whether to clean up. For instance, we do not want
// to cleanup under Robolectric.
val app = ApplicationProvider.getApplicationContext<Context>()
- return if (app is ObjectSandbox) app.shouldCleanUpOnDestroy() else true
+ return (app as? SandboxContext)?.shouldCleanUpOnDestroy() ?: true
}
override fun apply(statement: Statement, description: Description): Statement {
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/SandboxApplicationTest.kt b/tests/multivalentTests/src/com/android/launcher3/util/SandboxApplicationTest.kt
index d87a406..8a21cff 100644
--- a/tests/multivalentTests/src/com/android/launcher3/util/SandboxApplicationTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/util/SandboxApplicationTest.kt
@@ -16,7 +16,6 @@
package com.android.launcher3.util
-import android.content.Context
import android.hardware.display.DisplayManager
import android.view.Display
import android.view.Display.DEFAULT_DISPLAY
@@ -63,16 +62,4 @@
onDestroy()
}
}
-
- @Test
- fun testGetObject_objectCreatesDisplayContext_isSandboxed() {
- class TestSingleton(context: Context) : SafeCloseable {
- override fun close() = Unit
-
- val displayContext = context.createDisplayContext(display)
- }
-
- val displayContext = MainThreadInitializedObject { TestSingleton(it) }[app].displayContext
- assertThat(displayContext.applicationContext).isEqualTo(app)
- }
}
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/TestSandboxModelContextWrapper.java b/tests/multivalentTests/src/com/android/launcher3/util/TestSandboxModelContextWrapper.java
index 393282f..8be1341 100644
--- a/tests/multivalentTests/src/com/android/launcher3/util/TestSandboxModelContextWrapper.java
+++ b/tests/multivalentTests/src/com/android/launcher3/util/TestSandboxModelContextWrapper.java
@@ -18,10 +18,9 @@
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
-import static com.android.launcher3.util.MainThreadInitializedObject.SandboxContext;
-
import android.content.ContextWrapper;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.test.platform.app.InstrumentationRegistry;
@@ -44,7 +43,7 @@
* There are 2 constructors in this class. The base context can be {@link SandboxContext} or
* Instrumentation target context.
* Using {@link SandboxContext} as base context allows custom implementations for
- * MainThreadInitializedObject providers.
+ * providing objects in Dagger components.
*/
public class TestSandboxModelContextWrapper extends ActivityContextWrapper implements
@@ -57,7 +56,7 @@
protected ActivityAllAppsContainerView<ActivityContextWrapper> mAppsView;
- private final PopupDataProvider mPopupDataProvider = new PopupDataProvider(i -> {});
+ private final PopupDataProvider mPopupDataProvider = new PopupDataProvider(this);
private final WidgetPickerDataProvider mWidgetPickerDataProvider =
new WidgetPickerDataProvider();
protected final UserCache mUserCache;
@@ -80,7 +79,7 @@
mAllAppsStore = mAppsView.getAppsStore();
}
- @Nullable
+ @NonNull
@Override
public PopupDataProvider getPopupDataProvider() {
return mPopupDataProvider;