Merge "Allow recent task to have long-press menu" into main
diff --git a/aconfig/launcher.aconfig b/aconfig/launcher.aconfig
index a87e290..e99dae3 100644
--- a/aconfig/launcher.aconfig
+++ b/aconfig/launcher.aconfig
@@ -646,3 +646,13 @@
description: "Enable more grid scale options on the launcher for desktop experience"
bug: "375491272"
}
+
+flag {
+ name: "enable_gesture_nav_horizontal_touch_slop"
+ namespace: "launcher"
+ description: "Enables horizontal touch slop checking in non-vertical fling navigation gestures"
+ bug: "394364217"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
index 84ae0fe..7cf0605 100644
--- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
+++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
@@ -176,6 +176,7 @@
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
+import java.util.Map.Entry;
/**
* Manages the opening and closing app transitions from Launcher
@@ -1342,9 +1343,9 @@
? Collections.EMPTY_LIST
: runningTaskTarget.taskInfo.launchCookies;
- return mLauncher.getFirstMatchForAppClose(
+ return mLauncher.getFirstVisibleElementForAppClose(
StableViewInfo.fromLaunchCookies(launchCookies), packageName,
- UserHandle.of(runningTaskTarget.taskInfo.userId), true /* supportsAllAppsState */);
+ UserHandle.of(runningTaskTarget.taskInfo.userId));
}
private @NonNull RectF getDefaultWindowTargetRect() {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index b41dd25..0d9a0d2 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -1401,14 +1401,9 @@
AppLaunchType.UNMINIMIZE, Cuj.CUJ_DESKTOP_MODE_APP_LAUNCH_FROM_ICON)
: null;
- TaskView taskView = null;
- if (recents != null) {
- taskView = recents.getTaskViewByTaskId(info.getTaskId());
- }
- if (areDesktopTasksVisible() && taskView != null
- && mControllers.uiController.isInOverviewUi()) {
- RunnableList runnableList = taskView.launchWithAnimation();
+ if (areDesktopTasksVisible() && mControllers.uiController.isInOverviewUi()) {
+ RunnableList runnableList = recents.launchRunningDesktopTaskView();
if (runnableList != null) {
runnableList.add(() ->
// wrapped it in runnable here since we need the post for DW to be
@@ -1789,7 +1784,7 @@
folder.animateOpen();
getStatsLogManager().logger().withItemInfo(folder.mInfo).log(LAUNCHER_FOLDER_OPEN);
- folder.iterateOverItems((itemInfo, itemView) -> {
+ folder.mapOverItems((itemInfo, itemView) -> {
mControllers.taskbarViewController
.setClickAndLongClickListenersForIcon(itemView);
// To play haptic when dragging, like other Taskbar items do.
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
index 19e528a..3a478c2 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
@@ -232,26 +232,23 @@
@Override
public void onTaskMovedToFront(int taskId) {
- if (mPerceptibleTasks.contains(taskId)) {
- return;
- }
-
// This listens to any Task, so we filter them by the ones shown in the launcher.
// For Tasks restored after startup, they will by default not be Perceptible, and no
// need to until user interacts with it by bringing it to the foreground.
for (int i = 0; i < mTaskbars.size(); i++) {
- // get pinned tasks
+ // get pinned tasks - we care about all tasks, not just the one moved to the front
Set<Integer> taskbarPinnedTasks =
mTaskbars.valueAt(i).getControllers().taskbarViewController
.getTaskIdsForPinnedApps();
- // mark as perceptible if the foregrounded task is in the list of apps shown in
- // the launcher.
- if (taskbarPinnedTasks.contains(taskId)
- && ActivityManagerWrapper.getInstance()
- .setTaskIsPerceptible(taskId, true)
- ) {
- mPerceptibleTasks.add(taskId);
+ // filter out tasks already marked as perceptible
+ taskbarPinnedTasks.removeAll(mPerceptibleTasks);
+
+ // add the filtered tasks as perceptible
+ for (int pinnedTaskId : taskbarPinnedTasks) {
+ ActivityManagerWrapper.getInstance()
+ .setTaskIsPerceptible(pinnedTaskId, true);
+ mPerceptibleTasks.add(pinnedTaskId);
}
}
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java
index d4ad555..15c7a8d 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java
@@ -18,6 +18,7 @@
import android.util.SparseArray;
import android.view.View;
+import androidx.annotation.NonNull;
import androidx.annotation.UiThread;
import com.android.launcher3.LauncherSettings.Favorites;
@@ -120,14 +121,15 @@
}
@Override
- public void mapOverItems(ItemOperator op) {
+ public View mapOverItems(@NonNull ItemOperator op) {
final int itemCount = mContainer.getChildCount();
for (int itemIdx = 0; itemIdx < itemCount; itemIdx++) {
View item = mContainer.getChildAt(itemIdx);
if (item.getTag() instanceof ItemInfo itemInfo && op.evaluate(itemInfo, item)) {
- return;
+ return item;
}
}
+ return null;
}
@Override
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
index 573bc1d..07b77c9 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
@@ -75,7 +75,6 @@
import java.util.List;
import java.util.Objects;
import java.util.Set;
-import java.util.function.Predicate;
/**
* Hosts the Taskbar content such as Hotseat and Recent Apps. Drawn on top of other apps.
@@ -1121,29 +1120,6 @@
}
/**
- * 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.
- */
- public View getFirstMatch(Predicate<ItemInfo>... matchers) {
- for (Predicate<ItemInfo> matcher : matchers) {
- for (int i = 0; i < getChildCount(); i++) {
- View item = getChildAt(i);
- if (!(item.getTag() instanceof ItemInfo)) {
- // Should only happen for All Apps button.
- // Will also happen for Recent/Running app icons. (Which have GroupTask as tags)
- continue;
- }
- ItemInfo info = (ItemInfo) item.getTag();
- if (matcher.test(info)) {
- return item;
- }
- }
- }
- return mAllAppsButtonContainer;
- }
-
- /**
* This method only works for bubble bar enabled in persistent task bar and the taskbar is start
* aligned.
*/
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
index 7f980aa..0fe0224 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
@@ -1191,8 +1191,8 @@
* 3) All Apps button
*/
public View getFirstIconMatch(Predicate<ItemInfo> matcher) {
- Predicate<ItemInfo> collectionMatcher = ItemInfoMatcher.forFolderMatch(matcher);
- return mTaskbarView.getFirstMatch(matcher, collectionMatcher);
+ View icon = mModelCallbacks.getFirstMatch(matcher, ItemInfoMatcher.forFolderMatch(matcher));
+ return icon != null ? icon : mTaskbarView.getAllAppsButtonContainer();
}
/**
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index 484978c..1a42d21 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -84,6 +84,7 @@
import android.os.SystemProperties;
import android.os.Trace;
import android.os.UserHandle;
+import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.Display;
import android.view.HapticFeedbackConstants;
@@ -116,6 +117,7 @@
import com.android.launcher3.Utilities;
import com.android.launcher3.Workspace;
import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
+import com.android.launcher3.allapps.AllAppsRecyclerView;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.apppairs.AppPairIcon;
@@ -165,8 +167,10 @@
import com.android.launcher3.util.SplitConfigurationOptions;
import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
import com.android.launcher3.util.SplitConfigurationOptions.SplitSelectSource;
+import com.android.launcher3.util.StableViewInfo;
import com.android.launcher3.util.StartActivityParams;
import com.android.launcher3.util.TouchController;
+import com.android.launcher3.views.FloatingIconView;
import com.android.launcher3.widget.LauncherWidgetHolder;
import com.android.quickstep.OverviewCommandHelper;
import com.android.quickstep.OverviewComponentObserver;
@@ -1451,6 +1455,45 @@
mBubbleBarLocation = bubbleBarLocation;
}
+ /**
+ * Similar to {@link #getFirstHomeElementForAppClose} but also matches all apps if its visible
+ */
+ @Nullable
+ public View getFirstVisibleElementForAppClose(
+ @Nullable StableViewInfo svi, String packageName, UserHandle user) {
+ if (isInState(LauncherState.ALL_APPS)) {
+ AllAppsRecyclerView activeRecyclerView = getAppsView().getActiveRecyclerView();
+ View v = null;
+ if (svi != null) {
+ // Preferred item match
+ v = activeRecyclerView.findViewByPredicate(view ->
+ view.isAggregatedVisible()
+ && view.getTag() instanceof ItemInfo info && svi.matches(info));
+ }
+ if (v == null) {
+ // Package user match
+ v = activeRecyclerView.findViewByPredicate(view ->
+ view.isAggregatedVisible() && view.getTag() instanceof ItemInfo info
+ && info.itemType == ITEM_TYPE_APPLICATION
+ && info.user.equals(user)
+ && TextUtils.equals(info.getTargetPackage(), packageName));
+ }
+
+ if (v != null && activeRecyclerView.computeVerticalScrollOffset() > 0) {
+ RectF locationBounds = new RectF();
+ FloatingIconView.getLocationBoundsForView(this, v, false, locationBounds,
+ new Rect());
+ if (locationBounds.top < getAppsView().getHeaderBottom()) {
+ // Icon is covered by scrim, return null to play fallback animation.
+ return null;
+ }
+ }
+ return v;
+ }
+
+ return getFirstHomeElementForAppClose(svi, packageName, user);
+ }
+
@Override
public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
super.dump(prefix, fd, writer, args);
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchControllerDeprecated.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchControllerDeprecated.java
index b1a36c7..f26bd13 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchControllerDeprecated.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchControllerDeprecated.java
@@ -239,7 +239,7 @@
pa = new PendingAnimation(maxDuration);
mRecentsView.createTaskDismissAnimation(pa, mTaskBeingDragged,
true /* animateTaskView */, true /* removeTask */, maxDuration,
- false /* dismissingForSplitSelection*/);
+ false /* dismissingForSplitSelection*/, false /* isExpressiveDismiss */);
mEndDisplacement = -secondaryTaskDimension;
} else {
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 23b8e36..0566f09 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -31,6 +31,7 @@
import static com.android.launcher3.BaseActivity.INVISIBLE_BY_STATE_HANDLER;
import static com.android.launcher3.BaseActivity.STATE_HANDLER_INVISIBILITY_FLAGS;
import static com.android.launcher3.Flags.enableAdditionalHomeAnimations;
+import static com.android.launcher3.Flags.enableGestureNavHorizontalTouchSlop;
import static com.android.launcher3.Flags.enableScalingRevealHomeAnimation;
import static com.android.launcher3.Flags.msdlFeedback;
import static com.android.launcher3.PagedView.INVALID_PAGE;
@@ -1104,7 +1105,12 @@
public void onGestureCancelled() {
updateDisplacement(0);
mStateCallback.setStateOnUiThread(STATE_GESTURE_COMPLETED);
- handleNormalGestureEnd(0, false, new PointF(), true /* isCancel */);
+ handleNormalGestureEnd(
+ /* endVelocityPxPerMs= */ 0,
+ /* isFling= */ false,
+ /* velocityPxPerMs= */ new PointF(),
+ /* isCancel= */ true,
+ /* horizontalTouchSlopPassed= */ false);
}
/**
@@ -1113,7 +1119,8 @@
* @param velocityPxPerMs The x and y components of the velocity when the gesture ends.
*/
@UiThread
- public void onGestureEnded(float endVelocityPxPerMs, PointF velocityPxPerMs) {
+ public void onGestureEnded(
+ float endVelocityPxPerMs, PointF velocityPxPerMs, boolean horizontalTouchSlopPassed) {
float flingThreshold = mContext.getResources()
.getDimension(R.dimen.quickstep_fling_threshold_speed);
boolean isFling = mGestureStarted && !mIsMotionPaused
@@ -1126,7 +1133,11 @@
mLogDirectionUpOrLeft = velocityPxPerMs.x < 0;
}
Runnable handleNormalGestureEndCallback = () -> handleNormalGestureEnd(
- endVelocityPxPerMs, isFling, velocityPxPerMs, /* isCancel= */ false);
+ endVelocityPxPerMs,
+ isFling,
+ velocityPxPerMs,
+ /* isCancel= */ false,
+ horizontalTouchSlopPassed);
if (mRecentsView != null) {
mRecentsView.runOnPageScrollsInitialized(handleNormalGestureEndCallback);
} else {
@@ -1259,7 +1270,11 @@
}
private GestureEndTarget calculateEndTarget(
- PointF velocityPxPerMs, float endVelocityPxPerMs, boolean isFlingY, boolean isCancel) {
+ PointF velocityPxPerMs,
+ float endVelocityPxPerMs,
+ boolean isFlingY,
+ boolean isCancel,
+ boolean horizontalTouchSlopPassed) {
ActiveGestureProtoLogProxy.logOnCalculateEndTarget(
dpiFromPx(velocityPxPerMs.x),
dpiFromPx(velocityPxPerMs.y),
@@ -1276,7 +1291,7 @@
} else if (isFlingY) {
endTarget = calculateEndTargetForFlingY(velocityPxPerMs, endVelocityPxPerMs);
} else {
- endTarget = calculateEndTargetForNonFling(velocityPxPerMs);
+ endTarget = calculateEndTargetForNonFling(velocityPxPerMs, horizontalTouchSlopPassed);
}
if (mDeviceState.isOverviewDisabled() && endTarget == RECENTS) {
@@ -1314,12 +1329,14 @@
return willGoToNewTask ? NEW_TASK : HOME;
}
- private GestureEndTarget calculateEndTargetForNonFling(PointF velocity) {
+ private GestureEndTarget calculateEndTargetForNonFling(
+ PointF velocity, boolean horizontalTouchSlopPassed) {
final boolean isScrollingToNewTask = isScrollingToNewTask();
// Fully gestural mode.
final boolean isFlingX = Math.abs(velocity.x) > mContext.getResources()
- .getDimension(R.dimen.quickstep_fling_threshold_speed);
+ .getDimension(R.dimen.quickstep_fling_threshold_speed)
+ && (!enableGestureNavHorizontalTouchSlop() || horizontalTouchSlopPassed);
if (isScrollingToNewTask && isFlingX) {
// Flinging towards new task takes precedence over mIsMotionPaused (which only
// checks y-velocity).
@@ -1359,11 +1376,15 @@
@UiThread
private void handleNormalGestureEnd(
- float endVelocityPxPerMs, boolean isFling, PointF velocityPxPerMs, boolean isCancel) {
+ float endVelocityPxPerMs,
+ boolean isFling,
+ PointF velocityPxPerMs,
+ boolean isCancel,
+ boolean horizontalTouchSlopPassed) {
long duration = MAX_SWIPE_DURATION;
float currentShift = mCurrentShift.value;
final GestureEndTarget endTarget = calculateEndTarget(
- velocityPxPerMs, endVelocityPxPerMs, isFling, isCancel);
+ velocityPxPerMs, endVelocityPxPerMs, isFling, isCancel, horizontalTouchSlopPassed);
// Set the state, but don't notify until the animation completes
mGestureState.setEndTarget(endTarget, false /* isAtomic */);
mAnimationFactory.setEndTarget(endTarget);
diff --git a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
index 0a77688..4c56f35 100644
--- a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
+++ b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
@@ -308,10 +308,10 @@
return null;
}
- return mContainer.getFirstMatchForAppClose(StableViewInfo.fromLaunchCookies(launchCookies),
+ return mContainer.getFirstHomeElementForAppClose(
+ StableViewInfo.fromLaunchCookies(launchCookies),
sourceTaskView.getFirstTask().key.getComponent().getPackageName(),
- UserHandle.of(sourceTaskView.getFirstTask().key.userId),
- false /* supportsAllAppsState */);
+ UserHandle.of(sourceTaskView.getFirstTask().key.userId));
}
@Override
diff --git a/quickstep/src/com/android/quickstep/OverviewCommandHelper.kt b/quickstep/src/com/android/quickstep/OverviewCommandHelper.kt
index 6bd3400..984f390 100644
--- a/quickstep/src/com/android/quickstep/OverviewCommandHelper.kt
+++ b/quickstep/src/com/android/quickstep/OverviewCommandHelper.kt
@@ -435,7 +435,11 @@
logShowOverviewFrom(command.type)
containerInterface.runOnInitBackgroundStateUI {
Log.d(TAG, "recents animation started - onInitBackgroundStateUI: $command")
- interactionHandler.onGestureEnded(0f, PointF())
+ interactionHandler.onGestureEnded(
+ 0f,
+ PointF(),
+ /* horizontalTouchSlopPassed= */ false,
+ )
}
command.removeListener(this)
}
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
index f426bf5..d8662f2 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
@@ -136,7 +136,8 @@
if (tv != null) {
PendingAnimation pa = new PendingAnimation(TASK_DISMISS_DURATION);
createTaskDismissAnimation(pa, tv, true, false,
- TASK_DISMISS_DURATION, false /* dismissingForSplitSelection*/);
+ TASK_DISMISS_DURATION, false /* dismissingForSplitSelection*/,
+ false /* isExpressiveDismiss */);
pa.addEndListener(e -> setCurrentTask(-1));
AnimatorPlaybackController controller = pa.createPlaybackController();
controller.dispatchOnStart();
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
index 5963a7c..7cae5b8 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
@@ -483,7 +483,8 @@
: velocityYPxPerMs;
mInteractionHandler.updateDisplacement(getDisplacement(ev) - mStartDisplacement);
mInteractionHandler.onGestureEnded(velocityPxPerMs,
- new PointF(velocityXPxPerMs, velocityYPxPerMs));
+ new PointF(velocityXPxPerMs, velocityYPxPerMs),
+ Math.abs(mDownPos.x - mLastPos.x) > mTouchSlop);
}
} else {
// Since we start touch tracking on DOWN, we may reach this state without actually
diff --git a/quickstep/src/com/android/quickstep/views/RecentsDismissUtils.kt b/quickstep/src/com/android/quickstep/views/RecentsDismissUtils.kt
index 3430b39..4ce18f5 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsDismissUtils.kt
+++ b/quickstep/src/com/android/quickstep/views/RecentsDismissUtils.kt
@@ -21,6 +21,7 @@
import androidx.dynamicanimation.animation.FloatValueHolder
import androidx.dynamicanimation.animation.SpringAnimation
import androidx.dynamicanimation.animation.SpringForce
+import com.android.launcher3.Flags.enableGridOnlyOverview
import com.android.launcher3.R
import com.android.launcher3.Utilities.boundToRange
import com.android.launcher3.touch.SingleAxisSwipeDetector
@@ -31,6 +32,7 @@
import com.google.android.msdl.data.model.MSDLToken
import com.google.android.msdl.domain.InteractionProperties
import kotlin.math.abs
+import kotlin.math.roundToInt
/**
* Helper class for [RecentsView]. This util class contains refactored and extracted functions from
@@ -76,11 +78,18 @@
}
.addEndListener { _, _, _, _ ->
if (isDismissing) {
- recentsView.dismissTaskView(
- draggedTaskView,
- /* animateTaskView = */ false,
- /* removeTask = */ true,
- )
+ if (!recentsView.showAsGrid() || enableGridOnlyOverview()) {
+ runTaskGridReflowSpringAnimation(
+ draggedTaskView,
+ getDismissedTaskGapForReflow(draggedTaskView),
+ )
+ } else {
+ recentsView.dismissTaskView(
+ draggedTaskView,
+ /* animateTaskView = */ false,
+ /* removeTask = */ true,
+ )
+ }
} else {
recentsView.onDismissAnimationEnds()
}
@@ -160,8 +169,8 @@
if (recentsView.showAsGrid()) {
val taskGridNavHelper =
TaskGridNavHelper(
- recentsView.topRowIdArray,
- recentsView.bottomRowIdArray,
+ recentsView.mUtils.getTopRowIdArray(),
+ recentsView.mUtils.getBottomRowIdArray(),
recentsView.mUtils.getLargeTaskViewIds(),
hasAddDesktopButton = false,
)
@@ -237,6 +246,19 @@
)
}
+ private fun createExpressiveGridReflowSpringForce(
+ finalPosition: Float = Float.MAX_VALUE
+ ): SpringForce {
+ val resourceProvider = DynamicResource.provider(recentsView.mContainer)
+ return SpringForce(finalPosition)
+ .setDampingRatio(
+ resourceProvider.getFloat(R.dimen.expressive_dismiss_task_trans_x_damping_ratio)
+ )
+ .setStiffness(
+ resourceProvider.getFloat(R.dimen.expressive_dismiss_task_trans_x_stiffness)
+ )
+ }
+
/**
* Plays a haptic as the dragged task view settles back into its rest state.
*
@@ -286,6 +308,197 @@
.apply { animateToFinalPosition(RECENTS_SCALE_SPRING_MULTIPLIER * scale) }
}
+ /** Animates with springs the TaskViews beyond the dismissed task to fill the gap it left. */
+ private fun runTaskGridReflowSpringAnimation(
+ dismissedTaskView: TaskView,
+ dismissedTaskGap: Float,
+ ) {
+ // Empty spring animation exists for conditional start, and to drive neighboring springs.
+ val springAnimationDriver =
+ SpringAnimation(FloatValueHolder())
+ .setSpring(createExpressiveGridReflowSpringForce(finalPosition = dismissedTaskGap))
+ val towardsStart = if (recentsView.isRtl) dismissedTaskGap < 0 else dismissedTaskGap > 0
+
+ // Build the chains of Spring Animations
+ when {
+ !recentsView.showAsGrid() -> {
+ buildDismissReflowSpringAnimationChain(
+ getTasksToReflow(
+ recentsView.mUtils.taskViews.toList(),
+ dismissedTaskView,
+ towardsStart,
+ ),
+ dismissedTaskGap,
+ previousSpring = springAnimationDriver,
+ )
+ }
+ dismissedTaskView.isLargeTile -> {
+ val lastSpringAnimation =
+ buildDismissReflowSpringAnimationChain(
+ getTasksToReflow(
+ recentsView.mUtils.getLargeTaskViews(),
+ dismissedTaskView,
+ towardsStart,
+ ),
+ dismissedTaskGap,
+ previousSpring = springAnimationDriver,
+ )
+ // Add all top and bottom grid tasks when animating towards the end of the grid.
+ if (!towardsStart) {
+ buildDismissReflowSpringAnimationChain(
+ recentsView.mUtils.getTopRowTaskViews(),
+ dismissedTaskGap,
+ previousSpring = lastSpringAnimation,
+ )
+ buildDismissReflowSpringAnimationChain(
+ recentsView.mUtils.getBottomRowTaskViews(),
+ dismissedTaskGap,
+ previousSpring = lastSpringAnimation,
+ )
+ }
+ }
+ recentsView.isOnGridBottomRow(dismissedTaskView) -> {
+ buildDismissReflowSpringAnimationChain(
+ getTasksToReflow(
+ recentsView.mUtils.getBottomRowTaskViews(),
+ dismissedTaskView,
+ towardsStart,
+ ),
+ dismissedTaskGap,
+ previousSpring = springAnimationDriver,
+ )
+ }
+ else -> {
+ buildDismissReflowSpringAnimationChain(
+ getTasksToReflow(
+ recentsView.mUtils.getTopRowTaskViews(),
+ dismissedTaskView,
+ towardsStart,
+ ),
+ dismissedTaskGap,
+ previousSpring = springAnimationDriver,
+ )
+ }
+ }
+
+ // Start animations and remove the dismissed task at the end, dismiss immediately if no
+ // neighboring tasks exist.
+ val runGridEndAnimationAndRelayout = {
+ recentsView.expressiveDismissTaskView(dismissedTaskView)
+ }
+ springAnimationDriver?.apply {
+ addEndListener { _, _, _, _ -> runGridEndAnimationAndRelayout() }
+ animateToFinalPosition(dismissedTaskGap)
+ } ?: runGridEndAnimationAndRelayout()
+ }
+
+ private fun getDismissedTaskGapForReflow(dismissedTaskView: TaskView): Float {
+ val screenStart = recentsView.pagedOrientationHandler.getPrimaryScroll(recentsView)
+ val screenEnd =
+ screenStart + recentsView.pagedOrientationHandler.getMeasuredSize(recentsView)
+ val taskStart =
+ recentsView.pagedOrientationHandler.getChildStart(dismissedTaskView) +
+ dismissedTaskView.getOffsetAdjustment(recentsView.showAsGrid())
+ val taskSize =
+ recentsView.pagedOrientationHandler.getMeasuredSize(dismissedTaskView) *
+ dismissedTaskView.getSizeAdjustment(recentsView.showAsFullscreen())
+ val taskEnd = taskStart + taskSize
+
+ val isDismissedTaskBeyondEndOfScreen =
+ if (recentsView.isRtl) taskEnd > screenEnd else taskStart < screenStart
+ if (
+ dismissedTaskView.isLargeTile &&
+ isDismissedTaskBeyondEndOfScreen &&
+ recentsView.mUtils.getLargeTileCount() == 1
+ ) {
+ return with(recentsView) {
+ pagedOrientationHandler.getPrimaryScroll(this) -
+ getScrollForPage(indexOfChild(mUtils.getFirstNonDesktopTaskView()))
+ }
+ .toFloat()
+ }
+
+ // If current page is beyond last TaskView's index, use last TaskView to calculate offset.
+ val lastTaskViewIndex = recentsView.indexOfChild(recentsView.mUtils.getLastTaskView())
+ val currentPage = recentsView.currentPage.coerceAtMost(lastTaskViewIndex)
+ val dismissHorizontalFactor =
+ when {
+ dismissedTaskView.isGridTask -> 1f
+ currentPage == lastTaskViewIndex -> -1f
+ recentsView.indexOfChild(dismissedTaskView) < currentPage -> -1f
+ else -> 1f
+ } * (if (recentsView.isRtl) 1f else -1f)
+
+ return (dismissedTaskView.layoutParams.width + recentsView.pageSpacing) *
+ dismissHorizontalFactor
+ }
+
+ private fun getTasksToReflow(
+ taskViews: List<TaskView>,
+ dismissedTaskView: TaskView,
+ towardsStart: Boolean,
+ ): List<TaskView> {
+ val dismissedTaskViewIndex = taskViews.indexOf(dismissedTaskView)
+ if (dismissedTaskViewIndex == -1) {
+ return emptyList()
+ }
+ return if (towardsStart) {
+ taskViews.take(dismissedTaskViewIndex).reversed()
+ } else {
+ taskViews.takeLast(taskViews.size - dismissedTaskViewIndex - 1)
+ }
+ }
+
+ private fun willTaskBeVisibleAfterDismiss(taskView: TaskView, taskTranslation: Int): Boolean {
+ val screenStart = recentsView.pagedOrientationHandler.getPrimaryScroll(recentsView)
+ val screenEnd =
+ screenStart + recentsView.pagedOrientationHandler.getMeasuredSize(recentsView)
+ return recentsView.isTaskViewWithinBounds(
+ taskView,
+ screenStart,
+ screenEnd,
+ /* taskViewTranslation = */ taskTranslation,
+ )
+ }
+
+ /** Builds a chain of spring animations for task reflow after dismissal */
+ private fun buildDismissReflowSpringAnimationChain(
+ taskViews: Iterable<TaskView>,
+ dismissedTaskGap: Float,
+ previousSpring: SpringAnimation,
+ ): SpringAnimation {
+ var lastTaskViewSpring = previousSpring
+ taskViews
+ .filter { taskView ->
+ willTaskBeVisibleAfterDismiss(taskView, dismissedTaskGap.roundToInt())
+ }
+ .forEach { taskView ->
+ val taskViewSpringAnimation =
+ SpringAnimation(
+ taskView,
+ FloatPropertyCompat.createFloatPropertyCompat(
+ taskView.primaryDismissTranslationProperty
+ ),
+ )
+ .setSpring(createExpressiveGridReflowSpringForce(dismissedTaskGap))
+ // Update live tile on spring animation.
+ if (taskView.isRunningTask && recentsView.enableDrawingLiveTile) {
+ taskViewSpringAnimation.addUpdateListener { _, _, _ ->
+ recentsView.runActionOnRemoteHandles { remoteTargetHandle ->
+ remoteTargetHandle.taskViewSimulator.taskPrimaryTranslation.value =
+ taskView.primaryDismissTranslationProperty.get(taskView)
+ }
+ recentsView.redrawLiveTile()
+ }
+ }
+ lastTaskViewSpring.addUpdateListener { _, value, _ ->
+ taskViewSpringAnimation.animateToFinalPosition(value)
+ }
+ lastTaskViewSpring = taskViewSpringAnimation
+ }
+ return lastTaskViewSpring
+ }
+
private companion object {
// The additional damping to apply to tasks further from the dismissed task.
private const val ADDITIONAL_DISMISS_DAMPING_RATIO = 0.15f
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index e434252..89726aa 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -38,7 +38,6 @@
import static com.android.launcher3.Flags.enableAdditionalHomeAnimations;
import static com.android.launcher3.Flags.enableDesktopExplodedView;
import static com.android.launcher3.Flags.enableDesktopTaskAlphaAnimation;
-import static com.android.launcher3.Flags.enableExpressiveDismissTaskMotion;
import static com.android.launcher3.Flags.enableGridOnlyOverview;
import static com.android.launcher3.Flags.enableLargeDesktopWindowingTile;
import static com.android.launcher3.Flags.enableRefactorTaskThumbnail;
@@ -602,7 +601,7 @@
private float mTaskThumbnailSplashAlpha = 0;
private boolean mBorderEnabled = false;
private boolean mShowAsGridLastOnLayout = false;
- private final IntSet mTopRowIdSet = new IntSet();
+ protected final IntSet mTopRowIdSet = new IntSet();
private int mClearAllShortTotalWidthTranslation = 0;
// The GestureEndTarget that is still in progress.
@@ -1507,7 +1506,7 @@
@Nullable
private TaskView getLastGridTaskView() {
- return getLastGridTaskView(getTopRowIdArray(), getBottomRowIdArray());
+ return getLastGridTaskView(mUtils.getTopRowIdArray(), mUtils.getBottomRowIdArray());
}
@Nullable
@@ -1553,7 +1552,7 @@
* @param taskViewTranslation taskView is considered within bounds if either translated or
* original position of taskView is within screen bounds.
*/
- private boolean isTaskViewWithinBounds(TaskView taskView, int screenStart, int screenEnd,
+ protected boolean isTaskViewWithinBounds(TaskView taskView, int screenStart, int screenEnd,
int taskViewTranslation) {
int taskStart = getPagedOrientationHandler().getChildStart(taskView)
+ (int) taskView.getOffsetAdjustment(showAsGrid());
@@ -3552,7 +3551,7 @@
setGridProgress(mGridProgress);
}
- private boolean isSameGridRow(TaskView taskView1, TaskView taskView2) {
+ protected boolean isSameGridRow(TaskView taskView1, TaskView taskView2) {
if (taskView1 == null || taskView2 == null) {
return false;
}
@@ -3756,11 +3755,13 @@
* @param duration duration of the animation
* @param dismissingForSplitSelection task dismiss animation is used for entering split
* selection state from app icon
+ * @param isExpressiveDismiss runs expressive animations controlled via
+ * {@link RecentsDismissUtils}
*/
public void createTaskDismissAnimation(PendingAnimation anim,
@Nullable TaskView dismissedTaskView,
boolean animateTaskView, boolean shouldRemoveTask, long duration,
- boolean dismissingForSplitSelection) {
+ boolean dismissingForSplitSelection, boolean isExpressiveDismiss) {
if (mPendingAnimation != null) {
mPendingAnimation.createPlaybackController().dispatchOnCancel().dispatchOnEnd();
}
@@ -3878,7 +3879,7 @@
}
}
if (lastGridTaskView != null && (lastGridTaskView.isVisibleToUser() || (
- enableExpressiveDismissTaskMotion() && lastGridTaskView == dismissedTaskView))) {
+ isExpressiveDismiss && lastGridTaskView == dismissedTaskView))) {
// After dismissal, animate translation of the remaining tasks to fill any gap left
// between the end of the grid and the clear all button. Only animate if the clear
// all button is visible or would become visible after dismissal.
@@ -4018,12 +4019,17 @@
lastTaskViewIndex);
int scrollDiff = newScroll[i] - oldScroll[i] + offset;
if (scrollDiff != 0) {
- translateTaskWhenDismissed(
- child,
- Math.abs(i - dismissedIndex),
- scrollDiff,
- anim,
- splitTimings);
+ if (!isExpressiveDismiss) {
+ translateTaskWhenDismissed(
+ child,
+ Math.abs(i - dismissedIndex),
+ scrollDiff,
+ anim,
+ splitTimings);
+ }
+ if (child instanceof TaskView taskView) {
+ mTaskViewsDismissPrimaryTranslations.put(taskView, scrollDiffPerPage);
+ }
needsCurveUpdates = true;
}
} else if (child instanceof TaskView taskView) {
@@ -4108,13 +4114,16 @@
: finalTranslation + (mIsRtl ? -mLastComputedTaskSize.right
: mLastComputedTaskSize.right);
}
- Animator dismissAnimator = ObjectAnimator.ofFloat(taskView,
- taskView.getPrimaryDismissTranslationProperty(),
- startTranslation, finalTranslation);
- dismissAnimator.setInterpolator(
- clampToProgress(dismissInterpolator, animationStartProgress,
- animationEndProgress));
- anim.add(dismissAnimator);
+ // Expressive dismiss will animate the translations of taskViews itself.
+ if (!isExpressiveDismiss) {
+ Animator dismissAnimator = ObjectAnimator.ofFloat(taskView,
+ taskView.getPrimaryDismissTranslationProperty(),
+ startTranslation, finalTranslation);
+ dismissAnimator.setInterpolator(
+ clampToProgress(dismissInterpolator, animationStartProgress,
+ animationEndProgress));
+ anim.add(dismissAnimator);
+ }
mTaskViewsDismissPrimaryTranslations.put(taskView, (int) finalTranslation);
distanceFromDismissedTask++;
}
@@ -4214,8 +4223,8 @@
boolean isSnappedTaskInTopRow = mTopRowIdSet.contains(
snappedTaskViewId);
IntArray taskViewIdArray =
- isSnappedTaskInTopRow ? getTopRowIdArray()
- : getBottomRowIdArray();
+ isSnappedTaskInTopRow ? mUtils.getTopRowIdArray()
+ : mUtils.getBottomRowIdArray();
int snappedIndex = taskViewIdArray.indexOf(snappedTaskViewId);
taskViewIdArray.removeValue(dismissedTaskViewId);
if (finalNextFocusedTaskView != null) {
@@ -4230,8 +4239,8 @@
// dismissed row,
// snap to the same column in the other grid row
IntArray inverseRowTaskViewIdArray =
- isSnappedTaskInTopRow ? getBottomRowIdArray()
- : getTopRowIdArray();
+ isSnappedTaskInTopRow ? mUtils.getBottomRowIdArray()
+ : mUtils.getTopRowIdArray();
if (snappedIndex < inverseRowTaskViewIdArray.size()) {
taskViewIdToSnapTo = inverseRowTaskViewIdArray.get(
snappedIndex);
@@ -4313,8 +4322,8 @@
}
}
- IntArray topRowIdArray = getTopRowIdArray();
- IntArray bottomRowIdArray = getBottomRowIdArray();
+ IntArray topRowIdArray = mUtils.getTopRowIdArray();
+ IntArray bottomRowIdArray = mUtils.getBottomRowIdArray();
if (finalSnapToLastTask) {
// If snapping to last task, find the last task after dismissal.
pageToSnapTo = indexOfChild(
@@ -4437,10 +4446,6 @@
animationEndProgress
)
);
-
- if (view instanceof TaskView) {
- mTaskViewsDismissPrimaryTranslations.put((TaskView) view, scrollDiffPerPage);
- }
if (mEnableDrawingLiveTile && view instanceof TaskView
&& ((TaskView) view).isRunningTask()) {
pendingAnimation.addOnFrameCallback(() -> {
@@ -4485,41 +4490,6 @@
}
/**
- * Returns all the tasks in the top row, without the focused task
- */
- IntArray getTopRowIdArray() {
- if (mTopRowIdSet.isEmpty()) {
- return new IntArray(0);
- }
- IntArray topArray = new IntArray(mTopRowIdSet.size());
- for (TaskView taskView : getTaskViews()) {
- int taskViewId = taskView.getTaskViewId();
- if (mTopRowIdSet.contains(taskViewId)) {
- topArray.add(taskViewId);
- }
- }
- return topArray;
- }
-
- /**
- * Returns all the tasks in the bottom row, without the focused task
- */
- IntArray getBottomRowIdArray() {
- int bottomRowIdArraySize = getBottomRowTaskCountForTablet();
- if (bottomRowIdArraySize <= 0) {
- return new IntArray(0);
- }
- IntArray bottomArray = new IntArray(bottomRowIdArraySize);
- for (TaskView taskView : getTaskViews()) {
- int taskViewId = taskView.getTaskViewId();
- if (!mTopRowIdSet.contains(taskViewId) && !taskView.isLargeTile()) {
- bottomArray.add(taskViewId);
- }
- }
- return bottomArray;
- }
-
- /**
* Iterate the grid by columns instead of by TaskView index, starting after the focused task and
* up to the last balanced column.
*
@@ -4529,8 +4499,8 @@
if (mTopRowIdSet.isEmpty()) return null; // return earlier
TaskView lastVisibleTaskView = null;
- IntArray topRowIdArray = getTopRowIdArray();
- IntArray bottomRowIdArray = getBottomRowIdArray();
+ IntArray topRowIdArray = mUtils.getTopRowIdArray();
+ IntArray bottomRowIdArray = mUtils.getBottomRowIdArray();
int balancedColumns = Math.min(bottomRowIdArray.size(), topRowIdArray.size());
for (int i = 0; i < balancedColumns; i++) {
@@ -4625,8 +4595,9 @@
}
// Init task grid nav helper with top/bottom id arrays.
- TaskGridNavHelper taskGridNavHelper = new TaskGridNavHelper(getTopRowIdArray(),
- getBottomRowIdArray(), mUtils.getLargeTaskViewIds(), mAddDesktopButton != null);
+ TaskGridNavHelper taskGridNavHelper = new TaskGridNavHelper(mUtils.getTopRowIdArray(),
+ mUtils.getBottomRowIdArray(), mUtils.getLargeTaskViewIds(),
+ mAddDesktopButton != null);
// Get current page's task view ID.
TaskView currentPageTaskView = getCurrentPageTaskView();
@@ -4685,7 +4656,15 @@
public void dismissTaskView(TaskView taskView, boolean animateTaskView, boolean removeTask) {
PendingAnimation pa = new PendingAnimation(DISMISS_TASK_DURATION);
createTaskDismissAnimation(pa, taskView, animateTaskView, removeTask, DISMISS_TASK_DURATION,
- false /* dismissingForSplitSelection*/);
+ false /* dismissingForSplitSelection*/, false /* isExpressiveDismiss */);
+ runDismissAnimation(pa);
+ }
+
+ protected void expressiveDismissTaskView(TaskView taskView) {
+ PendingAnimation pa = new PendingAnimation(DISMISS_TASK_DURATION);
+ createTaskDismissAnimation(pa, taskView, false /* animateTaskView */, true /* removeTask */,
+ DISMISS_TASK_DURATION, false /* dismissingForSplitSelection*/,
+ true /* isExpressiveDismiss */);
runDismissAnimation(pa);
}
@@ -5414,7 +5393,7 @@
}
// Splitting from Overview for fullscreen task
createTaskDismissAnimation(builder, mSplitHiddenTaskView, true, false, duration,
- true /* dismissingForSplitSelection*/);
+ true /* dismissingForSplitSelection*/, false /* isExpressiveDismiss */);
} else {
// Splitting from Home
TaskView currentPageTaskView = getTaskViewAt(mCurrentPage);
@@ -5422,7 +5401,7 @@
// display correct animation in split mode
if (currentPageTaskView instanceof DesktopTaskView) {
createTaskDismissAnimation(builder, null, true, false, duration,
- true /* dismissingForSplitSelection*/);
+ true /* dismissingForSplitSelection*/, false /* isExpressiveDismiss */);
} else {
createInitialSplitSelectAnimation(builder);
}
@@ -6416,7 +6395,8 @@
* Returns how many pixels the page is offset from its scroll position.
*/
private int getOffsetFromScrollPosition(int pageIndex) {
- return getOffsetFromScrollPosition(pageIndex, getTopRowIdArray(), getBottomRowIdArray());
+ return getOffsetFromScrollPosition(pageIndex, mUtils.getTopRowIdArray(),
+ mUtils.getBottomRowIdArray());
}
private int getOffsetFromScrollPosition(
@@ -6715,7 +6695,7 @@
.displayOverviewTasksAsGrid(mContainer.getDeviceProfile()));
}
- private boolean showAsFullscreen() {
+ protected boolean showAsFullscreen() {
return mOverviewFullscreenEnabled
&& mCurrentGestureEndTarget != GestureState.GestureEndTarget.RECENTS;
}
diff --git a/quickstep/src/com/android/quickstep/views/RecentsViewUtils.kt b/quickstep/src/com/android/quickstep/views/RecentsViewUtils.kt
index 31ae890..1c37986 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsViewUtils.kt
+++ b/quickstep/src/com/android/quickstep/views/RecentsViewUtils.kt
@@ -83,6 +83,25 @@
/** Returns a list of all large TaskView Ids from [TaskView]s */
fun getLargeTaskViewIds(): List<Int> = taskViews.filter { it.isLargeTile }.map { it.taskViewId }
+ /** Returns a list of all large TaskViews [TaskView]s */
+ fun getLargeTaskViews(): List<TaskView> = taskViews.filter { it.isLargeTile }
+
+ /** Returns all the TaskViews in the top row, without the focused task */
+ fun getTopRowTaskViews(): List<TaskView> =
+ taskViews.filter { recentsView.mTopRowIdSet.contains(it.taskViewId) }
+
+ /** Returns all the task Ids in the top row, without the focused task */
+ fun getTopRowIdArray(): IntArray = getTopRowTaskViews().map { it.taskViewId }.toIntArray()
+
+ /** Returns all the TaskViews in the bottom row, without the focused task */
+ fun getBottomRowTaskViews(): List<TaskView> =
+ taskViews.filter { !recentsView.mTopRowIdSet.contains(it.taskViewId) && !it.isLargeTile }
+
+ /** Returns all the task Ids in the bottom row, without the focused task */
+ fun getBottomRowIdArray(): IntArray = getBottomRowTaskViews().map { it.taskViewId }.toIntArray()
+
+ private fun List<Int>.toIntArray() = IntArray(size).apply { this@toIntArray.forEach(::add) }
+
/** Counts [TaskView]s that are large tiles. */
fun getLargeTileCount(): Int = taskViews.count { it.isLargeTile }
@@ -266,8 +285,8 @@
return
}
getRowRect(getFirstLargeTaskView(), getLastLargeTaskView(), outTaskViewRowRect)
- getRowRect(recentsView.getTopRowIdArray(), outTopRowRect)
- getRowRect(recentsView.getBottomRowIdArray(), outBottomRowRect)
+ getRowRect(getTopRowIdArray(), outTopRowRect)
+ getRowRect(getBottomRowIdArray(), outBottomRowRect)
// Expand large tile Rect to include space between top/bottom row.
val nonEmptyRowRect =
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.kt b/quickstep/src/com/android/quickstep/views/TaskView.kt
index 3819772..ab78592 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskView.kt
@@ -220,7 +220,7 @@
SPLIT_SELECT_TRANSLATION_Y,
)
- protected val primaryDismissTranslationProperty: FloatProperty<TaskView>
+ val primaryDismissTranslationProperty: FloatProperty<TaskView>
get() =
pagedOrientationHandler.getPrimaryValue(DISMISS_TRANSLATION_X, DISMISS_TRANSLATION_Y)
@@ -743,9 +743,10 @@
// The TaskView lifecycle is starts the ViewModel during onBind, and cleans it in
// onRecycle. So it should be initialized at this point. TaskView Lifecycle:
// `bind` -> `onBind` -> onAttachedToWindow() -> onDetachFromWindow -> onRecycle
- coroutineJobs += coroutineScope.launch(dispatcherProvider.main) {
- viewModel!!.state.collectLatest(::updateTaskViewState)
- }
+ coroutineJobs +=
+ coroutineScope.launch(dispatcherProvider.main) {
+ viewModel!!.state.collectLatest(::updateTaskViewState)
+ }
}
}
@@ -1653,7 +1654,7 @@
protected fun getScrollAdjustment(gridEnabled: Boolean) =
if (gridEnabled) gridTranslationX else nonGridTranslationX
- protected fun getOffsetAdjustment(gridEnabled: Boolean) = getScrollAdjustment(gridEnabled)
+ fun getOffsetAdjustment(gridEnabled: Boolean) = getScrollAdjustment(gridEnabled)
fun getSizeAdjustment(fullscreenEnabled: Boolean) = if (fullscreenEnabled) nonGridScale else 1f
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/AbsSwipeUpHandlerTestCase.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/AbsSwipeUpHandlerTestCase.java
index 0c74610..6fbbd59 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/AbsSwipeUpHandlerTestCase.java
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/AbsSwipeUpHandlerTestCase.java
@@ -364,7 +364,7 @@
float xVelocityPxPerMs = isQuickSwitch ? 100 : 0;
float yVelocityPxPerMs = isQuickSwitch ? 0 : -100;
swipeHandler.onGestureEnded(
- yVelocityPxPerMs, new PointF(xVelocityPxPerMs, yVelocityPxPerMs));
+ yVelocityPxPerMs, new PointF(xVelocityPxPerMs, yVelocityPxPerMs), isQuickSwitch);
swipeHandler.onCalculateEndTarget();
runOnMainSync(swipeHandler::onSettledOnEndTarget);
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/LauncherSwipeHandlerV2Test.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/LauncherSwipeHandlerV2Test.kt
index 35f1218..5661dcf 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/LauncherSwipeHandlerV2Test.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/LauncherSwipeHandlerV2Test.kt
@@ -116,14 +116,14 @@
@Test
fun goHomeFromAppByTrackpad_updateEduStats() {
gestureState.setTrackpadGestureType(GestureState.TrackpadGestureType.THREE_FINGER)
- underTest.onGestureEnded(flingSpeed, PointF())
+ underTest.onGestureEnded(flingSpeed, PointF(), /* horizontalTouchSlopPassed= */ false)
verify(systemUiProxy)
.updateContextualEduStats(/* isTrackpadGesture= */ eq(true), eq(GestureType.HOME))
}
@Test
fun goHomeFromAppByTouch_updateEduStats() {
- underTest.onGestureEnded(flingSpeed, PointF())
+ underTest.onGestureEnded(flingSpeed, PointF(), /* horizontalTouchSlopPassed= */ false)
verify(systemUiProxy)
.updateContextualEduStats(/* isTrackpadGesture= */ eq(false), eq(GestureType.HOME))
}
diff --git a/res/values/config.xml b/res/values/config.xml
index 74e7bb0..d65580c 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -114,6 +114,8 @@
<!-- Expressive Dismiss -->
<item name="expressive_dismiss_task_trans_y_damping_ratio" type="dimen" format="float">0.6</item>
<item name="expressive_dismiss_task_trans_y_stiffness" type="dimen" format="float">900</item>
+ <item name="expressive_dismiss_task_trans_x_damping_ratio" type="dimen" format="float">0.8</item>
+ <item name="expressive_dismiss_task_trans_x_stiffness" type="dimen" format="float">900</item>
<!-- Taskbar -->
<!-- This is a float because it is converted to dp later in DeviceProfile -->
diff --git a/src/com/android/launcher3/DropTargetHandler.kt b/src/com/android/launcher3/DropTargetHandler.kt
index 0cc7fc7..3c162a2 100644
--- a/src/com/android/launcher3/DropTargetHandler.kt
+++ b/src/com/android/launcher3/DropTargetHandler.kt
@@ -72,9 +72,10 @@
AbstractFloatingView.TYPE_WIDGET_RESIZE_FRAME,
)
var pageItem: ItemInfo = item
- if (item.container <= 0) {
- val v = mLauncher.workspace.getHomescreenIconByItemId(item.container)
- v?.let { pageItem = v.tag as ItemInfo }
+ if (item.container >= 0) {
+ mLauncher.workspace.getViewByItemId(item.container)?.let {
+ pageItem = it.tag as ItemInfo
+ }
}
val pageIds =
if (pageItem.container == LauncherSettings.Favorites.CONTAINER_DESKTOP)
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 5c1a528..5644051 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -71,9 +71,11 @@
import static com.android.launcher3.LauncherState.NO_SCALE;
import static com.android.launcher3.LauncherState.SPRING_LOADED;
import static com.android.launcher3.Utilities.postAsyncCallback;
+import static com.android.launcher3.Workspace.mapOverCellLayouts;
import static com.android.launcher3.anim.AnimatorListeners.forEndCallback;
import static com.android.launcher3.config.FeatureFlags.FOLDABLE_SINGLE_PAGE;
import static com.android.launcher3.config.FeatureFlags.MULTI_SELECT_EDIT_MODE;
+import static com.android.launcher3.icons.BitmapRenderer.createHardwareBitmap;
import static com.android.launcher3.logging.KeyboardStateManager.KeyboardState.HIDE;
import static com.android.launcher3.logging.KeyboardStateManager.KeyboardState.SHOW;
import static com.android.launcher3.logging.StatsLogManager.EventEnum;
@@ -125,7 +127,6 @@
import android.content.res.Configuration;
import android.database.sqlite.SQLiteDatabase;
import android.graphics.Bitmap;
-import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Rect;
import android.graphics.RectF;
@@ -163,7 +164,6 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
-import androidx.annotation.StringRes;
import androidx.annotation.UiThread;
import androidx.annotation.VisibleForTesting;
import androidx.core.os.BuildCompat;
@@ -172,7 +172,6 @@
import com.android.launcher3.DropTarget.DragObject;
import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
import com.android.launcher3.allapps.ActivityAllAppsContainerView;
-import com.android.launcher3.allapps.AllAppsRecyclerView;
import com.android.launcher3.allapps.AllAppsTransitionController;
import com.android.launcher3.allapps.DiscoveryBounce;
import com.android.launcher3.anim.AnimationSuccessListener;
@@ -1485,7 +1484,7 @@
@Override
public @Nullable FolderIcon findFolderIcon(final int folderIconId) {
- return (FolderIcon) mWorkspace.getHomescreenIconByItemId(folderIconId);
+ return (FolderIcon) mWorkspace.getViewByItemId(folderIconId);
}
/**
@@ -1884,7 +1883,8 @@
if (dropView != null && dropView.containsAppWidgetHostView()) {
// Extracting Bitmap from dropView instead of its content view produces the correct
// bitmap.
- widgetPreviewBitmap = getBitmapFromView(dropView);
+ widgetPreviewBitmap = createHardwareBitmap(
+ dropView.getWidth(), dropView.getHeight(), dropView::draw);
}
}
@@ -2054,7 +2054,7 @@
public boolean removeItem(View v, final ItemInfo itemInfo, boolean deleteFromDb,
@Nullable final String reason) {
if (itemInfo instanceof WorkspaceItemInfo) {
- View collectionIcon = mWorkspace.getHomescreenIconByItemId(itemInfo.container);
+ View collectionIcon = mWorkspace.getViewByItemId(itemInfo.container);
if (collectionIcon instanceof FolderIcon) {
// Remove the shortcut from the folder before removing it from launcher
((FolderInfo) collectionIcon.getTag()).remove((WorkspaceItemInfo) itemInfo, true);
@@ -2433,52 +2433,25 @@
}
/**
- * Similar to {@link #getFirstMatch} but optimized to finding a suitable view for the app close
- * animation.
+ * Finds the first view on homescreen matching the provided parameters, optimized to finding a
+ * suitable view for the app close animation.
*
* @param svi The StableViewInfo of the preferred item to match to if it exists or null
* @param packageName The package name of the app to match.
* @param user The user of the app to match.
- * @param supportsAllAppsState If true and we are in All Apps state, looks for view in All Apps.
- * Else we only looks on the workspace.
*/
- public @Nullable View getFirstMatchForAppClose(
- @Nullable StableViewInfo svi, String packageName,
- UserHandle user, boolean supportsAllAppsState) {
+ public @Nullable View getFirstHomeElementForAppClose(
+ @Nullable StableViewInfo svi, String packageName, UserHandle user) {
final Predicate<ItemInfo> preferredItem = svi == null ? i -> false : svi::matches;
- final Predicate<ItemInfo> packageAndUserAndApp = info ->
- info != null
- && info.itemType == ITEM_TYPE_APPLICATION
- && info.user.equals(user)
- && info.getTargetComponent() != null
- && TextUtils.equals(info.getTargetComponent().getPackageName(),
- packageName);
-
- if (supportsAllAppsState && isInState(LauncherState.ALL_APPS)) {
- AllAppsRecyclerView activeRecyclerView = mAppsView.getActiveRecyclerView();
- View v = getFirstMatch(Collections.singletonList(activeRecyclerView),
- preferredItem, packageAndUserAndApp);
-
- if (v != null && activeRecyclerView.computeVerticalScrollOffset() > 0) {
- RectF locationBounds = new RectF();
- FloatingIconView.getLocationBoundsForView(this, v, false, locationBounds,
- new Rect());
- if (locationBounds.top < mAppsView.getHeaderBottom()) {
- // Icon is covered by scrim, return null to play fallback animation.
- return null;
- }
- }
-
- return v;
- }
+ final Predicate<ItemInfo> packageAndUserAndApp = info -> info != null
+ && info.itemType == ITEM_TYPE_APPLICATION
+ && info.user.equals(user)
+ && TextUtils.equals(info.getTargetPackage(), packageName);
// Look for the item inside the folder at the current page
Folder folder = Folder.getOpen(this);
if (folder != null) {
- View v = getFirstMatch(Collections.singletonList(
- folder.getContent().getCurrentCellLayout().getShortcutsAndWidgets()),
- preferredItem,
- packageAndUserAndApp);
+ View v = folder.getFirstMatch(preferredItem, packageAndUserAndApp);
if (v == null) {
folder.close(isStarted() && !isForceInvisible());
} else {
@@ -2486,72 +2459,19 @@
}
}
- List<ViewGroup> containers = new ArrayList<>(mWorkspace.getPanelCount() + 1);
- containers.add(mWorkspace.getHotseat().getShortcutsAndWidgets());
- mWorkspace.forEachVisiblePage(page
- -> containers.add(((CellLayout) page).getShortcutsAndWidgets()));
+ List<CellLayout> containers = new ArrayList<>(mWorkspace.getPanelCount() + 1);
+ containers.add(mWorkspace.getHotseat());
+ mWorkspace.forEachVisiblePage(page -> containers.add((CellLayout) page));
+ CellLayout[] containerArray = containers.toArray(new CellLayout[0]);
+ LauncherBindableItemsContainer visibleContainer =
+ op -> mapOverCellLayouts(containerArray, op);
// Order: Preferred item by itself or in folder, then by matching package/user
- return getFirstMatch(containers, preferredItem, forFolderMatch(preferredItem),
+ return visibleContainer.getFirstMatch(
+ preferredItem, forFolderMatch(preferredItem),
packageAndUserAndApp, forFolderMatch(packageAndUserAndApp));
}
- /**
- * Finds the first view matching the ordered operators across the given viewgroups in order.
- * @param containers List of ViewGroups to scan, in order of preference.
- * @param operators List of operators, in order starting from best matching operator.
- */
- @Nullable
- private static View getFirstMatch(Iterable<ViewGroup> containers,
- final Predicate<ItemInfo>... operators) {
- for (Predicate<ItemInfo> operator : operators) {
- for (ViewGroup container : containers) {
- View match = mapOverViewGroup(container, operator);
- if (match != null) {
- return match;
- }
- }
- }
- return null;
- }
-
- /** Convert a {@link View} to {@link Bitmap}. */
- private static Bitmap getBitmapFromView(@Nullable View view) {
- if (view == null) {
- return null;
- }
- Bitmap returnedBitmap =
- Bitmap.createBitmap(view.getWidth(), view.getHeight(), Bitmap.Config.ARGB_8888);
- Canvas canvas = new Canvas(returnedBitmap);
- view.draw(canvas);
- return returnedBitmap;
- }
-
- /**
- * Returns the first view matching the operator in the given ViewGroups, or null if none.
- * Forward iteration matters.
- */
- @Nullable
- private static View mapOverViewGroup(ViewGroup container, Predicate<ItemInfo> op) {
- final int itemCount = container.getChildCount();
- for (int itemIdx = 0; itemIdx < itemCount; itemIdx++) {
- View item = container.getChildAt(itemIdx);
- if (item.getVisibility() != View.VISIBLE) {
- continue;
- }
- if (item instanceof ViewGroup viewGroup) {
- View view = mapOverViewGroup(viewGroup, op);
- if (view != null) {
- return view;
- }
- }
- if (item.getTag() instanceof ItemInfo itemInfo && op.test(itemInfo)) {
- return item;
- }
- }
- return null;
- }
-
private ValueAnimator createNewAppBounceAnimation(View v, int i) {
ValueAnimator bounceAnim = new PropertyListBuilder().alpha(1).scale(1).build(v)
.setDuration(ItemInstallQueue.NEW_SHORTCUT_BOUNCE_DURATION);
@@ -2560,10 +2480,6 @@
return bounceAnim;
}
- private void announceForAccessibility(@StringRes int stringResId) {
- getDragLayer().announceForAccessibility(getString(stringResId));
- }
-
/**
* Informs us that the overlay (-1 screen, typically), has either become visible or invisible.
*/
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 1b5e2e6..6ed183a 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -67,6 +67,7 @@
import android.widget.FrameLayout;
import android.widget.Toast;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.core.view.ViewCompat;
@@ -3163,7 +3164,7 @@
+ "Workspace#onDropCompleted. Please file a bug. ");
}
}
- View cell = getHomescreenIconByItemId(d.originalDragInfo.id);
+ View cell = getViewByItemId(d.originalDragInfo.id);
if (d.cancelled && cell != null) {
cell.setVisibility(VISIBLE);
}
@@ -3312,29 +3313,9 @@
return layouts;
}
- public View getHomescreenIconByItemId(final int id) {
- return getFirstMatch((info, v) -> info != null && info.id == id);
- }
-
public LauncherAppWidgetHostView getWidgetForAppWidgetId(final int appWidgetId) {
- return (LauncherAppWidgetHostView) getFirstMatch((info, v) ->
- (info instanceof LauncherAppWidgetInfo) &&
- ((LauncherAppWidgetInfo) info).appWidgetId == appWidgetId);
- }
-
- public View getFirstMatch(final ItemOperator operator) {
- final View[] value = new View[1];
- mapOverItems(new ItemOperator() {
- @Override
- public boolean evaluate(ItemInfo info, View v) {
- if (operator.evaluate(info, v)) {
- value[0] = v;
- return true;
- }
- return false;
- }
- });
- return value[0];
+ return (LauncherAppWidgetHostView) mapOverItems((info, v) ->
+ (info instanceof LauncherAppWidgetInfo lawi) && lawi.appWidgetId == appWidgetId);
}
void clearDropTargets() {
@@ -3393,31 +3374,26 @@
}
@Override
- public void mapOverItems(ItemOperator op) {
- for (CellLayout layout : getWorkspaceAndHotseatCellLayouts()) {
- if (mapOverCellLayout(layout, op) != null) {
- return;
- }
- }
+ public View mapOverItems(@NonNull ItemOperator op) {
+ return mapOverCellLayouts(getWorkspaceAndHotseatCellLayouts(), op);
}
/**
- * Perform {param operator} over all the items in a given {param layout}.
- *
- * @return The first item that satisfies the operator or null.
+ * Perform {param op} over all the items in the provided {param layouts} until a match is found
*/
- public View mapOverCellLayout(CellLayout layout, ItemOperator operator) {
- // TODO(b/128460496) Potential race condition where layout is not yet loaded
- if (layout == null) {
- return null;
- }
- ShortcutAndWidgetContainer container = layout.getShortcutsAndWidgets();
- // map over all the shortcuts on the workspace
- final int itemCount = container.getChildCount();
- for (int itemIdx = 0; itemIdx < itemCount; itemIdx++) {
- View item = container.getChildAt(itemIdx);
- if (operator.evaluate((ItemInfo) item.getTag(), item)) {
- return item;
+ public static View mapOverCellLayouts(CellLayout[] layouts, ItemOperator op) {
+ for (CellLayout layout : layouts) {
+ // TODO(b/128460496) Potential race condition where layout is not yet loaded
+ if (layout == null) continue;
+
+ ShortcutAndWidgetContainer container = layout.getShortcutsAndWidgets();
+ // map over all the shortcuts on the layout
+ final int itemCount = container.getChildCount();
+ for (int itemIdx = 0; itemIdx < itemCount; itemIdx++) {
+ View item = container.getChildAt(itemIdx);
+ if (op.evaluate((ItemInfo) item.getTag(), item)) {
+ return item;
+ }
}
}
return null;
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index 0ce7249..2803256 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -101,6 +101,7 @@
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.pageindicators.PageIndicatorDots;
import com.android.launcher3.util.Executors;
+import com.android.launcher3.util.LauncherBindableItemsContainer;
import com.android.launcher3.util.LauncherBindableItemsContainer.ItemOperator;
import com.android.launcher3.util.Thunk;
import com.android.launcher3.views.ActivityContext;
@@ -123,7 +124,8 @@
*/
public class Folder extends AbstractFloatingView implements ClipPathView, DragSource,
View.OnLongClickListener, DropTarget, FolderListener, TextView.OnEditorActionListener,
- View.OnFocusChangeListener, DragListener, ExtendedEditText.OnBackKeyListener {
+ View.OnFocusChangeListener, DragListener, ExtendedEditText.OnBackKeyListener,
+ LauncherBindableItemsContainer {
private static final String TAG = "Launcher.Folder";
private static final boolean DEBUG = false;
@@ -1513,8 +1515,10 @@
/**
* Utility methods to iterate over items of the view
*/
- public void iterateOverItems(ItemOperator op) {
- mContent.iterateOverItems(op);
+ @Override
+ @Nullable
+ public View mapOverItems(@NonNull ItemOperator op) {
+ return mContent.iterateOverItems(op);
}
/**
diff --git a/src/com/android/launcher3/popup/PopupDataProvider.java b/src/com/android/launcher3/popup/PopupDataProvider.java
index 318b3ce..5c1a755 100644
--- a/src/com/android/launcher3/popup/PopupDataProvider.java
+++ b/src/com/android/launcher3/popup/PopupDataProvider.java
@@ -90,7 +90,7 @@
mContext.getContent().mapOverItems(op);
Folder folder = Folder.getOpen(mContext);
if (folder != null) {
- folder.iterateOverItems(op);
+ folder.mapOverItems(op);
}
ActivityAllAppsContainerView<?> appsView = mContext.getAppsView();
diff --git a/src/com/android/launcher3/util/LauncherBindableItemsContainer.kt b/src/com/android/launcher3/util/LauncherBindableItemsContainer.kt
index 1661796..8fdedef 100644
--- a/src/com/android/launcher3/util/LauncherBindableItemsContainer.kt
+++ b/src/com/android/launcher3/util/LauncherBindableItemsContainer.kt
@@ -25,8 +25,10 @@
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.util.LauncherBindableItemsContainer.ItemOperator
import com.android.launcher3.views.ActivityContext
import com.android.launcher3.widget.PendingAppWidgetHostView
+import java.util.function.Predicate
/** Interface representing a container which can bind Launcher items with some utility methods */
interface LauncherBindableItemsContainer {
@@ -54,11 +56,24 @@
}
mapOverItems(op)
- Folder.getOpen(context)?.iterateOverItems(op)
+ Folder.getOpen(context)?.mapOverItems(op)
}
- /** Map the [op] over the shortcuts and widgets. */
- fun mapOverItems(op: ItemOperator)
+ /** Returns the first view, matching the [op] */
+ @Deprecated("Use mapOverItems instead", ReplaceWith("mapOverItems(op)"))
+ fun getFirstMatch(op: ItemOperator): View? = mapOverItems(op)
+
+ /** Finds the first icon to match one of the given matchers, from highest to lowest priority. */
+ fun getFirstMatch(vararg matchers: Predicate<ItemInfo>): View? =
+ matchers.firstNotNullOfOrNull { mapOverItems { info, _ -> info != null && it.test(info) } }
+
+ fun getViewByItemId(id: Int): View? = mapOverItems { info, _ -> info != null && info.id == id }
+
+ /**
+ * Map the [op] over the shortcuts and widgets. Once we found the first view which matches, we
+ * will stop the iteration and return that view.
+ */
+ fun mapOverItems(op: ItemOperator): View?
fun interface ItemOperator {
diff --git a/src/com/android/launcher3/views/ActivityContext.java b/src/com/android/launcher3/views/ActivityContext.java
index 30af586..49adaec 100644
--- a/src/com/android/launcher3/views/ActivityContext.java
+++ b/src/com/android/launcher3/views/ActivityContext.java
@@ -195,7 +195,7 @@
*/
@NonNull
default LauncherBindableItemsContainer getContent() {
- return op -> { };
+ return op -> null;
}
/**
diff --git a/src/com/android/launcher3/views/FloatingSurfaceView.java b/src/com/android/launcher3/views/FloatingSurfaceView.java
index 5f8e2c0..a4055b6 100644
--- a/src/com/android/launcher3/views/FloatingSurfaceView.java
+++ b/src/com/android/launcher3/views/FloatingSurfaceView.java
@@ -159,9 +159,8 @@
if (mContract == null) {
return;
}
- View icon = mLauncher.getFirstMatchForAppClose(null /* StableViewInfo */,
- mContract.componentName.getPackageName(), mContract.user,
- false /* supportsAllAppsState */);
+ View icon = mLauncher.getFirstHomeElementForAppClose(null /* StableViewInfo */,
+ mContract.componentName.getPackageName(), mContract.user);
boolean iconChanged = mIcon != icon;
if (iconChanged) {
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/LauncherBindableItemsContainerTest.kt b/tests/multivalentTests/src/com/android/launcher3/util/LauncherBindableItemsContainerTest.kt
index 93be5f5..054c90b 100644
--- a/tests/multivalentTests/src/com/android/launcher3/util/LauncherBindableItemsContainerTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/util/LauncherBindableItemsContainerTest.kt
@@ -122,9 +122,10 @@
val items = mutableMapOf<ItemInfo, View>()
- override fun mapOverItems(op: ItemOperator) {
- items.forEach { (item, view) -> if (op.evaluate(item, view)) return@forEach }
- }
+ override fun mapOverItems(op: ItemOperator): View? =
+ items.firstNotNullOfOrNull { (item, view) ->
+ if (op.evaluate(item, view)) view else null
+ }
fun addIcon(info: WorkspaceItemInfo) {
val btv = BubbleTextView(this)