From 28cb71ba18e894e02de69256aeae83647aab7c6a Mon Sep 17 00:00:00 2001 From: Vinit Nayak Date: Tue, 17 Mar 2020 14:15:32 -0700 Subject: [PATCH 01/46] Default fixed_rotation to off Change-Id: I83f9f58acb1718ee4fc19530a3e8b25badefda0e --- src/com/android/launcher3/config/FeatureFlags.java | 2 +- src/com/android/launcher3/states/RotationHelper.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java index 8f58865a68..1773888dd2 100644 --- a/src/com/android/launcher3/config/FeatureFlags.java +++ b/src/com/android/launcher3/config/FeatureFlags.java @@ -158,7 +158,7 @@ public final class FeatureFlags { "Always use hardware optimization for folder animations."); public static final BooleanFlag ENABLE_FIXED_ROTATION_TRANSFORM = getDebugFlag( - FLAG_ENABLE_FIXED_ROTATION_TRANSFORM, true, + FLAG_ENABLE_FIXED_ROTATION_TRANSFORM, false, "Launch/close apps without rotation animation. Fix Launcher to portrait"); public static void initialize(Context context) { diff --git a/src/com/android/launcher3/states/RotationHelper.java b/src/com/android/launcher3/states/RotationHelper.java index fae0fe2d81..8c8aa9b8e5 100644 --- a/src/com/android/launcher3/states/RotationHelper.java +++ b/src/com/android/launcher3/states/RotationHelper.java @@ -134,7 +134,7 @@ public class RotationHelper implements OnSharedPreferenceChangeListener { */ private void updateForcedRotation(boolean setValueFromPrefs) { boolean isForcedRotation = mFeatureFlagsPrefs - .getBoolean(FLAG_ENABLE_FIXED_ROTATION_TRANSFORM, true) + .getBoolean(FLAG_ENABLE_FIXED_ROTATION_TRANSFORM, false) && !getAllowRotationDefaultValue(); if (mForcedRotation == isForcedRotation) { return; From cb6e713e00a74395beaa6a5ec60f56b66c0924a9 Mon Sep 17 00:00:00 2001 From: Samuel Fufa Date: Fri, 20 Mar 2020 16:35:06 -0700 Subject: [PATCH 02/46] Change work paused label font Bug: 151405736 Test: Manual Change-Id: I22c68fb998d84a19c1c57c968e76afac7242934b --- res/layout/work_apps_paused.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/layout/work_apps_paused.xml b/res/layout/work_apps_paused.xml index cf1e835872..f64b2d9cd7 100644 --- a/res/layout/work_apps_paused.xml +++ b/res/layout/work_apps_paused.xml @@ -20,7 +20,7 @@ android:gravity="center"> Date: Thu, 19 Mar 2020 16:08:14 -0700 Subject: [PATCH 03/46] Removing animation when stripping empty pages, as the animation is run by LayoutAnimation already Bug: 151978739 Change-Id: I75b9055c3e862a6813164cce6c1ba7b2fe1f870a --- src/com/android/launcher3/Launcher.java | 32 ++++---- src/com/android/launcher3/Workspace.java | 98 ++++++------------------ 2 files changed, 37 insertions(+), 93 deletions(-) diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index a83a694c9c..455eebc125 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -760,8 +760,8 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1) : -1; if (resultCode == RESULT_CANCELED) { completeTwoStageWidgetDrop(RESULT_CANCELED, appWidgetId, requestArgs); - mWorkspace.removeExtraEmptyScreenDelayed(true, exitSpringLoaded, - ON_ACTIVITY_RESULT_ANIMATION_DELAY, false); + mWorkspace.removeExtraEmptyScreenDelayed( + ON_ACTIVITY_RESULT_ANIMATION_DELAY, false, exitSpringLoaded); } else if (resultCode == RESULT_OK) { addAppWidgetImpl( appWidgetId, requestArgs, null, @@ -791,15 +791,9 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, "returned from the widget configuration activity."); result = RESULT_CANCELED; completeTwoStageWidgetDrop(result, appWidgetId, requestArgs); - final Runnable onComplete = new Runnable() { - @Override - public void run() { - getStateManager().goToState(NORMAL); - } - }; - - mWorkspace.removeExtraEmptyScreenDelayed(true, onComplete, - ON_ACTIVITY_RESULT_ANIMATION_DELAY, false); + mWorkspace.removeExtraEmptyScreenDelayed( + ON_ACTIVITY_RESULT_ANIMATION_DELAY, false, + () -> getStateManager().goToState(NORMAL)); } else { if (requestArgs.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) { // When the screen id represents an actual screen (as opposed to a rank) @@ -818,8 +812,8 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, dropLayout.setDropPending(false); } }; - mWorkspace.removeExtraEmptyScreenDelayed(true, onComplete, - ON_ACTIVITY_RESULT_ANIMATION_DELAY, false); + mWorkspace.removeExtraEmptyScreenDelayed( + ON_ACTIVITY_RESULT_ANIMATION_DELAY, false, onComplete); } return; } @@ -838,12 +832,12 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, // Handle custom shortcuts created using ACTION_CREATE_SHORTCUT. if (resultCode == RESULT_OK && requestArgs.container != ItemInfo.NO_ID) { completeAdd(requestCode, data, -1, requestArgs); - mWorkspace.removeExtraEmptyScreenDelayed(true, exitSpringLoaded, - ON_ACTIVITY_RESULT_ANIMATION_DELAY, false); + mWorkspace.removeExtraEmptyScreenDelayed( + ON_ACTIVITY_RESULT_ANIMATION_DELAY, false, exitSpringLoaded); } else if (resultCode == RESULT_CANCELED) { - mWorkspace.removeExtraEmptyScreenDelayed(true, exitSpringLoaded, - ON_ACTIVITY_RESULT_ANIMATION_DELAY, false); + mWorkspace.removeExtraEmptyScreenDelayed( + ON_ACTIVITY_RESULT_ANIMATION_DELAY, false, exitSpringLoaded); } } @@ -1682,7 +1676,7 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, }; completeAddAppWidget(appWidgetId, info, boundWidget, addFlowHandler.getProviderInfo(this)); - mWorkspace.removeExtraEmptyScreenDelayed(true, onComplete, delay, false); + mWorkspace.removeExtraEmptyScreenDelayed(delay, false, onComplete); } } @@ -2112,7 +2106,7 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, } // Remove the extra empty screen - mWorkspace.removeExtraEmptyScreen(false, false); + mWorkspace.removeExtraEmptyScreen(false); } /** diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java index c42e480475..2cc5a22c13 100644 --- a/src/com/android/launcher3/Workspace.java +++ b/src/com/android/launcher3/Workspace.java @@ -29,7 +29,6 @@ import static com.android.launcher3.logging.LoggerUtils.newContainerTarget; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.LayoutTransition; -import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.animation.ValueAnimator.AnimatorUpdateListener; import android.annotation.SuppressLint; @@ -125,9 +124,6 @@ public class Workspace extends PagedView private static final boolean ENFORCE_DRAG_EVENT_ORDER = false; - private static final int SNAP_OFF_EMPTY_SCREEN_DURATION = 400; - private static final int FADE_EMPTY_SCREEN_DURATION = 150; - private static final int ADJACENT_SCREEN_DROP_DURATION = 300; private static final int DEFAULT_PAGE = 0; @@ -140,7 +136,6 @@ public class Workspace extends PagedView @Thunk final IntSparseArrayMap mWorkspaceScreens = new IntSparseArrayMap<>(); @Thunk final IntArray mScreenOrder = new IntArray(); - @Thunk Runnable mRemoveEmptyScreenRunnable; @Thunk boolean mDeferRemoveExtraEmptyScreen = false; /** @@ -428,7 +423,7 @@ public class Workspace extends PagedView } if (!mDeferRemoveExtraEmptyScreen) { - removeExtraEmptyScreen(true, mDragSourceInternal != null); + removeExtraEmptyScreen(mDragSourceInternal != null); } updateChildrenLayersEnabled(); @@ -453,8 +448,16 @@ public class Workspace extends PagedView private void setupLayoutTransition() { // We want to show layout transitions when pages are deleted, to close the gap. mLayoutTransition = new LayoutTransition(); + mLayoutTransition.enableTransitionType(LayoutTransition.DISAPPEARING); mLayoutTransition.enableTransitionType(LayoutTransition.CHANGE_DISAPPEARING); + // Change the interpolators such that the fade animation plays before the move animation. + // This prevents empty adjacent pages to overlay during animation + mLayoutTransition.setInterpolator(LayoutTransition.DISAPPEARING, + Interpolators.clampToProgress(Interpolators.ACCEL_DEACCEL, 0, 0.5f)); + mLayoutTransition.setInterpolator(LayoutTransition.CHANGE_DISAPPEARING, + Interpolators.clampToProgress(Interpolators.ACCEL_DEACCEL, 0.5f, 1)); + mLayoutTransition.disableTransitionType(LayoutTransition.APPEARING); mLayoutTransition.disableTransitionType(LayoutTransition.CHANGE_APPEARING); setLayoutTransition(mLayoutTransition); @@ -571,9 +574,6 @@ public class Workspace extends PagedView boolean lastChildOnScreen = false; boolean childOnFinalScreen = false; - // Cancel any pending removal of empty screen - mRemoveEmptyScreenRunnable = null; - if (mDragSourceInternal != null) { if (mDragSourceInternal.getChildCount() == 1) { lastChildOnScreen = true; @@ -624,43 +624,34 @@ public class Workspace extends PagedView } } - public void removeExtraEmptyScreen(final boolean animate, boolean stripEmptyScreens) { - removeExtraEmptyScreenDelayed(animate, null, 0, stripEmptyScreens); + public void removeExtraEmptyScreen(boolean stripEmptyScreens) { + removeExtraEmptyScreenDelayed(0, stripEmptyScreens, null); } - public void removeExtraEmptyScreenDelayed(final boolean animate, final Runnable onComplete, - final int delay, final boolean stripEmptyScreens) { + public void removeExtraEmptyScreenDelayed( + int delay, boolean stripEmptyScreens, Runnable onComplete) { if (mLauncher.isWorkspaceLoading()) { // Don't strip empty screens if the workspace is still loading return; } if (delay > 0) { - postDelayed(new Runnable() { - @Override - public void run() { - removeExtraEmptyScreenDelayed(animate, onComplete, 0, stripEmptyScreens); - } - }, delay); + postDelayed( + () -> removeExtraEmptyScreenDelayed(0, stripEmptyScreens, onComplete), delay); return; } convertFinalScreenToEmptyScreenIfNecessary(); if (hasExtraEmptyScreen()) { - int emptyIndex = mScreenOrder.indexOf(EXTRA_EMPTY_SCREEN_ID); - if (getNextPage() == emptyIndex) { - snapToPage(getNextPage() - 1, SNAP_OFF_EMPTY_SCREEN_DURATION); - fadeAndRemoveEmptyScreen(SNAP_OFF_EMPTY_SCREEN_DURATION, FADE_EMPTY_SCREEN_DURATION, - onComplete, stripEmptyScreens); - } else { - snapToPage(getNextPage(), 0); - fadeAndRemoveEmptyScreen(0, FADE_EMPTY_SCREEN_DURATION, - onComplete, stripEmptyScreens); - } - return; - } else if (stripEmptyScreens) { - // If we're not going to strip the empty screens after removing - // the extra empty screen, do it right away. + removeView(mWorkspaceScreens.get(EXTRA_EMPTY_SCREEN_ID)); + mWorkspaceScreens.remove(EXTRA_EMPTY_SCREEN_ID); + mScreenOrder.removeValue(EXTRA_EMPTY_SCREEN_ID); + + // Update the page indicator to reflect the removed page. + showPageIndicatorAtCurrentScroll(); + } + + if (stripEmptyScreens) { stripEmptyScreens(); } @@ -669,44 +660,6 @@ public class Workspace extends PagedView } } - private void fadeAndRemoveEmptyScreen(int delay, int duration, final Runnable onComplete, - final boolean stripEmptyScreens) { - // XXX: Do we need to update LM workspace screens below? - final CellLayout cl = mWorkspaceScreens.get(EXTRA_EMPTY_SCREEN_ID); - - mRemoveEmptyScreenRunnable = new Runnable() { - @Override - public void run() { - if (hasExtraEmptyScreen()) { - mWorkspaceScreens.remove(EXTRA_EMPTY_SCREEN_ID); - mScreenOrder.removeValue(EXTRA_EMPTY_SCREEN_ID); - removeView(cl); - if (stripEmptyScreens) { - stripEmptyScreens(); - } - // Update the page indicator to reflect the removed page. - showPageIndicatorAtCurrentScroll(); - } - } - }; - - ObjectAnimator oa = ObjectAnimator.ofFloat(cl, ALPHA, 0f); - oa.setDuration(duration); - oa.setStartDelay(delay); - oa.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - if (mRemoveEmptyScreenRunnable != null) { - mRemoveEmptyScreenRunnable.run(); - } - if (onComplete != null) { - onComplete.run(); - } - } - }); - oa.start(); - } - public boolean hasExtraEmptyScreen() { return mWorkspaceScreens.containsKey(EXTRA_EMPTY_SCREEN_ID) && getChildCount() > 1; } @@ -793,8 +746,6 @@ public class Workspace extends PagedView } } - boolean isInAccessibleDrag = mLauncher.getAccessibilityDelegate().isInAccessibleDrag(); - // We enforce at least one page to add new items to. In the case that we remove the last // such screen, we convert the last screen to the empty screen int minScreens = 1; @@ -813,7 +764,6 @@ public class Workspace extends PagedView removeView(cl); } else { // if this is the last screen, convert it to the empty screen - mRemoveEmptyScreenRunnable = null; mWorkspaceScreens.put(EXTRA_EMPTY_SCREEN_ID, cl); mScreenOrder.add(EXTRA_EMPTY_SCREEN_ID); } From d763a40b7a525ae03d1e231c8041dc91374162a3 Mon Sep 17 00:00:00 2001 From: Joshua Tsuji Date: Fri, 20 Mar 2020 13:31:58 -0400 Subject: [PATCH 04/46] Ignore system gestures while bubbles are expanded. This flag is set by System UI in ag/10760162, when bubbles are expanded. We want to disallow system gestures from starting when bubbles are expanded, since otherwise a swipe up will both close the bubbles, and the app behind them. Test: install launcher, add bubbles, open an app, expand bubbles, observe that swiping up only closes bubbles Test: same but in three button nav, home button should both close bubbles + the app behind it Bug: 146167884 Change-Id: I27ed225d2029e3cb82f4959739c7b6f25fe8adb7 --- .../src/com/android/quickstep/RecentsAnimationDeviceState.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java index 1299a53728..491c6110db 100644 --- a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java +++ b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java @@ -22,6 +22,7 @@ import static com.android.quickstep.SysUINavigationMode.Mode.THREE_BUTTONS; import static com.android.quickstep.SysUINavigationMode.Mode.TWO_BUTTONS; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE; +import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_HOME_DISABLED; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NAV_BAR_HIDDEN; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED; @@ -356,6 +357,7 @@ public class RecentsAnimationDeviceState implements return (mSystemUiStateFlags & SYSUI_STATE_NAV_BAR_HIDDEN) == 0 && (mSystemUiStateFlags & SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED) == 0 && (mSystemUiStateFlags & SYSUI_STATE_QUICK_SETTINGS_EXPANDED) == 0 + && (mSystemUiStateFlags & SYSUI_STATE_BUBBLES_EXPANDED) == 0 && ((mSystemUiStateFlags & SYSUI_STATE_HOME_DISABLED) == 0 || (mSystemUiStateFlags & SYSUI_STATE_OVERVIEW_DISABLED) == 0); } From 062c84663a5cbcf9c0ebef446b7d78c5a5637694 Mon Sep 17 00:00:00 2001 From: Sunny Goyal Date: Mon, 23 Mar 2020 14:37:49 -0700 Subject: [PATCH 05/46] Enabling Least square velocity tracker algorithm Bug: 139750033 Change-Id: I6b4e755f983ae23343a242f69b51f3fccd7a9fc5 --- src/com/android/launcher3/config/FeatureFlags.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java index 4df3b0a7ce..92f511222d 100644 --- a/src/com/android/launcher3/config/FeatureFlags.java +++ b/src/com/android/launcher3/config/FeatureFlags.java @@ -154,7 +154,7 @@ public final class FeatureFlags { "Replace Smartspace with a version rendered by System UI."); public static final BooleanFlag ENABLE_LSQ_VELOCITY_PROVIDER = getDebugFlag( - "ENABLE_LSQ_VELOCITY_PROVIDER", false, + "ENABLE_LSQ_VELOCITY_PROVIDER", true, "Use Least Square algorithm for motion pause detection."); public static final BooleanFlag ALWAYS_USE_HARDWARE_OPTIMIZATION_FOR_FOLDER_ANIMATIONS = From 0570a795f99e67079fb999eb5b7e86d1672a4094 Mon Sep 17 00:00:00 2001 From: Samuel Fufa Date: Mon, 23 Mar 2020 10:03:06 -0700 Subject: [PATCH 06/46] enable prediction logging reverting accidental commit on ag/10673926 Bug: 142753423 Change-Id: I8d2f5f591b200484282f0b170fccab4f99b93fae --- .../launcher3/hybridhotseat/HotseatPredictionController.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java index d82e9f0115..801408f488 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java @@ -48,6 +48,7 @@ import com.android.launcher3.Launcher; import com.android.launcher3.LauncherSettings; import com.android.launcher3.LauncherState; import com.android.launcher3.R; +import com.android.launcher3.Utilities; import com.android.launcher3.Workspace; import com.android.launcher3.WorkspaceItemInfo; import com.android.launcher3.allapps.AllAppsStore; @@ -325,7 +326,7 @@ public class HotseatPredictionController implements DragController.DragListener, mComponentKeyMappers.add(new ComponentKeyMapper(key, mDynamicItemCache)); } predictionLog.append("]"); - if (false) FileLog.d(TAG, predictionLog.toString()); + if (Utilities.IS_DEBUG_DEVICE) FileLog.d(TAG, predictionLog.toString()); updateDependencies(); if (isReady()) { fillGapsWithPrediction(); From 3abaae6c3c0168f2b4d391f9b6364bc4be16cb47 Mon Sep 17 00:00:00 2001 From: Sunny Goyal Date: Tue, 24 Mar 2020 01:27:11 -0700 Subject: [PATCH 07/46] Fixing userControlled flag not properly set when building state animation Bug: 152278197 Change-Id: Icc7d7444f7744c0614b03241d65156d6d493df84 --- src/com/android/launcher3/LauncherStateManager.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/com/android/launcher3/LauncherStateManager.java b/src/com/android/launcher3/LauncherStateManager.java index 24d0c4136b..e071777ba3 100644 --- a/src/com/android/launcher3/LauncherStateManager.java +++ b/src/com/android/launcher3/LauncherStateManager.java @@ -324,6 +324,7 @@ public class LauncherStateManager { public AnimatorPlaybackController createAnimationToNewWorkspace(LauncherState state, StateAnimationConfig config) { + config.userControlled = true; mConfig.reset(); config.copyTo(mConfig); mConfig.playbackController = createAnimationToNewWorkspaceInternal(state) From 95613d93a2c1efd330e8cc9fd8c1743d4e7eed32 Mon Sep 17 00:00:00 2001 From: Sunny Goyal Date: Tue, 24 Mar 2020 12:13:17 -0700 Subject: [PATCH 08/46] Adding some wallpaper depth to drag and drop Change-Id: Ib9acee6dcf8b2883cb66b65159d049bd8cbc2e8b --- src/com/android/launcher3/states/SpringLoadedState.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/com/android/launcher3/states/SpringLoadedState.java b/src/com/android/launcher3/states/SpringLoadedState.java index 97c67c525b..2ba624cc1b 100644 --- a/src/com/android/launcher3/states/SpringLoadedState.java +++ b/src/com/android/launcher3/states/SpringLoadedState.java @@ -17,6 +17,7 @@ package com.android.launcher3.states; import static com.android.launcher3.states.RotationHelper.REQUEST_LOCK; +import android.content.Context; import android.graphics.Rect; import com.android.launcher3.DeviceProfile; @@ -76,6 +77,11 @@ public class SpringLoadedState extends LauncherState { return new ScaleAndTranslation(scale, 0, (desiredCellTop - actualCellTop) / scale); } + @Override + public float getDepth(Context context) { + return 0.5f; + } + @Override public ScaleAndTranslation getHotseatScaleAndTranslation(Launcher launcher) { return new ScaleAndTranslation(1, 0, 0); From 005fa3f5a789c77765845feab58a572142b26c13 Mon Sep 17 00:00:00 2001 From: Winson Chung Date: Tue, 24 Mar 2020 12:01:14 -0700 Subject: [PATCH 09/46] Remove extra binder calls when not detecting gestures - Previous refactoring meant that we were always fetching the running task to update the gesture state, even when you touch down in an area that is not in the gesture region (which is unnecessary). Also only transforming touch events (currently causes allocations) when we are actually handling a gesture. Change-Id: I32e21c3e4d7113a782afce593711d9df62551bc8 --- .../quickstep/TouchInteractionService.java | 28 +++++++++++++++---- .../com/android/quickstep/GestureState.java | 2 ++ 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java index eb5c7f9f69..496a3d858a 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java @@ -22,6 +22,7 @@ import static android.view.MotionEvent.ACTION_UP; import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE; import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; +import static com.android.quickstep.GestureState.DEFAULT_STATE; import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_INPUT_MONITOR; import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SYSUI_PROXY; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_TRACING_ENABLED; @@ -124,6 +125,7 @@ public class TouchInteractionService extends Service implements PluginListener mAM.getRunningTask(0))); + mDeviceState.setOrientationTransformIfNeeded(event); + GestureState newGestureState; if (mDeviceState.isInSwipeUpTouchRegion(event)) { + newGestureState = createGestureState(); mConsumer.onConsumerAboutToBeSwitched(); mConsumer = newConsumer(mGestureState, newGestureState, event); @@ -453,6 +453,7 @@ public class TouchInteractionService extends Service implements PluginListener mAM.getRunningTask(0))); + return gestureState; + } + private InputConsumer newConsumer(GestureState previousGestureState, GestureState newGestureState, MotionEvent event) { boolean canStartSystemGesture = mDeviceState.canStartSystemGesture(); diff --git a/quickstep/src/com/android/quickstep/GestureState.java b/quickstep/src/com/android/quickstep/GestureState.java index 501c6f0b74..f7e40cace5 100644 --- a/quickstep/src/com/android/quickstep/GestureState.java +++ b/quickstep/src/com/android/quickstep/GestureState.java @@ -64,6 +64,8 @@ public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationL private static final String TAG = "GestureState"; private static final ArrayList STATE_NAMES = new ArrayList<>(); + public static final GestureState DEFAULT_STATE = new GestureState(); + private static int FLAG_COUNT = 0; private static int getFlagForIndex(String name) { if (DEBUG_STATES) { From 284a6cfdd7894f94f3c3a7933c7ad26f706d61b1 Mon Sep 17 00:00:00 2001 From: Sunny Goyal Date: Tue, 24 Mar 2020 12:41:56 -0700 Subject: [PATCH 10/46] Removing base no-op implementation of depth controller Change-Id: Ibf46c0a147d2772033ad469b803e606376978cd7 --- .../AppToOverviewAnimationProvider.java | 35 ++++-------- .../quickstep/LauncherActivityInterface.java | 4 +- .../quickstep/LauncherSwipeHandler.java | 1 - .../com/android/quickstep/TaskViewUtils.java | 4 +- .../quickstep/views/LauncherRecentsView.java | 7 ++- .../android/quickstep/views/RecentsView.java | 4 +- .../launcher3/BaseQuickstepLauncher.java | 12 +++- .../QuickstepAppTransitionManagerImpl.java | 6 +- .../BackButtonAlphaHandler.java | 5 +- .../DepthController.java | 34 +++++++++--- .../quickstep/BaseActivityInterface.java | 2 +- src/com/android/launcher3/Launcher.java | 26 +-------- .../uioverrides/DepthController.java | 55 ------------------- 13 files changed, 66 insertions(+), 129 deletions(-) rename quickstep/src/com/android/launcher3/{uioverrides => statehandlers}/BackButtonAlphaHandler.java (96%) rename quickstep/src/com/android/launcher3/{uioverrides => statehandlers}/DepthController.java (84%) delete mode 100644 src_ui_overrides/com/android/launcher3/uioverrides/DepthController.java diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java index ce7fa0d413..9dce9847c2 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java @@ -19,7 +19,7 @@ import static com.android.launcher3.LauncherState.BACKGROUND_APP; import static com.android.launcher3.LauncherState.OVERVIEW; import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN; import static com.android.launcher3.anim.Interpolators.TOUCH_RESPONSE_INTERPOLATOR; -import static com.android.launcher3.uioverrides.DepthController.DEPTH; +import static com.android.launcher3.statehandlers.DepthController.DEPTH; import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING; import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_OPENING; @@ -34,7 +34,7 @@ import android.view.View; import com.android.launcher3.AbstractFloatingView; import com.android.launcher3.BaseDraggingActivity; import com.android.launcher3.anim.AnimationSuccessListener; -import com.android.launcher3.uioverrides.DepthController; +import com.android.launcher3.statehandlers.DepthController; import com.android.quickstep.util.AppWindowAnimationHelper; import com.android.quickstep.util.AppWindowAnimationHelper.TransformParams; import com.android.quickstep.util.RemoteAnimationProvider; @@ -105,12 +105,6 @@ final class AppToOverviewAnimationProvider exten mRecentsView.setRunningTaskIconScaledDown(true); } - DepthController depthController = mActivityInterface.getDepthController(); - if (depthController != null) { - // Update the surface to be the lowest closing app surface - depthController.setSurfaceToLauncher(mRecentsView); - } - AnimatorSet anim = new AnimatorSet(); anim.addListener(new AnimationSuccessListener() { @Override @@ -123,11 +117,17 @@ final class AppToOverviewAnimationProvider exten }); if (mActivity == null) { Log.e(TAG, "Animation created, before activity"); - anim.play(ValueAnimator.ofInt(0, 1).setDuration(RECENTS_LAUNCH_DURATION)) - .with(createDepthAnimator(depthController)); return anim; } + DepthController depthController = mActivityInterface.getDepthController(); + if (depthController != null) { + anim.play(ObjectAnimator.ofFloat(depthController, DEPTH, + BACKGROUND_APP.getDepth(mActivity), + OVERVIEW.getDepth(mActivity)) + .setDuration(RECENTS_LAUNCH_DURATION)); + } + RemoteAnimationTargets targets = new RemoteAnimationTargets(appTargets, wallpaperTargets, MODE_CLOSING); @@ -135,8 +135,6 @@ final class AppToOverviewAnimationProvider exten RemoteAnimationTargetCompat runningTaskTarget = targets.findTask(mTargetTaskId); if (runningTaskTarget == null) { Log.e(TAG, "No closing app"); - anim.play(ValueAnimator.ofInt(0, 1).setDuration(RECENTS_LAUNCH_DURATION)) - .with(createDepthAnimator(depthController)); return anim; } @@ -183,8 +181,6 @@ final class AppToOverviewAnimationProvider exten transaction.apply(); }); } - anim.play(valueAnimator) - .with(createDepthAnimator(depthController)); return anim; } @@ -196,15 +192,4 @@ final class AppToOverviewAnimationProvider exten long getRecentsLaunchDuration() { return RECENTS_LAUNCH_DURATION; } - - private Animator createDepthAnimator(DepthController depthController) { - if (depthController == null) { - // Dummy animation - return ValueAnimator.ofInt(0); - } - return ObjectAnimator.ofFloat(depthController, DEPTH, - BACKGROUND_APP.getDepth(mActivity), - OVERVIEW.getDepth(mActivity)) - .setDuration(RECENTS_LAUNCH_DURATION); - } } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java index 55e6ba2117..455ae764a9 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java @@ -55,9 +55,9 @@ import com.android.launcher3.LauncherState; import com.android.launcher3.allapps.DiscoveryBounce; import com.android.launcher3.anim.AnimatorPlaybackController; import com.android.launcher3.appprediction.PredictionUiStateManager; +import com.android.launcher3.statehandlers.DepthController; +import com.android.launcher3.statehandlers.DepthController.ClampedDepthProperty; import com.android.launcher3.touch.PagedOrientationHandler; -import com.android.launcher3.uioverrides.DepthController; -import com.android.launcher3.uioverrides.DepthController.ClampedDepthProperty; import com.android.launcher3.userevent.nano.LauncherLogProto; import com.android.launcher3.views.FloatingIconView; import com.android.quickstep.SysUINavigationMode.Mode; diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java index cafdb624a5..5bac84403e 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java @@ -294,7 +294,6 @@ public class LauncherSwipeHandler } setupRecentsViewUi(); - mActivityInterface.getDepthController().setSurfaceToLauncher(mRecentsView); if (mDeviceState.getNavMode() == TWO_BUTTONS) { // If the device is in two button mode, swiping up will show overview with predictions diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskViewUtils.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskViewUtils.java index 7ec083e5df..6a3e1feddb 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskViewUtils.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskViewUtils.java @@ -19,7 +19,7 @@ import static com.android.launcher3.LauncherState.BACKGROUND_APP; import static com.android.launcher3.anim.Interpolators.LINEAR; import static com.android.launcher3.anim.Interpolators.TOUCH_RESPONSE_INTERPOLATOR; import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE; -import static com.android.launcher3.uioverrides.DepthController.DEPTH; +import static com.android.launcher3.statehandlers.DepthController.DEPTH; import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_OPENING; import android.animation.Animator; @@ -35,7 +35,7 @@ import com.android.launcher3.BaseActivity; import com.android.launcher3.BaseDraggingActivity; import com.android.launcher3.ItemInfo; import com.android.launcher3.Utilities; -import com.android.launcher3.uioverrides.DepthController; +import com.android.launcher3.statehandlers.DepthController; import com.android.quickstep.util.AppWindowAnimationHelper; import com.android.quickstep.util.MultiValueUpdateListener; import com.android.quickstep.views.RecentsView; diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java index a027feaed5..24703bda4a 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java @@ -37,9 +37,9 @@ import android.view.MotionEvent; import android.view.View; import android.widget.FrameLayout; +import com.android.launcher3.BaseQuickstepLauncher; import com.android.launcher3.DeviceProfile; import com.android.launcher3.Hotseat; -import com.android.launcher3.Launcher; import com.android.launcher3.LauncherState; import com.android.launcher3.LauncherStateManager.StateListener; import com.android.launcher3.R; @@ -47,8 +47,8 @@ import com.android.launcher3.anim.Interpolators; import com.android.launcher3.appprediction.PredictionUiStateManager; import com.android.launcher3.appprediction.PredictionUiStateManager.Client; import com.android.launcher3.config.FeatureFlags; +import com.android.launcher3.statehandlers.DepthController; import com.android.launcher3.states.RotationHelper; -import com.android.launcher3.uioverrides.DepthController; import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper; import com.android.launcher3.util.TraceHelper; import com.android.launcher3.views.ScrimView; @@ -63,7 +63,8 @@ import com.android.systemui.plugins.RecentsExtraCard; * {@link RecentsView} used in Launcher activity */ @TargetApi(Build.VERSION_CODES.O) -public class LauncherRecentsView extends RecentsView implements StateListener { +public class LauncherRecentsView extends RecentsView + implements StateListener { private static final Rect sTempRect = new Rect(); diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java index 68c51a0b34..57a9940925 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java @@ -30,7 +30,7 @@ import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN; import static com.android.launcher3.anim.Interpolators.LINEAR; import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS; import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE; -import static com.android.launcher3.uioverrides.DepthController.DEPTH; +import static com.android.launcher3.statehandlers.DepthController.DEPTH; import static com.android.launcher3.uioverrides.touchcontrollers.TaskViewTouchController.SUCCESS_TRANSITION_PROGRESS; import static com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch.TAP; import static com.android.launcher3.userevent.nano.LauncherLogProto.ControlType.CLEAR_ALL_BUTTON; @@ -96,9 +96,9 @@ import com.android.launcher3.anim.SpringProperty; import com.android.launcher3.compat.AccessibilityManagerCompat; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.graphics.RotationMode; +import com.android.launcher3.statehandlers.DepthController; import com.android.launcher3.states.RotationHelper; import com.android.launcher3.touch.PagedOrientationHandler.CurveProperties; -import com.android.launcher3.uioverrides.DepthController; import com.android.launcher3.userevent.nano.LauncherLogProto; import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction; import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch; diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java index ec66f115f9..83c67bb5da 100644 --- a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java +++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java @@ -43,8 +43,9 @@ import com.android.launcher3.model.WellbeingModel; import com.android.launcher3.popup.SystemShortcut; import com.android.launcher3.proxy.ProxyActivityStarter; import com.android.launcher3.proxy.StartActivityParams; +import com.android.launcher3.statehandlers.BackButtonAlphaHandler; +import com.android.launcher3.statehandlers.DepthController; import com.android.launcher3.touch.PagedOrientationHandler; -import com.android.launcher3.uioverrides.BackButtonAlphaHandler; import com.android.launcher3.uioverrides.RecentsViewStateController; import com.android.launcher3.util.UiThreadHelper; import com.android.quickstep.RecentsModel; @@ -67,6 +68,7 @@ import java.util.stream.Stream; public abstract class BaseQuickstepLauncher extends Launcher implements NavigationModeChangeListener { + private DepthController mDepthController = new DepthController(this); protected SystemActions mSystemActions; /** @@ -249,6 +251,10 @@ public abstract class BaseQuickstepLauncher extends Launcher new BackButtonAlphaHandler(this)}; } + public DepthController getDepthController() { + return mDepthController; + } + @Override protected ScaleAndTranslation getOverviewScaleAndTranslationForNormalState() { if (SysUINavigationMode.getMode(this) == Mode.NO_BUTTON) { @@ -294,6 +300,10 @@ public abstract class BaseQuickstepLauncher extends Launcher onLauncherStateOrFocusChanged(); } + if ((changeBits & ACTIVITY_STATE_STARTED) != 0) { + mDepthController.setActivityStarted(isStarted()); + } + super.onActivityFlagsChanged(changeBits); } diff --git a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java index c93a4ba566..a30e102f07 100644 --- a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java +++ b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java @@ -33,7 +33,7 @@ import static com.android.launcher3.anim.Interpolators.EXAGGERATED_EASE; import static com.android.launcher3.anim.Interpolators.LINEAR; import static com.android.launcher3.config.FeatureFlags.KEYGUARD_ANIMATION; import static com.android.launcher3.dragndrop.DragLayer.ALPHA_INDEX_TRANSITIONS; -import static com.android.launcher3.uioverrides.DepthController.DEPTH; +import static com.android.launcher3.statehandlers.DepthController.DEPTH; import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION; import static com.android.quickstep.TaskUtils.taskIsATargetWithMode; import static com.android.systemui.shared.system.QuickStepContract.getWindowCornerRadius; @@ -72,7 +72,7 @@ import com.android.launcher3.allapps.AllAppsTransitionController; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.dragndrop.DragLayer; import com.android.launcher3.shortcuts.DeepShortcutView; -import com.android.launcher3.uioverrides.DepthController; +import com.android.launcher3.statehandlers.DepthController; import com.android.launcher3.util.DynamicResource; import com.android.launcher3.util.MultiValueAlpha; import com.android.launcher3.util.MultiValueAlpha.AlphaProperty; @@ -622,7 +622,7 @@ public abstract class QuickstepAppTransitionManagerImpl extends LauncherAppTrans backgroundRadiusAnim.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { - depthController.setSurfaceToLauncher(mLauncher.getDragLayer()); + depthController.setSurfaceToApp(null); } }); } diff --git a/quickstep/src/com/android/launcher3/uioverrides/BackButtonAlphaHandler.java b/quickstep/src/com/android/launcher3/statehandlers/BackButtonAlphaHandler.java similarity index 96% rename from quickstep/src/com/android/launcher3/uioverrides/BackButtonAlphaHandler.java rename to quickstep/src/com/android/launcher3/statehandlers/BackButtonAlphaHandler.java index e82a50455b..983702a97b 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/BackButtonAlphaHandler.java +++ b/quickstep/src/com/android/launcher3/statehandlers/BackButtonAlphaHandler.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.launcher3.uioverrides; +package com.android.launcher3.statehandlers; import static com.android.launcher3.anim.Interpolators.LINEAR; import static com.android.quickstep.AnimatedFloat.VALUE; @@ -29,6 +29,9 @@ import com.android.quickstep.AnimatedFloat; import com.android.quickstep.SysUINavigationMode; import com.android.quickstep.SystemUiProxy; +/** + * State handler for animating back button alpha + */ public class BackButtonAlphaHandler implements LauncherStateManager.StateHandler { private final BaseQuickstepLauncher mLauncher; diff --git a/quickstep/src/com/android/launcher3/uioverrides/DepthController.java b/quickstep/src/com/android/launcher3/statehandlers/DepthController.java similarity index 84% rename from quickstep/src/com/android/launcher3/uioverrides/DepthController.java rename to quickstep/src/com/android/launcher3/statehandlers/DepthController.java index 8995a7e1a0..24ba89a0d3 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/DepthController.java +++ b/quickstep/src/com/android/launcher3/statehandlers/DepthController.java @@ -14,13 +14,14 @@ * limitations under the License. */ -package com.android.launcher3.uioverrides; +package com.android.launcher3.statehandlers; import static com.android.launcher3.anim.Interpolators.LINEAR; import android.os.IBinder; import android.util.FloatProperty; import android.view.View; +import android.view.ViewTreeObserver; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherState; @@ -77,6 +78,16 @@ public class DepthController implements LauncherStateManager.StateHandler { } } + private final ViewTreeObserver.OnDrawListener mOnDrawListener = + new ViewTreeObserver.OnDrawListener() { + @Override + public void onDraw() { + View view = mLauncher.getDragLayer(); + setSurface(new SurfaceControlCompat(view)); + view.post(() -> view.getViewTreeObserver().removeOnDrawListener(this)); + } + }; + private final Launcher mLauncher; /** * Blur radius when completely zoomed out, in pixels. @@ -102,22 +113,29 @@ public class DepthController implements LauncherStateManager.StateHandler { mWallpaperManager = new WallpaperManagerCompat(mLauncher); } + /** + * Sets if the underlying activity is started or not + */ + public void setActivityStarted(boolean isStarted) { + if (isStarted) { + mLauncher.getDragLayer().getViewTreeObserver().addOnDrawListener(mOnDrawListener); + } else { + mLauncher.getDragLayer().getViewTreeObserver().removeOnDrawListener(mOnDrawListener); + setSurface(null); + } + } + /** * Sets the specified app target surface to apply the blur to. */ public void setSurfaceToApp(RemoteAnimationTargetCompat target) { if (target != null) { setSurface(target.leash); + } else { + setActivityStarted(mLauncher.isStarted()); } } - /** - * Sets the surface to apply the blur to as the launcher surface. - */ - public void setSurfaceToLauncher(View v) { - setSurface(v != null ? new SurfaceControlCompat(v) : null); - } - private void setSurface(SurfaceControlCompat surface) { if (mSurface != surface) { mSurface = surface; diff --git a/quickstep/src/com/android/quickstep/BaseActivityInterface.java b/quickstep/src/com/android/quickstep/BaseActivityInterface.java index 2a569f5d35..94ef15a076 100644 --- a/quickstep/src/com/android/quickstep/BaseActivityInterface.java +++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java @@ -32,8 +32,8 @@ import androidx.annotation.UiThread; import com.android.launcher3.BaseDraggingActivity; import com.android.launcher3.DeviceProfile; import com.android.launcher3.anim.AnimatorPlaybackController; +import com.android.launcher3.statehandlers.DepthController; import com.android.launcher3.touch.PagedOrientationHandler; -import com.android.launcher3.uioverrides.DepthController; import com.android.quickstep.util.ActivityInitListener; import com.android.quickstep.util.ShelfPeekAnim; import com.android.systemui.shared.recents.model.ThumbnailData; diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index a83a694c9c..0a22342515 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -77,7 +77,6 @@ import android.view.Menu; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; -import android.view.ViewTreeObserver; import android.view.accessibility.AccessibilityEvent; import android.view.animation.OvershootInterpolator; import android.widget.Toast; @@ -124,7 +123,6 @@ import com.android.launcher3.testing.TestLogging; import com.android.launcher3.testing.TestProtocol; import com.android.launcher3.touch.AllAppsSwipeController; import com.android.launcher3.touch.ItemClickHandler; -import com.android.launcher3.uioverrides.DepthController; import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper; import com.android.launcher3.userevent.nano.LauncherLogProto.Action; import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType; @@ -326,19 +324,6 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, private boolean mDeferOverlayCallbacks; private final Runnable mDeferredOverlayCallbacks = this::checkIfOverlayStillDeferred; - private DepthController mDepthController = - new DepthController(this); - - private final ViewTreeObserver.OnDrawListener mOnDrawListener = - new ViewTreeObserver.OnDrawListener() { - @Override - public void onDraw() { - getDepthController().setSurfaceToLauncher(mDragLayer); - mDragLayer.post(() -> mDragLayer.getViewTreeObserver().removeOnDrawListener( - this)); - } - }; - private long mLastTouchUpTime = -1; @Override @@ -942,8 +927,6 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, final int origDragLayerChildCount = mDragLayer.getChildCount(); super.onStop(); - mDragLayer.getViewTreeObserver().removeOnDrawListener(mOnDrawListener); - if (mDeferOverlayCallbacks) { checkIfOverlayStillDeferred(); } else { @@ -956,7 +939,6 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, NotificationListener.removeNotificationsChangedListener(); getStateManager().moveToRestState(); - getDepthController().setSurfaceToLauncher(null); // Workaround for b/78520668, explicitly trim memory once UI is hidden onTrimMemory(TRIM_MEMORY_UI_HIDDEN); @@ -984,7 +966,6 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, if (!mDeferOverlayCallbacks) { mOverlayManager.onActivityStarted(this); } - mDragLayer.getViewTreeObserver().addOnDrawListener(mOnDrawListener); mAppWidgetHost.setListenIfResumed(true); TraceHelper.INSTANCE.endSection(traceToken); @@ -2716,8 +2697,7 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, } protected StateHandler[] createStateHandlers() { - return new StateHandler[] { getAllAppsController(), getWorkspace(), - getDepthController() }; + return new StateHandler[] { getAllAppsController(), getWorkspace() }; } public TouchController[] createTouchControllers() { @@ -2754,10 +2734,6 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, return Stream.of(APP_INFO, WIDGETS, INSTALL); } - public DepthController getDepthController() { - return mDepthController; - } - public static Launcher getLauncher(Context context) { return fromContext(context); } diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/DepthController.java b/src_ui_overrides/com/android/launcher3/uioverrides/DepthController.java deleted file mode 100644 index 7ad85e22c3..0000000000 --- a/src_ui_overrides/com/android/launcher3/uioverrides/DepthController.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.launcher3.uioverrides; - - -import android.util.FloatProperty; -import android.view.View; - -import com.android.launcher3.Launcher; -import com.android.launcher3.LauncherState; -import com.android.launcher3.LauncherStateManager; -import com.android.launcher3.anim.PendingAnimation; -import com.android.launcher3.states.StateAnimationConfig; - -/** - * Controls blur and wallpaper zoom, for the Launcher surface only. - */ -public class DepthController implements LauncherStateManager.StateHandler { - - public static final FloatProperty DEPTH = - new FloatProperty("depth") { - @Override - public void setValue(DepthController depthController, float depth) {} - - @Override - public Float get(DepthController depthController) { - return 0f; - } - }; - - public DepthController(Launcher l) {} - - public void setSurfaceToLauncher(View v) {} - - @Override - public void setState(LauncherState toState) {} - - @Override - public void setStateWithAnimation(LauncherState toState, StateAnimationConfig config, - PendingAnimation animation) { } -} From deb91c46eb0cce056f939f5978eee40fbfd163f6 Mon Sep 17 00:00:00 2001 From: Sunny Goyal Date: Tue, 24 Mar 2020 02:17:59 -0700 Subject: [PATCH 11/46] Decoupling some dragController methods Change-Id: I7c4ab5f1504c49eaa0566afe643d7672faa80205 --- src/com/android/launcher3/Launcher.java | 17 ++++++--- src/com/android/launcher3/Workspace.java | 5 +-- .../launcher3/dragndrop/DragController.java | 37 +++---------------- .../launcher3/dragndrop/DragLayer.java | 31 +++++++++------- 4 files changed, 35 insertions(+), 55 deletions(-) diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index a83a694c9c..dd0745b663 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -1207,7 +1207,6 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, mScrimView = findViewById(R.id.scrim_view); // Setup the drag controller (drop targets have to be added in reverse order in priority) - mDragController.setMoveTarget(mWorkspace); mDropTargetBar.setup(mDragController); mAllAppsController.setupViews(mAppsView); @@ -1507,11 +1506,7 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, target.pageIndex = mWorkspace.getCurrentPage(); ued.logActionCommand(Action.Command.HOME_INTENT, target, newContainerTarget(ContainerType.WORKSPACE)); - - final View v = getWindow().peekDecorView(); - if (v != null && v.getWindowToken() != null) { - UiThreadHelper.hideKeyboardAsync(this, v.getWindowToken()); - } + hideKeyboard(); if (mLauncherCallbacks != null) { mLauncherCallbacks.onHomeIntent(internalStateHandled); @@ -1522,6 +1517,16 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, TraceHelper.INSTANCE.endSection(traceToken); } + /** + * Hides the keyboard if visible + */ + public void hideKeyboard() { + final View v = getWindow().peekDecorView(); + if (v != null && v.getWindowToken() != null) { + UiThreadHelper.hideKeyboardAsync(this, v.getWindowToken()); + } + } + @Override public void onRestoreInstanceState(Bundle state) { super.onRestoreInstanceState(state); diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java index c42e480475..ee08beb858 100644 --- a/src/com/android/launcher3/Workspace.java +++ b/src/com/android/launcher3/Workspace.java @@ -43,7 +43,6 @@ import android.graphics.Point; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.os.Handler; -import android.os.IBinder; import android.os.Message; import android.os.Parcelable; import android.os.UserHandle; @@ -1224,10 +1223,8 @@ public class Workspace extends PagedView protected void onAttachedToWindow() { super.onAttachedToWindow(); - IBinder windowToken = getWindowToken(); - mWallpaperOffset.setWindowToken(windowToken); + mWallpaperOffset.setWindowToken(getWindowToken()); computeScroll(); - mDragController.setWindowToken(windowToken); } protected void onDetachedFromWindow() { diff --git a/src/com/android/launcher3/dragndrop/DragController.java b/src/com/android/launcher3/dragndrop/DragController.java index d0d9aaf947..d6b9b76114 100644 --- a/src/com/android/launcher3/dragndrop/DragController.java +++ b/src/com/android/launcher3/dragndrop/DragController.java @@ -27,7 +27,6 @@ import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.Point; import android.graphics.Rect; -import android.os.IBinder; import android.view.DragEvent; import android.view.HapticFeedbackConstants; import android.view.KeyEvent; @@ -44,7 +43,6 @@ import com.android.launcher3.WorkspaceItemInfo; import com.android.launcher3.accessibility.DragViewStateAnnouncer; import com.android.launcher3.util.ItemInfoMatcher; import com.android.launcher3.util.TouchController; -import com.android.launcher3.util.UiThreadHelper; import java.util.ArrayList; @@ -86,21 +84,14 @@ public class DragController implements DragDriver.EventListener, TouchController private DropTarget.DragObject mDragObject; /** Who can receive drop events */ - private ArrayList mDropTargets = new ArrayList<>(); - private ArrayList mListeners = new ArrayList<>(); - - /** The window token used as the parent for the DragView. */ - private IBinder mWindowToken; - - private View mMoveTarget; + private final ArrayList mDropTargets = new ArrayList<>(); + private final ArrayList mListeners = new ArrayList<>(); private DropTarget mLastDropTarget; private int mLastTouchClassification; private int mDistanceSinceScroll = 0; - private Rect mDragLayerRect = new Rect(); - private boolean mIsInPreDrag; /** @@ -153,8 +144,7 @@ public class DragController implements DragDriver.EventListener, TouchController android.os.Debug.startMethodTracing("Launcher"); } - // Hide soft keyboard, if visible - UiThreadHelper.hideKeyboardAsync(mLauncher, mWindowToken); + mLauncher.hideKeyboard(); AbstractFloatingView.closeOpenViews(mLauncher, false, TYPE_DISCOVERY_BOUNCE); mOptions = options; @@ -362,9 +352,9 @@ public class DragController implements DragDriver.EventListener, TouchController * Clamps the position to the drag layer bounds. */ private Point getClampedDragLayerPos(float x, float y) { - mLauncher.getDragLayer().getLocalVisibleRect(mDragLayerRect); - mTmpPoint.x = (int) Math.max(mDragLayerRect.left, Math.min(x, mDragLayerRect.right - 1)); - mTmpPoint.y = (int) Math.max(mDragLayerRect.top, Math.min(y, mDragLayerRect.bottom - 1)); + mLauncher.getDragLayer().getLocalVisibleRect(mRectTemp); + mTmpPoint.x = (int) Math.max(mRectTemp.left, Math.min(x, mRectTemp.right - 1)); + mTmpPoint.y = (int) Math.max(mRectTemp.top, Math.min(y, mRectTemp.bottom - 1)); return mTmpPoint; } @@ -439,17 +429,6 @@ public class DragController implements DragDriver.EventListener, TouchController return mDragDriver != null && mDragDriver.onDragEvent(event); } - /** - * Sets the view that should handle move events. - */ - public void setMoveTarget(View view) { - mMoveTarget = view; - } - - public boolean dispatchUnhandledMove(View focused, int direction) { - return mMoveTarget != null && mMoveTarget.dispatchUnhandledMove(focused, direction); - } - private void handleMoveEvent(int x, int y) { mDragObject.dragView.move(x, y); @@ -593,10 +572,6 @@ public class DragController implements DragDriver.EventListener, TouchController return mLauncher.getWorkspace(); } - public void setWindowToken(IBinder token) { - mWindowToken = token; - } - /** * Sets the drag listener which will be notified when a drag starts or ends. */ diff --git a/src/com/android/launcher3/dragndrop/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java index e18ca547fd..9ece3d3bca 100644 --- a/src/com/android/launcher3/dragndrop/DragLayer.java +++ b/src/com/android/launcher3/dragndrop/DragLayer.java @@ -21,6 +21,7 @@ import static android.view.View.MeasureSpec.EXACTLY; import static android.view.View.MeasureSpec.getMode; import static android.view.View.MeasureSpec.getSize; +import static com.android.launcher3.anim.Interpolators.DEACCEL_1_5; import static com.android.launcher3.compat.AccessibilityManagerCompat.sendCustomAccessibilityEvent; import android.animation.Animator; @@ -49,7 +50,6 @@ import com.android.launcher3.Launcher; import com.android.launcher3.R; import com.android.launcher3.ShortcutAndWidgetContainer; import com.android.launcher3.Workspace; -import com.android.launcher3.anim.Interpolators; import com.android.launcher3.folder.Folder; import com.android.launcher3.graphics.OverviewScrim; import com.android.launcher3.graphics.RotationMode; @@ -74,11 +74,11 @@ public class DragLayer extends BaseDragLayer { public static final int ANIMATION_END_DISAPPEAR = 0; public static final int ANIMATION_END_REMAIN_VISIBLE = 2; - @Thunk DragController mDragController; + private DragController mDragController; // Variables relating to animation of views after drop private ValueAnimator mDropAnim = null; - private final TimeInterpolator mCubicEaseOutInterpolator = Interpolators.DEACCEL_1_5; + @Thunk DragView mDropView = null; @Thunk int mAnchorViewInitialScrollX = 0; @Thunk View mAnchorView = null; @@ -88,13 +88,14 @@ public class DragLayer extends BaseDragLayer { private int mTopViewIndex; private int mChildCountOnLastUpdate = -1; - private Rect mTmpRect = new Rect(); - // Related to adjacent page hints private final ViewGroupFocusHelper mFocusIndicatorHelper; private final WorkspaceAndHotseatScrim mWorkspaceScrim; private final OverviewScrim mOverviewScrim; + // View that should handle move events + private View mMoveTarget; + /** * Used to create a new DragLayer from XML. * @@ -116,6 +117,7 @@ public class DragLayer extends BaseDragLayer { public void setup(DragController dragController, Workspace workspace) { mDragController = dragController; mWorkspaceScrim.setWorkspace(workspace); + mMoveTarget = workspace; recreateControllers(); } @@ -223,7 +225,7 @@ public class DragLayer extends BaseDragLayer { @Override public boolean dispatchUnhandledMove(View focused, int direction) { return super.dispatchUnhandledMove(focused, direction) - || mDragController.dispatchUnhandledMove(focused, direction); + || mMoveTarget.dispatchUnhandledMove(focused, direction); } @Override @@ -260,9 +262,10 @@ public class DragLayer extends BaseDragLayer { parentChildren.measureChild(child); parentChildren.layoutChild(child); - getViewRectRelativeToSelf(dragView, mTmpRect); - final int fromX = mTmpRect.left; - final int fromY = mTmpRect.top; + Rect dragViewBounds = new Rect(); + getViewRectRelativeToSelf(dragView, dragViewBounds); + final int fromX = dragViewBounds.left; + final int fromY = dragViewBounds.top; float coord[] = new float[2]; float childScale = child.getScaleX(); @@ -283,15 +286,15 @@ public class DragLayer extends BaseDragLayer { if (child instanceof DraggableView) { DraggableView d = (DraggableView) child; - d.getVisualDragBounds(mTmpRect); + d.getVisualDragBounds(dragViewBounds); // This accounts for the offset of the DragView created by scaling it about its // center as it animates into place. float scaleShiftX = dragView.getMeasuredWidth() * (1 - scale) / 2; float scaleShiftY = dragView.getMeasuredHeight() * (1 - scale) / 2; - toX += scale * (mTmpRect.left - dragView.getBlurSizeOutline() / 2) - scaleShiftX; - toY += scale * (mTmpRect.top - dragView.getBlurSizeOutline() / 2) - scaleShiftY; + toX += scale * (dragViewBounds.left - dragView.getBlurSizeOutline() / 2) - scaleShiftX; + toY += scale * (dragViewBounds.top - dragView.getBlurSizeOutline() / 2) - scaleShiftY; } child.setVisibility(INVISIBLE); @@ -348,7 +351,7 @@ public class DragLayer extends BaseDragLayer { if (duration < 0) { duration = res.getInteger(R.integer.config_dropAnimMaxDuration); if (dist < maxDist) { - duration *= mCubicEaseOutInterpolator.getInterpolation(dist / maxDist); + duration *= DEACCEL_1_5.getInterpolation(dist / maxDist); } duration = Math.max(duration, res.getInteger(R.integer.config_dropAnimMinDuration)); } @@ -356,7 +359,7 @@ public class DragLayer extends BaseDragLayer { // Fall back to cubic ease out interpolator for the animation if none is specified TimeInterpolator interpolator = null; if (alphaInterpolator == null || motionInterpolator == null) { - interpolator = mCubicEaseOutInterpolator; + interpolator = DEACCEL_1_5; } // Animate the view From ac752e39f5d12ff02315f921269472dbd92ad227 Mon Sep 17 00:00:00 2001 From: vadimt Date: Tue, 24 Mar 2020 17:58:03 -0700 Subject: [PATCH 12/46] Debug tracing to repro a flake Bug: 152354290 Change-Id: I06c9ad131ab125226dfdd548121d0aa3f26c7bc7 --- src/com/android/launcher3/BaseRecyclerView.java | 6 ++++++ .../launcher3/compat/AccessibilityManagerCompat.java | 3 +++ src/com/android/launcher3/testing/TestProtocol.java | 1 + 3 files changed, 10 insertions(+) diff --git a/src/com/android/launcher3/BaseRecyclerView.java b/src/com/android/launcher3/BaseRecyclerView.java index 38e1201b70..9e8441a0dd 100644 --- a/src/com/android/launcher3/BaseRecyclerView.java +++ b/src/com/android/launcher3/BaseRecyclerView.java @@ -18,6 +18,7 @@ package com.android.launcher3; import android.content.Context; import android.util.AttributeSet; +import android.util.Log; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; @@ -25,6 +26,7 @@ import android.view.ViewGroup; import androidx.recyclerview.widget.RecyclerView; import com.android.launcher3.compat.AccessibilityManagerCompat; +import com.android.launcher3.testing.TestProtocol; import com.android.launcher3.views.RecyclerViewFastScroller; @@ -177,6 +179,10 @@ public abstract class BaseRecyclerView extends RecyclerView { public void onScrollStateChanged(int state) { super.onScrollStateChanged(state); + if (TestProtocol.sDebugTracing) { + Log.d(TestProtocol.NO_SCROLL_END_WIDGETS, "onScrollStateChanged: " + state); + } + if (state == SCROLL_STATE_IDLE) { AccessibilityManagerCompat.sendScrollFinishedEventToTest(getContext()); } diff --git a/src/com/android/launcher3/compat/AccessibilityManagerCompat.java b/src/com/android/launcher3/compat/AccessibilityManagerCompat.java index 1d32d1dfa2..737c97b1bc 100644 --- a/src/com/android/launcher3/compat/AccessibilityManagerCompat.java +++ b/src/com/android/launcher3/compat/AccessibilityManagerCompat.java @@ -75,6 +75,9 @@ public class AccessibilityManagerCompat { } public static void sendScrollFinishedEventToTest(Context context) { + if (TestProtocol.sDebugTracing) { + Log.d(TestProtocol.NO_SCROLL_END_WIDGETS, "sendScrollFinishedEventToTest"); + } final AccessibilityManager accessibilityManager = getAccessibilityManagerForTest(context); if (accessibilityManager == null) return; diff --git a/src/com/android/launcher3/testing/TestProtocol.java b/src/com/android/launcher3/testing/TestProtocol.java index 97ce65e640..dd97b10298 100644 --- a/src/com/android/launcher3/testing/TestProtocol.java +++ b/src/com/android/launcher3/testing/TestProtocol.java @@ -95,4 +95,5 @@ public final class TestProtocol { public static final String NO_BACKGROUND_TO_OVERVIEW_TAG = "b/138251824"; public static final String APP_NOT_DISABLED = "b/139891609"; + public static final String NO_SCROLL_END_WIDGETS = "b/152354290"; } From a306e59c69306d66ee0e57c3ed432d2b7f37f662 Mon Sep 17 00:00:00 2001 From: Sunny Goyal Date: Tue, 24 Mar 2020 01:51:41 -0700 Subject: [PATCH 13/46] Fixing drag getting stuck due to mismatch touch events Bug: 150825081 Change-Id: Ib0613ff145fe308800eae85f2148b22ee01db91d --- src/com/android/launcher3/Launcher.java | 20 +++++++++++++++++-- .../launcher3/dragndrop/DragController.java | 8 +++++++- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index a83a694c9c..775fd5cd31 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -340,6 +340,7 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, }; private long mLastTouchUpTime = -1; + private boolean mTouchInProgress; @Override protected void onCreate(Bundle savedInstanceState) { @@ -1840,13 +1841,28 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, @Override public boolean dispatchTouchEvent(MotionEvent ev) { - if (ev.getAction() == MotionEvent.ACTION_UP) { - mLastTouchUpTime = System.currentTimeMillis(); + switch (ev.getAction()) { + case MotionEvent.ACTION_DOWN: + mTouchInProgress = true; + break; + case MotionEvent.ACTION_UP: + mLastTouchUpTime = System.currentTimeMillis(); + // Follow through + case MotionEvent.ACTION_CANCEL: + mTouchInProgress = false; + break; } TestLogging.recordMotionEvent(TestProtocol.SEQUENCE_MAIN, "Touch event", ev); return super.dispatchTouchEvent(ev); } + /** + * Returns true if a touch interaction is in progress + */ + public boolean isTouchInProgress() { + return mTouchInProgress; + } + @Override public void onBackPressed() { if (finishAutoCancelActionMode()) { diff --git a/src/com/android/launcher3/dragndrop/DragController.java b/src/com/android/launcher3/dragndrop/DragController.java index d0d9aaf947..8fc2c5685e 100644 --- a/src/com/android/launcher3/dragndrop/DragController.java +++ b/src/com/android/launcher3/dragndrop/DragController.java @@ -20,6 +20,7 @@ import static com.android.launcher3.AbstractFloatingView.TYPE_DISCOVERY_BOUNCE; import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY; import static com.android.launcher3.LauncherState.NORMAL; import static com.android.launcher3.Utilities.ATLEAST_Q; +import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import android.animation.ValueAnimator; import android.content.ComponentName; @@ -64,7 +65,7 @@ public class DragController implements DragDriver.EventListener, TouchController private final FlingToDeleteHelper mFlingToDeleteHelper; // temporaries to avoid gc thrash - private Rect mRectTemp = new Rect(); + private final Rect mRectTemp = new Rect(); private final int[] mCoordinatesTemp = new int[2]; /** @@ -217,6 +218,11 @@ public class DragController implements DragDriver.EventListener, TouchController handleMoveEvent(mLastTouch.x, mLastTouch.y); mLauncher.getUserEventDispatcher().resetActionDurationMillis(); + + if (!mLauncher.isTouchInProgress() && options.simulatedDndStartPoint == null) { + // If it is an internal drag and the touch is already complete, cancel immediately + MAIN_EXECUTOR.submit(this::cancelDrag); + } return dragView; } From aa2aff5a8f29027b3e49246028f8e5261f3b2cc8 Mon Sep 17 00:00:00 2001 From: Samuel Fufa Date: Tue, 24 Mar 2020 18:34:12 -0700 Subject: [PATCH 14/46] Personalize hotseat education If a user has has 0 apps in the hotseat, jump directly to showing predications. Otherwise show migration dialog as usual and if user rejects it, show different tips based on the number of available spots. Bug: 142753423 Test: Manual Change-Id: Ic5202caf074db2409f6468dd9373875571f3f3c1 --- .../hybridhotseat/HotseatEduController.java | 43 ++++++++++++++++--- .../hybridhotseat/HotseatEduDialog.java | 13 +++--- .../HotseatPredictionController.java | 6 +-- quickstep/res/values/strings.xml | 15 ++++--- 4 files changed, 55 insertions(+), 22 deletions(-) diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduController.java index da588173af..a7fb6e14e0 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduController.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduController.java @@ -26,6 +26,7 @@ import android.view.View; import androidx.core.app.NotificationCompat; +import com.android.launcher3.ArrowTipView; import com.android.launcher3.CellLayout; import com.android.launcher3.FolderInfo; import com.android.launcher3.Hotseat; @@ -42,6 +43,7 @@ import com.android.launcher3.util.ActivityTracker; import com.android.launcher3.util.GridOccupancy; import com.android.launcher3.util.IntArray; import com.android.launcher3.util.Themes; +import com.android.launcher3.views.Snackbar; import java.util.ArrayDeque; import java.util.ArrayList; @@ -56,6 +58,9 @@ public class HotseatEduController { private static final String NOTIFICATION_CHANNEL_ID = "launcher_onboarding"; private static final int ONBOARDING_NOTIFICATION_ID = 7641; + private static final String SETTINGS_ACTION = + "android.settings.ACTION_CONTENT_SUGGESTIONS_SETTINGS"; + private final Launcher mLauncher; private final NotificationManager mNotificationManager; private final Notification mNotification; @@ -65,9 +70,11 @@ public class HotseatEduController { private ArrayList mNewItems = new ArrayList<>(); private IntArray mNewScreens = null; private Runnable mOnOnboardingComplete; + private Hotseat mHotseat; HotseatEduController(Launcher launcher, Runnable runnable) { mLauncher = launcher; + mHotseat = launcher.getHotseat(); mOnOnboardingComplete = runnable; mNotificationManager = mLauncher.getSystemService(NotificationManager.class); createNotificationChannel(); @@ -98,7 +105,7 @@ public class HotseatEduController { //separate folders and items that can get in folders for (int i = 0; i < mLauncher.getDeviceProfile().inv.numHotseatIcons; i++) { - View view = mLauncher.getHotseat().getChildAt(i, 0); + View view = mHotseat.getChildAt(i, 0); if (view == null) continue; ItemInfo info = (ItemInfo) view.getTag(); if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) { @@ -178,7 +185,6 @@ public class HotseatEduController { */ private int migrateHotseatWhole() { Workspace workspace = mLauncher.getWorkspace(); - Hotseat hotseatVG = mLauncher.getHotseat(); int pageId = -1; int toRow = 0; @@ -196,7 +202,7 @@ public class HotseatEduController { .getInt(LauncherSettings.Settings.EXTRA_VALUE); } for (int i = 0; i < mLauncher.getDeviceProfile().inv.numHotseatIcons; i++) { - View child = hotseatVG.getChildAt(i, 0); + View child = mHotseat.getChildAt(i, 0); if (child == null || child.getTag() == null) continue; ItemInfo tag = (ItemInfo) child.getTag(); mLauncher.getModelWriter().moveItemInDatabase(tag, @@ -211,8 +217,8 @@ public class HotseatEduController { mNotificationManager.cancel(ONBOARDING_NOTIFICATION_ID); } - void finishOnboarding() { - mLauncher.getHotseat().removeAllViewsInLayout(); + void moveHotseatItems() { + mHotseat.removeAllViewsInLayout(); if (!mNewItems.isEmpty()) { int lastPage = mNewItems.get(mNewItems.size() - 1).screenId; ArrayList animated = new ArrayList<>(); @@ -227,11 +233,25 @@ public class HotseatEduController { } mLauncher.bindAppsAdded(mNewScreens, nonAnimated, animated); } + } + + void finishOnboarding() { mOnOnboardingComplete.run(); destroy(); mLauncher.getSharedPrefs().edit().putBoolean(KEY_HOTSEAT_EDU_SEEN, true).apply(); } + void showDimissTip() { + if (mHotseat.getShortcutsAndWidgets().getChildCount() + < mLauncher.getDeviceProfile().inv.numHotseatIcons) { + Snackbar.show(mLauncher, R.string.hotseat_tip_gaps_filled, R.string.hotseat_turn_off, + null, () -> mLauncher.startActivity(new Intent(SETTINGS_ACTION))); + } else { + new ArrowTipView(mLauncher).show( + mLauncher.getString(R.string.hotseat_tip_no_empty_slots), mHotseat.getTop()); + } + } + void setPredictedApps(List predictedApps) { mPredictedApps = predictedApps; if (!mPredictedApps.isEmpty() @@ -275,6 +295,17 @@ public class HotseatEduController { } } + void showEdu() { + // hotseat is already empty and does not require migration. show edu tip + if (mHotseat.getShortcutsAndWidgets().getChildCount() == 0) { + new ArrowTipView(mLauncher).show(mLauncher.getString(R.string.hotseat_auto_enrolled), + mHotseat.getTop()); + finishOnboarding(); + } else { + showDialog(); + } + } + void showDialog() { if (mPredictedApps == null || mPredictedApps.isEmpty()) { return; @@ -291,7 +322,7 @@ public class HotseatEduController { ActivityTracker.SchedulerCallback { @Override public boolean init(QuickstepLauncher activity, boolean alreadyOnHome) { - activity.getHotseatPredictionController().showEduDialog(); + activity.getHotseatPredictionController().showEdu(); return true; } } diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java index bcce168b95..322ec5dd7c 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java @@ -16,7 +16,8 @@ package com.android.launcher3.hybridhotseat; import static com.android.launcher3.logging.LoggerUtils.newLauncherEvent; -import static com.android.launcher3.userevent.nano.LauncherLogProto.ControlType.HYBRID_HOTSEAT_CANCELED; +import static com.android.launcher3.userevent.nano.LauncherLogProto.ControlType + .HYBRID_HOTSEAT_CANCELED; import android.animation.PropertyValuesHolder; import android.content.Context; @@ -27,9 +28,7 @@ import android.view.LayoutInflater; import android.view.View; import android.widget.Button; import android.widget.TextView; -import android.widget.Toast; -import com.android.launcher3.ArrowTipView; import com.android.launcher3.CellLayout; import com.android.launcher3.DeviceProfile; import com.android.launcher3.Insettable; @@ -109,18 +108,16 @@ public class HotseatEduDialog extends AbstractSlideInView implements Insettable private void onAccept(View v) { mHotseatEduController.migrate(); handleClose(true); + + mHotseatEduController.moveHotseatItems(); mHotseatEduController.finishOnboarding(); //TODO: pass actual page index here. // Temporarily we're passing 1 for folder migration and 2 for page migration logUserAction(true, FeatureFlags.HOTSEAT_MIGRATE_TO_FOLDER.get() ? 1 : 2); - int toastStringRes = !FeatureFlags.HOTSEAT_MIGRATE_TO_FOLDER.get() - ? R.string.hotseat_items_migrated : R.string.hotseat_items_migrated_alt; - Toast.makeText(mLauncher, toastStringRes, Toast.LENGTH_LONG).show(); } private void onDismiss(View v) { - int top = mLauncher.getHotseat().getTop(); - new ArrowTipView(mLauncher).show(mLauncher.getString(R.string.hotseat_no_migration), top); + mHotseatEduController.showDimissTip(); mHotseatEduController.finishOnboarding(); logUserAction(false, -1); handleClose(true); diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java index 801408f488..7eb82a9428 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java @@ -147,12 +147,12 @@ public class HotseatPredictionController implements DragController.DragListener, } /** - * Transitions to NORMAL workspace mode and shows edu dialog + * Transitions to NORMAL workspace mode and shows edu */ - public void showEduDialog() { + public void showEdu() { if (mHotseatEduController == null) return; mLauncher.getStateManager().goToState(LauncherState.NORMAL, true, - () -> mHotseatEduController.showDialog()); + () -> mHotseatEduController.showEdu()); } @Override diff --git a/quickstep/res/values/strings.xml b/quickstep/res/values/strings.xml index b55b042dda..31a9bdf47b 100644 --- a/quickstep/res/values/strings.xml +++ b/quickstep/res/values/strings.xml @@ -78,16 +78,21 @@ Easily access your most-used apps right on the Home screen. Suggestions will change based on your routines. Apps on the bottom row will move up to your Home screen. Easily access your most-used apps, right on the Home screen. Suggestions will change based on your routines. Apps on the bottom row will move to a new folder. - - Your hotseat items have been moved up to your homescreen - Your hotseat items have been moved to a folder - - Drag apps off the bottom row to see app suggestions Get app suggestions No thanks + + Settings + + + Most-used apps appear here, and change based on routines + + Drag apps off the bottom row to get app suggestions + + App suggestions added to empty space. + Try the back gesture From 6fb959d8cce1be2a0609a20fece9a6a4679afe03 Mon Sep 17 00:00:00 2001 From: Samuel Fufa Date: Fri, 20 Mar 2020 13:21:01 -0700 Subject: [PATCH 15/46] Highlight personal tab on work profile reinstallation Bug: 145595763 Test: Manual Change-Id: I4da5e03c3aa66b324bc375d328e7b81278d58aca --- .../launcher3/allapps/AllAppsContainerView.java | 13 ++++++------- .../allapps/PersonalWorkSlidingTabStrip.java | 1 + 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java index 10a3060f5e..7fefcbb88b 100644 --- a/src/com/android/launcher3/allapps/AllAppsContainerView.java +++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java @@ -405,6 +405,11 @@ public class AllAppsContainerView extends SpringRelativeLayout implements DragSo setupWorkToggle(); mAH[AdapterHolder.MAIN].setup(mViewPager.getChildAt(0), mPersonalMatcher); mAH[AdapterHolder.WORK].setup(mViewPager.getChildAt(1), mWorkMatcher); + mViewPager.getPageIndicator().setActiveMarker(AdapterHolder.MAIN); + findViewById(R.id.tab_personal) + .setOnClickListener((View view) -> mViewPager.snapToPage(AdapterHolder.MAIN)); + findViewById(R.id.tab_work) + .setOnClickListener((View view) -> mViewPager.snapToPage(AdapterHolder.WORK)); onTabChanged(mViewPager.getNextPage()); } else { mAH[AdapterHolder.MAIN].setup(findViewById(R.id.apps_list_view), null); @@ -455,16 +460,10 @@ public class AllAppsContainerView extends SpringRelativeLayout implements DragSo public void onTabChanged(int pos) { mHeader.setMainActive(pos == 0); - reset(true /* animate */); - mViewPager.getPageIndicator().updateTabTextColor(pos); if (mAH[pos].recyclerView != null) { mAH[pos].recyclerView.bindFastScrollbar(); - - findViewById(R.id.tab_personal) - .setOnClickListener((View view) -> mViewPager.snapToPage(AdapterHolder.MAIN)); - findViewById(R.id.tab_work) - .setOnClickListener((View view) -> mViewPager.snapToPage(AdapterHolder.WORK)); } + reset(true /* animate */); if (mWorkModeSwitch != null) { mWorkModeSwitch.setWorkTabVisible(pos == AdapterHolder.WORK); } diff --git a/src/com/android/launcher3/allapps/PersonalWorkSlidingTabStrip.java b/src/com/android/launcher3/allapps/PersonalWorkSlidingTabStrip.java index 3e40392438..2515c248c1 100644 --- a/src/com/android/launcher3/allapps/PersonalWorkSlidingTabStrip.java +++ b/src/com/android/launcher3/allapps/PersonalWorkSlidingTabStrip.java @@ -135,6 +135,7 @@ public class PersonalWorkSlidingTabStrip extends LinearLayout implements PageInd @Override public void setActiveMarker(int activePage) { updateTabTextColor(activePage); + updateIndicatorPosition(activePage); if (mContainerView != null && mLastActivePage != activePage) { mContainerView.onTabChanged(activePage); } From 41d909ff672c58e325f26d839da727344d51044d Mon Sep 17 00:00:00 2001 From: Samuel Fufa Date: Fri, 20 Mar 2020 16:27:09 -0700 Subject: [PATCH 16/46] Go home when new tab is installed Bug: 150943778 Test: Manual Change-Id: Iccd7f62c4cad856849fc1afb2faac9b9fa2f9f97 --- src/com/android/launcher3/Launcher.java | 8 ++++++++ src/com/android/launcher3/pm/UserCache.java | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index a83a694c9c..b324b36dea 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -115,6 +115,7 @@ import com.android.launcher3.model.BgDataModel.Callbacks; import com.android.launcher3.model.ModelWriter; import com.android.launcher3.notification.NotificationListener; import com.android.launcher3.pm.PinRequestHelper; +import com.android.launcher3.pm.UserCache; import com.android.launcher3.popup.PopupContainerWithArrow; import com.android.launcher3.popup.PopupDataProvider; import com.android.launcher3.popup.SystemShortcut; @@ -139,6 +140,7 @@ import com.android.launcher3.util.MultiValueAlpha.AlphaProperty; import com.android.launcher3.util.PackageManagerHelper; import com.android.launcher3.util.PackageUserKey; import com.android.launcher3.util.PendingRequestArgs; +import com.android.launcher3.util.SafeCloseable; import com.android.launcher3.util.ShortcutUtil; import com.android.launcher3.util.SystemUiController; import com.android.launcher3.util.Themes; @@ -341,6 +343,8 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, private long mLastTouchUpTime = -1; + private SafeCloseable mUserChangedCallbackCloseable; + @Override protected void onCreate(Bundle savedInstanceState) { Object traceToken = TraceHelper.INSTANCE.beginSection(ON_CREATE_EVT, @@ -459,6 +463,9 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, }); TraceHelper.INSTANCE.endSection(traceToken); + + mUserChangedCallbackCloseable = UserCache.INSTANCE.get(this).addUserChangeListener( + () -> getStateManager().goToState(NORMAL)); } protected LauncherOverlayManager getDefaultOverlay() { @@ -1589,6 +1596,7 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, mOverlayManager.onActivityDestroyed(this); mAppTransitionManager.unregisterRemoteAnimations(); + mUserChangedCallbackCloseable.close(); } public LauncherAccessibilityDelegate getAccessibilityDelegate() { diff --git a/src/com/android/launcher3/pm/UserCache.java b/src/com/android/launcher3/pm/UserCache.java index 678b647400..f723256510 100644 --- a/src/com/android/launcher3/pm/UserCache.java +++ b/src/com/android/launcher3/pm/UserCache.java @@ -95,7 +95,7 @@ public class UserCache { private void removeUserChangeListener(Runnable command) { synchronized (this) { - mUserChangeListeners.add(command); + mUserChangeListeners.remove(command); if (mUserChangeListeners.isEmpty()) { // Disable cache and stop listening mContext.unregisterReceiver(mUserChangeReceiver); From 28f3136c6a0f6101be7ca6e9132becabe9a9bbf6 Mon Sep 17 00:00:00 2001 From: Samuel Fufa Date: Tue, 24 Mar 2020 16:46:12 -0700 Subject: [PATCH 17/46] Resume UI update on user active resume UI update for hybrid hotseat if user returns to launcher while app launch transition is animating. Bug: 142753423 Test: Manual Change-Id: I11ffa080bb78e7b4269747b1602b32d706f2405d --- .../com/android/launcher3/uioverrides/QuickstepLauncher.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java index d1a487acf6..a6eea0c0b9 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java @@ -183,8 +183,8 @@ public class QuickstepLauncher extends BaseQuickstepLauncher { onStateOrResumeChanged(); } - if ((changeBits & ACTIVITY_STATE_STARTED) != 0 && mHotseatPredictionController != null - && (getActivityFlags() & ACTIVITY_STATE_USER_ACTIVE) == 0) { + if (mHotseatPredictionController != null && ((changeBits & ACTIVITY_STATE_STARTED) != 0 + || (changeBits & getActivityFlags() & ACTIVITY_STATE_DEFERRED_RESUMED) != 0)) { mHotseatPredictionController.setPauseUIUpdate(false); } } From ca1c32537c94872393ca2d2568c5a251c0246913 Mon Sep 17 00:00:00 2001 From: Samuel Fufa Date: Wed, 25 Mar 2020 13:42:23 -0700 Subject: [PATCH 18/46] Remove Edu notification if predictions are empty Bug: 142753423 Test: Manual Change-Id: I1111fa15b9b677bb077478309a185e70e6784bce --- .../android/launcher3/hybridhotseat/HotseatEduController.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduController.java index a7fb6e14e0..dd3802bd5b 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduController.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduController.java @@ -258,6 +258,9 @@ public class HotseatEduController { && mLauncher.getOrientation() == Configuration.ORIENTATION_PORTRAIT) { mNotificationManager.notify(ONBOARDING_NOTIFICATION_ID, mNotification); } + else { + removeNotification(); + } } private void createNotificationChannel() { From 6b13c841c90289c3ad75dcee32d0741c6b502e9b Mon Sep 17 00:00:00 2001 From: Samuel Fufa Date: Wed, 25 Mar 2020 13:11:38 -0700 Subject: [PATCH 19/46] Beta specific hotseat opt in behavior https://docs.google.com/document/d/1CKK-3vTl-SPGxx3Cwty7r69jk_vaDs9MlanbhpH85cY/edit Bug: 142753423 Test: Manual Change-Id: I51e949fa9962bc8e9d0cdb66d07fbe9e344fb35d --- .../hybridhotseat/HotseatEduController.java | 2 +- .../hybridhotseat/HotseatEduDialog.java | 1 + .../HotseatPredictionController.java | 46 ++++++++++++++++++- quickstep/res/values/strings.xml | 8 ++-- 4 files changed, 50 insertions(+), 7 deletions(-) diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduController.java index a7fb6e14e0..c03d2c4f73 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduController.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduController.java @@ -62,6 +62,7 @@ public class HotseatEduController { "android.settings.ACTION_CONTENT_SUGGESTIONS_SETTINGS"; private final Launcher mLauncher; + private final Hotseat mHotseat; private final NotificationManager mNotificationManager; private final Notification mNotification; private List mPredictedApps; @@ -70,7 +71,6 @@ public class HotseatEduController { private ArrayList mNewItems = new ArrayList<>(); private IntArray mNewScreens = null; private Runnable mOnOnboardingComplete; - private Hotseat mHotseat; HotseatEduController(Launcher launcher, Runnable runnable) { mLauncher = launcher; diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java index 322ec5dd7c..8944088213 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java @@ -163,6 +163,7 @@ public class HotseatEduDialog extends AbstractSlideInView implements Insettable target.rank = MIGRATION_EXPERIMENT_IDENTIFIER; // encoding migration type on pageIndex target.pageIndex = pageIndex; + target.cardinality = HotseatPredictionController.MAX_ITEMS_FOR_MIGRATION; LauncherLogProto.LauncherEvent event = newLauncherEvent(action, target); UserEventDispatcher.newInstance(getContext()).dispatchUserEvent(event, null); } diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java index 7eb82a9428..d3bb4f9062 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java @@ -16,6 +16,9 @@ package com.android.launcher3.hybridhotseat; import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY; +import static com.android.launcher3.logging.LoggerUtils.newAction; +import static com.android.launcher3.logging.LoggerUtils.newContainerTarget; +import static com.android.launcher3.logging.LoggerUtils.newLauncherEvent; import android.animation.Animator; import android.animation.AnimatorSet; @@ -28,6 +31,7 @@ import android.app.prediction.AppTargetEvent; import android.app.prediction.AppTargetId; import android.content.ComponentName; import android.os.Bundle; +import android.provider.DeviceConfig; import android.util.Log; import android.view.View; import android.view.ViewGroup; @@ -59,9 +63,11 @@ import com.android.launcher3.dragndrop.DragController; import com.android.launcher3.dragndrop.DragOptions; import com.android.launcher3.icons.IconCache; import com.android.launcher3.logging.FileLog; +import com.android.launcher3.logging.UserEventDispatcher; import com.android.launcher3.popup.SystemShortcut; import com.android.launcher3.shortcuts.ShortcutKey; import com.android.launcher3.touch.ItemLongClickListener; +import com.android.launcher3.uioverrides.DeviceFlag; import com.android.launcher3.uioverrides.PredictedAppIcon; import com.android.launcher3.uioverrides.QuickstepLauncher; import com.android.launcher3.userevent.nano.LauncherLogProto; @@ -85,6 +91,9 @@ public class HotseatPredictionController implements DragController.DragListener, private static final String TAG = "PredictiveHotseat"; private static final boolean DEBUG = false; + public static final int MAX_ITEMS_FOR_MIGRATION = DeviceConfig.getInt( + DeviceFlag.NAMESPACE_LAUNCHER, "max_homepage_items_for_migration", 5); + //TODO: replace this with AppTargetEvent.ACTION_UNPIN (b/144119543) private static final int APPTARGET_ACTION_UNPIN = 4; @@ -113,8 +122,8 @@ public class HotseatPredictionController implements DragController.DragListener, private HotseatEduController mHotseatEduController; - private List mOutlineDrawings = new ArrayList<>(); + private List mOutlineDrawings = new ArrayList<>(); private final View.OnLongClickListener mPredictionLongClickListener = v -> { if (!ItemLongClickListener.canStartDrag(mLauncher)) return false; @@ -278,7 +287,7 @@ public class HotseatPredictionController implements DragController.DragListener, mAppPredictor.registerPredictionUpdates(mLauncher.getMainExecutor(), this::setPredictedApps); setPauseUIUpdate(false); - + performBetaCheck(); if (!isReady()) { mHotseatEduController = new HotseatEduController(mLauncher, this::createPredictor); } @@ -590,6 +599,39 @@ public class HotseatPredictionController implements DragController.DragListener, } } + private void performBetaCheck() { + if (isReady()) return; + int hotseatItemsCount = mHotseat.getShortcutsAndWidgets().getChildCount(); + + // -1 to exclude smart space + int workspaceItemCount = mLauncher.getWorkspace().getScreenWithId( + Workspace.FIRST_SCREEN_ID).getShortcutsAndWidgets().getChildCount() - 1; + + // opt user into the feature without onboarding tip or migration if they don't have any + // open spots in their hotseat and have more than maxItems in their hotseat + workspace + + if (hotseatItemsCount == mHotSeatItemsCount && workspaceItemCount + hotseatItemsCount + > MAX_ITEMS_FOR_MIGRATION) { + mLauncher.getSharedPrefs().edit().putBoolean(HotseatEduController.KEY_HOTSEAT_EDU_SEEN, + true).apply(); + + LauncherLogProto.Action action = newAction(LauncherLogProto.Action.Type.TOUCH); + LauncherLogProto.Target target = newContainerTarget(LauncherLogProto.ContainerType.TIP); + action.touch = LauncherLogProto.Action.Touch.TAP; + target.tipType = LauncherLogProto.TipType.HYBRID_HOTSEAT; + target.controlType = LauncherLogProto.ControlType.HYBRID_HOTSEAT_CANCELED; + + // temporarily encode details in log target (go/hotseat_migration) + target.rank = 2; + target.cardinality = MAX_ITEMS_FOR_MIGRATION; + target.pageIndex = (workspaceItemCount * 1000) + hotseatItemsCount; + LauncherLogProto.LauncherEvent event = newLauncherEvent(action, target); + UserEventDispatcher.newInstance(mLauncher).dispatchUserEvent(event, null); + + + } + } + /** * Fill in predicted_rank field based on app prediction. * Only applicable when {@link ItemInfo#itemType} is PREDICTED_HOTSEAT diff --git a/quickstep/res/values/strings.xml b/quickstep/res/values/strings.xml index 31a9bdf47b..ab34f47f72 100644 --- a/quickstep/res/values/strings.xml +++ b/quickstep/res/values/strings.xml @@ -83,15 +83,15 @@ No thanks - + Settings Most-used apps appear here, and change based on routines - - Drag apps off the bottom row to get app suggestions - App suggestions added to empty space. + Drag apps off the bottom row to get app suggestions + + App suggestions added to empty space From c0000450b557c5ffd6367fff337c2c3834b0b9cf Mon Sep 17 00:00:00 2001 From: Tracy Zhou Date: Tue, 17 Mar 2020 18:28:38 -0700 Subject: [PATCH 20/46] Support grid preview with v2 migration algorithm The focus of ag/10346770 is around the actual algorithm, while in the meantime our preview logic has changed during the code review of ag/10100264. GridSizeMigrationTaskV2 addresses both cases, the difference being preview passes in constructed IDP while actual migration uses IDP from the current Context. When doing actual migration, we call METHOD_UPDATE_CURRENT_OPEN_HELPER to update the current db helper and copy the favorites table from the previous db into the current db in favorites_tmp table. Then we do migration from there. When calculating preview, I added METHOD_PREP_FOR_PREVIEW in this change to copy the favorites table from the intended grid setting to the current grid setting in favorites_preview table. Then we calculate migration from the current favorites table to favorites_preview table and save into favorites_preview table. Bug: 144052802 Fixes: 144052839 Test: Manual Change-Id: I64a8b61a4e0bf8399c0ae1af4ef9d2bde0f1ee2f --- .../android/launcher3/LauncherProvider.java | 61 ++++++++---- .../android/launcher3/LauncherSettings.java | 8 +- .../graphics/LauncherPreviewRenderer.java | 6 +- .../model/GridSizeMigrationTaskV2.java | 92 +++++++++++++------ .../launcher3/provider/LauncherDbUtils.java | 1 + 5 files changed, 117 insertions(+), 51 deletions(-) diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java index a699c32848..8d20bd64df 100644 --- a/src/com/android/launcher3/LauncherProvider.java +++ b/src/com/android/launcher3/LauncherProvider.java @@ -85,6 +85,7 @@ import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Arrays; import java.util.Locale; +import java.util.function.Supplier; public class LauncherProvider extends ContentProvider { private static final String TAG = "LauncherProvider"; @@ -145,7 +146,7 @@ public class LauncherProvider extends ContentProvider { */ protected synchronized void createDbIfNotExists() { if (mOpenHelper == null) { - mOpenHelper = new DatabaseHelper(getContext()); + mOpenHelper = DatabaseHelper.createDatabaseHelper(getContext()); if (RestoreDbTask.isPending(getContext())) { if (!RestoreDbTask.performRestore(getContext(), mOpenHelper, @@ -159,17 +160,17 @@ public class LauncherProvider extends ContentProvider { } } - private synchronized boolean updateCurrentOpenHelper() { - final InvariantDeviceProfile idp = InvariantDeviceProfile.INSTANCE.get(getContext()); - if (TextUtils.equals(idp.dbFile, mOpenHelper.getDatabaseName())) { + private synchronized boolean prepForMigration(String dbFile, String targetTableName, + Supplier src, Supplier dst) { + if (TextUtils.equals(dbFile, mOpenHelper.getDatabaseName())) { return false; } - DatabaseHelper oldHelper = mOpenHelper; - mOpenHelper = new DatabaseHelper(getContext()); - copyTable(oldHelper.getReadableDatabase(), Favorites.TABLE_NAME, - mOpenHelper.getWritableDatabase(), Favorites.TMP_TABLE, getContext()); - oldHelper.close(); + final DatabaseHelper helper = src.get(); + mOpenHelper = dst.get(); + copyTable(helper.getReadableDatabase(), Favorites.TABLE_NAME, + mOpenHelper.getWritableDatabase(), targetTableName, getContext()); + helper.close(); return true; } @@ -425,7 +426,23 @@ public class LauncherProvider extends ContentProvider { if (MULTI_DB_GRID_MIRATION_ALGO.get()) { Bundle result = new Bundle(); result.putBoolean(LauncherSettings.Settings.EXTRA_VALUE, - updateCurrentOpenHelper()); + prepForMigration( + InvariantDeviceProfile.INSTANCE.get(getContext()).dbFile, + Favorites.TMP_TABLE, + () -> mOpenHelper, + () -> DatabaseHelper.createDatabaseHelper(getContext()))); + return result; + } + } + case LauncherSettings.Settings.METHOD_PREP_FOR_PREVIEW: { + if (MULTI_DB_GRID_MIRATION_ALGO.get()) { + Bundle result = new Bundle(); + result.putBoolean(LauncherSettings.Settings.EXTRA_VALUE, + prepForMigration( + arg /* dbFile */, + Favorites.PREVIEW_TABLE_NAME, + () -> DatabaseHelper.createDatabaseHelper(getContext(), arg), + () -> mOpenHelper)); return result; } } @@ -596,23 +613,31 @@ public class LauncherProvider extends ContentProvider { private int mMaxScreenId = -1; private boolean mBackupTableExists; - DatabaseHelper(Context context) { - this(context, MULTI_DB_GRID_MIRATION_ALGO.get() ? InvariantDeviceProfile.INSTANCE.get( - context).dbFile : LauncherFiles.LAUNCHER_DB); + static DatabaseHelper createDatabaseHelper(Context context) { + return createDatabaseHelper(context, null); + } + + static DatabaseHelper createDatabaseHelper(Context context, String dbName) { + if (dbName == null) { + dbName = MULTI_DB_GRID_MIRATION_ALGO.get() ? InvariantDeviceProfile.INSTANCE.get( + context).dbFile : LauncherFiles.LAUNCHER_DB; + } + DatabaseHelper databaseHelper = new DatabaseHelper(context, dbName); // Table creation sometimes fails silently, which leads to a crash loop. // This way, we will try to create a table every time after crash, so the device // would eventually be able to recover. - if (!tableExists(getReadableDatabase(), Favorites.TABLE_NAME)) { + if (!tableExists(databaseHelper.getReadableDatabase(), Favorites.TABLE_NAME)) { Log.e(TAG, "Tables are missing after onCreate has been called. Trying to recreate"); // This operation is a no-op if the table already exists. - addFavoritesTable(getWritableDatabase(), true); + databaseHelper.addFavoritesTable(databaseHelper.getWritableDatabase(), true); } if (!MULTI_DB_GRID_MIRATION_ALGO.get()) { - mBackupTableExists = tableExists(getReadableDatabase(), - Favorites.BACKUP_TABLE_NAME); + databaseHelper.mBackupTableExists = tableExists( + databaseHelper.getReadableDatabase(), Favorites.BACKUP_TABLE_NAME); } - initIds(); + databaseHelper.initIds(); + return databaseHelper; } /** diff --git a/src/com/android/launcher3/LauncherSettings.java b/src/com/android/launcher3/LauncherSettings.java index f516446ccb..5262b185db 100644 --- a/src/com/android/launcher3/LauncherSettings.java +++ b/src/com/android/launcher3/LauncherSettings.java @@ -326,10 +326,16 @@ public class LauncherSettings { public static final String METHOD_UPDATE_CURRENT_OPEN_HELPER = "update_current_open_helper"; + public static final String METHOD_PREP_FOR_PREVIEW = "prep_for_preview"; + public static final String EXTRA_VALUE = "value"; public static Bundle call(ContentResolver cr, String method) { - return cr.call(CONTENT_URI, method, null, null); + return call(cr, method, null); + } + + public static Bundle call(ContentResolver cr, String method, String arg) { + return cr.call(CONTENT_URI, method, arg, null); } } } diff --git a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java index 5bc66106ab..7d4eb0e999 100644 --- a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java +++ b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java @@ -21,7 +21,6 @@ import static android.view.View.VISIBLE; import static com.android.launcher3.config.FeatureFlags.ENABLE_LAUNCHER_PREVIEW_IN_GRID_PICKER; import static com.android.launcher3.config.FeatureFlags.MULTI_DB_GRID_MIRATION_ALGO; -import static com.android.launcher3.model.GridSizeMigrationTask.needsToMigrate; import static com.android.launcher3.model.ModelUtils.filterCurrentWorkspaceItems; import static com.android.launcher3.model.ModelUtils.sortWorkspaceItemsSpatially; import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; @@ -397,7 +396,10 @@ public class LauncherPreviewRenderer implements Callable { private void populate() { if (ENABLE_LAUNCHER_PREVIEW_IN_GRID_PICKER.get()) { - boolean needsToMigrate = needsToMigrate(mContext, mIdp); + boolean needsToMigrate = + MULTI_DB_GRID_MIRATION_ALGO.get() + ? GridSizeMigrationTaskV2.needsToMigrate(mContext, mIdp) + : GridSizeMigrationTask.needsToMigrate(mContext, mIdp); boolean success = false; if (needsToMigrate) { success = MULTI_DB_GRID_MIRATION_ALGO.get() diff --git a/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java b/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java index 0bdccfa16c..79ae4c5edb 100644 --- a/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java +++ b/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java @@ -123,8 +123,16 @@ public class GridSizeMigrationTaskV2 { } /** - * Run the migration algorithm if needed. For preview, we provide the intended idp because it - * has not been changed. If idp is null, we read it from the context, for actual grid migration. + * When migrating the grid for preview, we copy the table + * {@link LauncherSettings.Favorites.TABLE_NAME} into + * {@link LauncherSettings.Favorites.PREVIEW_TABLE_NAME}, run grid size migration from the + * former to the later, then use the later table for preview. + * + * Similarly when doing the actual grid migration, the former grid option's table + * {@link LauncherSettings.Favorites.TABLE_NAME} is copied into the new grid option's + * {@link LauncherSettings.Favorites.TMP_TABLE}, we then run the grid size migration algorithm + * to migrate the later to the former, and load the workspace from the default + * {@link LauncherSettings.Favorites.TABLE_NAME}. * * @return false if the migration failed. */ @@ -151,7 +159,14 @@ public class GridSizeMigrationTaskV2 { HashSet validPackages = getValidPackages(context); int srcHotseatCount = prefs.getInt(KEY_MIGRATION_SRC_HOTSEAT_COUNT, idp.numHotseatIcons); - if (!LauncherSettings.Settings.call( + if (migrateForPreview) { + if (!LauncherSettings.Settings.call( + context.getContentResolver(), + LauncherSettings.Settings.METHOD_PREP_FOR_PREVIEW, idp.dbFile).getBoolean( + LauncherSettings.Settings.EXTRA_VALUE)) { + return false; + } + } else if (!LauncherSettings.Settings.call( context.getContentResolver(), LauncherSettings.Settings.METHOD_UPDATE_CURRENT_OPEN_HELPER).getBoolean( LauncherSettings.Settings.EXTRA_VALUE)) { @@ -164,9 +179,13 @@ public class GridSizeMigrationTaskV2 { LauncherSettings.Settings.METHOD_NEW_TRANSACTION).getBinder( LauncherSettings.Settings.EXTRA_VALUE)) { - DbReader srcReader = new DbReader(t.getDb(), LauncherSettings.Favorites.TMP_TABLE, + DbReader srcReader = new DbReader(t.getDb(), + migrateForPreview ? LauncherSettings.Favorites.TABLE_NAME + : LauncherSettings.Favorites.TMP_TABLE, context, validPackages, srcHotseatCount); - DbReader destReader = new DbReader(t.getDb(), LauncherSettings.Favorites.TABLE_NAME, + DbReader destReader = new DbReader(t.getDb(), + migrateForPreview ? LauncherSettings.Favorites.PREVIEW_TABLE_NAME + : LauncherSettings.Favorites.TABLE_NAME, context, validPackages, idp.numHotseatIcons); Point targetSize = new Point(idp.numColumns, idp.numRows); @@ -174,7 +193,9 @@ public class GridSizeMigrationTaskV2 { srcReader, destReader, idp.numHotseatIcons, targetSize); task.migrate(); - dropTable(t.getDb(), LauncherSettings.Favorites.TMP_TABLE); + if (!migrateForPreview) { + dropTable(t.getDb(), LauncherSettings.Favorites.TMP_TABLE); + } t.commit(); return true; @@ -186,11 +207,13 @@ public class GridSizeMigrationTaskV2 { Log.v(TAG, "Workspace migration completed in " + (System.currentTimeMillis() - migrationStartTime)); - // Save current configuration, so that the migration does not run again. - prefs.edit() - .putString(KEY_MIGRATION_SRC_WORKSPACE_SIZE, gridSizeString) - .putInt(KEY_MIGRATION_SRC_HOTSEAT_COUNT, idp.numHotseatIcons) - .apply(); + if (!migrateForPreview) { + // Save current configuration, so that the migration does not run again. + prefs.edit() + .putString(KEY_MIGRATION_SRC_WORKSPACE_SIZE, gridSizeString) + .putInt(KEY_MIGRATION_SRC_HOTSEAT_COUNT, idp.numHotseatIcons) + .apply(); + } } } @@ -202,7 +225,7 @@ public class GridSizeMigrationTaskV2 { // Migrate hotseat HotseatPlacementSolution hotseatSolution = new HotseatPlacementSolution(mDb, mSrcReader, - mContext, mDestHotseatSize, mHotseatItems, mHotseatDiff); + mDestReader, mContext, mDestHotseatSize, mHotseatItems, mHotseatDiff); hotseatSolution.find(); // Sort the items by the reading order. @@ -215,7 +238,7 @@ public class GridSizeMigrationTaskV2 { } List entries = mDestReader.loadWorkspaceEntries(screenId); GridPlacementSolution workspaceSolution = new GridPlacementSolution(mDb, mSrcReader, - mContext, entries, screenId, mTrgX, mTrgY, mWorkspaceDiff); + mDestReader, mContext, entries, screenId, mTrgX, mTrgY, mWorkspaceDiff); workspaceSolution.find(); if (mWorkspaceDiff.isEmpty()) { break; @@ -225,7 +248,8 @@ public class GridSizeMigrationTaskV2 { int screenId = mDestReader.mLastScreenId + 1; while (!mWorkspaceDiff.isEmpty()) { GridPlacementSolution workspaceSolution = new GridPlacementSolution(mDb, mSrcReader, - mContext, new ArrayList<>(), screenId, mTrgX, mTrgY, mWorkspaceDiff); + mDestReader, mContext, new ArrayList<>(), screenId, mTrgX, mTrgY, + mWorkspaceDiff); workspaceSolution.find(); screenId++; } @@ -246,7 +270,8 @@ public class GridSizeMigrationTaskV2 { } private static void insertEntryInDb(SQLiteDatabase db, Context context, - ArrayList entriesFromSrcDb, DbEntry entry) { + ArrayList entriesFromSrcDb, DbEntry entry, String srcTableName, + String destTableName) { int id = -1; switch (entry.itemType) { case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: @@ -283,8 +308,8 @@ public class GridSizeMigrationTaskV2 { return; } - Cursor c = db.query(LauncherSettings.Favorites.TMP_TABLE, null, - LauncherSettings.Favorites._ID + " = '" + id + "'", null, null, null, null); + Cursor c = db.query(srcTableName, null, LauncherSettings.Favorites._ID + " = '" + id + "'", + null, null, null, null); while (c.moveToNext()) { ContentValues values = new ContentValues(); @@ -294,14 +319,14 @@ public class GridSizeMigrationTaskV2 { LauncherSettings.Settings.call(context.getContentResolver(), LauncherSettings.Settings.METHOD_NEW_ITEM_ID).getInt( LauncherSettings.Settings.EXTRA_VALUE)); - db.insert(LauncherSettings.Favorites.TABLE_NAME, null, values); + db.insert(destTableName, null, values); } c.close(); } - private static void removeEntryFromDb(SQLiteDatabase db, IntArray entryId) { - db.delete(LauncherSettings.Favorites.TABLE_NAME, Utilities.createDbSelectionQuery( - LauncherSettings.Favorites._ID, entryId), null); + private static void removeEntryFromDb(SQLiteDatabase db, String tableName, IntArray entryId) { + db.delete(tableName, + Utilities.createDbSelectionQuery(LauncherSettings.Favorites._ID, entryId), null); } private static HashSet getValidPackages(Context context) { @@ -325,6 +350,7 @@ public class GridSizeMigrationTaskV2 { private final SQLiteDatabase mDb; private final DbReader mSrcReader; + private final DbReader mDestReader; private final Context mContext; private final GridOccupancy mOccupied; private final int mScreenId; @@ -335,11 +361,12 @@ public class GridSizeMigrationTaskV2 { private int mNextStartX; private int mNextStartY; - GridPlacementSolution(SQLiteDatabase db, DbReader srcReader, Context context, - List placedWorkspaceItems, int screenId, int trgX, + GridPlacementSolution(SQLiteDatabase db, DbReader srcReader, DbReader destReader, + Context context, List placedWorkspaceItems, int screenId, int trgX, int trgY, List itemsToPlace) { mDb = db; mSrcReader = srcReader; + mDestReader = destReader; mContext = context; mOccupied = new GridOccupancy(trgX, trgY); mScreenId = screenId; @@ -362,7 +389,8 @@ public class GridSizeMigrationTaskV2 { continue; } if (findPlacement(entry)) { - insertEntryInDb(mDb, mContext, mSrcReader.mWorkspaceEntries, entry); + insertEntryInDb(mDb, mContext, mSrcReader.mWorkspaceEntries, entry, + mSrcReader.mTableName, mDestReader.mTableName); iterator.remove(); } } @@ -397,14 +425,17 @@ public class GridSizeMigrationTaskV2 { private final SQLiteDatabase mDb; private final DbReader mSrcReader; + private final DbReader mDestReader; private final Context mContext; private final HotseatOccupancy mOccupied; private final List mItemsToPlace; - HotseatPlacementSolution(SQLiteDatabase db, DbReader srcReader, Context context, - int hotseatSize, List placedHotseatItems, List itemsToPlace) { + HotseatPlacementSolution(SQLiteDatabase db, DbReader srcReader, DbReader destReader, + Context context, int hotseatSize, List placedHotseatItems, + List itemsToPlace) { mDb = db; mSrcReader = srcReader; + mDestReader = destReader; mContext = context; mOccupied = new HotseatOccupancy(hotseatSize); for (DbEntry entry : placedHotseatItems) { @@ -422,7 +453,8 @@ public class GridSizeMigrationTaskV2 { // to something other than -1. entry.cellX = i; entry.cellY = 0; - insertEntryInDb(mDb, mContext, mSrcReader.mHotseatEntries, entry); + insertEntryInDb(mDb, mContext, mSrcReader.mHotseatEntries, entry, + mSrcReader.mTableName, mDestReader.mTableName); mOccupied.markCells(entry, true); } } @@ -519,7 +551,7 @@ public class GridSizeMigrationTaskV2 { } mHotseatEntries.add(entry); } - removeEntryFromDb(mDb, entriesToRemove); + removeEntryFromDb(mDb, mTableName, entriesToRemove); c.close(); return mHotseatEntries; } @@ -639,7 +671,7 @@ public class GridSizeMigrationTaskV2 { } mWorkspaceEntries.add(entry); } - removeEntryFromDb(mDb, entriesToRemove); + removeEntryFromDb(mDb, mTableName, entriesToRemove); c.close(); return mWorkspaceEntries; } @@ -657,7 +689,7 @@ public class GridSizeMigrationTaskV2 { total++; entry.mFolderItems.add(intent); } catch (Exception e) { - removeEntryFromDb(mDb, IntArray.wrap(c.getInt(0))); + removeEntryFromDb(mDb, mTableName, IntArray.wrap(c.getInt(0))); } } c.close(); diff --git a/src/com/android/launcher3/provider/LauncherDbUtils.java b/src/com/android/launcher3/provider/LauncherDbUtils.java index dacea84613..7e05a5a78d 100644 --- a/src/com/android/launcher3/provider/LauncherDbUtils.java +++ b/src/com/android/launcher3/provider/LauncherDbUtils.java @@ -129,6 +129,7 @@ public class LauncherDbUtils { toDb.execSQL("ATTACH DATABASE '" + fromDb.getPath() + "' AS from_db"); toDb.execSQL( "INSERT INTO " + toTable + " SELECT * FROM from_db." + fromTable); + toDb.execSQL("DETACH DATABASE 'from_db'"); } else { toDb.execSQL("INSERT INTO " + toTable + " SELECT * FROM " + fromTable); } From c94a5bfbce6a03b16424afbbf9910d8864ca8857 Mon Sep 17 00:00:00 2001 From: Tony Wickham Date: Wed, 25 Mar 2020 19:58:51 -0700 Subject: [PATCH 21/46] Remove max displacement threshold in 2-zone model We had a max displacement for detecting pause before because people were falsing into overview when they dragged all apps all the way up. In this mode, there's no way to get to all apps from the nav bar so the threshold doesn't make sense. Bug: 151039912 Change-Id: I349f38ec1589f8b151cfbe32542159b3eb92bf61 --- .../touchcontrollers/FlingAndHoldTouchController.java | 6 +++++- .../NoButtonNavbarToOverviewTouchController.java | 7 +++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java index 6fc03b1bf1..8af2747352 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java @@ -73,7 +73,11 @@ public class FlingAndHoldTouchController extends PortraitStatesTouchController { super(l, false /* allowDragToOverview */); mMotionPauseDetector = new MotionPauseDetector(l); mMotionPauseMinDisplacement = ViewConfiguration.get(l).getScaledTouchSlop(); - mMotionPauseMaxDisplacement = getShiftRange() * MAX_DISPLACEMENT_PERCENT; + mMotionPauseMaxDisplacement = getMotionPauseMaxDisplacement(); + } + + protected float getMotionPauseMaxDisplacement() { + return getShiftRange() * MAX_DISPLACEMENT_PERCENT; } @Override diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java index 064133cfd9..71aa2e8d99 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java @@ -65,6 +65,13 @@ public class NoButtonNavbarToOverviewTouchController extends FlingAndHoldTouchCo mRecentsView = l.getOverviewPanel(); } + @Override + protected float getMotionPauseMaxDisplacement() { + // No need to disallow pause when swiping up all the way up the screen (unlike + // FlingAndHoldTouchController where user is probably intending to go to all apps). + return Float.MAX_VALUE; + } + @Override protected boolean canInterceptTouch(MotionEvent ev) { mDidTouchStartInNavBar = (ev.getEdgeFlags() & EDGE_NAV_BAR) != 0; From cafe918ff3c08de412341c77263c1a543320081a Mon Sep 17 00:00:00 2001 From: Jon Miranda Date: Wed, 25 Mar 2020 11:46:11 -0700 Subject: [PATCH 22/46] Remove accessibility long click option if no long click action occurs. Bug: https://b.corp.google.com/issues/149370403 Change-Id: I265b60ed0f8f816c842d167520657e0bff442a04 --- .../LauncherAccessibilityDelegate.java | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java index 6f7f8e6a29..24c846cf83 100644 --- a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java +++ b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java @@ -146,11 +146,21 @@ public class LauncherAccessibilityDelegate extends AccessibilityDelegate impleme } } + if (!fromKeyboard && !itemSupportsLongClick(host, item)) { + info.setLongClickable(false); + info.removeAction(AccessibilityAction.ACTION_LONG_CLICK); + } + if ((item instanceof AppInfo) || (item instanceof PendingAddItemInfo)) { info.addAction(mActions.get(ADD_TO_WORKSPACE)); } } + private boolean itemSupportsLongClick(View host, ItemInfo info) { + return new CustomActionsPopup(mLauncher, host).canShow() + || ShortcutUtil.supportsShortcuts(info); + } + private boolean itemSupportsAccessibleDrag(ItemInfo item) { if (item instanceof WorkspaceItemInfo) { // Support the action unless the item is in a context menu. @@ -171,18 +181,18 @@ public class LauncherAccessibilityDelegate extends AccessibilityDelegate impleme public boolean performAction(final View host, final ItemInfo item, int action) { if (action == ACTION_LONG_CLICK) { - if (ShortcutUtil.isDeepShortcut(item)) { - CustomActionsPopup popup = new CustomActionsPopup(mLauncher, host); - if (popup.canShow()) { - popup.show(); - return true; - } - } else if (host instanceof BubbleTextView) { + if (ShortcutUtil.supportsShortcuts(item)) { // Long press should be consumed for workspace items, and it should invoke the // Shortcuts / Notifications / Actions pop-up menu, and not start a drag as the // standard long press path does. PopupContainerWithArrow.showForIcon((BubbleTextView) host); return true; + } else { + CustomActionsPopup popup = new CustomActionsPopup(mLauncher, host); + if (popup.canShow()) { + popup.show(); + return true; + } } } From 3b99a6c7d60470ca2694321bbaddc9a91c64f374 Mon Sep 17 00:00:00 2001 From: Hongwei Wang Date: Wed, 25 Mar 2020 22:07:56 +0000 Subject: [PATCH 23/46] Revert "Default fixed_rotation to off" This reverts commit c0b289c93eee2ecf126acf561d6e72fabc7b354f. Reason for revert: fixed in ag/10826395 Bug: 151727009 Change-Id: I0c71d5d47a6825c2445bf8dc9d8a9a1ca23f9e64 (cherry picked from commit 08c4d9923782acee8c450bb02d034d004b04cfc6) --- src/com/android/launcher3/config/FeatureFlags.java | 2 +- src/com/android/launcher3/states/RotationHelper.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java index 1cc8a98fd4..92f511222d 100644 --- a/src/com/android/launcher3/config/FeatureFlags.java +++ b/src/com/android/launcher3/config/FeatureFlags.java @@ -163,7 +163,7 @@ public final class FeatureFlags { "Always use hardware optimization for folder animations."); public static final BooleanFlag ENABLE_FIXED_ROTATION_TRANSFORM = getDebugFlag( - FLAG_ENABLE_FIXED_ROTATION_TRANSFORM, false, + FLAG_ENABLE_FIXED_ROTATION_TRANSFORM, true, "Launch/close apps without rotation animation. Fix Launcher to portrait"); public static void initialize(Context context) { diff --git a/src/com/android/launcher3/states/RotationHelper.java b/src/com/android/launcher3/states/RotationHelper.java index 8c8aa9b8e5..fae0fe2d81 100644 --- a/src/com/android/launcher3/states/RotationHelper.java +++ b/src/com/android/launcher3/states/RotationHelper.java @@ -134,7 +134,7 @@ public class RotationHelper implements OnSharedPreferenceChangeListener { */ private void updateForcedRotation(boolean setValueFromPrefs) { boolean isForcedRotation = mFeatureFlagsPrefs - .getBoolean(FLAG_ENABLE_FIXED_ROTATION_TRANSFORM, false) + .getBoolean(FLAG_ENABLE_FIXED_ROTATION_TRANSFORM, true) && !getAllowRotationDefaultValue(); if (mForcedRotation == isForcedRotation) { return; From 0928456d55324bee19fe53ef7a01e7f46b0996db Mon Sep 17 00:00:00 2001 From: Samuel Fufa Date: Thu, 26 Mar 2020 14:09:58 -0700 Subject: [PATCH 24/46] Fix NPE on FeatureFlag.addChangeListener Bug: 152400212 Change-Id: I64890370f1d638048b0340bcb537ffdbf7011681 --- .../src/com/android/launcher3/uioverrides/DeviceFlag.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/quickstep/src/com/android/launcher3/uioverrides/DeviceFlag.java b/quickstep/src/com/android/launcher3/uioverrides/DeviceFlag.java index 5836ebdb58..010694b0c5 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/DeviceFlag.java +++ b/quickstep/src/com/android/launcher3/uioverrides/DeviceFlag.java @@ -54,6 +54,9 @@ public class DeviceFlag extends DebugFlag { @Override public void addChangeListener(Context context, Runnable r) { + if (mListeners == null) { + initialize(context); + } mListeners.add(r); } From aa8a77b8c3f370d57ff5f19954c6450e26c874f9 Mon Sep 17 00:00:00 2001 From: Tony Wickham Date: Thu, 26 Mar 2020 13:42:32 -0700 Subject: [PATCH 25/46] Fix flicker near end of quick switch Unlike isStarted(), mHasLauncherTransitionControllerStarted is true even after the animation has ended. Once it's ended, we shouldn't continue updating it even if window shift is still changing. This can happen when springs are enabled, as that can increase the recents scroll duration beyond the window/launcher animation duration (and we updateFinalShift() when the scroll changes). Test: quick switch by swiping up and over at angle, or really hard directly to the right, to engage the springs long enough to have a few frames where you can notice the flicker before this change. Bug: 147302669 Change-Id: I6a38662612de91352c0f956e6a3137f6c24eba66 --- .../quickstep/LauncherSwipeHandler.java | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java index 5bac84403e..a3366e29a2 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java @@ -505,16 +505,22 @@ public class LauncherSwipeHandler } private void buildAnimationController() { - if (mGestureState.getEndTarget() == HOME || mHasLauncherTransitionControllerStarted) { - // We don't want a new mLauncherTransitionController if - // mGestureState.getEndTarget() == HOME (it has its own animation) or if we're already - // animating the current controller. + if (!canCreateNewOrUpdateExistingLauncherTransitionController()) { return; } initTransitionEndpoints(mActivity.getDeviceProfile()); mAnimationFactory.createActivityInterface(mTransitionDragLength); } + /** + * We don't want to change mLauncherTransitionController if mGestureState.getEndTarget() == HOME + * (it has its own animation) or if we're already animating the current controller. + * @return Whether we can create the launcher controller or update its progress. + */ + private boolean canCreateNewOrUpdateExistingLauncherTransitionController() { + return mGestureState.getEndTarget() != HOME && !mHasLauncherTransitionControllerStarted; + } + @Override public WindowInsets onApplyWindowInsets(View view, WindowInsets windowInsets) { WindowInsets result = view.onApplyWindowInsets(windowInsets); @@ -558,15 +564,12 @@ public class LauncherSwipeHandler } } - if (mLauncherTransitionController == null || mLauncherTransitionController - .getAnimationPlayer().isStarted()) { - return; - } updateLauncherTransitionProgress(); } private void updateLauncherTransitionProgress() { - if (mGestureState.getEndTarget() == HOME) { + if (mLauncherTransitionController == null + || !canCreateNewOrUpdateExistingLauncherTransitionController()) { return; } // Normalize the progress to 0 to 1, as the animation controller will clamp it to that From 0dd1bd1113b12b5055b237294844ef1f56a41f00 Mon Sep 17 00:00:00 2001 From: vadimt Date: Fri, 14 Feb 2020 14:42:35 -0800 Subject: [PATCH 26/46] TAPL: Verifying activity start/stop events It also turned out that Pilfer event seems to come in a non-deterministic order relative to the events from the Main and TIS sequences. So I moved it to its own sequence. Change-Id: Ie4ea5865afd900bebbd8287dad2372c94dce8ad5 --- .../inputconsumers/DelegateInputConsumer.java | 2 +- .../DeviceLockedInputConsumer.java | 2 +- .../OtherActivityInputConsumer.java | 2 +- .../inputconsumers/OverviewInputConsumer.java | 2 +- .../OverviewWithoutFocusInputConsumer.java | 2 +- .../quickstep/AppPredictionsUITests.java | 1 - src/com/android/launcher3/Launcher.java | 3 ++ .../launcher3/testing/TestProtocol.java | 1 + .../launcher3/ui/TaplTestsLauncher3.java | 4 +-- .../ui/widget/AddConfigWidgetTest.java | 2 +- .../launcher3/ui/widget/AddWidgetTest.java | 4 +-- .../android/launcher3/tapl/Background.java | 31 +++++++++++++++---- .../android/launcher3/tapl/Launchable.java | 10 ++++-- .../tapl/LauncherInstrumentation.java | 19 ++++++++++-- .../launcher3/tapl/OptionsPopupMenuItem.java | 10 ++++++ .../android/launcher3/tapl/OverviewTask.java | 2 ++ .../com/android/launcher3/tapl/Workspace.java | 9 ++++-- 17 files changed, 83 insertions(+), 23 deletions(-) diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java index a87e7ebf6d..71465eb201 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java @@ -41,7 +41,7 @@ public abstract class DelegateInputConsumer implements InputConsumer { protected void setActive(MotionEvent ev) { mState = STATE_ACTIVE; - TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "pilferPointers"); + TestLogging.recordEvent(TestProtocol.SEQUENCE_PILFER, "pilferPointers"); mInputMonitor.pilferPointers(); // Send cancel event diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java index ba1d38cf6a..7b8d40c911 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java @@ -204,7 +204,7 @@ public class DeviceLockedInputConsumer implements InputConsumer, private void startRecentsTransition() { mThresholdCrossed = true; - TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "pilferPointers"); + TestLogging.recordEvent(TestProtocol.SEQUENCE_PILFER, "pilferPointers"); mInputMonitorCompat.pilferPointers(); Intent intent = new Intent(Intent.ACTION_MAIN) diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java index 416d7a1bca..a462949738 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java @@ -314,7 +314,7 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC if (mInteractionHandler == null) { return; } - TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "pilferPointers"); + TestLogging.recordEvent(TestProtocol.SEQUENCE_PILFER, "pilferPointers"); mInputMonitorCompat.pilferPointers(); mActivityInterface.closeOverlay(); diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java index f161cc0fd6..6bfabcdd84 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java @@ -108,7 +108,7 @@ public class OverviewInputConsumer ActiveGestureLog.INSTANCE.addLog("startQuickstep"); } if (mInputMonitor != null) { - TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "pilferPointers"); + TestLogging.recordEvent(TestProtocol.SEQUENCE_PILFER, "pilferPointers"); mInputMonitor.pilferPointers(); } } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java index 823b254d80..ac1c3a8742 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java @@ -65,7 +65,7 @@ public class OverviewWithoutFocusInputConsumer implements InputConsumer { private void onInterceptTouch() { if (mInputMonitor != null) { - TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "pilferPointers"); + TestLogging.recordEvent(TestProtocol.SEQUENCE_PILFER, "pilferPointers"); mInputMonitor.pilferPointers(); } } diff --git a/quickstep/tests/src/com/android/quickstep/AppPredictionsUITests.java b/quickstep/tests/src/com/android/quickstep/AppPredictionsUITests.java index 8e4762da7f..5904fcd691 100644 --- a/quickstep/tests/src/com/android/quickstep/AppPredictionsUITests.java +++ b/quickstep/tests/src/com/android/quickstep/AppPredictionsUITests.java @@ -88,7 +88,6 @@ public class AppPredictionsUITests extends AbstractQuickStepTest { */ @Test public void testPredictionExistsInAllApps() { - mDevice.pressHome(); mLauncher.pressHome().switchToAllApps(); // Dispatch an update diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index 566a04cfa3..b9e1aebc36 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -956,6 +956,8 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, } }); } + + TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "Activity.onStop"); } @Override @@ -969,6 +971,7 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, mAppWidgetHost.setListenIfResumed(true); TraceHelper.INSTANCE.endSection(traceToken); + TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "Activity.onStart"); } private void handleDeferredResume() { diff --git a/src/com/android/launcher3/testing/TestProtocol.java b/src/com/android/launcher3/testing/TestProtocol.java index dd97b10298..e70b570a5e 100644 --- a/src/com/android/launcher3/testing/TestProtocol.java +++ b/src/com/android/launcher3/testing/TestProtocol.java @@ -35,6 +35,7 @@ public final class TestProtocol { public static final String TAPL_EVENTS_TAG = "TaplEvents"; public static final String SEQUENCE_MAIN = "Main"; public static final String SEQUENCE_TIS = "TIS"; + public static final String SEQUENCE_PILFER = "Pilfer"; public static String stateOrdinalToString(int ordinal) { switch (ordinal) { diff --git a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java index f8bbf21e0b..de1ada4d6a 100644 --- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java +++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java @@ -314,7 +314,7 @@ public class TaplTestsLauncher3 extends AbstractLauncherUiTest { switchToAllApps(); allApps.freeze(); try { - allApps.getAppIcon(APP_NAME).dragToWorkspace(); + allApps.getAppIcon(APP_NAME).dragToWorkspace(false); mLauncher.getWorkspace().getWorkspaceAppIcon(APP_NAME).launch(getAppPackageName()); } finally { allApps.unfreeze(); @@ -342,7 +342,7 @@ public class TaplTestsLauncher3 extends AbstractLauncherUiTest { getMenuItem(0); final String shortcutName = menuItem.getText(); - menuItem.dragToWorkspace(); + menuItem.dragToWorkspace(false); mLauncher.getWorkspace().getWorkspaceAppIcon(shortcutName).launch(getAppPackageName()); } finally { allApps.unfreeze(); diff --git a/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java index de9757fa58..d93915c33b 100644 --- a/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java +++ b/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java @@ -94,7 +94,7 @@ public class AddConfigWidgetTest extends AbstractLauncherUiTest { WidgetConfigStartupMonitor monitor = new WidgetConfigStartupMonitor(); widgets. getWidget(mWidgetInfo.getLabel(mTargetContext.getPackageManager())). - dragToWorkspace(); + dragToWorkspace(true); // Widget id for which the config activity was opened mWidgetId = monitor.getWidgetId(); diff --git a/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java index f9d1d93b3b..788e0410b8 100644 --- a/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java +++ b/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java @@ -57,7 +57,7 @@ public class AddWidgetTest extends AbstractLauncherUiTest { getWorkspace(). openAllWidgets(). getWidget(widgetInfo.getLabel(mTargetContext.getPackageManager())). - dragToWorkspace(); + dragToWorkspace(false); assertTrue(mActivityMonitor.itemExists( (info, view) -> info instanceof LauncherAppWidgetInfo && @@ -83,7 +83,7 @@ public class AddWidgetTest extends AbstractLauncherUiTest { mDevice.pressHome(); mLauncher.getWorkspace().openAllWidgets() .getWidget("com.android.launcher3.testcomponent.CustomShortcutConfigActivity") - .dragToWorkspace(); + .dragToWorkspace(false); mLauncher.getWorkspace().getWorkspaceAppIcon("Shortcut") .launch(getAppPackageName()); } diff --git a/tests/tapl/com/android/launcher3/tapl/Background.java b/tests/tapl/com/android/launcher3/tapl/Background.java index 2acab97454..80b8e89601 100644 --- a/tests/tapl/com/android/launcher3/tapl/Background.java +++ b/tests/tapl/com/android/launcher3/tapl/Background.java @@ -25,7 +25,6 @@ import android.os.SystemClock; import android.view.MotionEvent; import androidx.annotation.NonNull; -import androidx.test.uiautomator.By; import androidx.test.uiautomator.UiObject2; import com.android.launcher3.testing.TestProtocol; @@ -72,6 +71,7 @@ public class Background extends LauncherInstrumentation.VisibleContainer { } protected void goToOverviewUnchecked() { + final boolean launcherWasVisible = mLauncher.isLauncherVisible(); switch (mLauncher.getNavigationModel()) { case ZERO_BUTTON: { final int centerX = mLauncher.getDevice().getDisplayWidth() / 2; @@ -137,6 +137,15 @@ public class Background extends LauncherInstrumentation.VisibleContainer { OVERVIEW_STATE_ORDINAL); break; } + expectSwitchToOverviewEvents(); + + if (!launcherWasVisible) { + mLauncher.expectEvent( + TestProtocol.SEQUENCE_MAIN, LauncherInstrumentation.EVENT_START_ACTIVITY); + } + } + + private void expectSwitchToOverviewEvents() { } /** @@ -157,6 +166,7 @@ public class Background extends LauncherInstrumentation.VisibleContainer { } protected void quickSwitchToPreviousApp(int expectedState) { + final boolean launcherWasVisible = mLauncher.isLauncherVisible(); boolean transposeInLandscape = false; switch (mLauncher.getNavigationModel()) { case TWO_BUTTON: @@ -180,15 +190,17 @@ public class Background extends LauncherInstrumentation.VisibleContainer { endX = startX; endY = 0; } - final boolean launcherIsVisible = - mLauncher.hasLauncherObject(By.textStartsWith("")); final boolean isZeroButton = mLauncher.getNavigationModel() == LauncherInstrumentation.NavigationModel.ZERO_BUTTON; + if (!launcherWasVisible) { + mLauncher.expectEvent( + TestProtocol.SEQUENCE_MAIN, + LauncherInstrumentation.EVENT_START_ACTIVITY); + } mLauncher.swipeToState(startX, startY, endX, endY, 20, expectedState, - launcherIsVisible && isZeroButton + launcherWasVisible && isZeroButton ? LauncherInstrumentation.GestureScope.INSIDE_TO_OUTSIDE - : LauncherInstrumentation.GestureScope.OUTSIDE - ); + : LauncherInstrumentation.GestureScope.OUTSIDE); break; } @@ -196,6 +208,11 @@ public class Background extends LauncherInstrumentation.VisibleContainer { // Double press the recents button. UiObject2 recentsButton = mLauncher.waitForSystemUiObject("recent_apps"); mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, SQUARE_BUTTON_EVENT); + if (!launcherWasVisible) { + mLauncher.expectEvent( + TestProtocol.SEQUENCE_MAIN, + LauncherInstrumentation.EVENT_START_ACTIVITY); + } mLauncher.runToState(() -> recentsButton.click(), OVERVIEW_STATE_ORDINAL); mLauncher.getOverview(); mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, SQUARE_BUTTON_EVENT); @@ -203,6 +220,8 @@ public class Background extends LauncherInstrumentation.VisibleContainer { break; } mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, TASK_START_EVENT); + mLauncher.expectEvent( + TestProtocol.SEQUENCE_MAIN, LauncherInstrumentation.EVENT_STOP_ACTIVITY); } protected String getSwipeHeightRequestName() { diff --git a/tests/tapl/com/android/launcher3/tapl/Launchable.java b/tests/tapl/com/android/launcher3/tapl/Launchable.java index b20384ef6b..d1a1254eed 100644 --- a/tests/tapl/com/android/launcher3/tapl/Launchable.java +++ b/tests/tapl/com/android/launcher3/tapl/Launchable.java @@ -25,6 +25,8 @@ import androidx.test.uiautomator.BySelector; import androidx.test.uiautomator.UiObject2; import androidx.test.uiautomator.Until; +import com.android.launcher3.testing.TestProtocol; + /** * Ancestor for AppIcon and AppMenuItem. */ @@ -62,6 +64,8 @@ abstract class Launchable { event -> event.getEventType() == TYPE_WINDOW_STATE_CHANGED, () -> "Launching an app didn't open a new window: " + mObject.getText()); expectActivityStartEvents(); + mLauncher.expectEvent( + TestProtocol.SEQUENCE_MAIN, LauncherInstrumentation.EVENT_STOP_ACTIVITY); mLauncher.assertTrue( "App didn't start: " + selector, @@ -72,8 +76,9 @@ abstract class Launchable { /** * Drags an object to the center of homescreen. + * @param startsActivity whether it's expected to start an activity. */ - public void dragToWorkspace() { + public void dragToWorkspace(boolean startsActivity) { try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) { final Point launchableCenter = getObject().getVisibleCenter(); final Point displaySize = mLauncher.getRealDisplaySize(); @@ -86,7 +91,8 @@ abstract class Launchable { ? launchableCenter.x - width / 2 : launchableCenter.x + width / 2, displaySize.y / 2), - getLongPressIndicator()); + getLongPressIndicator(), + startsActivity); } } diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java index d894843261..421edb0e6f 100644 --- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java +++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java @@ -97,6 +97,8 @@ public final class LauncherInstrumentation { private static final Pattern EVENT_TOUCH_UP = getTouchEventPattern("ACTION_UP"); private static final Pattern EVENT_TOUCH_CANCEL = getTouchEventPattern("ACTION_CANCEL"); private static final Pattern EVENT_PILFER_POINTERS = Pattern.compile("pilferPointers"); + static final Pattern EVENT_START_ACTIVITY = Pattern.compile("Activity\\.onStart"); + static final Pattern EVENT_STOP_ACTIVITY = Pattern.compile("Activity\\.onStop"); static final Pattern EVENT_TOUCH_DOWN_TIS = getTouchEventPatternTIS("ACTION_DOWN"); static final Pattern EVENT_TOUCH_UP_TIS = getTouchEventPatternTIS("ACTION_UP"); @@ -637,6 +639,7 @@ public final class LauncherInstrumentation { // otherwise waitForIdle may return immediately in case when there was a big enough // pause in accessibility events prior to pressing Home. final String action; + final boolean launcherWasVisible = isLauncherVisible(); if (getNavigationModel() == NavigationModel.ZERO_BUTTON) { checkForAnomaly(); @@ -665,12 +668,18 @@ public final class LauncherInstrumentation { displaySize.x / 2, displaySize.y - 1, displaySize.x / 2, 0, ZERO_BUTTON_STEPS_FROM_BACKGROUND_TO_HOME, NORMAL_STATE_ORDINAL, - hasLauncherObject(By.textStartsWith("")) + launcherWasVisible ? GestureScope.INSIDE_TO_OUTSIDE : GestureScope.OUTSIDE); } + if (!launcherWasVisible) { + expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_START_ACTIVITY); + } } } else { + if (!launcherWasVisible) { + expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_START_ACTIVITY); + } log("Hierarchy before clicking home:"); dumpViewHierarchy(); log(action = "clicking home button from " + getVisibleStateMessage()); @@ -681,6 +690,7 @@ public final class LauncherInstrumentation { expectEvent(TestProtocol.SEQUENCE_TIS, EVENT_TOUCH_DOWN_TIS); expectEvent(TestProtocol.SEQUENCE_TIS, EVENT_TOUCH_UP_TIS); } + runToState( waitForSystemUiObject("home")::click, NORMAL_STATE_ORDINAL, @@ -697,6 +707,11 @@ public final class LauncherInstrumentation { } } + boolean isLauncherVisible() { + mDevice.waitForIdle(); + return hasLauncherObject(By.textStartsWith("")); + } + /** * Gets the Workspace object if the current state is "active home", i.e. workspace. Fails if the * launcher is not in that state. @@ -1116,7 +1131,7 @@ public final class LauncherInstrumentation { break; case MotionEvent.ACTION_UP: if (notLauncher3 && gestureScope != GestureScope.INSIDE) { - expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_PILFER_POINTERS); + expectEvent(TestProtocol.SEQUENCE_PILFER, EVENT_PILFER_POINTERS); } if (gestureScope != GestureScope.OUTSIDE) { expectEvent(TestProtocol.SEQUENCE_MAIN, gestureScope == GestureScope.INSIDE diff --git a/tests/tapl/com/android/launcher3/tapl/OptionsPopupMenuItem.java b/tests/tapl/com/android/launcher3/tapl/OptionsPopupMenuItem.java index c2f701b560..b8e6c0edb3 100644 --- a/tests/tapl/com/android/launcher3/tapl/OptionsPopupMenuItem.java +++ b/tests/tapl/com/android/launcher3/tapl/OptionsPopupMenuItem.java @@ -15,11 +15,15 @@ */ package com.android.launcher3.tapl; +import android.os.Build; + import androidx.annotation.NonNull; import androidx.test.uiautomator.By; import androidx.test.uiautomator.UiObject2; import androidx.test.uiautomator.Until; +import com.android.launcher3.testing.TestProtocol; + public class OptionsPopupMenuItem { private final LauncherInstrumentation mLauncher; @@ -39,6 +43,12 @@ public class OptionsPopupMenuItem { LauncherInstrumentation.log("OptionsPopupMenuItem before click " + mObject.getVisibleCenter() + " in " + mObject.getVisibleBounds()); mLauncher.clickLauncherObject(mObject); + if (!Build.MODEL.contains("Cuttlefish") || + Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q && + !"R".equals(Build.VERSION.CODENAME)) { + mLauncher.expectEvent( + TestProtocol.SEQUENCE_MAIN, LauncherInstrumentation.EVENT_STOP_ACTIVITY); + } mLauncher.assertTrue( "App didn't start: " + By.pkg(expectedPackageName), mLauncher.getDevice().wait(Until.hasObject(By.pkg(expectedPackageName)), diff --git a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java index f955cf23c9..5c51782826 100644 --- a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java +++ b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java @@ -79,6 +79,8 @@ public final class OverviewTask { () -> "Launching task didn't open a new window: " + mTask.getParent().getContentDescription()); mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, TASK_START_EVENT); + mLauncher.expectEvent( + TestProtocol.SEQUENCE_MAIN, LauncherInstrumentation.EVENT_STOP_ACTIVITY); } return new Background(mLauncher); } diff --git a/tests/tapl/com/android/launcher3/tapl/Workspace.java b/tests/tapl/com/android/launcher3/tapl/Workspace.java index 3f5dc8d246..36c9849e61 100644 --- a/tests/tapl/com/android/launcher3/tapl/Workspace.java +++ b/tests/tapl/com/android/launcher3/tapl/Workspace.java @@ -177,7 +177,8 @@ public final class Workspace extends Home { getHotseatAppIcon("Chrome"), new Point(mLauncher.getDevice().getDisplayWidth(), workspace.getVisibleBounds().centerY()), - "deep_shortcuts_container"); + "deep_shortcuts_container", + false); verifyActiveContainer(); } } @@ -198,7 +199,7 @@ public final class Workspace extends Home { static void dragIconToWorkspace( LauncherInstrumentation launcher, Launchable launchable, Point dest, - String longPressIndicator) { + String longPressIndicator, boolean startsActivity) { LauncherInstrumentation.log("dragIconToWorkspace: begin"); final Point launchableCenter = launchable.getObject().getVisibleCenter(); final long downTime = SystemClock.uptimeMillis(); @@ -219,6 +220,10 @@ public final class Workspace extends Home { downTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, dest, LauncherInstrumentation.GestureScope.INSIDE), NORMAL_STATE_ORDINAL); + if (startsActivity) { + launcher.expectEvent( + TestProtocol.SEQUENCE_MAIN, LauncherInstrumentation.EVENT_STOP_ACTIVITY); + } LauncherInstrumentation.log("dragIconToWorkspace: end"); launcher.waitUntilGone("drop_target_bar"); } From 17feee8995d5a874d2d497770eddf33bf4adffdd Mon Sep 17 00:00:00 2001 From: Sunny Goyal Date: Tue, 24 Mar 2020 13:55:15 -0700 Subject: [PATCH 27/46] Merging stylus click logic in longpress helper for better state-management Bug: 150825081 Change-Id: I7c507c41e67c09bff5a4ad3abc7a7a62fecf910e --- src/com/android/launcher3/BubbleTextView.java | 50 ++------ .../launcher3/CheckLongPressHelper.java | 119 +++++++++++++----- .../SimpleOnStylusPressListener.java | 25 ---- .../android/launcher3/StylusEventHelper.java | 109 ---------------- .../android/launcher3/folder/FolderIcon.java | 35 +----- .../widget/LauncherAppWidgetHostView.java | 71 ++--------- .../android/launcher3/widget/WidgetCell.java | 24 ++-- 7 files changed, 123 insertions(+), 310 deletions(-) delete mode 100644 src/com/android/launcher3/SimpleOnStylusPressListener.java delete mode 100644 src/com/android/launcher3/StylusEventHelper.java diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java index 21a8fd406e..c8e73ba8d4 100644 --- a/src/com/android/launcher3/BubbleTextView.java +++ b/src/com/android/launcher3/BubbleTextView.java @@ -39,7 +39,6 @@ import android.util.TypedValue; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; -import android.view.ViewConfiguration; import android.view.ViewDebug; import android.widget.TextView; @@ -109,8 +108,6 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, private final int mDisplay; private final CheckLongPressHelper mLongPressHelper; - private final StylusEventHelper mStylusEventHelper; - private final float mSlop; private final boolean mLayoutHorizontal; private final int mIconSize; @@ -137,9 +134,6 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, @ViewDebug.ExportedProperty(category = "launcher") private boolean mDisableRelayout = false; - @ViewDebug.ExportedProperty(category = "launcher") - private final boolean mIgnorePaddingTouch; - private IconLoadRequest mIconLoadRequest; public BubbleTextView(Context context) { @@ -153,7 +147,6 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, public BubbleTextView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); mActivity = ActivityContext.lookupContext(context); - mSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.BubbleTextView, defStyle, 0); @@ -166,23 +159,19 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.iconTextSizePx); setCompoundDrawablePadding(grid.iconDrawablePaddingPx); defaultIconSize = grid.iconSizePx; - mIgnorePaddingTouch = true; } else if (mDisplay == DISPLAY_ALL_APPS) { DeviceProfile grid = mActivity.getDeviceProfile(); setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.allAppsIconTextSizePx); setCompoundDrawablePadding(grid.allAppsIconDrawablePaddingPx); defaultIconSize = grid.allAppsIconSizePx; - mIgnorePaddingTouch = true; } else if (mDisplay == DISPLAY_FOLDER) { DeviceProfile grid = mActivity.getDeviceProfile(); setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.folderChildTextSizePx); setCompoundDrawablePadding(grid.folderChildDrawablePaddingPx); defaultIconSize = grid.folderChildIconSizePx; - mIgnorePaddingTouch = true; } else { // widget_selection or shortcut_popup defaultIconSize = mActivity.getDeviceProfile().iconSizePx; - mIgnorePaddingTouch = false; } mCenterVertically = a.getBoolean(R.styleable.BubbleTextView_centerVertically, false); @@ -192,7 +181,6 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, a.recycle(); mLongPressHelper = new CheckLongPressHelper(this); - mStylusEventHelper = new StylusEventHelper(new SimpleOnStylusPressListener(this), this); mDotParams = new DotRenderer.DrawParams(); @@ -333,42 +321,21 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, @Override public boolean onTouchEvent(MotionEvent event) { // ignore events if they happen in padding area - if (event.getAction() == MotionEvent.ACTION_DOWN && mIgnorePaddingTouch + if (event.getAction() == MotionEvent.ACTION_DOWN && (event.getY() < getPaddingTop() || event.getX() < getPaddingLeft() || event.getY() > getHeight() - getPaddingBottom() || event.getX() > getWidth() - getPaddingRight())) { return false; } - - // Call the superclass onTouchEvent first, because sometimes it changes the state to - // isPressed() on an ACTION_UP - boolean result = super.onTouchEvent(event); - - // Check for a stylus button press, if it occurs cancel any long press checks. - if (mStylusEventHelper.onMotionEvent(event)) { - mLongPressHelper.cancelLongPress(); - result = true; + if (isLongClickable()) { + super.onTouchEvent(event); + mLongPressHelper.onTouchEvent(event); + // Keep receiving the rest of the events + return true; + } else { + return super.onTouchEvent(event); } - - switch (event.getAction()) { - case MotionEvent.ACTION_DOWN: - // If we're in a stylus button press, don't check for long press. - if (!mStylusEventHelper.inStylusButtonPressed()) { - mLongPressHelper.postCheckForLongPress(); - } - break; - case MotionEvent.ACTION_CANCEL: - case MotionEvent.ACTION_UP: - mLongPressHelper.cancelLongPress(); - break; - case MotionEvent.ACTION_MOVE: - if (!Utilities.pointInView(this, event.getX(), event.getY(), mSlop)) { - mLongPressHelper.cancelLongPress(); - } - break; - } - return result; } void setStayPressed(boolean stayPressed) { @@ -531,7 +498,6 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, @Override public void cancelLongPress() { super.cancelLongPress(); - mLongPressHelper.cancelLongPress(); } diff --git a/src/com/android/launcher3/CheckLongPressHelper.java b/src/com/android/launcher3/CheckLongPressHelper.java index 639c173dcc..ef353f9404 100644 --- a/src/com/android/launcher3/CheckLongPressHelper.java +++ b/src/com/android/launcher3/CheckLongPressHelper.java @@ -16,46 +16,68 @@ package com.android.launcher3; +import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; -import com.android.launcher3.util.Thunk; - +/** + * Utility class to handle tripper long press on a view with custom timeout and stylus event + */ public class CheckLongPressHelper { public static final float DEFAULT_LONG_PRESS_TIMEOUT_FACTOR = 0.75f; - @Thunk View mView; - @Thunk View.OnLongClickListener mListener; - @Thunk boolean mHasPerformedLongPress; - private float mLongPressTimeoutFactor = DEFAULT_LONG_PRESS_TIMEOUT_FACTOR; - private CheckForLongPress mPendingCheckForLongPress; + private final View mView; + private final View.OnLongClickListener mListener; + private final float mSlop; - class CheckForLongPress implements Runnable { - public void run() { - if ((mView.getParent() != null) && mView.hasWindowFocus() - && !mHasPerformedLongPress) { - boolean handled; - if (mListener != null) { - handled = mListener.onLongClick(mView); - } else { - handled = mView.performLongClick(); - } - if (handled) { - mView.setPressed(false); - mHasPerformedLongPress = true; - } - } - } - } + private float mLongPressTimeoutFactor = DEFAULT_LONG_PRESS_TIMEOUT_FACTOR; + + private boolean mHasPerformedLongPress; + + private Runnable mPendingCheckForLongPress; public CheckLongPressHelper(View v) { - mView = v; + this(v, null); } public CheckLongPressHelper(View v, View.OnLongClickListener listener) { mView = v; mListener = listener; + mSlop = ViewConfiguration.get(mView.getContext()).getScaledTouchSlop(); + } + + /** + * Handles the touch event on a view + * + * @see View#onTouchEvent(MotionEvent) + */ + public void onTouchEvent(MotionEvent ev) { + switch (ev.getAction()) { + case MotionEvent.ACTION_DOWN: { + // Just in case the previous long press hasn't been cleared, we make sure to + // start fresh on touch down. + cancelLongPress(); + + postCheckForLongPress(); + if (isStylusButtonPressed(ev)) { + triggerLongPress(); + } + break; + } + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_UP: + cancelLongPress(); + break; + case MotionEvent.ACTION_MOVE: + if (!Utilities.pointInView(mView, ev.getX(), ev.getY(), mSlop)) { + cancelLongPress(); + } else if (mPendingCheckForLongPress != null && isStylusButtonPressed(ev)) { + // Only trigger long press if it has not been cancelled before + triggerLongPress(); + } + break; + } } /** @@ -65,25 +87,64 @@ public class CheckLongPressHelper { mLongPressTimeoutFactor = longPressTimeoutFactor; } - public void postCheckForLongPress() { + private void postCheckForLongPress() { mHasPerformedLongPress = false; if (mPendingCheckForLongPress == null) { - mPendingCheckForLongPress = new CheckForLongPress(); + mPendingCheckForLongPress = this::triggerLongPress; } mView.postDelayed(mPendingCheckForLongPress, (long) (ViewConfiguration.getLongPressTimeout() * mLongPressTimeoutFactor)); } + /** + * Cancels any pending long press + */ public void cancelLongPress() { mHasPerformedLongPress = false; + clearCallbacks(); + } + + /** + * Returns true if long press has been performed in the current touch gesture + */ + public boolean hasPerformedLongPress() { + return mHasPerformedLongPress; + } + + private void triggerLongPress() { + if ((mView.getParent() != null) && mView.hasWindowFocus() && !mHasPerformedLongPress) { + boolean handled; + if (mListener != null) { + handled = mListener.onLongClick(mView); + } else { + handled = mView.performLongClick(); + } + if (handled) { + mView.setPressed(false); + mHasPerformedLongPress = true; + } + clearCallbacks(); + } + } + + private void clearCallbacks() { if (mPendingCheckForLongPress != null) { mView.removeCallbacks(mPendingCheckForLongPress); mPendingCheckForLongPress = null; } } - public boolean hasPerformedLongPress() { - return mHasPerformedLongPress; + + /** + * Identifies if the provided {@link MotionEvent} is a stylus with the primary stylus button + * pressed. + * + * @param event The event to check. + * @return Whether a stylus button press occurred. + */ + private static boolean isStylusButtonPressed(MotionEvent event) { + return event.getToolType(0) == MotionEvent.TOOL_TYPE_STYLUS + && event.isButtonPressed(MotionEvent.BUTTON_SECONDARY); } } diff --git a/src/com/android/launcher3/SimpleOnStylusPressListener.java b/src/com/android/launcher3/SimpleOnStylusPressListener.java deleted file mode 100644 index 6b97dcee69..0000000000 --- a/src/com/android/launcher3/SimpleOnStylusPressListener.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.android.launcher3; - -import android.view.MotionEvent; -import android.view.View; - -import com.android.launcher3.StylusEventHelper.StylusButtonListener; - -/** - * Simple listener that performs a long click on the view after a stylus button press. - */ -public class SimpleOnStylusPressListener implements StylusButtonListener { - private View mView; - - public SimpleOnStylusPressListener(View view) { - mView = view; - } - - public boolean onPressed(MotionEvent event) { - return mView.isLongClickable() && mView.performLongClick(); - } - - public boolean onReleased(MotionEvent event) { - return false; - } -} \ No newline at end of file diff --git a/src/com/android/launcher3/StylusEventHelper.java b/src/com/android/launcher3/StylusEventHelper.java deleted file mode 100644 index d5fc0fad47..0000000000 --- a/src/com/android/launcher3/StylusEventHelper.java +++ /dev/null @@ -1,109 +0,0 @@ -package com.android.launcher3; - -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewConfiguration; - -/** - * Helper for identifying when a stylus touches a view while the primary stylus button is pressed. - * This can occur in {@value MotionEvent#ACTION_DOWN} or {@value MotionEvent#ACTION_MOVE}. - */ -public class StylusEventHelper { - - /** - * Implement this interface to receive callbacks for a stylus button press and release. - */ - public interface StylusButtonListener { - /** - * Called when the stylus button is pressed. - * - * @param event The MotionEvent that the button press occurred for. - * @return Whether the event was handled. - */ - public boolean onPressed(MotionEvent event); - - /** - * Called when the stylus button is released after a button press. This is also called if - * the event is canceled or the stylus is lifted off the screen. - * - * @param event The MotionEvent the button release occurred for. - * @return Whether the event was handled. - */ - public boolean onReleased(MotionEvent event); - } - - private boolean mIsButtonPressed; - private View mView; - private StylusButtonListener mListener; - private final float mSlop; - - /** - * Constructs a helper for listening to stylus button presses and releases. Ensure that { - * {@link #onMotionEvent(MotionEvent)} and {@link #onGenericMotionEvent(MotionEvent)} are called on - * the helper to correctly identify stylus events. - * - * @param listener The listener to call for stylus events. - * @param view Optional view associated with the touch events. - */ - public StylusEventHelper(StylusButtonListener listener, View view) { - mListener = listener; - mView = view; - if (mView != null) { - mSlop = ViewConfiguration.get(mView.getContext()).getScaledTouchSlop(); - } else { - mSlop = ViewConfiguration.getTouchSlop(); - } - } - - public boolean onMotionEvent(MotionEvent event) { - final boolean stylusButtonPressed = isStylusButtonPressed(event); - switch (event.getAction()) { - case MotionEvent.ACTION_DOWN: - mIsButtonPressed = stylusButtonPressed; - if (mIsButtonPressed) { - return mListener.onPressed(event); - } - break; - case MotionEvent.ACTION_MOVE: - if (!Utilities.pointInView(mView, event.getX(), event.getY(), mSlop)) { - return false; - } - if (!mIsButtonPressed && stylusButtonPressed) { - mIsButtonPressed = true; - return mListener.onPressed(event); - } else if (mIsButtonPressed && !stylusButtonPressed) { - mIsButtonPressed = false; - return mListener.onReleased(event); - } - break; - case MotionEvent.ACTION_UP: - case MotionEvent.ACTION_CANCEL: - if (mIsButtonPressed) { - mIsButtonPressed = false; - return mListener.onReleased(event); - } - break; - } - return false; - } - - /** - * Whether a stylus button press is occurring. - */ - public boolean inStylusButtonPressed() { - return mIsButtonPressed; - } - - /** - * Identifies if the provided {@link MotionEvent} is a stylus with the primary stylus button - * pressed. - * - * @param event The event to check. - * @return Whether a stylus button press occurred. - */ - private static boolean isStylusButtonPressed(MotionEvent event) { - return event.getToolType(0) == MotionEvent.TOOL_TYPE_STYLUS - && ((event.getButtonState() & MotionEvent.BUTTON_SECONDARY) - == MotionEvent.BUTTON_SECONDARY); - } -} \ No newline at end of file diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java index f0d18ae63c..8251d68b70 100644 --- a/src/com/android/launcher3/folder/FolderIcon.java +++ b/src/com/android/launcher3/folder/FolderIcon.java @@ -31,7 +31,6 @@ import android.util.Property; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; -import android.view.ViewConfiguration; import android.view.ViewDebug; import android.view.ViewGroup; import android.widget.FrameLayout; @@ -52,8 +51,6 @@ import com.android.launcher3.Launcher; import com.android.launcher3.LauncherSettings; import com.android.launcher3.OnAlarmListener; import com.android.launcher3.R; -import com.android.launcher3.SimpleOnStylusPressListener; -import com.android.launcher3.StylusEventHelper; import com.android.launcher3.Utilities; import com.android.launcher3.Workspace; import com.android.launcher3.WorkspaceItemInfo; @@ -87,7 +84,6 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel private FolderInfo mInfo; private CheckLongPressHelper mLongPressHelper; - private StylusEventHelper mStylusEventHelper; static final int DROP_IN_ANIMATION_DURATION = 400; @@ -110,8 +106,6 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel boolean mAnimating = false; - private float mSlop; - private Alarm mOpenAlarm = new Alarm(); private boolean mForceHideDot; @@ -149,9 +143,7 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel private void init() { mLongPressHelper = new CheckLongPressHelper(this); - mStylusEventHelper = new StylusEventHelper(new SimpleOnStylusPressListener(this), this); mPreviewLayoutRule = new ClippedFolderIconLayoutRule(); - mSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); mPreviewItemManager = new PreviewItemManager(this); mDotParams = new DotRenderer.DrawParams(); } @@ -663,29 +655,10 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel public boolean onTouchEvent(MotionEvent event) { // Call the superclass onTouchEvent first, because sometimes it changes the state to // isPressed() on an ACTION_UP - boolean result = super.onTouchEvent(event); - - // Check for a stylus button press, if it occurs cancel any long press checks. - if (mStylusEventHelper.onMotionEvent(event)) { - mLongPressHelper.cancelLongPress(); - return true; - } - - switch (event.getAction()) { - case MotionEvent.ACTION_DOWN: - mLongPressHelper.postCheckForLongPress(); - break; - case MotionEvent.ACTION_CANCEL: - case MotionEvent.ACTION_UP: - mLongPressHelper.cancelLongPress(); - break; - case MotionEvent.ACTION_MOVE: - if (!Utilities.pointInView(this, event.getX(), event.getY(), mSlop)) { - mLongPressHelper.cancelLongPress(); - } - break; - } - return result; + super.onTouchEvent(event); + mLongPressHelper.onTouchEvent(event); + // Keep receiving the rest of the events + return true; } @Override diff --git a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java index c1310e38fb..78acc344d9 100644 --- a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java +++ b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java @@ -27,7 +27,6 @@ import android.util.SparseBooleanArray; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; -import android.view.ViewConfiguration; import android.view.ViewDebug; import android.view.ViewGroup; import android.view.accessibility.AccessibilityNodeInfo; @@ -41,8 +40,6 @@ import com.android.launcher3.Launcher; import com.android.launcher3.LauncherAppWidgetInfo; import com.android.launcher3.LauncherAppWidgetProviderInfo; import com.android.launcher3.R; -import com.android.launcher3.SimpleOnStylusPressListener; -import com.android.launcher3.StylusEventHelper; import com.android.launcher3.Utilities; import com.android.launcher3.dragndrop.DragLayer; import com.android.launcher3.dragndrop.DraggableView; @@ -66,14 +63,11 @@ public class LauncherAppWidgetHostView extends NavigableAppWidgetHostView protected final LayoutInflater mInflater; private final CheckLongPressHelper mLongPressHelper; - private final StylusEventHelper mStylusEventHelper; protected final Launcher mLauncher; @ViewDebug.ExportedProperty(category = "launcher") private boolean mReinflateOnConfigChange; - private float mSlop; - private boolean mIsScrollable; private boolean mIsAttachedToWindow; private boolean mIsAutoAdvanceRegistered; @@ -93,7 +87,6 @@ public class LauncherAppWidgetHostView extends NavigableAppWidgetHostView super(context); mLauncher = Launcher.getLauncher(context); mLongPressHelper = new CheckLongPressHelper(this, this); - mStylusEventHelper = new StylusEventHelper(new SimpleOnStylusPressListener(this), this); mInflater = LayoutInflater.from(context); setAccessibilityDelegate(mLauncher.getAccessibilityDelegate()); setBackgroundResource(R.drawable.widget_internal_focus_bg); @@ -157,68 +150,19 @@ public class LauncherAppWidgetHostView extends NavigableAppWidgetHostView } public boolean onInterceptTouchEvent(MotionEvent ev) { - // Just in case the previous long press hasn't been cleared, we make sure to start fresh - // on touch down. if (ev.getAction() == MotionEvent.ACTION_DOWN) { - mLongPressHelper.cancelLongPress(); - } - - // Consume any touch events for ourselves after longpress is triggered - if (mLongPressHelper.hasPerformedLongPress()) { - mLongPressHelper.cancelLongPress(); - return true; - } - - // Watch for longpress or stylus button press events at this level to - // make sure users can always pick up this widget - if (mStylusEventHelper.onMotionEvent(ev)) { - mLongPressHelper.cancelLongPress(); - return true; - } - - switch (ev.getAction()) { - case MotionEvent.ACTION_DOWN: { - DragLayer dragLayer = Launcher.getLauncher(getContext()).getDragLayer(); - - if (mIsScrollable) { - dragLayer.requestDisallowInterceptTouchEvent(true); - } - if (!mStylusEventHelper.inStylusButtonPressed()) { - mLongPressHelper.postCheckForLongPress(); - } - dragLayer.setTouchCompleteListener(this); - break; + DragLayer dragLayer = Launcher.getLauncher(getContext()).getDragLayer(); + if (mIsScrollable) { + dragLayer.requestDisallowInterceptTouchEvent(true); } - - case MotionEvent.ACTION_UP: - case MotionEvent.ACTION_CANCEL: - mLongPressHelper.cancelLongPress(); - break; - case MotionEvent.ACTION_MOVE: - if (!Utilities.pointInView(this, ev.getX(), ev.getY(), mSlop)) { - mLongPressHelper.cancelLongPress(); - } - break; + dragLayer.setTouchCompleteListener(this); } - - // Otherwise continue letting touch events fall through to children - return false; + mLongPressHelper.onTouchEvent(ev); + return mLongPressHelper.hasPerformedLongPress(); } public boolean onTouchEvent(MotionEvent ev) { - // If the widget does not handle touch, then cancel - // long press when we release the touch - switch (ev.getAction()) { - case MotionEvent.ACTION_UP: - case MotionEvent.ACTION_CANCEL: - mLongPressHelper.cancelLongPress(); - break; - case MotionEvent.ACTION_MOVE: - if (!Utilities.pointInView(this, ev.getX(), ev.getY(), mSlop)) { - mLongPressHelper.cancelLongPress(); - } - break; - } + mLongPressHelper.onTouchEvent(ev); // We want to keep receiving though events to be able to cancel long press on ACTION_UP return true; } @@ -226,7 +170,6 @@ public class LauncherAppWidgetHostView extends NavigableAppWidgetHostView @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); - mSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); mIsAttachedToWindow = true; checkIfAutoAdvance(); diff --git a/src/com/android/launcher3/widget/WidgetCell.java b/src/com/android/launcher3/widget/WidgetCell.java index f055adf102..4a0b4ef832 100644 --- a/src/com/android/launcher3/widget/WidgetCell.java +++ b/src/com/android/launcher3/widget/WidgetCell.java @@ -31,10 +31,9 @@ import android.widget.LinearLayout; import android.widget.TextView; import com.android.launcher3.BaseActivity; +import com.android.launcher3.CheckLongPressHelper; import com.android.launcher3.DeviceProfile; import com.android.launcher3.R; -import com.android.launcher3.SimpleOnStylusPressListener; -import com.android.launcher3.StylusEventHelper; import com.android.launcher3.WidgetPreviewLoader; import com.android.launcher3.icons.BaseIconFactory; import com.android.launcher3.model.WidgetItem; @@ -71,7 +70,6 @@ public class WidgetCell extends LinearLayout implements OnLayoutChangeListener { protected WidgetItem mItem; private WidgetPreviewLoader mWidgetPreviewLoader; - private StylusEventHelper mStylusEventHelper; protected CancellationSignal mActiveRequest; private boolean mAnimatePreview = true; @@ -80,7 +78,8 @@ public class WidgetCell extends LinearLayout implements OnLayoutChangeListener { private Bitmap mDeferredBitmap; protected final BaseActivity mActivity; - protected DeviceProfile mDeviceProfile; + protected final DeviceProfile mDeviceProfile; + private final CheckLongPressHelper mLongPressHelper; public WidgetCell(Context context) { this(context, null); @@ -95,8 +94,9 @@ public class WidgetCell extends LinearLayout implements OnLayoutChangeListener { mActivity = BaseActivity.fromContext(context); mDeviceProfile = mActivity.getDeviceProfile(); - mStylusEventHelper = new StylusEventHelper(new SimpleOnStylusPressListener(this), this); + mLongPressHelper = new CheckLongPressHelper(this); + mLongPressHelper.setLongPressTimeoutFactor(1); setContainerWidth(); setWillNotDraw(false); setClipToPadding(false); @@ -210,11 +210,15 @@ public class WidgetCell extends LinearLayout implements OnLayoutChangeListener { @Override public boolean onTouchEvent(MotionEvent ev) { - boolean handled = super.onTouchEvent(ev); - if (mStylusEventHelper.onMotionEvent(ev)) { - return true; - } - return handled; + super.onTouchEvent(ev); + mLongPressHelper.onTouchEvent(ev); + return true; + } + + @Override + public void cancelLongPress() { + super.cancelLongPress(); + mLongPressHelper.cancelLongPress(); } /** From 75add7cef573d2240fcfd7a60021d5955153550d Mon Sep 17 00:00:00 2001 From: vadimt Date: Fri, 27 Mar 2020 15:08:36 -0700 Subject: [PATCH 28/46] Debug tracing for "widget not present" flake Bug: 152645831 Change-Id: I1e318650e45ab8e2a60eccef2f4f3c247b2a6b5b --- .../launcher3/ui/widget/BindWidgetTest.java | 5 ++++- .../tapl/LauncherInstrumentation.java | 2 +- .../com/android/launcher3/tapl/Workspace.java | 20 ++++++++++++------- 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java index 793af48e29..a3c70ecbe9 100644 --- a/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java +++ b/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java @@ -45,6 +45,7 @@ import com.android.launcher3.LauncherAppWidgetProviderInfo; import com.android.launcher3.LauncherSettings; import com.android.launcher3.R; import com.android.launcher3.pm.InstallSessionHelper; +import com.android.launcher3.tapl.Widget; import com.android.launcher3.tapl.Workspace; import com.android.launcher3.ui.AbstractLauncherUiTest; import com.android.launcher3.ui.TestViewHelpers; @@ -267,8 +268,10 @@ public class BindWidgetTest extends AbstractLauncherUiTest { } private void verifyWidgetPresent(LauncherAppWidgetProviderInfo info) { + final Widget widget = mLauncher.getWorkspace().tryGetWidget(info.label, DEFAULT_UI_TIMEOUT); + if (widget == null) mLauncher.dumpViewHierarchy(); // b/152645831 assertTrue("Widget is not present", - mLauncher.getWorkspace().tryGetWidget(info.label, DEFAULT_UI_TIMEOUT) != null); + widget != null); } private void verifyPendingWidgetPresent() { diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java index d894843261..4648c2ecea 100644 --- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java +++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java @@ -316,7 +316,7 @@ public final class LauncherInstrumentation { }; } - private void dumpViewHierarchy() { + public void dumpViewHierarchy() { final ByteArrayOutputStream stream = new ByteArrayOutputStream(); try { mDevice.dumpWindowHierarchy(stream); diff --git a/tests/tapl/com/android/launcher3/tapl/Workspace.java b/tests/tapl/com/android/launcher3/tapl/Workspace.java index 3f5dc8d246..e730a8017b 100644 --- a/tests/tapl/com/android/launcher3/tapl/Workspace.java +++ b/tests/tapl/com/android/launcher3/tapl/Workspace.java @@ -281,16 +281,22 @@ public final class Workspace extends Home { @Nullable public Widget tryGetWidget(String label, long timeout) { - final UiObject2 widget = mLauncher.tryWaitForLauncherObject( - By.clazz("com.android.launcher3.widget.LauncherAppWidgetHostView").desc(label), - timeout); - return widget != null ? new Widget(mLauncher, widget) : null; + try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer( + "getting widget " + label + " on workspace with timeout " + timeout)) { + final UiObject2 widget = mLauncher.tryWaitForLauncherObject( + By.clazz("com.android.launcher3.widget.LauncherAppWidgetHostView").desc(label), + timeout); + return widget != null ? new Widget(mLauncher, widget) : null; + } } @Nullable public Widget tryGetPendingWidget(long timeout) { - final UiObject2 widget = mLauncher.tryWaitForLauncherObject( - By.clazz("com.android.launcher3.widget.PendingAppWidgetHostView"), timeout); - return widget != null ? new Widget(mLauncher, widget) : null; + try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer( + "getting pending widget on workspace with timeout " + timeout)) { + final UiObject2 widget = mLauncher.tryWaitForLauncherObject( + By.clazz("com.android.launcher3.widget.PendingAppWidgetHostView"), timeout); + return widget != null ? new Widget(mLauncher, widget) : null; + } } } \ No newline at end of file From 1963d3f3eded20d9c52a6032f479067c7a0e52bb Mon Sep 17 00:00:00 2001 From: Sreyas Date: Mon, 9 Mar 2020 18:10:08 -0700 Subject: [PATCH 29/46] OverviewActions: Adding action buttons to Oem quickstep. Change-Id: Id5c0d8f1b41107535c1bac982b47f67eb2574c21 --- quickstep/AndroidManifest.xml | 11 ++ .../android/quickstep/ImageActionsApi.java | 94 ++++++++++ .../android/quickstep/TaskOverlayFactory.java | 96 ++++++++-- .../quickstep/views/OverviewActionsView.java | 79 +++++++++ quickstep/res/drawable/ic_screenshot.xml | 23 +++ quickstep/res/drawable/ic_share.xml | 23 +++ quickstep/res/layout/overview_actions.xml | 41 +++++ .../res/layout/overview_actions_container.xml | 23 +++ quickstep/res/values/dimens.xml | 2 +- quickstep/res/values/strings.xml | 6 + quickstep/res/values/styles.xml | 9 + .../res/xml/overview_file_provider_paths.xml | 5 + .../quickstep/util/ImageActionUtils.java | 164 ++++++++++++++++++ 13 files changed, 556 insertions(+), 20 deletions(-) create mode 100644 quickstep/recents_ui_overrides/src/com/android/quickstep/ImageActionsApi.java create mode 100644 quickstep/recents_ui_overrides/src/com/android/quickstep/views/OverviewActionsView.java create mode 100644 quickstep/res/drawable/ic_screenshot.xml create mode 100644 quickstep/res/drawable/ic_share.xml create mode 100644 quickstep/res/layout/overview_actions.xml create mode 100644 quickstep/res/layout/overview_actions_container.xml create mode 100644 quickstep/res/xml/overview_file_provider_paths.xml create mode 100644 quickstep/src/com/android/quickstep/util/ImageActionUtils.java diff --git a/quickstep/AndroidManifest.xml b/quickstep/AndroidManifest.xml index 1d0b0455c8..04506b5647 100644 --- a/quickstep/AndroidManifest.xml +++ b/quickstep/AndroidManifest.xml @@ -73,6 +73,17 @@ + + + + + diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/ImageActionsApi.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/ImageActionsApi.java new file mode 100644 index 0000000000..33fe5a9e0a --- /dev/null +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/ImageActionsApi.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.quickstep; + +import static android.content.Intent.EXTRA_STREAM; + +import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; +import static com.android.quickstep.util.ImageActionUtils.persistBitmapAndStartActivity; + +import android.content.Context; +import android.content.Intent; +import android.graphics.Bitmap; +import android.graphics.Insets; +import android.graphics.Rect; +import android.util.Log; + +import androidx.annotation.Nullable; +import androidx.annotation.UiThread; + +import com.android.launcher3.BuildConfig; +import com.android.quickstep.util.ImageActionUtils; + +import java.util.function.Supplier; + +/** + * Contains image selection functions necessary to complete overview action button functions. + */ +public class ImageActionsApi { + + private static final String TAG = BuildConfig.APPLICATION_ID + "ImageActionsApi"; + private final Context mContext; + private final Supplier mBitmapSupplier; + private final SystemUiProxy mSystemUiProxy; + + public ImageActionsApi(Context context, Supplier bitmapSupplier) { + mContext = context; + mBitmapSupplier = bitmapSupplier; + mSystemUiProxy = SystemUiProxy.INSTANCE.get(context); + } + + /** + * Share the image this api was constructed with using the provided intent. The implementation + * should add an {@link Intent#EXTRA_STREAM} with the URI pointing to the image to the intent. + */ + @UiThread + public void shareWithExplicitIntent(@Nullable Rect crop, Intent intent) { + if (mBitmapSupplier.get() == null) { + Log.e(TAG, "No snapshot available, not starting share."); + return; + } + + UI_HELPER_EXECUTOR.execute(() -> persistBitmapAndStartActivity(mContext, + mBitmapSupplier.get(), crop, intent, (uri, intentForUri) -> { + intentForUri.putExtra(EXTRA_STREAM, uri); + return new Intent[]{intentForUri}; + }, TAG)); + + } + + /** + * Share the image this api was constructed with. + */ + @UiThread + public void startShareActivity() { + ImageActionUtils.startShareActivity(mContext, mBitmapSupplier, null, null, TAG); + } + + /** + * @param screenshot to be saved to the media store. + * @param screenshotBounds the location of where the bitmap was laid out on the screen in + * screen coordinates. + * @param visibleInsets that are used to draw the screenshot within the bounds. + * @param taskId of the task that the screenshot was taken of. + */ + public void saveScreenshot(Bitmap screenshot, Rect screenshotBounds, + Insets visibleInsets, int taskId) { + ImageActionUtils.saveScreenshot(mSystemUiProxy, screenshot, screenshotBounds, visibleInsets, + taskId); + } +} diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java index 33d9d9aa55..fbf29af9fb 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java @@ -16,12 +16,13 @@ package com.android.quickstep; +import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS; import static com.android.launcher3.util.MainThreadInitializedObject.forOverride; +import android.content.Context; +import android.graphics.Insets; import android.graphics.Matrix; -import android.view.View; - -import androidx.annotation.Nullable; +import android.graphics.Rect; import com.android.launcher3.BaseActivity; import com.android.launcher3.BaseDraggingActivity; @@ -29,6 +30,7 @@ import com.android.launcher3.R; import com.android.launcher3.popup.SystemShortcut; import com.android.launcher3.util.MainThreadInitializedObject; import com.android.launcher3.util.ResourceBasedOverride; +import com.android.quickstep.views.OverviewActionsView; import com.android.quickstep.views.TaskThumbnailView; import com.android.quickstep.views.TaskView; import com.android.systemui.plugins.OverscrollPlugin; @@ -43,16 +45,6 @@ import java.util.List; */ public class TaskOverlayFactory implements ResourceBasedOverride { - /** Note that these will be shown in order from top to bottom, if available for the task. */ - private static final TaskShortcutFactory[] MENU_OPTIONS = new TaskShortcutFactory[]{ - TaskShortcutFactory.APP_INFO, - TaskShortcutFactory.SPLIT_SCREEN, - TaskShortcutFactory.PIN, - TaskShortcutFactory.INSTALL, - TaskShortcutFactory.FREE_FORM, - TaskShortcutFactory.WELLBEING - }; - public static List getEnabledShortcuts(TaskView taskView) { final ArrayList shortcuts = new ArrayList<>(); final BaseDraggingActivity activity = BaseActivity.fromContext(taskView.getContext()); @@ -76,25 +68,68 @@ public class TaskOverlayFactory implements ResourceBasedOverride { } public TaskOverlay createOverlay(TaskThumbnailView thumbnailView) { - return new TaskOverlay(); + return new TaskOverlay(thumbnailView); } + /** Note that these will be shown in order from top to bottom, if available for the task. */ + private static final TaskShortcutFactory[] MENU_OPTIONS = new TaskShortcutFactory[]{ + TaskShortcutFactory.APP_INFO, + TaskShortcutFactory.SPLIT_SCREEN, + TaskShortcutFactory.PIN, + TaskShortcutFactory.INSTALL, + TaskShortcutFactory.FREE_FORM, + TaskShortcutFactory.WELLBEING + }; + + /** + * Overlay on each task handling Overview Action Buttons. + */ public static class TaskOverlay { + private final Context mApplicationContext; + private OverviewActionsView mActionsView; + private final TaskThumbnailView mThumbnailView; + + + protected TaskOverlay(TaskThumbnailView taskThumbnailView) { + mApplicationContext = taskThumbnailView.getContext().getApplicationContext(); + mThumbnailView = taskThumbnailView; + } + /** * Called when the current task is interactive for the user */ - public void initOverlay(Task task, ThumbnailData thumbnail, Matrix matrix) { } + public void initOverlay(Task task, ThumbnailData thumbnail, Matrix matrix) { + ImageActionsApi imageApi = new ImageActionsApi( + mApplicationContext, mThumbnailView::getThumbnail); + + if (mActionsView == null && ENABLE_OVERVIEW_ACTIONS.get() + && SysUINavigationMode.removeShelfFromOverview(mApplicationContext)) { + mActionsView = BaseActivity.fromContext(mThumbnailView.getContext()).findViewById( + R.id.overview_actions_view); + } + if (mActionsView != null) { + mActionsView.setListener(new OverviewActionsView.Listener() { + @Override + public void onShare() { + imageApi.startShareActivity(); + } + + @Override + public void onScreenshot() { + imageApi.saveScreenshot(mThumbnailView.getThumbnail(), + getTaskSnapshotBounds(), getTaskSnapshotInsets(), task.key.id); + } + }); + } - @Nullable - public View getActionsView() { - return null; } /** * Called when the overlay is no longer used. */ - public void reset() { } + public void reset() { + } /** * Whether the overlay is modal, which means only tapping is enabled, but no swiping. @@ -102,5 +137,28 @@ public class TaskOverlayFactory implements ResourceBasedOverride { public boolean isOverlayModal() { return false; } + + /** + * Gets the task snapshot as it is displayed on the screen. + * + * @return the bounds of the snapshot in screen coordinates. + */ + public Rect getTaskSnapshotBounds() { + int[] location = new int[2]; + mThumbnailView.getLocationOnScreen(location); + + return new Rect(location[0], location[1], mThumbnailView.getWidth() + location[0], + mThumbnailView.getHeight() + location[1]); + } + + /** + * Gets the insets that the snapshot is drawn with. + * + * @return the insets in screen coordinates. + */ + public Insets getTaskSnapshotInsets() { + // TODO: return the real insets + return Insets.of(0, 0, 0, 0); + } } } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/OverviewActionsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/OverviewActionsView.java new file mode 100644 index 0000000000..6a37e2b7de --- /dev/null +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/OverviewActionsView.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.quickstep.views; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.FrameLayout; + +import androidx.annotation.Nullable; + +import com.android.launcher3.R; + +/** + * View for showing action buttons in Overview + */ +public class OverviewActionsView extends FrameLayout { + + private final View mScreenshotButton; + private final View mShareButton; + + /** + * Listener for taps on the various actions. + */ + public interface Listener { + /** User has initiated the share actions. */ + void onShare(); + + /** User has initiated the screenshot action. */ + void onScreenshot(); + } + + public OverviewActionsView(Context context) { + this(context, null); + } + + public OverviewActionsView(Context context, @Nullable AttributeSet attrs) { + this(context, attrs, 0); + } + + public OverviewActionsView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public OverviewActionsView(Context context, AttributeSet attrs, int defStyleAttr, + int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + LayoutInflater.from(context).inflate(R.layout.overview_actions, this, true); + mShareButton = findViewById(R.id.action_share); + mScreenshotButton = findViewById(R.id.action_screenshot); + } + + /** + * Set listener for callbacks on action button taps. + * + * @param listener for callbacks, or {@code null} to clear the listener. + */ + public void setListener(@Nullable OverviewActionsView.Listener listener) { + mShareButton.setOnClickListener( + listener == null ? null : view -> listener.onShare()); + mScreenshotButton.setOnClickListener( + listener == null ? null : view -> listener.onScreenshot()); + } +} diff --git a/quickstep/res/drawable/ic_screenshot.xml b/quickstep/res/drawable/ic_screenshot.xml new file mode 100644 index 0000000000..d97eae1d15 --- /dev/null +++ b/quickstep/res/drawable/ic_screenshot.xml @@ -0,0 +1,23 @@ + + + + diff --git a/quickstep/res/drawable/ic_share.xml b/quickstep/res/drawable/ic_share.xml new file mode 100644 index 0000000000..ff4baec79b --- /dev/null +++ b/quickstep/res/drawable/ic_share.xml @@ -0,0 +1,23 @@ + + + + diff --git a/quickstep/res/layout/overview_actions.xml b/quickstep/res/layout/overview_actions.xml new file mode 100644 index 0000000000..ad5efb6ae0 --- /dev/null +++ b/quickstep/res/layout/overview_actions.xml @@ -0,0 +1,41 @@ + + + + + +