Merge "Adding main thread binder tracing in development build." into udc-qpr-dev
diff --git a/ext_tests/src/com/android/launcher3/testing/DebugTestInformationHandler.java b/ext_tests/src/com/android/launcher3/testing/DebugTestInformationHandler.java
index 3fddd9d..dc28c6a 100644
--- a/ext_tests/src/com/android/launcher3/testing/DebugTestInformationHandler.java
+++ b/ext_tests/src/com/android/launcher3/testing/DebugTestInformationHandler.java
@@ -16,7 +16,6 @@
package com.android.launcher3.testing;
-import static com.android.launcher3.testing.shared.TestProtocol.VIEW_AND_ACTIVITY_LEAKS;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
@@ -25,10 +24,7 @@
import android.content.Context;
import android.os.Binder;
import android.os.Bundle;
-import android.os.Process;
import android.system.Os;
-import android.util.Log;
-import android.view.View;
import androidx.annotation.Keep;
import androidx.annotation.Nullable;
@@ -42,7 +38,6 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
-import java.util.LinkedList;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.concurrent.CountDownLatch;
@@ -52,7 +47,6 @@
* Class to handle requests from tests, including debug ones.
*/
public class DebugTestInformationHandler extends TestInformationHandler {
- private static LinkedList sLeaks;
private static Collection<String> sEvents;
private static Application.ActivityLifecycleCallbacks sActivityLifecycleCallbacks;
private static final Map<Activity, Boolean> sActivities =
@@ -158,19 +152,6 @@
return response;
}
- case TestProtocol.REQUEST_VIEW_LEAK: {
- if (sLeaks == null) sLeaks = new LinkedList();
- Log.d(VIEW_AND_ACTIVITY_LEAKS, "forcefully leaking 2 views");
- sLeaks.add(new View(mContext));
- sLeaks.add(new View(mContext));
- return response;
- }
-
- case TestProtocol.PRINT_VIEW_LEAK: {
- Log.d(VIEW_AND_ACTIVITY_LEAKS, "(pid=" + Process.myPid() + ") sLeaks=" + sLeaks);
- return response;
- }
-
case TestProtocol.REQUEST_START_EVENT_LOGGING: {
sEvents = new ArrayList<>();
TestLogging.setEventConsumer(
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index d626608..de46ba0 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -472,6 +472,10 @@
public void onDestroy() {
mAppTransitionManager.onActivityDestroyed();
if (mUnfoldTransitionProgressProvider != null) {
+ if (FeatureFlags.RECEIVE_UNFOLD_EVENTS_FROM_SYSUI.get()) {
+ SystemUiProxy.INSTANCE.get(this).setUnfoldAnimationListener(null);
+ }
+
mUnfoldTransitionProgressProvider.destroy();
}
@@ -889,7 +893,9 @@
private void onTISConnected(TISBinder binder) {
mTaskbarManager = binder.getTaskbarManager();
- mTaskbarManager.setActivity(this);
+ if (mTaskbarManager != null) {
+ mTaskbarManager.setActivity(this);
+ }
mOverviewCommandHelper = binder.getOverviewCommandHelper();
}
diff --git a/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java b/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
index 031d409..211aeb9 100644
--- a/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
+++ b/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
@@ -79,6 +79,13 @@
return response;
}
+ case TestProtocol.REQUEST_GET_OVERVIEW_TASK_BORDER_WIDTH: {
+ Resources res = mContext.getResources();
+ response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD,
+ res.getDimensionPixelSize(R.dimen.keyboard_quick_switch_border_width));
+ return response;
+ }
+
case TestProtocol.REQUEST_HAS_TIS: {
response.putBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD, true);
return response;
diff --git a/quickstep/src/com/android/quickstep/RecentsActivity.java b/quickstep/src/com/android/quickstep/RecentsActivity.java
index c1b6e53..ec8a591 100644
--- a/quickstep/src/com/android/quickstep/RecentsActivity.java
+++ b/quickstep/src/com/android/quickstep/RecentsActivity.java
@@ -144,7 +144,9 @@
private void onTISConnected(TouchInteractionService.TISBinder binder) {
mTaskbarManager = binder.getTaskbarManager();
- mTaskbarManager.setActivity(this);
+ if (mTaskbarManager != null) {
+ mTaskbarManager.setActivity(this);
+ }
}
@Override
diff --git a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
index 2aa0be6..1744b08 100644
--- a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
@@ -28,7 +28,6 @@
import android.graphics.Rect;
import android.os.Build;
import android.view.View;
-import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
@@ -42,6 +41,7 @@
import com.android.launcher3.popup.SystemShortcut;
import com.android.launcher3.util.ResourceBasedOverride;
import com.android.launcher3.views.ActivityContext;
+import com.android.launcher3.views.Snackbar;
import com.android.quickstep.util.RecentsOrientedState;
import com.android.quickstep.views.OverviewActionsView;
import com.android.quickstep.views.RecentsView;
@@ -272,10 +272,8 @@
String message = activityContext.getStringCache() != null
? activityContext.getStringCache().disabledByAdminMessage
: mThumbnailView.getContext().getString(R.string.blocked_by_policy);
- Toast.makeText(
- mThumbnailView.getContext(),
- message,
- Toast.LENGTH_LONG).show();
+
+ Snackbar.show(BaseActivity.fromContext(mThumbnailView.getContext()), message, null);
}
/** Called when the snapshot has updated its full screen drawing parameters. */
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index a060c7c..af57172 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -144,8 +144,10 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.lang.ref.WeakReference;
import java.util.Arrays;
import java.util.LinkedList;
+import java.util.function.Consumer;
import java.util.function.Function;
/**
@@ -162,15 +164,21 @@
private static final String HAS_ENABLED_QUICKSTEP_ONCE = "launcher.has_enabled_quickstep_once";
- private final TISBinder mTISBinder = new TISBinder();
+ private final TISBinder mTISBinder = new TISBinder(this);
/**
* Local IOverviewProxy implementation with some methods for local components
*/
- public class TISBinder extends IOverviewProxy.Stub {
+ public static class TISBinder extends IOverviewProxy.Stub {
+
+ private final WeakReference<TouchInteractionService> mTis;
@Nullable private Runnable mOnOverviewTargetChangeListener = null;
+ private TISBinder(TouchInteractionService tis) {
+ mTis = new WeakReference<>(tis);
+ }
+
@BinderThread
public void onInitialize(Bundle bundle) {
ISystemUiProxy proxy = ISystemUiProxy.Stub.asInterface(
@@ -198,14 +206,14 @@
bundle.getBinder(KEY_EXTRA_UNFOLD_ANIMATION_FORWARDER));
IDragAndDrop dragAndDrop = IDragAndDrop.Stub.asInterface(
bundle.getBinder(KEY_EXTRA_SHELL_DRAG_AND_DROP));
- MAIN_EXECUTOR.execute(() -> {
- SystemUiProxy.INSTANCE.get(TouchInteractionService.this).setProxy(proxy, pip,
+ MAIN_EXECUTOR.execute(() -> executeForTouchInteractionService(tis -> {
+ SystemUiProxy.INSTANCE.get(tis).setProxy(proxy, pip,
bubbles, splitscreen, onehanded, shellTransitions, startingWindow,
recentTasks, launcherUnlockAnimationController, backAnimation, desktopMode,
unfoldTransition, dragAndDrop);
- TouchInteractionService.this.initInputMonitor("TISBinder#onInitialize()");
- preloadOverview(true /* fromInit */);
- });
+ tis.initInputMonitor("TISBinder#onInitialize()");
+ tis.preloadOverview(true /* fromInit */);
+ }));
sIsInitialized = true;
}
@@ -213,65 +221,72 @@
@Override
public void onTaskbarToggled() {
if (!FeatureFlags.ENABLE_KEYBOARD_TASKBAR_TOGGLE.get()) return;
- MAIN_EXECUTOR.execute(() -> {
+ MAIN_EXECUTOR.execute(() -> executeForTouchInteractionService(tis -> {
TaskbarActivityContext activityContext =
- mTaskbarManager.getCurrentActivityContext();
+ tis.mTaskbarManager.getCurrentActivityContext();
if (activityContext != null) {
activityContext.toggleTaskbarStash();
}
- });
+ }));
}
@BinderThread
public void onOverviewToggle() {
TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "onOverviewToggle");
- // If currently screen pinning, do not enter overview
- if (mDeviceState.isScreenPinningActive()) {
- return;
- }
- TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
- mOverviewCommandHelper.addCommand(OverviewCommandHelper.TYPE_TOGGLE);
+ executeForTouchInteractionService(tis -> {
+ // If currently screen pinning, do not enter overview
+ if (tis.mDeviceState.isScreenPinningActive()) {
+ return;
+ }
+ TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
+ tis.mOverviewCommandHelper.addCommand(OverviewCommandHelper.TYPE_TOGGLE);
+ });
}
@BinderThread
@Override
public void onOverviewShown(boolean triggeredFromAltTab) {
- if (triggeredFromAltTab) {
- TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
- mOverviewCommandHelper.addCommand(OverviewCommandHelper.TYPE_KEYBOARD_INPUT);
- } else {
- mOverviewCommandHelper.addCommand(OverviewCommandHelper.TYPE_SHOW);
- }
+ executeForTouchInteractionService(tis -> {
+ if (triggeredFromAltTab) {
+ TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
+ tis.mOverviewCommandHelper.addCommand(
+ OverviewCommandHelper.TYPE_KEYBOARD_INPUT);
+ } else {
+ tis.mOverviewCommandHelper.addCommand(OverviewCommandHelper.TYPE_SHOW);
+ }
+ });
}
@BinderThread
@Override
public void onOverviewHidden(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
- if (triggeredFromAltTab && !triggeredFromHomeKey) {
- // onOverviewShownFromAltTab hides the overview and ends at the target app
- mOverviewCommandHelper.addCommand(OverviewCommandHelper.TYPE_HIDE);
- }
+ executeForTouchInteractionService(tis -> {
+ if (triggeredFromAltTab && !triggeredFromHomeKey) {
+ // onOverviewShownFromAltTab hides the overview and ends at the target app
+ tis.mOverviewCommandHelper.addCommand(OverviewCommandHelper.TYPE_HIDE);
+ }
+ });
}
@BinderThread
@Override
public void onAssistantAvailable(boolean available, boolean longPressHomeEnabled) {
- MAIN_EXECUTOR.execute(() -> {
- mDeviceState.setAssistantAvailable(available);
- TouchInteractionService.this.onAssistantVisibilityChanged();
- executeForTaskbarManager(() -> mTaskbarManager
+ MAIN_EXECUTOR.execute(() -> executeForTouchInteractionService(tis -> {
+ tis.mDeviceState.setAssistantAvailable(available);
+ tis.onAssistantVisibilityChanged();
+ executeForTaskbarManager(taskbarManager -> taskbarManager
.onLongPressHomeEnabled(longPressHomeEnabled));
- });
+ }));
}
@BinderThread
@Override
public void onAssistantVisibilityChanged(float visibility) {
- MAIN_EXECUTOR.execute(() -> {
- mDeviceState.setAssistantVisibility(visibility);
- TouchInteractionService.this.onAssistantVisibilityChanged();
- });
+ MAIN_EXECUTOR.execute(() -> executeForTouchInteractionService(tis -> {
+ tis.mDeviceState.setAssistantVisibility(visibility);
+ tis.onAssistantVisibilityChanged();
+ }));
}
@Override
@@ -281,16 +296,17 @@
@BinderThread
public void onSystemUiStateChanged(int stateFlags) {
- MAIN_EXECUTOR.execute(() -> {
- int lastFlags = mDeviceState.getSystemUiStateFlags();
- mDeviceState.setSystemUiFlags(stateFlags);
- TouchInteractionService.this.onSystemUiFlagsChanged(lastFlags);
- });
+ MAIN_EXECUTOR.execute(() -> executeForTouchInteractionService(tis -> {
+ int lastFlags = tis.mDeviceState.getSystemUiStateFlags();
+ tis.mDeviceState.setSystemUiFlags(stateFlags);
+ tis.onSystemUiFlagsChanged(lastFlags);
+ }));
}
@BinderThread
public void onActiveNavBarRegionChanges(Region region) {
- MAIN_EXECUTOR.execute(() -> mDeviceState.setDeferredGestureRegion(region));
+ MAIN_EXECUTOR.execute(() -> executeForTouchInteractionService(
+ tis -> tis.mDeviceState.setDeferredGestureRegion(region)));
}
@BinderThread
@@ -314,75 +330,105 @@
@BinderThread
@Override
public void enterStageSplitFromRunningApp(boolean leftOrTop) {
- StatefulActivity activity =
- mOverviewComponentObserver.getActivityInterface().getCreatedActivity();
- if (activity != null) {
- activity.enterStageSplitFromRunningApp(leftOrTop);
- }
+ executeForTouchInteractionService(tis -> {
+ StatefulActivity activity =
+ tis.mOverviewComponentObserver.getActivityInterface().getCreatedActivity();
+ if (activity != null) {
+ activity.enterStageSplitFromRunningApp(leftOrTop);
+ }
+ });
}
/**
* Preloads the Overview activity.
- *
+ * <p>
* This method should only be used when the All Set page of the SUW is reached to safely
* preload the Launcher for the SUW first reveal.
*/
public void preloadOverviewForSUWAllSet() {
- preloadOverview(false, true);
+ executeForTouchInteractionService(tis -> tis.preloadOverview(false, true));
}
@Override
public void onRotationProposal(int rotation, boolean isValid) {
- executeForTaskbarManager(() -> mTaskbarManager.onRotationProposal(rotation, isValid));
+ executeForTaskbarManager(taskbarManager ->
+ taskbarManager.onRotationProposal(rotation, isValid));
}
@Override
public void disable(int displayId, int state1, int state2, boolean animate) {
- executeForTaskbarManager(() -> mTaskbarManager
- .disableNavBarElements(displayId, state1, state2, animate));
+ executeForTaskbarManager(taskbarManager ->
+ taskbarManager.disableNavBarElements(displayId, state1, state2, animate));
}
@Override
public void onSystemBarAttributesChanged(int displayId, int behavior) {
- executeForTaskbarManager(() -> mTaskbarManager
- .onSystemBarAttributesChanged(displayId, behavior));
+ executeForTaskbarManager(taskbarManager ->
+ taskbarManager.onSystemBarAttributesChanged(displayId, behavior));
}
@Override
public void onNavButtonsDarkIntensityChanged(float darkIntensity) {
- executeForTaskbarManager(() -> mTaskbarManager
- .onNavButtonsDarkIntensityChanged(darkIntensity));
+ executeForTaskbarManager(taskbarManager ->
+ taskbarManager.onNavButtonsDarkIntensityChanged(darkIntensity));
}
- private void executeForTaskbarManager(final Runnable r) {
- MAIN_EXECUTOR.execute(() -> {
- if (mTaskbarManager == null) {
- return;
- }
- r.run();
- });
+ private void executeForTouchInteractionService(
+ @NonNull Consumer<TouchInteractionService> tisConsumer) {
+ TouchInteractionService tis = mTis.get();
+ if (tis == null) return;
+ tisConsumer.accept(tis);
}
+ private void executeForTaskbarManager(
+ @NonNull Consumer<TaskbarManager> taskbarManagerConsumer) {
+ MAIN_EXECUTOR.execute(() -> executeForTouchInteractionService(tis -> {
+ TaskbarManager taskbarManager = tis.mTaskbarManager;
+ if (taskbarManager == null) return;
+ taskbarManagerConsumer.accept(taskbarManager);
+ }));
+ }
+
+ /**
+ * Returns the {@link TaskbarManager}.
+ * <p>
+ * Returns {@code null} if TouchInteractionService is not connected
+ */
+ @Nullable
public TaskbarManager getTaskbarManager() {
- return mTaskbarManager;
+ TouchInteractionService tis = mTis.get();
+ if (tis == null) return null;
+ return tis.mTaskbarManager;
}
+ /**
+ * Returns the {@link OverviewCommandHelper}.
+ * <p>
+ * Returns {@code null} if TouchInteractionService is not connected
+ */
+ @Nullable
public OverviewCommandHelper getOverviewCommandHelper() {
- return mOverviewCommandHelper;
+ TouchInteractionService tis = mTis.get();
+ if (tis == null) return null;
+ return tis.mOverviewCommandHelper;
}
/**
* Sets a proxy to bypass swipe up behavior
*/
public void setSwipeUpProxy(Function<GestureState, AnimatedFloat> proxy) {
- mSwipeUpProxyProvider = proxy != null ? proxy : (i -> null);
+ TouchInteractionService tis = mTis.get();
+ if (tis == null) return;
+ tis.mSwipeUpProxyProvider = proxy != null ? proxy : (i -> null);
}
/**
* Sets the task id where gestures should be blocked
*/
public void setGestureBlockedTaskId(int taskId) {
- mDeviceState.setGestureBlockingTaskId(taskId);
+ TouchInteractionService tis = mTis.get();
+ if (tis == null) return;
+ tis.mDeviceState.setGestureBlockingTaskId(taskId);
}
/** Sets a listener to be run on Overview Target updates. */
diff --git a/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java b/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java
index 5e1a46e..b3a567d 100644
--- a/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java
+++ b/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java
@@ -61,6 +61,7 @@
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimatedFloat;
import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.taskbar.TaskbarManager;
import com.android.launcher3.util.Executors;
import com.android.quickstep.GestureState;
import com.android.quickstep.TouchInteractionService.TISBinder;
@@ -100,6 +101,7 @@
private TISBindHelper mTISBindHelper;
private TISBinder mBinder;
+ @Nullable private TaskbarManager mTaskbarManager = null;
private final AnimatedFloat mSwipeProgress = new AnimatedFloat(this::onSwipeProgressUpdate);
private BgDrawable mBackground;
@@ -262,19 +264,25 @@
mAnimatedBackground.playAnimation();
}
+ private void setSetupUIVisible(boolean visible) {
+ if (mBinder == null || mTaskbarManager == null) return;
+ mTaskbarManager.setSetupUIVisible(visible);
+ }
+
@Override
protected void onResume() {
super.onResume();
maybeResumeOrPauseBackgroundAnimation();
if (mBinder != null) {
- mBinder.getTaskbarManager().setSetupUIVisible(true);
+ setSetupUIVisible(true);
mBinder.setSwipeUpProxy(this::createSwipeUpProxy);
}
}
private void onTISConnected(TISBinder binder) {
mBinder = binder;
- mBinder.getTaskbarManager().setSetupUIVisible(isResumed());
+ mTaskbarManager = mBinder.getTaskbarManager();
+ setSetupUIVisible(isResumed());
mBinder.setSwipeUpProxy(isResumed() ? this::createSwipeUpProxy : null);
mBinder.setOverviewTargetChangeListener(mBinder::preloadOverviewForSUWAllSet);
mBinder.preloadOverviewForSUWAllSet();
@@ -293,7 +301,7 @@
private void clearBinderOverride() {
if (mBinder != null) {
- mBinder.getTaskbarManager().setSetupUIVisible(false);
+ setSetupUIVisible(false);
mBinder.setSwipeUpProxy(null);
mBinder.setOverviewTargetChangeListener(null);
}
@@ -352,9 +360,8 @@
mContentView.setAlpha(alpha);
mContentView.setTranslationY((alpha - 1) * mSwipeUpShift);
- if (mLauncherStartAnim == null) {
- mLauncherStartAnim = mBinder.getTaskbarManager().createLauncherStartFromSuwAnim(
- MAX_SWIPE_DURATION);
+ if (mLauncherStartAnim == null && mTaskbarManager != null) {
+ mLauncherStartAnim = mTaskbarManager.createLauncherStartFromSuwAnim(MAX_SWIPE_DURATION);
}
if (mLauncherStartAnim != null) {
mLauncherStartAnim.setPlayFraction(Utilities.mapBoundToRange(
diff --git a/quickstep/src/com/android/quickstep/util/BorderAnimator.java b/quickstep/src/com/android/quickstep/util/BorderAnimator.java
index 7563187..f252949 100644
--- a/quickstep/src/com/android/quickstep/util/BorderAnimator.java
+++ b/quickstep/src/com/android/quickstep/util/BorderAnimator.java
@@ -28,6 +28,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Px;
+import androidx.annotation.VisibleForTesting;
import com.android.app.animation.Interpolators;
import com.android.launcher3.anim.AnimatedFloat;
@@ -175,6 +176,12 @@
}
}
+ @NonNull
+ @VisibleForTesting
+ public AnimatedFloat getBorderAnimationProgress() {
+ return mBorderAnimationProgress;
+ }
+
/**
* Callback to update the border bounds when building this animation.
*/
diff --git a/quickstep/src/com/android/quickstep/util/LauncherUnfoldAnimationController.java b/quickstep/src/com/android/quickstep/util/LauncherUnfoldAnimationController.java
index 6d15e8b..e0b5272 100644
--- a/quickstep/src/com/android/quickstep/util/LauncherUnfoldAnimationController.java
+++ b/quickstep/src/com/android/quickstep/util/LauncherUnfoldAnimationController.java
@@ -60,6 +60,8 @@
private final NaturalRotationUnfoldProgressProvider mNaturalOrientationProgressProvider;
private final UnfoldMoveFromCenterHotseatAnimator mUnfoldMoveFromCenterHotseatAnimator;
private final UnfoldMoveFromCenterWorkspaceAnimator mUnfoldMoveFromCenterWorkspaceAnimator;
+ private final TransitionStatusProvider mExternalTransitionStatusProvider =
+ new TransitionStatusProvider();
private PreemptiveUnfoldTransitionProgressProvider mPreemptiveProgressProvider = null;
private Boolean mIsTablet = null;
@@ -88,6 +90,8 @@
unfoldTransitionProgressProvider);
}
+ unfoldTransitionProgressProvider.addCallback(mExternalTransitionStatusProvider);
+
mUnfoldMoveFromCenterHotseatAnimator = new UnfoldMoveFromCenterHotseatAnimator(launcher,
windowManager, rotationChangeProvider);
mUnfoldMoveFromCenterWorkspaceAnimator = new UnfoldMoveFromCenterWorkspaceAnimator(launcher,
@@ -166,11 +170,26 @@
}
if (mIsTablet != null && dp.isTablet != mIsTablet) {
- if (dp.isTablet && SystemUiProxy.INSTANCE.get(mLauncher).isActive()) {
+ // We should preemptively start the animation only if:
+ // - We changed to the unfolded screen
+ // - SystemUI IPC connection is alive, so we won't end up in a situation that we won't
+ // receive transition progress events from SystemUI later because there was no
+ // IPC connection established (e.g. because of SystemUI crash)
+ // - SystemUI has not already sent unfold animation progress events. This might happen
+ // if Launcher was not open during unfold, in this case we receive the configuration
+ // change only after we went back to home screen and we don't want to start the
+ // animation in this case.
+ if (dp.isTablet
+ && SystemUiProxy.INSTANCE.get(mLauncher).isActive()
+ && !mExternalTransitionStatusProvider.hasRun()) {
// Preemptively start the unfold animation to make sure that we have drawn
// the first frame of the animation before the screen gets unblocked
preemptivelyStartAnimationOnNextFrame();
}
+
+ if (!dp.isTablet) {
+ mExternalTransitionStatusProvider.onFolded();
+ }
}
mIsTablet = dp.isTablet;
@@ -222,4 +241,48 @@
HOTSEAT_SCALE_PROPERTY.setValue(mLauncher.getHotseat(), value);
}
}
+
+ /**
+ * Class to track the current status of the external transition provider (the events are coming
+ * from the SystemUI side through IPC), it allows to check if the transition has already
+ * finished or currently running on the SystemUI side since last unfold.
+ */
+ private static class TransitionStatusProvider implements TransitionProgressListener {
+
+ private boolean mHasRun = false;
+
+ @Override
+ public void onTransitionStarted() {
+ markAsRun();
+ }
+
+ @Override
+ public void onTransitionProgress(float progress) {
+ markAsRun();
+ }
+
+ @Override
+ public void onTransitionFinished() {
+ markAsRun();
+ }
+
+ /**
+ * Called when the device is folded, so we can reset the status of the animation
+ */
+ public void onFolded() {
+ mHasRun = false;
+ }
+
+ /**
+ * Returns true if there was an animation already (or it is currently running) after
+ * unfolding the device
+ */
+ public boolean hasRun() {
+ return mHasRun;
+ }
+
+ private void markAsRun() {
+ mHasRun = true;
+ }
+ }
}
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index 46dd94b..839c3fd 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -71,6 +71,7 @@
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
import com.android.app.animation.Interpolators;
import com.android.launcher3.DeviceProfile;
@@ -413,7 +414,11 @@
private boolean mIsClickableAsLiveTile = true;
- @Nullable private final BorderAnimator mBorderAnimator;
+ @Nullable private BorderAnimator mBorderAnimator;
+
+ private final boolean mCursorHoverStatesEnabled;
+
+ private final boolean mKeyboardFocusHighlightEnabled;
public TaskView(Context context) {
this(context, null);
@@ -436,26 +441,29 @@
mCurrentFullscreenParams = new FullscreenDrawParams(context);
mDigitalWellBeingToast = new DigitalWellBeingToast(mActivity, this);
- boolean keyboardFocusHighlightEnabled = FeatureFlags.ENABLE_KEYBOARD_QUICK_SWITCH.get()
+ mKeyboardFocusHighlightEnabled = FeatureFlags.ENABLE_KEYBOARD_QUICK_SWITCH.get()
|| DesktopTaskView.DESKTOP_MODE_SUPPORTED;
+ mCursorHoverStatesEnabled = FeatureFlags.ENABLE_CURSOR_HOVER_STATES.get();
+ if (mCursorHoverStatesEnabled) {
+ setOnHoverListener(this::onHover);
+ }
- setWillNotDraw(!keyboardFocusHighlightEnabled);
+ setWillNotDraw(!mKeyboardFocusHighlightEnabled && !mCursorHoverStatesEnabled);
- TypedArray ta = context.obtainStyledAttributes(
- attrs, R.styleable.TaskView, defStyleAttr, defStyleRes);
-
- mBorderAnimator = !keyboardFocusHighlightEnabled
- ? null
- : new BorderAnimator(
- /* borderRadiusPx= */ (int) mCurrentFullscreenParams.mCornerRadius,
- /* borderColor= */ ta.getColor(
- R.styleable.TaskView_borderColor, DEFAULT_BORDER_COLOR),
- /* borderAnimationParams= */ new BorderAnimator.SimpleParams(
- /* borderWidthPx= */ context.getResources().getDimensionPixelSize(
- R.dimen.keyboard_quick_switch_border_width),
- /* boundsBuilder= */ this::updateBorderBounds,
- /* targetView= */ this));
- ta.recycle();
+ if (mKeyboardFocusHighlightEnabled || mCursorHoverStatesEnabled) {
+ TypedArray ta = context.obtainStyledAttributes(
+ attrs, R.styleable.TaskView, defStyleAttr, defStyleRes);
+ mBorderAnimator = new BorderAnimator(
+ /* borderRadiusPx= */ (int) mCurrentFullscreenParams.mCornerRadius,
+ /* borderColor= */ ta.getColor(
+ R.styleable.TaskView_borderColor, DEFAULT_BORDER_COLOR),
+ /* borderAnimationParams= */ new BorderAnimator.SimpleParams(
+ /* borderWidthPx= */ context.getResources().getDimensionPixelSize(
+ R.dimen.keyboard_quick_switch_border_width),
+ /* boundsBuilder= */ this::updateBorderBounds,
+ /* targetView= */ this));
+ ta.recycle();
+ }
}
protected void updateBorderBounds(Rect bounds) {
@@ -498,6 +506,12 @@
return stubInfo;
}
+ @Nullable
+ @VisibleForTesting
+ public BorderAnimator getBorderAnimator() {
+ return mBorderAnimator;
+ }
+
@Override
protected void onFinishInflate() {
super.onFinishInflate();
@@ -509,12 +523,23 @@
@Override
protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
- if (mBorderAnimator != null) {
+ if (mKeyboardFocusHighlightEnabled) {
mBorderAnimator.buildAnimator(gainFocus).start();
}
}
@Override
+ public boolean onInterceptHoverEvent(MotionEvent event) {
+ if (mCursorHoverStatesEnabled) {
+ // avoid triggering hover event on child elements which would cause HOVER_EXIT for this
+ // task view
+ return true;
+ } else {
+ return super.onInterceptHoverEvent(event);
+ }
+ }
+
+ @Override
public void draw(Canvas canvas) {
super.draw(canvas);
if (mBorderAnimator != null) {
@@ -752,6 +777,57 @@
.log(LAUNCHER_TASK_LAUNCH_TAP);
}
+ private boolean onHover(View v, MotionEvent event) {
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_HOVER_MOVE:
+ if (mKeyboardFocusHighlightEnabled && !isFocused()) {
+ // existing focus is on another task selected by keyboard,
+ // cursor then moves inside this task thumbnail and steals the focus
+ requestFocusAndExitTouchMode(v);
+ }
+ return true;
+ case MotionEvent.ACTION_HOVER_ENTER:
+ if (mKeyboardFocusHighlightEnabled) {
+ if (isFocused()) {
+ // the task is already focused with border, no action is needed
+ return true;
+ } else {
+ requestFocusAndExitTouchMode(v);
+ }
+ } else {
+ // mKeyboardFocusHighlightEnabled is turned off so it only shows hover
+ // state animation but not steals the focus
+ mBorderAnimator.buildAnimator(true).start();
+ }
+ return true;
+ case MotionEvent.ACTION_HOVER_EXIT:
+ if (mKeyboardFocusHighlightEnabled) {
+ // clearFocus() does not work here because parent element is not focusable
+ // so it changes to touch mode to clear focus
+ v.getViewRootImpl().touchModeChanged(true);
+ } else {
+ // just show the disappearing animation but not change the focus when
+ // mKeyboardFocusHighlightEnabled is off
+ mBorderAnimator.buildAnimator(false).start();
+ }
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ private void requestFocusAndExitTouchMode(View v) {
+ if (isInTouchMode()) {
+ // Tasks are not focusable in touch mode by default. As hover state would steal focus
+ // when both mKeyboardFocusHighlightEnabled and mCursorHoverStatesEnabled are on,
+ // touch mode needs to be set to false when hovering so it can steal focus to current
+ // task and show border animation as hover state
+ v.getViewRootImpl().touchModeChanged(false);
+ }
+
+ requestFocus();
+ }
+
/**
* @return {@code true} if user is already in split select mode and this tap was to choose the
* second app. {@code false} otherwise
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index ac46c82..3ba8a1d 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -517,11 +517,12 @@
// TODO: move the SearchConfig to SearchState when new LauncherState is created.
mBaseSearchConfig = new BaseSearchConfig();
+ setupViews();
+
mAppWidgetManager = new WidgetManagerHelper(this);
mAppWidgetHolder = createAppWidgetHolder();
mAppWidgetHolder.startListening();
- setupViews();
mPopupDataProvider = new PopupDataProvider(this::updateNotificationDots);
boolean internalStateHandled = ACTIVITY_TRACKER.handleCreate(this);
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index 8a01ea2..80b6452 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -303,14 +303,14 @@
"Enable widget transition animation when resizing the widgets");
public static final BooleanFlag PREEMPTIVE_UNFOLD_ANIMATION_START = getDebugFlag(270397209,
- "PREEMPTIVE_UNFOLD_ANIMATION_START", DISABLED,
+ "PREEMPTIVE_UNFOLD_ANIMATION_START", ENABLED,
"Enables starting the unfold animation preemptively when unfolding, without"
+ "waiting for SystemUI and then merging the SystemUI progress whenever we "
+ "start receiving the events");
// TODO(Block 23): Clean up flags
public static final BooleanFlag ENABLE_GRID_ONLY_OVERVIEW = getDebugFlag(270397206,
- "ENABLE_GRID_ONLY_OVERVIEW", DISABLED,
+ "ENABLE_GRID_ONLY_OVERVIEW", TEAMFOOD,
"Enable a grid-only overview without a focused task.");
public static final BooleanFlag ENABLE_CURSOR_HOVER_STATES = getDebugFlag(243191650,
diff --git a/src/com/android/launcher3/icons/IconCache.java b/src/com/android/launcher3/icons/IconCache.java
index 1e3b003..2f7f51e 100644
--- a/src/com/android/launcher3/icons/IconCache.java
+++ b/src/com/android/launcher3/icons/IconCache.java
@@ -100,6 +100,7 @@
private final UserCache mUserManager;
private final InstantAppResolver mInstantAppResolver;
private final IconProvider mIconProvider;
+ private final HandlerRunnable mCancelledRunnable;
private final SparseArray<BitmapInfo> mWidgetCategoryBitmapInfos;
@@ -117,6 +118,10 @@
mInstantAppResolver = InstantAppResolver.newInstance(mContext);
mIconProvider = iconProvider;
mWidgetCategoryBitmapInfos = new SparseArray<>();
+
+ mCancelledRunnable = new HandlerRunnable(
+ mWorkerHandler, () -> null, MAIN_EXECUTOR, c -> { });
+ mCancelledRunnable.cancel();
}
@Override
@@ -172,23 +177,30 @@
public HandlerRunnable updateIconInBackground(final ItemInfoUpdateReceiver caller,
final ItemInfoWithIcon info) {
Preconditions.assertUIThread();
+ Supplier<ItemInfoWithIcon> task;
+ if (info instanceof AppInfo || info instanceof WorkspaceItemInfo) {
+ task = () -> {
+ getTitleAndIcon(info, false);
+ return info;
+ };
+ } else if (info instanceof PackageItemInfo pii) {
+ task = () -> {
+ getTitleAndIconForApp(pii, false);
+ return pii;
+ };
+ } else {
+ Log.i(TAG, "Icon update not supported for "
+ + info == null ? "null" : info.getClass().getName());
+ return mCancelledRunnable;
+ }
+
if (mPendingIconRequestCount <= 0) {
MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_FOREGROUND);
}
mPendingIconRequestCount++;
HandlerRunnable<ItemInfoWithIcon> request = new HandlerRunnable<>(mWorkerHandler,
- () -> {
- if (info instanceof AppInfo || info instanceof WorkspaceItemInfo) {
- getTitleAndIcon(info, false);
- } else if (info instanceof PackageItemInfo) {
- getTitleAndIconForApp((PackageItemInfo) info, false);
- }
- return info;
- },
- MAIN_EXECUTOR,
- caller::reapplyItemInfo,
- this::onIconRequestEnd);
+ task, MAIN_EXECUTOR, caller::reapplyItemInfo, this::onIconRequestEnd);
Utilities.postAsyncCallback(mWorkerHandler, request);
return request;
}
diff --git a/src/com/android/launcher3/util/ViewOnDrawExecutor.java b/src/com/android/launcher3/util/ViewOnDrawExecutor.java
index c2767be..fada4a3 100644
--- a/src/com/android/launcher3/util/ViewOnDrawExecutor.java
+++ b/src/com/android/launcher3/util/ViewOnDrawExecutor.java
@@ -59,6 +59,7 @@
private void attachObserver() {
if (!mCompleted) {
mAttachedView.getViewTreeObserver().addOnDrawListener(this);
+ mAttachedView.getRootView().invalidate();
}
}
diff --git a/src/com/android/launcher3/views/Snackbar.java b/src/com/android/launcher3/views/Snackbar.java
index 2d81db8..99040ff 100644
--- a/src/com/android/launcher3/views/Snackbar.java
+++ b/src/com/android/launcher3/views/Snackbar.java
@@ -65,9 +65,26 @@
show(activity, labelStringRedId, NO_ID, onDismissed, null);
}
+ /** Show a snackbar with just a label. */
+ public static <T extends Context & ActivityContext> void show(T activity, String labelString,
+ Runnable onDismissed) {
+ show(activity, labelString, NO_ID, onDismissed, null);
+ }
+
/** Show a snackbar with a label and action. */
public static <T extends Context & ActivityContext> void show(T activity, int labelStringResId,
int actionStringResId, Runnable onDismissed, @Nullable Runnable onActionClicked) {
+ show(
+ activity,
+ activity.getResources().getString(labelStringResId),
+ actionStringResId,
+ onDismissed,
+ onActionClicked);
+ }
+
+ /** Show a snackbar with a label and action. */
+ public static <T extends Context & ActivityContext> void show(T activity, String labelString,
+ int actionStringResId, Runnable onDismissed, @Nullable Runnable onActionClicked) {
closeOpenViews(activity, true, TYPE_SNACKBAR);
Snackbar snackbar = new Snackbar(activity, null);
// Set some properties here since inflated xml only contains the children.
@@ -105,8 +122,7 @@
: insets.bottom));
TextView labelView = snackbar.findViewById(R.id.label);
- String labelText = res.getString(labelStringResId);
- labelView.setText(labelText);
+ labelView.setText(labelString);
TextView actionView = snackbar.findViewById(R.id.action);
float actionWidth;
@@ -127,7 +143,7 @@
actionView.setVisibility(GONE);
}
- int totalContentWidth = (int) (labelView.getPaint().measureText(labelText) + actionWidth)
+ int totalContentWidth = (int) (labelView.getPaint().measureText(labelString) + actionWidth)
+ labelView.getPaddingRight() + labelView.getPaddingLeft()
+ padding * 2;
if (totalContentWidth > params.width) {
diff --git a/tests/shared/com/android/launcher3/testing/shared/TestProtocol.java b/tests/shared/com/android/launcher3/testing/shared/TestProtocol.java
index 0809eb8..7abdf8a 100644
--- a/tests/shared/com/android/launcher3/testing/shared/TestProtocol.java
+++ b/tests/shared/com/android/launcher3/testing/shared/TestProtocol.java
@@ -105,8 +105,6 @@
public static final String REQUEST_WINDOW_INSETS = "window-insets";
public static final String REQUEST_PID = "pid";
public static final String REQUEST_FORCE_GC = "gc";
- public static final String REQUEST_VIEW_LEAK = "view-leak";
- public static final String PRINT_VIEW_LEAK = "print-leak";
public static final String REQUEST_RECENT_TASKS_LIST = "recent-tasks-list";
public static final String REQUEST_START_EVENT_LOGGING = "start-event-logging";
public static final String REQUEST_GET_TEST_EVENTS = "get-test-events";
@@ -142,6 +140,10 @@
public static final String REQUEST_GET_GRID_TASK_SIZE_RECT_FOR_TABLET =
"get-grid-task-size-rect-for-tablet";
public static final String REQUEST_GET_OVERVIEW_PAGE_SPACING = "get-overview-page-spacing";
+
+ public static final String REQUEST_GET_OVERVIEW_TASK_BORDER_WIDTH =
+ "get-overview-task-border-width";
+
public static final String REQUEST_ENABLE_ROTATION = "enable_rotation";
public static final String REQUEST_ENABLE_SUGGESTION = "enable-suggestion";
public static final String REQUEST_MODEL_QUEUE_CLEARED = "model-queue-cleared";
@@ -155,7 +157,6 @@
public static final String REQUEST_MOCK_SENSOR_ROTATION = "mock-sensor-rotation";
public static final String PERMANENT_DIAG_TAG = "TaplTarget";
- public static final String VIEW_AND_ACTIVITY_LEAKS = "b/260260325";
public static final String WORK_TAB_MISSING = "b/243688989";
public static final String TWO_TASKBAR_LONG_CLICKS = "b/262282528";
public static final String WORKSPACE_LOADS_FOREVER = "b/267200150";
diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index 352447f..09c4b1f 100644
--- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -232,17 +232,7 @@
public void setUp() throws Exception {
mLauncher.onTestStart();
- final boolean keyguardAlreadyVisible = sSeenKeygard;
-
- sSeenKeygard = sSeenKeygard
- || !TestHelpers.wait(
- Until.gone(By.res(SYSTEMUI_PACKAGE, "keyguard_status_view")), 60000);
-
- Assert.assertFalse(
- "Keyguard is visible, which is likely caused by a crash in SysUI, seeing keyguard"
- + " for the first time = "
- + !keyguardAlreadyVisible,
- sSeenKeygard);
+ verifyKeyguardInvisible();
final String launcherPackageName = mDevice.getLauncherPackageName();
try {
@@ -275,6 +265,20 @@
}
}
+ private static void verifyKeyguardInvisible() {
+ final boolean keyguardAlreadyVisible = sSeenKeygard;
+
+ sSeenKeygard = sSeenKeygard
+ || !TestHelpers.wait(
+ Until.gone(By.res(SYSTEMUI_PACKAGE, "keyguard_status_view")), 60000);
+
+ Assert.assertFalse(
+ "Keyguard is visible, which is likely caused by a crash in SysUI, seeing keyguard"
+ + " for the first time = "
+ + !keyguardAlreadyVisible,
+ sSeenKeygard);
+ }
+
@After
public void verifyLauncherState() {
try {
@@ -392,6 +396,7 @@
// flakiness.
protected void waitForLauncherCondition(
String message, Function<Launcher, Boolean> condition, long timeout) {
+ verifyKeyguardInvisible();
if (!TestHelpers.isInLauncherProcess()) return;
Wait.atMost(message, () -> getFromLauncher(condition), timeout, mLauncher);
}
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index 0aefd68..667d06e 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -375,6 +375,11 @@
.getBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD);
}
+ public int getOverviewTaskBorderWidth() {
+ return getTestInfo(TestProtocol.REQUEST_GET_OVERVIEW_TASK_BORDER_WIDTH)
+ .getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD);
+ }
+
int getFocusedTaskHeightForTablet() {
return getTestInfo(TestProtocol.REQUEST_GET_FOCUSED_TASK_HEIGHT_FOR_TABLET).getInt(
TestProtocol.TEST_INFO_RESPONSE_FIELD);
@@ -1856,14 +1861,6 @@
return testInfo != null ? testInfo.getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD) : null;
}
- public void produceViewLeak() {
- getTestInfo(TestProtocol.REQUEST_VIEW_LEAK);
- }
-
- public void printViewLeak() {
- getTestInfo(TestProtocol.PRINT_VIEW_LEAK);
- }
-
public ArrayList<ComponentName> getRecentTasks() {
ArrayList<ComponentName> tasks = new ArrayList<>();
ArrayList<String> components = getTestInfo(TestProtocol.REQUEST_RECENT_TASKS_LIST)
diff --git a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
index 39b93b4..1d25614 100644
--- a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
+++ b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
@@ -18,7 +18,13 @@
import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED;
+import static com.android.launcher3.tapl.LauncherInstrumentation.CALLBACK_RUN_POINT.CALLBACK_HOVER_ENTER;
+import static com.android.launcher3.tapl.LauncherInstrumentation.CALLBACK_RUN_POINT.CALLBACK_HOVER_EXIT;
+
+import android.graphics.Point;
import android.graphics.Rect;
+import android.os.SystemClock;
+import android.view.MotionEvent;
import androidx.annotation.NonNull;
import androidx.test.uiautomator.By;
@@ -187,4 +193,35 @@
boolean isTaskSplit() {
return mLauncher.findObjectInContainer(mTask.getParent(), "bottomright_snapshot") != null;
}
+
+ /**
+ * Returns this task's visible bounds.
+ */
+ public Rect getVisibleBounds() {
+ return mTask.getVisibleBounds();
+ }
+
+ /**
+ * Emulate the cursor entering and exiting a hover over this task.
+ */
+ public void hoverCursor() {
+ try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
+ LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+ "cursor hover entering task")) {
+ long downTime = SystemClock.uptimeMillis();
+ mLauncher.sendPointer(downTime, downTime, MotionEvent.ACTION_HOVER_ENTER,
+ new Point(mTask.getVisibleCenter().x, mTask.getVisibleCenter().y),
+ null);
+ mLauncher.runCallbackIfActive(CALLBACK_HOVER_ENTER);
+
+ try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer(
+ "cursor hover exiting task")) {
+ downTime = SystemClock.uptimeMillis();
+ mLauncher.sendPointer(downTime, downTime, MotionEvent.ACTION_HOVER_EXIT,
+ new Point(mTask.getVisibleCenter().x, mTask.getVisibleCenter().y),
+ null);
+ mLauncher.runCallbackIfActive(CALLBACK_HOVER_EXIT);
+ }
+ }
+ }
}