From c2980782411f80c34d07547d9142159c86211581 Mon Sep 17 00:00:00 2001 From: Jon Miranda Date: Tue, 17 Mar 2020 14:24:42 -0700 Subject: [PATCH 01/36] Add staggered workspace animation to keyguard unlock animation. Added behind FeatureFlag.KEYGUARD_ANIMATION due to dependency on Ie94ab8ecd35e9dcc28d0b8f7aaeb058e15b0f80b. Added dynamic resource unlock_staggered_velocity_dp_per_s so that the animation can be tuned. Bug: 151238866 Change-Id: I4d349d32cc7d86be3cdc582c2a7c565be2931444 --- .../FlingAndHoldTouchController.java | 4 +- .../util/StaggeredWorkspaceAnim.java | 6 +++ .../QuickstepAppTransitionManagerImpl.java | 37 +++++++++++++------ res/values/config.xml | 2 + .../launcher3/config/FeatureFlags.java | 3 ++ .../graphics/WorkspaceAndHotseatScrim.java | 27 +++++++++----- .../AbstractStateChangeTouchController.java | 2 +- 7 files changed, 57 insertions(+), 24 deletions(-) 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 785a480174..6fc03b1bf1 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 @@ -120,8 +120,8 @@ public class FlingAndHoldTouchController extends PortraitStatesTouchController { mPeekAnim.start(); VibratorWrapper.INSTANCE.get(mLauncher).vibrate(OVERVIEW_HAPTIC); - mLauncher.getDragLayer().getScrim().animateToSysuiMultiplier(isPaused ? 0 : 1, - peekDuration, 0); + mLauncher.getDragLayer().getScrim().createSysuiMultiplierAnim(isPaused ? 0 : 1) + .setDuration(peekDuration).start(); } /** diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java index 43c5cb5251..9cf45b3151 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java @@ -126,6 +126,8 @@ public class StaggeredWorkspaceAnim { addScrimAnimationForState(launcher, NORMAL, ALPHA_DURATION_MS); } + mAnimators.play(launcher.getDragLayer().getScrim().createSysuiMultiplierAnim(0f, 1f) + .setDuration(ALPHA_DURATION_MS)); mAnimators.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { @@ -153,6 +155,10 @@ public class StaggeredWorkspaceAnim { launcher.getOverviewPanel().getScroller().forceFinished(true); } + public AnimatorSet getAnimators() { + return mAnimators; + } + /** * Starts the animation. */ diff --git a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java index b9bd6b1d54..1b39242ef4 100644 --- a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java +++ b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java @@ -16,6 +16,8 @@ package com.android.launcher3; +import static android.util.TypedValue.COMPLEX_UNIT_DIP; + import static com.android.launcher3.BaseActivity.INVISIBLE_ALL; import static com.android.launcher3.BaseActivity.INVISIBLE_BY_APP_TRANSITIONS; import static com.android.launcher3.BaseActivity.INVISIBLE_BY_PENDING_FLAGS; @@ -29,6 +31,7 @@ import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE; import static com.android.launcher3.anim.Interpolators.DEACCEL_1_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.BackgroundBlurController.BACKGROUND_BLUR; import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION; @@ -58,6 +61,7 @@ import android.os.CancellationSignal; import android.os.Handler; import android.os.Looper; import android.util.Pair; +import android.util.TypedValue; import android.view.View; import androidx.annotation.NonNull; @@ -70,12 +74,14 @@ import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.dragndrop.DragLayer; import com.android.launcher3.shortcuts.DeepShortcutView; import com.android.launcher3.uioverrides.BackgroundBlurController; +import com.android.launcher3.util.DynamicResource; import com.android.launcher3.util.MultiValueAlpha; import com.android.launcher3.util.MultiValueAlpha.AlphaProperty; import com.android.launcher3.views.FloatingIconView; import com.android.quickstep.RemoteAnimationTargets; import com.android.quickstep.util.MultiValueUpdateListener; import com.android.quickstep.util.RemoteAnimationProvider; +import com.android.quickstep.util.StaggeredWorkspaceAnim; import com.android.systemui.shared.system.ActivityCompat; import com.android.systemui.shared.system.ActivityOptionsCompat; import com.android.systemui.shared.system.QuickStepContract; @@ -156,6 +162,7 @@ public abstract class QuickstepAppTransitionManagerImpl extends LauncherAppTrans // Strong refs to runners which are cleared when the launcher activity is destroyed private WrappedAnimationRunnerImpl mWallpaperOpenRunner; private WrappedAnimationRunnerImpl mAppLaunchRunner; + private WrappedAnimationRunnerImpl mKeyguardGoingAwayRunner; private final AnimatorListenerAdapter mForceInvisibleListener = new AnimatorListenerAdapter() { @Override @@ -623,6 +630,17 @@ public abstract class QuickstepAppTransitionManagerImpl extends LauncherAppTrans new WrappedLauncherAnimationRunner<>(mWallpaperOpenRunner, false /* startAtFrontOfQueue */), CLOSING_TRANSITION_DURATION_MS, 0 /* statusBarTransitionDelay */)); + + if (KEYGUARD_ANIMATION.get()) { + mKeyguardGoingAwayRunner = createWallpaperOpenRunner(true /* fromUnlock */); + definition.addRemoteAnimation( + WindowManagerWrapper.TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER, + new RemoteAnimationAdapterCompat( + new WrappedLauncherAnimationRunner<>(mKeyguardGoingAwayRunner, + true /* startAtFrontOfQueue */), + CLOSING_TRANSITION_DURATION_MS, 0 /* statusBarTransitionDelay */)); + } + new ActivityCompat(mLauncher).registerRemoteAnimations(definition); } } @@ -639,6 +657,7 @@ public abstract class QuickstepAppTransitionManagerImpl extends LauncherAppTrans // definition so we don't have to wait for the system gc mWallpaperOpenRunner = null; mAppLaunchRunner = null; + mKeyguardGoingAwayRunner = null; } } @@ -868,18 +887,14 @@ public abstract class QuickstepAppTransitionManagerImpl extends LauncherAppTrans || mLauncher.isForceInvisible()) { // Only register the content animation for cancellation when state changes mLauncher.getStateManager().setCurrentAnimation(anim); + if (mFromUnlock) { - Pair contentAnimator = - getLauncherContentAnimator(false /* isAppOpening */, - new float[] {mContentTransY, 0}); - contentAnimator.first.setStartDelay(0); - anim.play(contentAnimator.first); - anim.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - contentAnimator.second.run(); - } - }); + float velocityDpPerS = DynamicResource.provider(mLauncher) + .getDimension(R.dimen.unlock_staggered_velocity_dp_per_s); + float velocityPxPerS = TypedValue.applyDimension(COMPLEX_UNIT_DIP, + velocityDpPerS, mLauncher.getResources().getDisplayMetrics()); + anim.play(new StaggeredWorkspaceAnim(mLauncher, velocityPxPerS, false) + .getAnimators()); } else { createLauncherResumeAnimation(anim); } diff --git a/res/values/config.xml b/res/values/config.xml index df0f2330f0..6c239bd8a6 100644 --- a/res/values/config.xml +++ b/res/values/config.xml @@ -142,6 +142,7 @@ 0.7 150 + 3dp 18dp @@ -170,6 +171,7 @@ @dimen/staggered_damping_ratio @dimen/staggered_stiffness + @dimen/unlock_staggered_velocity_dp_per_s @dimen/swipe_up_fling_min_visible_change @dimen/swipe_up_y_overshoot diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java index 8453c41d36..0237b50ce6 100644 --- a/src/com/android/launcher3/config/FeatureFlags.java +++ b/src/com/android/launcher3/config/FeatureFlags.java @@ -83,6 +83,9 @@ public final class FeatureFlags { public static final BooleanFlag UNSTABLE_SPRINGS = getDebugFlag( "UNSTABLE_SPRINGS", false, "Enable unstable springs for quickstep animations"); + public static final BooleanFlag KEYGUARD_ANIMATION = getDebugFlag( + "KEYGUARD_ANIMATION", false, "Enable animation for keyguard going away on wallpaper"); + public static final BooleanFlag ADAPTIVE_ICON_WINDOW_ANIM = getDebugFlag( "ADAPTIVE_ICON_WINDOW_ANIM", true, "Use adaptive icons for window animations."); diff --git a/src/com/android/launcher3/graphics/WorkspaceAndHotseatScrim.java b/src/com/android/launcher3/graphics/WorkspaceAndHotseatScrim.java index 83349bc8f9..2c7f89185c 100644 --- a/src/com/android/launcher3/graphics/WorkspaceAndHotseatScrim.java +++ b/src/com/android/launcher3/graphics/WorkspaceAndHotseatScrim.java @@ -19,6 +19,7 @@ package com.android.launcher3.graphics; import static android.content.Intent.ACTION_SCREEN_OFF; import static android.content.Intent.ACTION_USER_PRESENT; +import static com.android.launcher3.config.FeatureFlags.KEYGUARD_ANIMATION; import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound; import android.animation.ObjectAnimator; @@ -81,6 +82,10 @@ public class WorkspaceAndHotseatScrim extends Scrim { } }; + /** + * Receiver used to get a signal that the user unlocked their device. + * @see KEYGUARD_ANIMATION For proper signal. + */ private final BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { @@ -164,8 +169,10 @@ public class WorkspaceAndHotseatScrim extends Scrim { mSysUiAnimMultiplier = 0; reapplySysUiAlphaNoInvalidate(); - animateToSysuiMultiplier(1, 600, - mLauncher.getWindow().getTransitionBackgroundFadeDuration()); + ObjectAnimator oa = createSysuiMultiplierAnim(1); + oa.setDuration(600); + oa.setStartDelay(mLauncher.getWindow().getTransitionBackgroundFadeDuration()); + oa.start(); mAnimateScrimOnNextDraw = false; } @@ -178,13 +185,13 @@ public class WorkspaceAndHotseatScrim extends Scrim { } } - public void animateToSysuiMultiplier(float toMultiplier, long duration, - long startDelay) { - ObjectAnimator anim = ObjectAnimator.ofFloat(this, SYSUI_ANIM_MULTIPLIER, toMultiplier); + /** + * @return an ObjectAnimator that controls the fade in/out of the sys ui scrim. + */ + public ObjectAnimator createSysuiMultiplierAnim(float... values) { + ObjectAnimator anim = ObjectAnimator.ofFloat(this, SYSUI_ANIM_MULTIPLIER, values); anim.setAutoCancel(true); - anim.setDuration(duration); - anim.setStartDelay(startDelay); - anim.start(); + return anim; } public void onInsetsChanged(Rect insets) { @@ -197,7 +204,7 @@ public class WorkspaceAndHotseatScrim extends Scrim { public void onViewAttachedToWindow(View view) { super.onViewAttachedToWindow(view); - if (mTopScrim != null) { + if (!KEYGUARD_ANIMATION.get() && mTopScrim != null) { IntentFilter filter = new IntentFilter(ACTION_SCREEN_OFF); filter.addAction(ACTION_USER_PRESENT); // When the device wakes up + keyguard is gone mRoot.getContext().registerReceiver(mReceiver, filter); @@ -207,7 +214,7 @@ public class WorkspaceAndHotseatScrim extends Scrim { @Override public void onViewDetachedFromWindow(View view) { super.onViewDetachedFromWindow(view); - if (mTopScrim != null) { + if (!KEYGUARD_ANIMATION.get() && mTopScrim != null) { mRoot.getContext().unregisterReceiver(mReceiver); } } diff --git a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java index 1d14a8f039..cbc5257b2d 100644 --- a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java +++ b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java @@ -535,7 +535,7 @@ public abstract class AbstractStateChangeTouchController // case the user started interacting with it before the animation finished. mLauncher.getStateManager().goToState(targetState, false /* animated */); } - mLauncher.getDragLayer().getScrim().animateToSysuiMultiplier(1, 0, 0); + mLauncher.getDragLayer().getScrim().createSysuiMultiplierAnim(1f).setDuration(0).start(); } private void logReachedState(int logAction, LauncherState targetState) { From 26729a129ac9397fda6984f224947369608f2835 Mon Sep 17 00:00:00 2001 From: Sunny Goyal Date: Tue, 17 Mar 2020 15:01:15 -0700 Subject: [PATCH 02/36] Force finishing spring animation on second touch, if the animation is close to completion Bug: 151180649 Change-Id: I7085bb76d4937910d55cbaf1cb71a0e6c24922d5 --- .../TaskViewTouchController.java | 4 ++++ .../android/quickstep/views/RecentsView.java | 18 +++++++++--------- .../anim/AnimatorPlaybackController.java | 13 +++++++++++++ .../launcher3/anim/PendingAnimation.java | 4 ---- 4 files changed, 26 insertions(+), 13 deletions(-) diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java index b0125a893f..1b3610a92d 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java @@ -80,6 +80,9 @@ public abstract class TaskViewTouchController } private boolean canInterceptTouch() { + if (mCurrentAnimation != null) { + mCurrentAnimation.forceFinishIfCloseToEnd(); + } if (mCurrentAnimation != null) { // If we are already animating from a previous state, we can intercept. return true; @@ -126,6 +129,7 @@ public abstract class TaskViewTouchController for (int i = 0; i < mRecentsView.getTaskViewCount(); i++) { TaskView view = mRecentsView.getTaskViewAt(i); + if (mRecentsView.isTaskViewVisible(view) && mActivity.getDragLayer() .isEventOverView(view, ev)) { // Disable swiping up and down if the task overlay is modal. 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 8322d8c754..e15ac46432 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 @@ -68,7 +68,6 @@ import android.view.HapticFeedbackConstants; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.MotionEvent; -import android.view.OrientationEventListener; import android.view.View; import android.view.ViewDebug; import android.view.ViewGroup; @@ -88,7 +87,6 @@ import com.android.launcher3.LauncherState; import com.android.launcher3.PagedView; import com.android.launcher3.R; import com.android.launcher3.Utilities; -import com.android.launcher3.anim.AnimationSuccessListener; import com.android.launcher3.anim.AnimatorPlaybackController; import com.android.launcher3.anim.PendingAnimation; import com.android.launcher3.anim.PendingAnimation.EndState; @@ -96,7 +94,6 @@ import com.android.launcher3.anim.PropertyListBuilder; import com.android.launcher3.anim.SpringProperty; import com.android.launcher3.compat.AccessibilityManagerCompat; import com.android.launcher3.config.FeatureFlags; -import com.android.launcher3.dragndrop.DragLayer; import com.android.launcher3.graphics.RotationMode; import com.android.launcher3.states.RotationHelper; import com.android.launcher3.touch.PagedOrientationHandler.CurveProperties; @@ -1165,7 +1162,7 @@ public abstract class RecentsView extends PagedView impl } private void addDismissedTaskAnimations(View taskView, long duration, PendingAnimation anim) { - anim.add(ObjectAnimator.ofFloat(taskView, ALPHA, 0).setDuration(duration), ACCEL_2); + anim.setViewAlpha(taskView, 0, ACCEL_2); FloatProperty secondaryViewTranslate = mOrientationHandler.getSecondaryViewTranslate(); int secondaryTaskDimension = mOrientationHandler.getSecondaryDimension(taskView); @@ -1257,9 +1254,7 @@ public abstract class RecentsView extends PagedView impl } if (needsCurveUpdates) { - ValueAnimator va = ValueAnimator.ofFloat(0, 1); - va.addUpdateListener((a) -> updateCurveProperties()); - anim.add(va); + anim.addOnFrameCallback(this::updateCurveProperties); } // Add a tiny bit of translation Z, so that it draws on top of other views @@ -1279,6 +1274,7 @@ public abstract class RecentsView extends PagedView impl } } + @SuppressWarnings("WrongCall") private void onEnd(EndState endState) { if (endState.isSuccess) { if (shouldRemoveTask) { @@ -1290,15 +1286,18 @@ public abstract class RecentsView extends PagedView impl pageToSnapTo == (getTaskViewCount() - 1)) { pageToSnapTo -= 1; } - removeView(taskView); + removeViewInLayout(taskView); if (getTaskViewCount() == 0) { - removeView(mClearAllButton); + removeViewInLayout(mClearAllButton); hideActionsView(); startHome(); } else { snapToPageImmediately(pageToSnapTo); } + // Update the layout synchronously so that the position of next view is + // immediately available. + onLayout(false /* changed */, getLeft(), getTop(), getRight(), getBottom()); } resetTaskVisuals(); mPendingAnimation = null; @@ -1548,6 +1547,7 @@ public abstract class RecentsView extends PagedView impl @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); + updateEmptyStateUi(changed); // Set the pivot points to match the task preview center diff --git a/src/com/android/launcher3/anim/AnimatorPlaybackController.java b/src/com/android/launcher3/anim/AnimatorPlaybackController.java index 958c86366c..f12789a90f 100644 --- a/src/com/android/launcher3/anim/AnimatorPlaybackController.java +++ b/src/com/android/launcher3/anim/AnimatorPlaybackController.java @@ -77,6 +77,9 @@ public class AnimatorPlaybackController implements ValueAnimator.AnimatorUpdateL } }; + // Progress factor after which an animation is considered almost completed. + private static final float ANIMATION_COMPLETE_THRESHOLD = 0.95f; + private final ValueAnimator mAnimationPlayer; private final long mDuration; @@ -209,6 +212,16 @@ public class AnimatorPlaybackController implements ValueAnimator.AnimatorUpdateL mAnimationPlayer.start(); } + /** + * Tries to finish the running animation if it is close to completion. + */ + public void forceFinishIfCloseToEnd() { + if (mAnimationPlayer.isRunning() + && mAnimationPlayer.getAnimatedFraction() > ANIMATION_COMPLETE_THRESHOLD) { + mAnimationPlayer.end(); + } + } + /** * Pauses the currently playing animation. */ diff --git a/src/com/android/launcher3/anim/PendingAnimation.java b/src/com/android/launcher3/anim/PendingAnimation.java index 9a25c47a74..a95a5e17e6 100644 --- a/src/com/android/launcher3/anim/PendingAnimation.java +++ b/src/com/android/launcher3/anim/PendingAnimation.java @@ -62,10 +62,6 @@ public class PendingAnimation implements PropertySetter { /** * Utility method to sent an interpolator on an animation and add it to the list */ - public void add(Animator anim, TimeInterpolator interpolator) { - add(anim, interpolator, SpringProperty.DEFAULT); - } - public void add(Animator anim, TimeInterpolator interpolator, SpringProperty springProperty) { anim.setInterpolator(interpolator); add(anim, springProperty); From 9c05d2db04b458f43436bef04c319656f49bea83 Mon Sep 17 00:00:00 2001 From: jayaprakashs Date: Tue, 17 Mar 2020 15:31:39 -0700 Subject: [PATCH 03/36] Show IME suggestions while tapping on Folder EditText Current behaviour doesn't show the suggestions in IME if the folder name is non-empty (Shown only if the folder name is empty). This change shows the IME suggestions always, but primary suggestions are shown only if the folder name is empty (not overwritting the current folder name with suggestion). Bug: 151762006 Tested: on phone. (created a folder name, assigned custom name, added the third app to Folder, tap on Folder EditText and it shows IME suggestions) Change-Id: Ic4d43660a371b8dd7d18acb42fe3dee06a730347 --- src/com/android/launcher3/folder/Folder.java | 27 +++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java index 2be8ff463d..5f496f4aba 100644 --- a/src/com/android/launcher3/folder/Folder.java +++ b/src/com/android/launcher3/folder/Folder.java @@ -324,13 +324,11 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo public void startEditingFolderName() { post(() -> { if (FeatureFlags.FOLDER_NAME_SUGGEST.get()) { - if (isEmpty(mFolderName.getText())) { - ofNullable(mInfo) - .map(info -> info.suggestedFolderNames) - .map(folderNames -> (FolderNameInfo[]) folderNames - .getParcelableArrayExtra(FolderInfo.EXTRA_FOLDER_SUGGESTIONS)) - .ifPresent(nameInfos -> showLabelSuggestion(nameInfos, false)); - } + ofNullable(mInfo) + .map(info -> info.suggestedFolderNames) + .map(folderNames -> (FolderNameInfo[]) folderNames + .getParcelableArrayExtra(FolderInfo.EXTRA_FOLDER_SUGGESTIONS)) + .ifPresent(nameInfos -> showLabelSuggestion(nameInfos, false)); } mFolderName.setHint(""); mIsEditingName = true; @@ -485,19 +483,24 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo nameInfos[1].getLabel()); if (shouldOpen) { - CharSequence firstLabel = nameInfos[0] == null ? "" : nameInfos[0].getLabel(); - if (!isEmpty(firstLabel)) { - mFolderName.setHint(""); - mFolderName.setText(firstLabel); + // update the primary suggestion if the folder name is empty. + if (isEmpty(mFolderName.getText())) { + CharSequence firstLabel = nameInfos[0] == null ? "" : nameInfos[0].getLabel(); + if (!isEmpty(firstLabel)) { + mFolderName.setHint(""); + mFolderName.setText(firstLabel); + } } if (animate) { animateOpen(mInfo.contents, 0, true); } mFolderName.showKeyboard(); mFolderName.displayCompletions( - asList(nameInfos).subList(1, nameInfos.length).stream() + asList(nameInfos).subList(0, nameInfos.length).stream() .filter(Objects::nonNull) .map(s -> s.getLabel().toString()) + .filter(s -> !s.isEmpty()) + .filter(s -> !s.equalsIgnoreCase(mFolderName.getText().toString())) .collect(Collectors.toList())); } } From 39467a9a273280e3af678c0446eb993ff138fce7 Mon Sep 17 00:00:00 2001 From: Tracy Zhou Date: Tue, 17 Mar 2020 18:29:24 -0700 Subject: [PATCH 04/36] Enable USE_SURFACE_VIEW_FOR_GRID_PREVIEW flag This flag is behind grid_options flag, so it's safe Fixes: 150224413 Test: N/A Change-Id: Icebb4b8476d987a7f1697115183058a2345e6acc --- 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 8453c41d36..cb8c81d35f 100644 --- a/src/com/android/launcher3/config/FeatureFlags.java +++ b/src/com/android/launcher3/config/FeatureFlags.java @@ -134,7 +134,7 @@ public final class FeatureFlags { "ENABLE_LAUNCHER_PREVIEW_IN_GRID_PICKER", true, "Show launcher preview in grid picker"); public static final BooleanFlag USE_SURFACE_VIEW_FOR_GRID_PREVIEW = getDebugFlag( - "USE_SURFACE_VIEW_FOR_GRID_PREVIEW", false, "Use surface view for grid preview"); + "USE_SURFACE_VIEW_FOR_GRID_PREVIEW", true, "Use surface view for grid preview"); public static final BooleanFlag ENABLE_OVERVIEW_ACTIONS = getDebugFlag( "ENABLE_OVERVIEW_ACTIONS", true, "Show app actions instead of the shelf in Overview." From 5e46f072b4a14c81daf62f2edd5b78629f67454b Mon Sep 17 00:00:00 2001 From: vadimt Date: Mon, 2 Mar 2020 18:05:58 -0800 Subject: [PATCH 05/36] Fixing TouchInteractionService referring destroyed activity Bug: 139137636 Change-Id: I2d36bcd9478e070e21cb6c8e2cde617807af8dd1 --- .../quickstep/TouchInteractionService.java | 16 +++++++++++++--- .../inputconsumers/DelegateInputConsumer.java | 5 +++++ .../OtherActivityInputConsumer.java | 5 +++++ .../src/com/android/quickstep/InputConsumer.java | 10 ++++++++++ 4 files changed, 33 insertions(+), 3 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 25a307852d..eb5c7f9f69 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java @@ -15,7 +15,9 @@ */ package com.android.quickstep; +import static android.view.MotionEvent.ACTION_CANCEL; import static android.view.MotionEvent.ACTION_DOWN; +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; @@ -435,7 +437,8 @@ public class TouchInteractionService extends Service implements PluginListener Date: Wed, 18 Mar 2020 11:17:23 -0700 Subject: [PATCH 06/36] Update window threshold in both directions - Only use the task flags when we should be using the home screen state Change-Id: I1b0f682eb9c2861b36953da7695c798c187db517 --- .../src/com/android/quickstep/LauncherSwipeHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 3328abcaf5..4c838209ee 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java @@ -588,7 +588,7 @@ public class LauncherSwipeHandler // We will handle the sysui flags based on the centermost task view. if (mRecentsAnimationController != null) { mRecentsAnimationController.setWindowThresholdCrossed(centermostTaskFlags != 0 - || useHomeScreenFlags); + && useHomeScreenFlags); } int sysuiFlags = useHomeScreenFlags ? 0 : centermostTaskFlags; mActivity.getSystemUiController().updateUiState(UI_STATE_OVERVIEW, sysuiFlags); From 9794d4d52a8083514186774fb9a57764dff35ec4 Mon Sep 17 00:00:00 2001 From: Jon Miranda Date: Wed, 18 Mar 2020 12:22:50 -0700 Subject: [PATCH 07/36] Ensure current animation is cancelled before building new folder animation. Fixes bug where folder clip padding gets stuck as false. Bug: 146884730 Change-Id: I3a24e561d15fbc46837bb21cd8f97283e69f9ce1 --- src/com/android/launcher3/folder/Folder.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java index 5f496f4aba..94f7bdd20f 100644 --- a/src/com/android/launcher3/folder/Folder.java +++ b/src/com/android/launcher3/folder/Folder.java @@ -519,9 +519,6 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo } private void startAnimation(final AnimatorSet a) { - if (mCurrentAnimator != null && mCurrentAnimator.isRunning()) { - mCurrentAnimator.cancel(); - } final Workspace workspace = mLauncher.getWorkspace(); final CellLayout currentCellLayout = (CellLayout) workspace.getChildAt(workspace.getCurrentPage()); @@ -638,6 +635,9 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo // dropping. One resulting issue is that replaceFolderWithFinalItem() can be called twice. mDeleteFolderOnDropCompleted = false; + if (mCurrentAnimator != null && mCurrentAnimator.isRunning()) { + mCurrentAnimator.cancel(); + } AnimatorSet anim = new FolderAnimationManager(this, true /* isOpening */).getAnimator(); anim.addListener(new AnimatorListenerAdapter() { @Override @@ -741,6 +741,9 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo } private void animateClosed() { + if (mCurrentAnimator != null && mCurrentAnimator.isRunning()) { + mCurrentAnimator.cancel(); + } AnimatorSet a = new FolderAnimationManager(this, false /* isOpening */).getAnimator(); a.addListener(new AnimatorListenerAdapter() { @Override From 9b180109901b1f9fe0e10b3d8406217b2aac6a02 Mon Sep 17 00:00:00 2001 From: Sunny Goyal Date: Wed, 11 Mar 2020 10:02:29 -0700 Subject: [PATCH 08/36] Decoupling some drag and drop code handling > Removing some special checks around accessibility drag > Unifying folder alarm code path for accessible and normal DnD > Maintaining mDragStartTime inside the dragDriver instead of the callers > Simplifying some accessibility callbacks Future cl will create a Accessibility DragDriver and unify it with other DnD flow Change-Id: I1919ef218de0174678110f271b450bcb9aaf4a5c --- .../android/launcher3/ButtonDropTarget.java | 2 +- src/com/android/launcher3/CellLayout.java | 49 +--- src/com/android/launcher3/DropTarget.java | 3 - src/com/android/launcher3/Launcher.java | 15 +- src/com/android/launcher3/Workspace.java | 97 ++----- .../AccessibleDragListenerAdapter.java | 37 ++- .../DragAndDropAccessibilityDelegate.java | 15 +- .../LauncherAccessibilityDelegate.java | 3 +- .../dragndrop/BaseItemDragListener.java | 7 +- .../launcher3/dragndrop/DragController.java | 158 +++------- .../launcher3/dragndrop/DragDriver.java | 274 ++++++++++-------- .../launcher3/dragndrop/DragOptions.java | 7 +- .../android/launcher3/dragndrop/DragView.java | 11 - .../dragndrop/FlingToDeleteHelper.java | 27 -- src/com/android/launcher3/folder/Folder.java | 20 +- 15 files changed, 313 insertions(+), 412 deletions(-) diff --git a/src/com/android/launcher3/ButtonDropTarget.java b/src/com/android/launcher3/ButtonDropTarget.java index 2b0da434a9..34d70673ae 100644 --- a/src/com/android/launcher3/ButtonDropTarget.java +++ b/src/com/android/launcher3/ButtonDropTarget.java @@ -155,7 +155,7 @@ public abstract class ButtonDropTarget extends TextView @Override public final void onDragEnter(DragObject d) { - if (!d.accessibleDrag && !mTextVisible) { + if (!mAccessibleDrag && !mTextVisible) { // Show tooltip hideTooltip(); diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java index e3eb38792a..4742bbc766 100644 --- a/src/com/android/launcher3/CellLayout.java +++ b/src/com/android/launcher3/CellLayout.java @@ -53,8 +53,6 @@ import androidx.core.view.ViewCompat; import com.android.launcher3.LauncherSettings.Favorites; import com.android.launcher3.accessibility.DragAndDropAccessibilityDelegate; -import com.android.launcher3.accessibility.FolderAccessibilityHelper; -import com.android.launcher3.accessibility.WorkspaceAccessibilityHelper; import com.android.launcher3.anim.Interpolators; import com.android.launcher3.anim.PropertyListBuilder; import com.android.launcher3.config.FeatureFlags; @@ -79,9 +77,6 @@ import java.util.Comparator; import java.util.Stack; public class CellLayout extends ViewGroup implements Transposable { - public static final int WORKSPACE_ACCESSIBILITY_DRAG = 2; - public static final int FOLDER_ACCESSIBILITY_DRAG = 1; - private static final String TAG = "CellLayout"; private static final boolean LOGD = false; @@ -182,7 +177,6 @@ public class CellLayout extends ViewGroup implements Transposable { private static final Paint sPaint = new Paint(); // Related to accessible drag and drop - private DragAndDropAccessibilityDelegate mTouchHelper; private boolean mUseTouchHelper = false; private RotationMode mRotationMode = RotationMode.NORMAL; @@ -292,26 +286,20 @@ public class CellLayout extends ViewGroup implements Transposable { addView(mShortcutsAndWidgets); } - public void enableAccessibleDrag(boolean enable, int dragType) { - mUseTouchHelper = enable; - if (!enable) { - ViewCompat.setAccessibilityDelegate(this, null); - setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO); - getShortcutsAndWidgets().setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO); - setOnClickListener(null); - } else { - if (dragType == WORKSPACE_ACCESSIBILITY_DRAG && - !(mTouchHelper instanceof WorkspaceAccessibilityHelper)) { - mTouchHelper = new WorkspaceAccessibilityHelper(this); - } else if (dragType == FOLDER_ACCESSIBILITY_DRAG && - !(mTouchHelper instanceof FolderAccessibilityHelper)) { - mTouchHelper = new FolderAccessibilityHelper(this); - } - ViewCompat.setAccessibilityDelegate(this, mTouchHelper); - setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); - getShortcutsAndWidgets().setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); - setOnClickListener(mTouchHelper); - } + + /** + * Sets or clears a delegate used for accessible drag and drop + */ + public void setDragAndDropAccessibilityDelegate(DragAndDropAccessibilityDelegate delegate) { + setOnClickListener(delegate); + setOnHoverListener(delegate); + ViewCompat.setAccessibilityDelegate(this, delegate); + + mUseTouchHelper = delegate != null; + int accessibilityFlag = mUseTouchHelper + ? IMPORTANT_FOR_ACCESSIBILITY_YES : IMPORTANT_FOR_ACCESSIBILITY_NO; + setImportantForAccessibility(accessibilityFlag); + getShortcutsAndWidgets().setImportantForAccessibility(accessibilityFlag); // Invalidate the accessibility hierarchy if (getParent() != null) { @@ -338,15 +326,6 @@ public class CellLayout extends ViewGroup implements Transposable { super.setPadding(mTempRect.left, mTempRect.top, mTempRect.right, mTempRect.bottom); } - @Override - public boolean dispatchHoverEvent(MotionEvent event) { - // Always attempt to dispatch hover events to accessibility first. - if (mUseTouchHelper && mTouchHelper.dispatchHoverEvent(event)) { - return true; - } - return super.dispatchHoverEvent(event); - } - @Override public boolean onInterceptTouchEvent(MotionEvent ev) { if (mUseTouchHelper || diff --git a/src/com/android/launcher3/DropTarget.java b/src/com/android/launcher3/DropTarget.java index a32fd1239f..c03011b7b2 100644 --- a/src/com/android/launcher3/DropTarget.java +++ b/src/com/android/launcher3/DropTarget.java @@ -59,9 +59,6 @@ public interface DropTarget { /** Where the drag originated */ public DragSource dragSource = null; - /** The object is part of an accessible drag operation */ - public boolean accessibleDrag; - /** Indicates that the drag operation was cancelled */ public boolean cancelled = false; diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index 1413a5cd1f..1dd2e41567 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -339,6 +339,8 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, } }; + private long mLastTouchUpTime = -1; + @Override protected void onCreate(Bundle savedInstanceState) { Object traceToken = TraceHelper.INSTANCE.beginSection(ON_CREATE_EVT, @@ -1115,7 +1117,7 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, super.onPause(); mDragController.cancelDrag(); - mDragController.resetLastGestureUpTime(); + mLastTouchUpTime = -1; mDropTargetBar.animateToVisibility(false); if (!mDeferOverlayCallbacks) { @@ -1838,6 +1840,9 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, @Override public boolean dispatchTouchEvent(MotionEvent ev) { + if (ev.getAction() == MotionEvent.ACTION_UP) { + mLastTouchUpTime = System.currentTimeMillis(); + } TestLogging.recordMotionEvent(TestProtocol.SEQUENCE_MAIN, "Touch event", ev); return super.dispatchTouchEvent(ev); } @@ -2465,8 +2470,12 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, } private boolean canRunNewAppsAnimation() { - long diff = System.currentTimeMillis() - mDragController.getLastGestureUpTime(); - return diff > (NEW_APPS_ANIMATION_INACTIVE_TIMEOUT_SECONDS * 1000); + if (mDragController.isDragging()) { + return false; + } else { + return (System.currentTimeMillis() - mLastTouchUpTime) + > (NEW_APPS_ANIMATION_INACTIVE_TIMEOUT_SECONDS * 1000); + } } private ValueAnimator createNewAppBounceAnimation(View v, int i) { diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java index ead6018505..46493b7959 100644 --- a/src/com/android/launcher3/Workspace.java +++ b/src/com/android/launcher3/Workspace.java @@ -192,10 +192,7 @@ public class Workspace extends PagedView final WallpaperOffsetInterpolator mWallpaperOffset; private boolean mUnlockWallpaperFromDefaultPageOnLayout; - // Variables relating to the creation of user folders by hovering shortcuts over shortcuts - private static final int FOLDER_CREATION_TIMEOUT = 0; public static final int REORDER_TIMEOUT = 650; - private final Alarm mFolderCreationAlarm = new Alarm(); private final Alarm mReorderAlarm = new Alarm(); private PreviewBackground mFolderCreateBg; private FolderIcon mDragOverFolderIcon = null; @@ -567,11 +564,6 @@ public class Workspace extends PagedView addView(newScreen, insertIndex); mStateTransitionAnimation.applyChildState( mLauncher.getStateManager().getState(), newScreen, insertIndex); - - if (mLauncher.getAccessibilityDelegate().isInAccessibleDrag()) { - newScreen.enableAccessibleDrag(true, CellLayout.WORKSPACE_ACCESSIBILITY_DRAG); - } - return newScreen; } @@ -818,11 +810,6 @@ public class Workspace extends PagedView if (indexOfChild(cl) < currentPage) { pageShift++; } - - if (isInAccessibleDrag) { - cl.enableAccessibleDrag(false, CellLayout.WORKSPACE_ACCESSIBILITY_DRAG); - } - removeView(cl); } else { // if this is the last screen, convert it to the empty screen @@ -1444,14 +1431,14 @@ public class Workspace extends PagedView child.setVisibility(INVISIBLE); if (options.isAccessibleDrag) { - mDragController.addDragListener(new AccessibleDragListenerAdapter( - this, CellLayout.WORKSPACE_ACCESSIBILITY_DRAG) { - @Override - protected void enableAccessibleDrag(boolean enable) { - super.enableAccessibleDrag(enable); - setEnableForLayout(mLauncher.getHotseat(),enable); - } - }); + mDragController.addDragListener( + new AccessibleDragListenerAdapter(this, WorkspaceAccessibilityHelper::new) { + @Override + protected void enableAccessibleDrag(boolean enable) { + super.enableAccessibleDrag(enable); + setEnableForLayout(mLauncher.getHotseat(), enable); + } + }); } beginDragShared(child, this, options); @@ -1883,12 +1870,10 @@ public class Workspace extends PagedView final LauncherAppWidgetHostView hostView = (LauncherAppWidgetHostView) cell; AppWidgetProviderInfo pInfo = hostView.getAppWidgetInfo(); if (pInfo != null && pInfo.resizeMode != AppWidgetProviderInfo.RESIZE_NONE - && !d.accessibleDrag) { - onCompleteRunnable = new Runnable() { - public void run() { - if (!isPageInTransition()) { - AppWidgetResizeFrame.showForWidget(hostView, cellLayout); - } + && !options.isAccessibleDrag) { + onCompleteRunnable = () -> { + if (!isPageInTransition()) { + AppWidgetResizeFrame.showForWidget(hostView, cellLayout); } }; } @@ -2088,8 +2073,6 @@ public class Workspace extends PagedView if (mFolderCreateBg != null) { mFolderCreateBg.animateToRest(); } - mFolderCreationAlarm.setOnAlarmListener(null); - mFolderCreationAlarm.cancelAlarm(); } private void cleanupAddToFolder() { @@ -2196,7 +2179,7 @@ public class Workspace extends PagedView float targetCellDistance = mDragTargetLayout.getDistanceFromCell( mDragViewVisualCenter[0], mDragViewVisualCenter[1], mTargetCell); - manageFolderFeedback(mDragTargetLayout, mTargetCell, targetCellDistance, d); + manageFolderFeedback(targetCellDistance, d); boolean nearestDropOccupied = mDragTargetLayout.isNearestDropLocationOccupied((int) mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1], item.spanX, @@ -2294,8 +2277,7 @@ public class Workspace extends PagedView return null; } - private void manageFolderFeedback(CellLayout targetLayout, - int[] targetCell, float distance, DragObject dragObject) { + private void manageFolderFeedback(float distance, DragObject dragObject) { if (distance > mMaxDistanceForFolderCreation) { if (mDragMode != DRAG_MODE_NONE) { setDragMode(DRAG_MODE_NONE); @@ -2306,18 +2288,18 @@ public class Workspace extends PagedView final View dragOverView = mDragTargetLayout.getChildAt(mTargetCell[0], mTargetCell[1]); ItemInfo info = dragObject.dragInfo; boolean userFolderPending = willCreateUserFolder(info, dragOverView, false); - if (mDragMode == DRAG_MODE_NONE && userFolderPending && - !mFolderCreationAlarm.alarmPending()) { + if (mDragMode == DRAG_MODE_NONE && userFolderPending) { - FolderCreationAlarmListener listener = new - FolderCreationAlarmListener(targetLayout, targetCell[0], targetCell[1]); + mFolderCreateBg = new PreviewBackground(); + mFolderCreateBg.setup(mLauncher, mLauncher, null, + dragOverView.getMeasuredWidth(), dragOverView.getPaddingTop()); - if (!dragObject.accessibleDrag) { - mFolderCreationAlarm.setOnAlarmListener(listener); - mFolderCreationAlarm.setAlarm(FOLDER_CREATION_TIMEOUT); - } else { - listener.onAlarm(mFolderCreationAlarm); - } + // The full preview background should appear behind the icon + mFolderCreateBg.isClipping = false; + + mFolderCreateBg.animateToAccept(mDragTargetLayout, mTargetCell[0], mTargetCell[1]); + mDragTargetLayout.clearDragOutlines(); + setDragMode(DRAG_MODE_CREATE_FOLDER); if (dragObject.stateAnnouncer != null) { dragObject.stateAnnouncer.announce(WorkspaceAccessibilityHelper @@ -2330,8 +2312,8 @@ public class Workspace extends PagedView if (willAddToFolder && mDragMode == DRAG_MODE_NONE) { mDragOverFolderIcon = ((FolderIcon) dragOverView); mDragOverFolderIcon.onDragEnter(info); - if (targetLayout != null) { - targetLayout.clearDragOutlines(); + if (mDragTargetLayout != null) { + mDragTargetLayout.clearDragOutlines(); } setDragMode(DRAG_MODE_ADD_TO_FOLDER); @@ -2350,33 +2332,6 @@ public class Workspace extends PagedView } } - class FolderCreationAlarmListener implements OnAlarmListener { - final CellLayout layout; - final int cellX; - final int cellY; - - final PreviewBackground bg = new PreviewBackground(); - - public FolderCreationAlarmListener(CellLayout layout, int cellX, int cellY) { - this.layout = layout; - this.cellX = cellX; - this.cellY = cellY; - - BubbleTextView cell = (BubbleTextView) layout.getChildAt(cellX, cellY); - bg.setup(mLauncher, mLauncher, null, cell.getMeasuredWidth(), cell.getPaddingTop()); - - // The full preview background should appear behind the icon - bg.isClipping = false; - } - - public void onAlarm(Alarm alarm) { - mFolderCreateBg = bg; - mFolderCreateBg.animateToAccept(layout, cellX, cellY); - layout.clearDragOutlines(); - setDragMode(DRAG_MODE_CREATE_FOLDER); - } - } - class ReorderAlarmListener implements OnAlarmListener { final float[] dragViewCenter; final int minSpanX, minSpanY, spanX, spanY; diff --git a/src/com/android/launcher3/accessibility/AccessibleDragListenerAdapter.java b/src/com/android/launcher3/accessibility/AccessibleDragListenerAdapter.java index f8df5d7be7..0d7df2b44d 100644 --- a/src/com/android/launcher3/accessibility/AccessibleDragListenerAdapter.java +++ b/src/com/android/launcher3/accessibility/AccessibleDragListenerAdapter.java @@ -16,7 +16,9 @@ package com.android.launcher3.accessibility; +import android.view.View; import android.view.ViewGroup; +import android.view.ViewGroup.OnHierarchyChangeListener; import com.android.launcher3.CellLayout; import com.android.launcher3.DropTarget.DragObject; @@ -24,36 +26,55 @@ import com.android.launcher3.Launcher; import com.android.launcher3.dragndrop.DragController.DragListener; import com.android.launcher3.dragndrop.DragOptions; +import java.util.function.Function; + /** * Utility listener to enable/disable accessibility drag flags for a ViewGroup * containing CellLayouts */ -public class AccessibleDragListenerAdapter implements DragListener { +public class AccessibleDragListenerAdapter implements DragListener, OnHierarchyChangeListener { private final ViewGroup mViewGroup; - private final int mDragType; + private final Function mDelegateFactory; /** - * @param parent - * @param dragType either {@link CellLayout#WORKSPACE_ACCESSIBILITY_DRAG} or - * {@link CellLayout#FOLDER_ACCESSIBILITY_DRAG} + * @param parent the viewgroup containing all the children + * @param delegateFactory function to create no delegates */ - public AccessibleDragListenerAdapter(ViewGroup parent, int dragType) { + public AccessibleDragListenerAdapter(ViewGroup parent, + Function delegateFactory) { mViewGroup = parent; - mDragType = dragType; + mDelegateFactory = delegateFactory; } @Override public void onDragStart(DragObject dragObject, DragOptions options) { + mViewGroup.setOnHierarchyChangeListener(this); enableAccessibleDrag(true); } @Override public void onDragEnd() { + mViewGroup.setOnHierarchyChangeListener(null); enableAccessibleDrag(false); Launcher.getLauncher(mViewGroup.getContext()).getDragController().removeDragListener(this); } + + @Override + public void onChildViewAdded(View parent, View child) { + if (parent == mViewGroup) { + setEnableForLayout((CellLayout) child, true); + } + } + + @Override + public void onChildViewRemoved(View parent, View child) { + if (parent == mViewGroup) { + setEnableForLayout((CellLayout) child, false); + } + } + protected void enableAccessibleDrag(boolean enable) { for (int i = 0; i < mViewGroup.getChildCount(); i++) { setEnableForLayout((CellLayout) mViewGroup.getChildAt(i), enable); @@ -61,6 +82,6 @@ public class AccessibleDragListenerAdapter implements DragListener { } protected final void setEnableForLayout(CellLayout layout, boolean enable) { - layout.enableAccessibleDrag(enable, mDragType); + layout.setDragAndDropAccessibilityDelegate(enable ? mDelegateFactory.apply(layout) : null); } } diff --git a/src/com/android/launcher3/accessibility/DragAndDropAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/DragAndDropAccessibilityDelegate.java index 117296d5f6..ddb547ffb7 100644 --- a/src/com/android/launcher3/accessibility/DragAndDropAccessibilityDelegate.java +++ b/src/com/android/launcher3/accessibility/DragAndDropAccessibilityDelegate.java @@ -19,24 +19,26 @@ package com.android.launcher3.accessibility; import android.content.Context; import android.graphics.Rect; import android.os.Bundle; +import android.view.MotionEvent; import android.view.View; import android.view.View.OnClickListener; +import android.view.View.OnHoverListener; import android.view.accessibility.AccessibilityEvent; +import androidx.core.view.accessibility.AccessibilityNodeInfoCompat; +import androidx.customview.widget.ExploreByTouchHelper; + import com.android.launcher3.CellLayout; import com.android.launcher3.Launcher; import com.android.launcher3.R; import java.util.List; -import androidx.core.view.accessibility.AccessibilityNodeInfoCompat; -import androidx.customview.widget.ExploreByTouchHelper; - /** * Helper class to make drag-and-drop in a {@link CellLayout} accessible. */ public abstract class DragAndDropAccessibilityDelegate extends ExploreByTouchHelper - implements OnClickListener { + implements OnClickListener, OnHoverListener { protected static final int INVALID_POSITION = -1; private static final int[] sTempArray = new int[2]; @@ -123,6 +125,11 @@ public abstract class DragAndDropAccessibilityDelegate extends ExploreByTouchHel node.setFocusable(true); } + @Override + public boolean onHover(View view, MotionEvent motionEvent) { + return dispatchHoverEvent(motionEvent); + } + protected abstract String getLocationDescriptionForIconDrop(int id); protected abstract String getConfirmationForIconDrop(int id); diff --git a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java index 0b439ec362..6f7f8e6a29 100644 --- a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java +++ b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java @@ -7,6 +7,7 @@ import static com.android.launcher3.LauncherState.NORMAL; import android.app.AlertDialog; import android.appwidget.AppWidgetProviderInfo; import android.content.DialogInterface; +import android.graphics.Point; import android.graphics.Rect; import android.os.Bundle; import android.os.Handler; @@ -400,11 +401,11 @@ public class LauncherAccessibilityDelegate extends AccessibilityDelegate impleme Rect pos = new Rect(); mLauncher.getDragLayer().getDescendantRectRelativeToSelf(item, pos); - mLauncher.getDragController().prepareAccessibleDrag(pos.centerX(), pos.centerY()); mLauncher.getDragController().addDragListener(this); DragOptions options = new DragOptions(); options.isAccessibleDrag = true; + options.simulatedDndStartPoint = new Point(pos.centerX(), pos.centerY()); ItemLongClickListener.beginDrag(item, mLauncher, info, options); } diff --git a/src/com/android/launcher3/dragndrop/BaseItemDragListener.java b/src/com/android/launcher3/dragndrop/BaseItemDragListener.java index 75693c63d0..9b91a1de67 100644 --- a/src/com/android/launcher3/dragndrop/BaseItemDragListener.java +++ b/src/com/android/launcher3/dragndrop/BaseItemDragListener.java @@ -26,7 +26,6 @@ import android.graphics.Point; import android.graphics.Rect; import android.os.Handler; import android.os.Looper; -import android.os.SystemClock; import android.util.Log; import android.view.DragEvent; import android.view.View; @@ -63,7 +62,6 @@ public abstract class BaseItemDragListener implements View.OnDragListener, DragS protected Launcher mLauncher; private DragController mDragController; - private long mDragStartTime; public BaseItemDragListener(Rect previewRect, int previewBitmapWidth, int previewViewWidth) { mPreviewRect = previewRect; @@ -102,7 +100,7 @@ public abstract class BaseItemDragListener implements View.OnDragListener, DragS return false; } } - return mDragController.onDragEvent(mDragStartTime, event); + return mDragController.onDragEvent(event); } protected boolean onDragStart(DragEvent event) { @@ -118,7 +116,7 @@ public abstract class BaseItemDragListener implements View.OnDragListener, DragS Point downPos = new Point((int) event.getX(), (int) event.getY()); DragOptions options = new DragOptions(); - options.systemDndStartPoint = downPos; + options.simulatedDndStartPoint = downPos; options.preDragCondition = preDragCondition; // We use drag event position as the screenPos for the preview image. Since mPreviewRect @@ -128,7 +126,6 @@ public abstract class BaseItemDragListener implements View.OnDragListener, DragS // to source window. createDragHelper().startDrag(new Rect(mPreviewRect), mPreviewBitmapWidth, mPreviewViewWidth, downPos, this, options); - mDragStartTime = SystemClock.uptimeMillis(); return true; } diff --git a/src/com/android/launcher3/dragndrop/DragController.java b/src/com/android/launcher3/dragndrop/DragController.java index de7bc6d0e2..15397474d4 100644 --- a/src/com/android/launcher3/dragndrop/DragController.java +++ b/src/com/android/launcher3/dragndrop/DragController.java @@ -43,7 +43,6 @@ import com.android.launcher3.R; import com.android.launcher3.WorkspaceItemInfo; import com.android.launcher3.accessibility.DragViewStateAnnouncer; import com.android.launcher3.util.ItemInfoMatcher; -import com.android.launcher3.util.Thunk; import com.android.launcher3.util.TouchController; import com.android.launcher3.util.UiThreadHelper; @@ -61,8 +60,8 @@ public class DragController implements DragDriver.EventListener, TouchController */ private static final int DEEP_PRESS_DISTANCE_FACTOR = 3; - @Thunk Launcher mLauncher; - private FlingToDeleteHelper mFlingToDeleteHelper; + private final Launcher mLauncher; + private final FlingToDeleteHelper mFlingToDeleteHelper; // temporaries to avoid gc thrash private Rect mRectTemp = new Rect(); @@ -77,11 +76,12 @@ public class DragController implements DragDriver.EventListener, TouchController /** Options controlling the drag behavior. */ private DragOptions mOptions; - /** X coordinate of the down event. */ - private int mMotionDownX; + /** Coordinate for motion down event */ + private final Point mMotionDown = new Point(); + /** Coordinate for last touch event **/ + private final Point mLastTouch = new Point(); - /** Y coordinate of the down event. */ - private int mMotionDownY; + private final Point mTmpPoint = new Point(); private DropTarget.DragObject mDragObject; @@ -96,12 +96,9 @@ public class DragController implements DragDriver.EventListener, TouchController private DropTarget mLastDropTarget; - private final int[] mLastTouch = new int[2]; - private long mLastTouchUpTime = -1; private int mLastTouchClassification; private int mDistanceSinceScroll = 0; - private int mTmpPoint[] = new int[2]; private Rect mDragLayerRect = new Rect(); private boolean mIsInPreDrag; @@ -159,13 +156,13 @@ public class DragController implements DragDriver.EventListener, TouchController AbstractFloatingView.closeOpenViews(mLauncher, false, TYPE_DISCOVERY_BOUNCE); mOptions = options; - if (mOptions.systemDndStartPoint != null) { - mLastTouch[0] = mMotionDownX = mOptions.systemDndStartPoint.x; - mLastTouch[1] = mMotionDownY = mOptions.systemDndStartPoint.y; + if (mOptions.simulatedDndStartPoint != null) { + mLastTouch.x = mMotionDown.x = mOptions.simulatedDndStartPoint.x; + mLastTouch.y = mMotionDown.y = mOptions.simulatedDndStartPoint.y; } - final int registrationX = mMotionDownX - dragLayerX; - final int registrationY = mMotionDownY - dragLayerY; + final int registrationX = mMotionDown.x - dragLayerX; + final int registrationY = mMotionDown.y - dragLayerY; final int dragRegionLeft = dragRegion == null ? 0 : dragRegion.left; final int dragRegionTop = dragRegion == null ? 0 : dragRegion.top; @@ -184,17 +181,13 @@ public class DragController implements DragDriver.EventListener, TouchController registrationY, initialDragViewScale, dragViewScaleOnDrop, scaleDps); dragView.setItemInfo(dragInfo); mDragObject.dragComplete = false; - if (mOptions.isAccessibleDrag) { - // For an accessible drag, we assume the view is being dragged from the center. - mDragObject.xOffset = b.getWidth() / 2; - mDragObject.yOffset = b.getHeight() / 2; - mDragObject.accessibleDrag = true; - } else { - mDragObject.xOffset = mMotionDownX - (dragLayerX + dragRegionLeft); - mDragObject.yOffset = mMotionDownY - (dragLayerY + dragRegionTop); - mDragObject.stateAnnouncer = DragViewStateAnnouncer.createFor(dragView); - mDragDriver = DragDriver.create(mLauncher, this, mDragObject, mOptions); + mDragObject.xOffset = mMotionDown.x - (dragLayerX + dragRegionLeft); + mDragObject.yOffset = mMotionDown.y - (dragLayerY + dragRegionTop); + + mDragDriver = DragDriver.create(this, mOptions, mFlingToDeleteHelper::recordMotionEvent); + if (!mOptions.isAccessibleDrag) { + mDragObject.stateAnnouncer = DragViewStateAnnouncer.createFor(dragView); } mDragObject.dragSource = source; @@ -210,7 +203,7 @@ public class DragController implements DragDriver.EventListener, TouchController } mLauncher.getDragLayer().performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); - dragView.show(mLastTouch[0], mLastTouch[1]); + dragView.show(mLastTouch.x, mLastTouch.y); mDistanceSinceScroll = 0; if (!mIsInPreDrag) { @@ -219,7 +212,7 @@ public class DragController implements DragDriver.EventListener, TouchController mOptions.preDragCondition.onPreDragStart(mDragObject); } - handleMoveEvent(mLastTouch[0], mLastTouch[1]); + handleMoveEvent(mLastTouch.x, mLastTouch.y); mLauncher.getUserEventDispatcher().resetActionDurationMillis(); return dragView; } @@ -336,7 +329,7 @@ public class DragController implements DragDriver.EventListener, TouchController } } }; - mDragObject.dragView.animateTo(mMotionDownX, mMotionDownY, onCompleteRunnable, duration); + mDragObject.dragView.animateTo(mMotionDown.x, mMotionDown.y, onCompleteRunnable, duration); } private void callOnDragEnd() { @@ -365,30 +358,17 @@ public class DragController implements DragDriver.EventListener, TouchController /** * Clamps the position to the drag layer bounds. */ - private int[] getClampedDragLayerPos(float x, float y) { + private Point getClampedDragLayerPos(float x, float y) { mLauncher.getDragLayer().getLocalVisibleRect(mDragLayerRect); - mTmpPoint[0] = (int) Math.max(mDragLayerRect.left, Math.min(x, mDragLayerRect.right - 1)); - mTmpPoint[1] = (int) Math.max(mDragLayerRect.top, Math.min(y, mDragLayerRect.bottom - 1)); + 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)); return mTmpPoint; } - public long getLastGestureUpTime() { - if (mDragDriver != null) { - return System.currentTimeMillis(); - } else { - return mLastTouchUpTime; - } - } - - public void resetLastGestureUpTime() { - mLastTouchUpTime = -1; - } - @Override public void onDriverDragMove(float x, float y) { - final int[] dragLayerPos = getClampedDragLayerPos(x, y); - - handleMoveEvent(dragLayerPos[0], dragLayerPos[1]); + Point dragLayerPos = getClampedDragLayerPos(x, y); + handleMoveEvent(dragLayerPos.x, dragLayerPos.y); } @Override @@ -422,53 +402,38 @@ public class DragController implements DragDriver.EventListener, TouchController /** * Call this from a drag source view. */ + @Override public boolean onControllerInterceptTouchEvent(MotionEvent ev) { if (mOptions != null && mOptions.isAccessibleDrag) { return false; } - // Update the velocity tracker - mFlingToDeleteHelper.recordMotionEvent(ev); + Point dragLayerPos = getClampedDragLayerPos(ev.getX(), ev.getY()); + mLastTouch.set(dragLayerPos.x, dragLayerPos.y); + if (ev.getAction() == MotionEvent.ACTION_DOWN) { + // Remember location of down touch + mMotionDown.set(dragLayerPos.x, dragLayerPos.y); + } - final int action = ev.getAction(); - final int[] dragLayerPos = getClampedDragLayerPos(ev.getX(), ev.getY()); - final int dragLayerX = dragLayerPos[0]; - final int dragLayerY = dragLayerPos[1]; - mLastTouch[0] = dragLayerX; - mLastTouch[1] = dragLayerY; if (ATLEAST_Q) { mLastTouchClassification = ev.getClassification(); } - - switch (action) { - case MotionEvent.ACTION_DOWN: - // Remember location of down touch - mMotionDownX = dragLayerX; - mMotionDownY = dragLayerY; - break; - case MotionEvent.ACTION_UP: - mLastTouchUpTime = System.currentTimeMillis(); - break; - } - return mDragDriver != null && mDragDriver.onInterceptTouchEvent(ev); } /** * Call this from a drag source view. */ - public boolean onDragEvent(long dragStartTime, DragEvent event) { - mFlingToDeleteHelper.recordDragEvent(dragStartTime, event); - return mDragDriver != null && mDragDriver.onDragEvent(event); + @Override + public boolean onControllerTouchEvent(MotionEvent ev) { + return mDragDriver != null && mDragDriver.onTouchEvent(ev); } /** - * Call this from a drag view. + * Call this from a drag source view. */ - public void onDragViewAnimationEnd() { - if (mDragDriver != null) { - mDragDriver.onDragViewAnimationEnd(); - } + public boolean onDragEvent(DragEvent event) { + return mDragDriver != null && mDragDriver.onDragEvent(event); } /** @@ -493,9 +458,8 @@ public class DragController implements DragDriver.EventListener, TouchController checkTouchMove(dropTarget); // Check if we are hovering over the scroll areas - mDistanceSinceScroll += Math.hypot(mLastTouch[0] - x, mLastTouch[1] - y); - mLastTouch[0] = x; - mLastTouch[1] = y; + mDistanceSinceScroll += Math.hypot(mLastTouch.x - x, mLastTouch.y - y); + mLastTouch.set(x, y); int distanceDragged = mDistanceSinceScroll; if (ATLEAST_Q && mLastTouchClassification == MotionEvent.CLASSIFICATION_DEEP_PRESS) { @@ -513,7 +477,7 @@ public class DragController implements DragDriver.EventListener, TouchController public void forceTouchMove() { int[] dummyCoordinates = mCoordinatesTemp; - DropTarget dropTarget = findDropTarget(mLastTouch[0], mLastTouch[1], dummyCoordinates); + DropTarget dropTarget = findDropTarget(mLastTouch.x, mLastTouch.y, dummyCoordinates); mDragObject.x = dummyCoordinates[0]; mDragObject.y = dummyCoordinates[1]; checkTouchMove(dropTarget); @@ -536,44 +500,6 @@ public class DragController implements DragDriver.EventListener, TouchController mLastDropTarget = dropTarget; } - /** - * Call this from a drag source view. - */ - public boolean onControllerTouchEvent(MotionEvent ev) { - if (mDragDriver == null || mOptions == null || mOptions.isAccessibleDrag) { - return false; - } - - // Update the velocity tracker - mFlingToDeleteHelper.recordMotionEvent(ev); - - final int action = ev.getAction(); - final int[] dragLayerPos = getClampedDragLayerPos(ev.getX(), ev.getY()); - final int dragLayerX = dragLayerPos[0]; - final int dragLayerY = dragLayerPos[1]; - - switch (action) { - case MotionEvent.ACTION_DOWN: - // Remember where the motion event started - mMotionDownX = dragLayerX; - mMotionDownY = dragLayerY; - break; - } - - return mDragDriver.onTouchEvent(ev); - } - - /** - * Since accessible drag and drop won't cause the same sequence of touch events, we manually - * inject the appropriate state which would have been otherwise initiated via touch events. - */ - public void prepareAccessibleDrag(int x, int y) { - mMotionDownX = x; - mMotionDownY = y; - mLastTouch[0] = x; - mLastTouch[1] = y; - } - /** * As above, since accessible drag and drop won't cause the same sequence of touch events, * we manually ensure appropriate drag and drop events get emulated for accessible drag. diff --git a/src/com/android/launcher3/dragndrop/DragDriver.java b/src/com/android/launcher3/dragndrop/DragDriver.java index 87461d57fe..d4ce3080c0 100644 --- a/src/com/android/launcher3/dragndrop/DragDriver.java +++ b/src/com/android/launcher3/dragndrop/DragDriver.java @@ -16,19 +16,19 @@ package com.android.launcher3.dragndrop; -import android.content.Context; -import android.util.Log; +import android.os.SystemClock; import android.view.DragEvent; import android.view.MotionEvent; -import com.android.launcher3.DropTarget.DragObject; -import com.android.launcher3.testing.TestProtocol; +import java.util.function.Consumer; /** * Base class for driving a drag/drop operation. */ public abstract class DragDriver { + protected final EventListener mEventListener; + protected final Consumer mSecondaryEventConsumer; public interface EventListener { void onDriverDragMove(float x, float y); @@ -37,131 +37,175 @@ public abstract class DragDriver { void onDriverDragCancel(); } - public DragDriver(EventListener eventListener) { + public DragDriver(EventListener eventListener, Consumer sec) { mEventListener = eventListener; + mSecondaryEventConsumer = sec; } /** - * Handles ending of the DragView animation. + * Called to handle system touch event */ - public void onDragViewAnimationEnd() { } - public boolean onTouchEvent(MotionEvent ev) { - final int action = ev.getAction(); - - switch (action) { - case MotionEvent.ACTION_MOVE: - mEventListener.onDriverDragMove(ev.getX(), ev.getY()); - break; - case MotionEvent.ACTION_UP: - mEventListener.onDriverDragMove(ev.getX(), ev.getY()); - mEventListener.onDriverDragEnd(ev.getX(), ev.getY()); - break; - case MotionEvent.ACTION_CANCEL: - mEventListener.onDriverDragCancel(); - break; - } - - return true; + return false; } - public abstract boolean onDragEvent (DragEvent event); - - + /** + * Called to handle system touch intercept event + */ public boolean onInterceptTouchEvent(MotionEvent ev) { - final int action = ev.getAction(); - - switch (action) { - case MotionEvent.ACTION_UP: - mEventListener.onDriverDragEnd(ev.getX(), ev.getY()); - break; - case MotionEvent.ACTION_CANCEL: - mEventListener.onDriverDragCancel(); - break; - } - - return true; + return false; } - public static DragDriver create(Context context, DragController dragController, - DragObject dragObject, DragOptions options) { - if (options.systemDndStartPoint != null) { - return new SystemDragDriver(dragController, context, dragObject); + /** + * Called to handle system drag event + */ + public boolean onDragEvent(DragEvent event) { + return false; + } + + /** + * Created a driver for handing the actual events + */ + public static DragDriver create(DragController dragController, DragOptions options, + Consumer sec) { + if (options.simulatedDndStartPoint != null) { + if (options.isAccessibleDrag) { + return null; + } + return new SystemDragDriver(dragController, sec); } else { - return new InternalDragDriver(dragController); + return new InternalDragDriver(dragController, sec); + } + } + + /** + * Class for driving a system (i.e. framework) drag/drop operation. + */ + static class SystemDragDriver extends DragDriver { + + private final long mDragStartTime; + float mLastX = 0; + float mLastY = 0; + + SystemDragDriver(DragController dragController, Consumer sec) { + super(dragController, sec); + mDragStartTime = SystemClock.uptimeMillis(); + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + return false; + } + + /** + * It creates a temporary {@link MotionEvent} object for secondary consumer + */ + private void simulateSecondaryMotionEvent(DragEvent event) { + final int motionAction; + switch (event.getAction()) { + case DragEvent.ACTION_DRAG_STARTED: + motionAction = MotionEvent.ACTION_DOWN; + break; + case DragEvent.ACTION_DRAG_LOCATION: + motionAction = MotionEvent.ACTION_MOVE; + break; + case DragEvent.ACTION_DRAG_ENDED: + motionAction = MotionEvent.ACTION_UP; + break; + default: + return; + } + MotionEvent emulatedEvent = MotionEvent.obtain(mDragStartTime, + SystemClock.uptimeMillis(), motionAction, event.getX(), event.getY(), 0); + mSecondaryEventConsumer.accept(emulatedEvent); + emulatedEvent.recycle(); + } + + @Override + public boolean onDragEvent(DragEvent event) { + simulateSecondaryMotionEvent(event); + final int action = event.getAction(); + + switch (action) { + case DragEvent.ACTION_DRAG_STARTED: + mLastX = event.getX(); + mLastY = event.getY(); + return true; + + case DragEvent.ACTION_DRAG_ENTERED: + return true; + + case DragEvent.ACTION_DRAG_LOCATION: + mLastX = event.getX(); + mLastY = event.getY(); + mEventListener.onDriverDragMove(event.getX(), event.getY()); + return true; + + case DragEvent.ACTION_DROP: + mLastX = event.getX(); + mLastY = event.getY(); + mEventListener.onDriverDragMove(event.getX(), event.getY()); + mEventListener.onDriverDragEnd(mLastX, mLastY); + return true; + case DragEvent.ACTION_DRAG_EXITED: + mEventListener.onDriverDragExitWindow(); + return true; + + case DragEvent.ACTION_DRAG_ENDED: + mEventListener.onDriverDragCancel(); + return true; + + default: + return false; + } + } + } + + /** + * Class for driving an internal (i.e. not using framework) drag/drop operation. + */ + static class InternalDragDriver extends DragDriver { + InternalDragDriver(DragController dragController, Consumer sec) { + super(dragController, sec); + } + + @Override + public boolean onTouchEvent(MotionEvent ev) { + mSecondaryEventConsumer.accept(ev); + final int action = ev.getAction(); + + switch (action) { + case MotionEvent.ACTION_MOVE: + mEventListener.onDriverDragMove(ev.getX(), ev.getY()); + break; + case MotionEvent.ACTION_UP: + mEventListener.onDriverDragMove(ev.getX(), ev.getY()); + mEventListener.onDriverDragEnd(ev.getX(), ev.getY()); + break; + case MotionEvent.ACTION_CANCEL: + mEventListener.onDriverDragCancel(); + break; + } + + return true; + } + + + public boolean onInterceptTouchEvent(MotionEvent ev) { + mSecondaryEventConsumer.accept(ev); + final int action = ev.getAction(); + + switch (action) { + case MotionEvent.ACTION_UP: + mEventListener.onDriverDragEnd(ev.getX(), ev.getY()); + break; + case MotionEvent.ACTION_CANCEL: + mEventListener.onDriverDragCancel(); + break; + } + return true; } } } -/** - * Class for driving a system (i.e. framework) drag/drop operation. - */ -class SystemDragDriver extends DragDriver { - float mLastX = 0; - float mLastY = 0; - - SystemDragDriver(DragController dragController, Context context, DragObject dragObject) { - super(dragController); - } - - @Override - public boolean onTouchEvent(MotionEvent ev) { - return false; - } - - @Override - public boolean onInterceptTouchEvent(MotionEvent ev) { - return false; - } - - @Override - public boolean onDragEvent (DragEvent event) { - final int action = event.getAction(); - - switch (action) { - case DragEvent.ACTION_DRAG_STARTED: - mLastX = event.getX(); - mLastY = event.getY(); - return true; - - case DragEvent.ACTION_DRAG_ENTERED: - return true; - - case DragEvent.ACTION_DRAG_LOCATION: - mLastX = event.getX(); - mLastY = event.getY(); - mEventListener.onDriverDragMove(event.getX(), event.getY()); - return true; - - case DragEvent.ACTION_DROP: - mLastX = event.getX(); - mLastY = event.getY(); - mEventListener.onDriverDragMove(event.getX(), event.getY()); - mEventListener.onDriverDragEnd(mLastX, mLastY); - return true; - case DragEvent.ACTION_DRAG_EXITED: - mEventListener.onDriverDragExitWindow(); - return true; - - case DragEvent.ACTION_DRAG_ENDED: - mEventListener.onDriverDragCancel(); - return true; - - default: - return false; - } - } -} - -/** - * Class for driving an internal (i.e. not using framework) drag/drop operation. - */ -class InternalDragDriver extends DragDriver { - InternalDragDriver(DragController dragController) { - super(dragController); - } - - @Override - public boolean onDragEvent (DragEvent event) { return false; } -} diff --git a/src/com/android/launcher3/dragndrop/DragOptions.java b/src/com/android/launcher3/dragndrop/DragOptions.java index 2d19f36421..959602beb4 100644 --- a/src/com/android/launcher3/dragndrop/DragOptions.java +++ b/src/com/android/launcher3/dragndrop/DragOptions.java @@ -28,8 +28,11 @@ public class DragOptions { /** Whether or not an accessible drag operation is in progress. */ public boolean isAccessibleDrag = false; - /** Specifies the start location for the system DnD, null when using internal DnD */ - public Point systemDndStartPoint = null; + /** + * Specifies the start location for a simulated DnD (like system drag or accessibility drag), + * null when using internal DnD + */ + public Point simulatedDndStartPoint = null; /** Determines when a pre-drag should transition to a drag. By default, this is immediate. */ public PreDragCondition preDragCondition = null; diff --git a/src/com/android/launcher3/dragndrop/DragView.java b/src/com/android/launcher3/dragndrop/DragView.java index 145885a458..7c76d34e03 100644 --- a/src/com/android/launcher3/dragndrop/DragView.java +++ b/src/com/android/launcher3/dragndrop/DragView.java @@ -19,8 +19,6 @@ package com.android.launcher3.dragndrop; import static com.android.launcher3.Utilities.getBadge; import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; import android.animation.FloatArrayEvaluator; import android.animation.ValueAnimator; import android.animation.ValueAnimator.AnimatorUpdateListener; @@ -146,15 +144,6 @@ public class DragView extends View implements LauncherStateManager.StateListener } }); - mAnim.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - if (!mAnimationCancelled) { - mDragController.onDragViewAnimationEnd(); - } - } - }); - mBitmap = bitmap; setDragRegion(new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight())); diff --git a/src/com/android/launcher3/dragndrop/FlingToDeleteHelper.java b/src/com/android/launcher3/dragndrop/FlingToDeleteHelper.java index 06b5c409de..7788f9366e 100644 --- a/src/com/android/launcher3/dragndrop/FlingToDeleteHelper.java +++ b/src/com/android/launcher3/dragndrop/FlingToDeleteHelper.java @@ -17,8 +17,6 @@ package com.android.launcher3.dragndrop; import android.graphics.PointF; -import android.os.SystemClock; -import android.view.DragEvent; import android.view.MotionEvent; import android.view.VelocityTracker; import android.view.ViewConfiguration; @@ -55,31 +53,6 @@ public class FlingToDeleteHelper { mVelocityTracker.addMovement(ev); } - /** - * Same as {@link #recordMotionEvent}. It creates a temporary {@link MotionEvent} object - * using {@param event} for tracking velocity. - */ - public void recordDragEvent(long dragStartTime, DragEvent event) { - final int motionAction; - switch (event.getAction()) { - case DragEvent.ACTION_DRAG_STARTED: - motionAction = MotionEvent.ACTION_DOWN; - break; - case DragEvent.ACTION_DRAG_LOCATION: - motionAction = MotionEvent.ACTION_MOVE; - break; - case DragEvent.ACTION_DRAG_ENDED: - motionAction = MotionEvent.ACTION_UP; - break; - default: - return; - } - MotionEvent emulatedEvent = MotionEvent.obtain(dragStartTime, SystemClock.uptimeMillis(), - motionAction, event.getX(), event.getY(), 0); - recordMotionEvent(emulatedEvent); - emulatedEvent.recycle(); - } - public void releaseVelocityTracker() { if (mVelocityTracker != null) { mVelocityTracker.recycle(); diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java index 5f496f4aba..c20717af29 100644 --- a/src/com/android/launcher3/folder/Folder.java +++ b/src/com/android/launcher3/folder/Folder.java @@ -85,6 +85,7 @@ import com.android.launcher3.Workspace; import com.android.launcher3.Workspace.ItemOperator; import com.android.launcher3.WorkspaceItemInfo; import com.android.launcher3.accessibility.AccessibleDragListenerAdapter; +import com.android.launcher3.accessibility.FolderAccessibilityHelper; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.dragndrop.DragController; import com.android.launcher3.dragndrop.DragController.DragListener; @@ -271,16 +272,15 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo mDragController.addDragListener(this); if (options.isAccessibleDrag) { mDragController.addDragListener(new AccessibleDragListenerAdapter( - mContent, CellLayout.FOLDER_ACCESSIBILITY_DRAG) { - - @Override - protected void enableAccessibleDrag(boolean enable) { - super.enableAccessibleDrag(enable); - mFooter.setImportantForAccessibility(enable - ? IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS - : IMPORTANT_FOR_ACCESSIBILITY_AUTO); - } - }); + mContent, FolderAccessibilityHelper::new) { + @Override + protected void enableAccessibleDrag(boolean enable) { + super.enableAccessibleDrag(enable); + mFooter.setImportantForAccessibility(enable + ? IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS + : IMPORTANT_FOR_ACCESSIBILITY_AUTO); + } + }); } mLauncher.getWorkspace().beginDragShared(v, this, options); From 4918ed38d2a0b49f83241de2390e720fe29a143d Mon Sep 17 00:00:00 2001 From: Lucas Dupin Date: Tue, 17 Mar 2020 17:11:32 -0700 Subject: [PATCH 09/36] Add wallpaper zoom to areas that have blur Whenever blurring, the wallpaper should also zoom out. This is the mental model of our Depth System. Test: manual Bug: 149792636 Change-Id: I1783eb87fefeb6f917f0ba64f2c6ec8f1f2004fa --- .../res/values/config.xml | 5 +- .../LauncherAppTransitionManagerImpl.java | 2 +- .../states/BackgroundAppState.java | 5 +- .../uioverrides/states/OverviewState.java | 4 +- .../AppToOverviewAnimationProvider.java | 26 ++--- .../quickstep/LauncherActivityInterface.java | 25 +++-- .../quickstep/LauncherSwipeHandler.java | 2 +- .../android/quickstep/RecentsActivity.java | 2 +- .../com/android/quickstep/TaskViewUtils.java | 12 +-- .../quickstep/views/LauncherRecentsView.java | 8 +- .../android/quickstep/views/RecentsView.java | 16 ++-- .../launcher3/BaseQuickstepLauncher.java | 2 +- .../QuickstepAppTransitionManagerImpl.java | 14 +-- ...urController.java => DepthController.java} | 96 +++++++++++-------- .../uioverrides/states/AllAppsState.java | 5 +- .../quickstep/BaseActivityInterface.java | 5 +- src/com/android/launcher3/Launcher.java | 16 ++-- src/com/android/launcher3/LauncherState.java | 10 +- .../folder/FolderAnimationManager.java | 10 -- ...urController.java => DepthController.java} | 22 ++--- 20 files changed, 141 insertions(+), 146 deletions(-) rename quickstep/src/com/android/launcher3/uioverrides/{BackgroundBlurController.java => DepthController.java} (53%) rename src_ui_overrides/com/android/launcher3/uioverrides/{BackgroundBlurController.java => DepthController.java} (66%) diff --git a/quickstep/recents_ui_overrides/res/values/config.xml b/quickstep/recents_ui_overrides/res/values/config.xml index 527eec6457..120e03456f 100644 --- a/quickstep/recents_ui_overrides/res/values/config.xml +++ b/quickstep/recents_ui_overrides/res/values/config.xml @@ -14,8 +14,5 @@ limitations under the License. --> - 150 - 90 - 50 - 20 + 150 \ No newline at end of file diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java index 9afa862a44..0019ecb427 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java @@ -91,7 +91,7 @@ public final class LauncherAppTransitionManagerImpl extends QuickstepAppTransiti AppWindowAnimationHelper helper = new AppWindowAnimationHelper(recentsView.getPagedViewOrientedState(), mLauncher); Animator recentsAnimator = getRecentsWindowAnimator(taskView, skipLauncherChanges, - appTargets, wallpaperTargets, mLauncher.getBackgroundBlurController(), helper); + appTargets, wallpaperTargets, mLauncher.getDepthController(), helper); anim.play(recentsAnimator.setDuration(RECENTS_LAUNCH_DURATION)); Animator childStateAnimation = null; diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java index 5bac964b1a..de3fce13ce 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java @@ -19,7 +19,6 @@ import android.content.Context; import com.android.launcher3.AbstractFloatingView; import com.android.launcher3.Launcher; -import com.android.launcher3.R; import com.android.launcher3.allapps.AllAppsTransitionController; import com.android.launcher3.userevent.nano.LauncherLogProto; import com.android.quickstep.util.LayoutUtils; @@ -107,7 +106,7 @@ public class BackgroundAppState extends OverviewState { } @Override - public int getBackgroundBlurRadius(Context context) { - return context.getResources().getInteger(R.integer.app_background_blur_radius); + public float getDepth(Context context) { + return 1f; } } diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java index 6a34917a26..024872fec4 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java @@ -207,8 +207,8 @@ public class OverviewState extends LauncherState { } @Override - public int getBackgroundBlurRadius(Context context) { - return context.getResources().getInteger(R.integer.overview_background_blur_radius); + public float getDepth(Context context) { + return 1f; } @Override 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 aaf7619dc6..ce7fa0d413 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.BackgroundBlurController.BACKGROUND_BLUR; +import static com.android.launcher3.uioverrides.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.BackgroundBlurController; +import com.android.launcher3.uioverrides.DepthController; import com.android.quickstep.util.AppWindowAnimationHelper; import com.android.quickstep.util.AppWindowAnimationHelper.TransformParams; import com.android.quickstep.util.RemoteAnimationProvider; @@ -105,10 +105,10 @@ final class AppToOverviewAnimationProvider exten mRecentsView.setRunningTaskIconScaledDown(true); } - BackgroundBlurController blurController = mActivityInterface.getBackgroundBlurController(); - if (blurController != null) { + DepthController depthController = mActivityInterface.getDepthController(); + if (depthController != null) { // Update the surface to be the lowest closing app surface - blurController.setSurfaceToLauncher(mRecentsView); + depthController.setSurfaceToLauncher(mRecentsView); } AnimatorSet anim = new AnimatorSet(); @@ -124,7 +124,7 @@ 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(createBackgroundBlurAnimator(blurController)); + .with(createDepthAnimator(depthController)); return anim; } @@ -136,7 +136,7 @@ final class AppToOverviewAnimationProvider exten if (runningTaskTarget == null) { Log.e(TAG, "No closing app"); anim.play(ValueAnimator.ofInt(0, 1).setDuration(RECENTS_LAUNCH_DURATION)) - .with(createBackgroundBlurAnimator(blurController)); + .with(createDepthAnimator(depthController)); return anim; } @@ -184,7 +184,7 @@ final class AppToOverviewAnimationProvider exten }); } anim.play(valueAnimator) - .with(createBackgroundBlurAnimator(blurController)); + .with(createDepthAnimator(depthController)); return anim; } @@ -197,14 +197,14 @@ final class AppToOverviewAnimationProvider exten return RECENTS_LAUNCH_DURATION; } - private Animator createBackgroundBlurAnimator(BackgroundBlurController blurController) { - if (blurController == null) { + private Animator createDepthAnimator(DepthController depthController) { + if (depthController == null) { // Dummy animation return ValueAnimator.ofInt(0); } - return ObjectAnimator.ofInt(blurController, BACKGROUND_BLUR, - BACKGROUND_APP.getBackgroundBlurRadius(mActivity), - OVERVIEW.getBackgroundBlurRadius(mActivity)) + 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 d402a75602..55e6ba2117 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java @@ -56,8 +56,8 @@ import com.android.launcher3.allapps.DiscoveryBounce; import com.android.launcher3.anim.AnimatorPlaybackController; import com.android.launcher3.appprediction.PredictionUiStateManager; import com.android.launcher3.touch.PagedOrientationHandler; -import com.android.launcher3.uioverrides.BackgroundBlurController; -import com.android.launcher3.uioverrides.BackgroundBlurController.ClampedBlurProperty; +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; @@ -330,18 +330,17 @@ public final class LauncherActivityInterface implements BaseActivityInterface } setupRecentsViewUi(); - mActivityInterface.getBackgroundBlurController().setSurfaceToLauncher(mRecentsView); + 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/RecentsActivity.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java index 3ab0f19069..42d944fb78 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java @@ -186,7 +186,7 @@ public final class RecentsActivity extends BaseRecentsActivity { AppWindowAnimationHelper helper = new AppWindowAnimationHelper( mFallbackRecentsView.getPagedViewOrientedState(), this); Animator recentsAnimator = getRecentsWindowAnimator(taskView, !activityClosing, appTargets, - wallpaperTargets, null /* backgroundBlurController */, + wallpaperTargets, null /* depthController */, helper); target.play(recentsAnimator.setDuration(RECENTS_LAUNCH_DURATION)); 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 38b86cea43..7ec083e5df 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.BackgroundBlurController.BACKGROUND_BLUR; +import static com.android.launcher3.uioverrides.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.BackgroundBlurController; +import com.android.launcher3.uioverrides.DepthController; import com.android.quickstep.util.AppWindowAnimationHelper; import com.android.quickstep.util.MultiValueUpdateListener; import com.android.quickstep.views.RecentsView; @@ -123,7 +123,7 @@ public final class TaskViewUtils { public static Animator getRecentsWindowAnimator(TaskView v, boolean skipViewChanges, RemoteAnimationTargetCompat[] appTargets, RemoteAnimationTargetCompat[] wallpaperTargets, - BackgroundBlurController backgroundBlurController, + DepthController depthController, final AppWindowAnimationHelper inOutHelper) { SyncRtSurfaceTransactionApplierCompat applier = new SyncRtSurfaceTransactionApplierCompat(v); @@ -215,9 +215,9 @@ public final class TaskViewUtils { } }); - if (backgroundBlurController != null) { - ObjectAnimator backgroundRadiusAnim = ObjectAnimator.ofInt(backgroundBlurController, - BACKGROUND_BLUR, BACKGROUND_APP.getBackgroundBlurRadius(v.getContext())); + if (depthController != null) { + ObjectAnimator backgroundRadiusAnim = ObjectAnimator.ofFloat(depthController, + DEPTH, BACKGROUND_APP.getDepth(v.getContext())); animatorSet.playTogether(appAnimator, backgroundRadiusAnim); } else { animatorSet.play(appAnimator); 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 3ed7530e6c..a027feaed5 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 @@ -46,9 +46,9 @@ import com.android.launcher3.R; import com.android.launcher3.anim.Interpolators; import com.android.launcher3.appprediction.PredictionUiStateManager; import com.android.launcher3.appprediction.PredictionUiStateManager.Client; -import com.android.launcher3.states.RotationHelper; -import com.android.launcher3.uioverrides.BackgroundBlurController; import com.android.launcher3.config.FeatureFlags; +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; @@ -409,7 +409,7 @@ public class LauncherRecentsView extends RecentsView implements StateL } @Override - protected BackgroundBlurController getBackgroundBlurController() { - return mActivity.getBackgroundBlurController(); + protected DepthController getDepthController() { + return mActivity.getDepthController(); } } 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 e15ac46432..4917cbe17f 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 @@ -29,7 +29,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.BackgroundBlurController.BACKGROUND_BLUR; +import static com.android.launcher3.uioverrides.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; @@ -97,7 +97,7 @@ import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.graphics.RotationMode; import com.android.launcher3.states.RotationHelper; import com.android.launcher3.touch.PagedOrientationHandler.CurveProperties; -import com.android.launcher3.uioverrides.BackgroundBlurController; +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; @@ -1702,11 +1702,11 @@ public abstract class RecentsView extends PagedView impl appWindowAnimationHelper.prepareAnimation(mActivity.getDeviceProfile(), true /* isOpening */); AnimatorSet anim = createAdjacentPageAnimForTaskLaunch(tv, appWindowAnimationHelper); - BackgroundBlurController blurController = getBackgroundBlurController(); - if (blurController != null) { - ObjectAnimator backgroundBlur = ObjectAnimator.ofInt(blurController, BACKGROUND_BLUR, - BACKGROUND_APP.getBackgroundBlurRadius(mActivity)); - anim.play(backgroundBlur); + DepthController depthController = getDepthController(); + if (depthController != null) { + ObjectAnimator depthAnimator = ObjectAnimator.ofFloat(depthController, DEPTH, + BACKGROUND_APP.getDepth(mActivity)); + anim.play(depthAnimator); } anim.play(progressAnim); anim.setDuration(duration).setInterpolator(interpolator); @@ -2009,7 +2009,7 @@ public abstract class RecentsView extends PagedView impl } @Nullable - protected BackgroundBlurController getBackgroundBlurController() { + protected DepthController getDepthController() { return null; } diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java index a7d00c5c72..ec66f115f9 100644 --- a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java +++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java @@ -244,7 +244,7 @@ public abstract class BaseQuickstepLauncher extends Launcher return new StateHandler[] { getAllAppsController(), getWorkspace(), - getBackgroundBlurController(), + getDepthController(), new RecentsViewStateController(this), new BackButtonAlphaHandler(this)}; } diff --git a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java index 1b39242ef4..a366f081e2 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.BackgroundBlurController.BACKGROUND_BLUR; +import static com.android.launcher3.uioverrides.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; @@ -73,7 +73,7 @@ import com.android.launcher3.anim.Interpolators; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.dragndrop.DragLayer; import com.android.launcher3.shortcuts.DeepShortcutView; -import com.android.launcher3.uioverrides.BackgroundBlurController; +import com.android.launcher3.uioverrides.DepthController; import com.android.launcher3.util.DynamicResource; import com.android.launcher3.util.MultiValueAlpha; import com.android.launcher3.util.MultiValueAlpha.AlphaProperty; @@ -596,17 +596,17 @@ public abstract class QuickstepAppTransitionManagerImpl extends LauncherAppTrans // When launching an app from overview that doesn't map to a task, we still want to just // blur the wallpaper instead of the launcher surface as well boolean allowBlurringLauncher = mLauncher.getStateManager().getState() != OVERVIEW; - BackgroundBlurController blurController = mLauncher.getBackgroundBlurController(); - ObjectAnimator backgroundRadiusAnim = ObjectAnimator.ofInt(blurController, BACKGROUND_BLUR, - BACKGROUND_APP.getBackgroundBlurRadius(mLauncher)) + DepthController depthController = mLauncher.getDepthController(); + ObjectAnimator backgroundRadiusAnim = ObjectAnimator.ofFloat(depthController, DEPTH, + BACKGROUND_APP.getDepth(mLauncher)) .setDuration(APP_LAUNCH_DURATION); if (allowBlurringLauncher) { - blurController.setSurfaceToApp(RemoteAnimationProvider.findLowestOpaqueLayerTarget( + depthController.setSurfaceToApp(RemoteAnimationProvider.findLowestOpaqueLayerTarget( appTargets, MODE_OPENING)); backgroundRadiusAnim.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { - blurController.setSurfaceToLauncher(mLauncher.getDragLayer()); + depthController.setSurfaceToLauncher(mLauncher.getDragLayer()); } }); } diff --git a/quickstep/src/com/android/launcher3/uioverrides/BackgroundBlurController.java b/quickstep/src/com/android/launcher3/uioverrides/DepthController.java similarity index 53% rename from quickstep/src/com/android/launcher3/uioverrides/BackgroundBlurController.java rename to quickstep/src/com/android/launcher3/uioverrides/DepthController.java index 513310e945..8995a7e1a0 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/BackgroundBlurController.java +++ b/quickstep/src/com/android/launcher3/uioverrides/DepthController.java @@ -18,7 +18,8 @@ package com.android.launcher3.uioverrides; import static com.android.launcher3.anim.Interpolators.LINEAR; -import android.util.IntProperty; +import android.os.IBinder; +import android.util.FloatProperty; import android.view.View; import com.android.launcher3.Launcher; @@ -31,22 +32,23 @@ import com.android.launcher3.states.StateAnimationConfig; import com.android.systemui.shared.system.RemoteAnimationTargetCompat; import com.android.systemui.shared.system.SurfaceControlCompat; import com.android.systemui.shared.system.TransactionCompat; +import com.android.systemui.shared.system.WallpaperManagerCompat; /** - * Controls the blur, for the Launcher surface only. + * Controls blur and wallpaper zoom, for the Launcher surface only. */ -public class BackgroundBlurController implements LauncherStateManager.StateHandler { +public class DepthController implements LauncherStateManager.StateHandler { - public static final IntProperty BACKGROUND_BLUR = - new IntProperty("backgroundBlur") { + public static final FloatProperty DEPTH = + new FloatProperty("depth") { @Override - public void setValue(BackgroundBlurController blurController, int blurRadius) { - blurController.setBackgroundBlurRadius(blurRadius); + public void setValue(DepthController depthController, float depth) { + depthController.setDepth(depth); } @Override - public Integer get(BackgroundBlurController blurController) { - return blurController.mBackgroundBlurRadius; + public Float get(DepthController depthController) { + return depthController.mDepth; } }; @@ -54,42 +56,50 @@ public class BackgroundBlurController implements LauncherStateManager.StateHandl * A property that updates the background blur within a given range of values (ie. even if the * animator goes beyond 0..1, the interpolated value will still be bounded). */ - public static class ClampedBlurProperty extends IntProperty { - private final int mMinValue; - private final int mMaxValue; + public static class ClampedDepthProperty extends FloatProperty { + private final float mMinValue; + private final float mMaxValue; - public ClampedBlurProperty(int minValue, int maxValue) { - super(("backgroundBlurClamped")); + public ClampedDepthProperty(float minValue, float maxValue) { + super("depthClamped"); mMinValue = minValue; mMaxValue = maxValue; } @Override - public void setValue(BackgroundBlurController blurController, int blurRadius) { - blurController.setBackgroundBlurRadius(Utilities.boundToRange(blurRadius, - mMinValue, mMaxValue)); + public void setValue(DepthController depthController, float depth) { + depthController.setDepth(Utilities.boundToRange(depth, mMinValue, mMaxValue)); } @Override - public Integer get(BackgroundBlurController blurController) { - return blurController.mBackgroundBlurRadius; + public Float get(DepthController depthController) { + return depthController.mDepth; } } private final Launcher mLauncher; + /** + * Blur radius when completely zoomed out, in pixels. + */ + private int mMaxBlurRadius; + private WallpaperManagerCompat mWallpaperManager; private SurfaceControlCompat mSurface; - private int mBackgroundBlurRadius; + /** + * Ratio from 0 to 1, where 0 is fully zoomed out, and 1 is zoomed in. + * @see android.service.wallpaper.WallpaperService.Engine#onZoomChanged(float) + */ + private float mDepth; - public BackgroundBlurController(Launcher l) { + public DepthController(Launcher l) { mLauncher = l; } - /** - * @return the background blur adjustment for folders - */ - public int getFolderBackgroundBlurAdjustment() { - return mLauncher.getResources().getInteger( - R.integer.folder_background_blur_radius_adjustment); + private void ensureDependencies() { + if (mWallpaperManager != null) { + return; + } + mMaxBlurRadius = mLauncher.getResources().getInteger(R.integer.max_depth_blur_radius); + mWallpaperManager = new WallpaperManagerCompat(mLauncher); } /** @@ -112,10 +122,10 @@ public class BackgroundBlurController implements LauncherStateManager.StateHandl if (mSurface != surface) { mSurface = surface; if (surface != null) { - setBackgroundBlurRadius(mBackgroundBlurRadius); + setDepth(mDepth); } else { - // If there is no surface, then reset the blur radius - setBackgroundBlurRadius(0); + // If there is no surface, then reset the ratio + setDepth(0f); } } } @@ -126,9 +136,9 @@ public class BackgroundBlurController implements LauncherStateManager.StateHandl return; } - int toBackgroundBlurRadius = toState.getBackgroundBlurRadius(mLauncher); - if (mBackgroundBlurRadius != toBackgroundBlurRadius) { - setBackgroundBlurRadius(toBackgroundBlurRadius); + float toDepth = toState.getDepth(mLauncher); + if (Float.compare(mDepth, toDepth) != 0) { + setDepth(toDepth); } } @@ -139,22 +149,24 @@ public class BackgroundBlurController implements LauncherStateManager.StateHandl return; } - int toBackgroundBlurRadius = toState.getBackgroundBlurRadius(mLauncher); - if (mBackgroundBlurRadius != toBackgroundBlurRadius) { - animation.setInt(this, BACKGROUND_BLUR, toBackgroundBlurRadius, LINEAR); + float toDepth = toState.getDepth(mLauncher); + if (Float.compare(mDepth, toDepth) != 0) { + animation.setFloat(this, DEPTH, toDepth, LINEAR); } } - private void setBackgroundBlurRadius(int blurRadius) { - // TODO: Do nothing if the shadows are not enabled - // Always update the background blur as it will be reapplied when a surface is next - // available - mBackgroundBlurRadius = blurRadius; + private void setDepth(float depth) { + mDepth = depth; if (mSurface == null || !mSurface.isValid()) { return; } + ensureDependencies(); + IBinder windowToken = mLauncher.getRootView().getWindowToken(); + if (windowToken != null) { + mWallpaperManager.setWallpaperZoomOut(windowToken, mDepth); + } new TransactionCompat() - .setBackgroundBlurRadius(mSurface, blurRadius) + .setBackgroundBlurRadius(mSurface, (int) (mDepth * mMaxBlurRadius)) .apply(); } } diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java b/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java index 971d917445..93e02a111f 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java +++ b/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java @@ -22,7 +22,6 @@ import android.content.Context; import com.android.launcher3.AbstractFloatingView; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherState; -import com.android.launcher3.R; import com.android.launcher3.allapps.AllAppsContainerView; import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType; import com.android.quickstep.SysUINavigationMode; @@ -88,8 +87,8 @@ public class AllAppsState extends LauncherState { } @Override - public int getBackgroundBlurRadius(Context context) { - return context.getResources().getInteger(R.integer.allapps_background_blur_radius); + public float getDepth(Context context) { + return 1f; } @Override diff --git a/quickstep/src/com/android/quickstep/BaseActivityInterface.java b/quickstep/src/com/android/quickstep/BaseActivityInterface.java index be0bdd8cac..2a569f5d35 100644 --- a/quickstep/src/com/android/quickstep/BaseActivityInterface.java +++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java @@ -33,7 +33,7 @@ import com.android.launcher3.BaseDraggingActivity; import com.android.launcher3.DeviceProfile; import com.android.launcher3.anim.AnimatorPlaybackController; import com.android.launcher3.touch.PagedOrientationHandler; -import com.android.launcher3.uioverrides.BackgroundBlurController; +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; @@ -81,7 +81,8 @@ public interface BaseActivityInterface { @Nullable T getCreatedActivity(); - default @Nullable BackgroundBlurController getBackgroundBlurController() { + @Nullable + default DepthController getDepthController() { return null; } diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index 1413a5cd1f..921d34ade8 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -124,7 +124,7 @@ 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.BackgroundBlurController; +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,14 +326,14 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, private boolean mDeferOverlayCallbacks; private final Runnable mDeferredOverlayCallbacks = this::checkIfOverlayStillDeferred; - private BackgroundBlurController mBackgroundBlurController = - new BackgroundBlurController(this); + private DepthController mDepthController = + new DepthController(this); private final ViewTreeObserver.OnDrawListener mOnDrawListener = new ViewTreeObserver.OnDrawListener() { @Override public void onDraw() { - getBackgroundBlurController().setSurfaceToLauncher(mDragLayer); + getDepthController().setSurfaceToLauncher(mDragLayer); mDragLayer.post(() -> mDragLayer.getViewTreeObserver().removeOnDrawListener( this)); } @@ -954,7 +954,7 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, NotificationListener.removeNotificationsChangedListener(); getStateManager().moveToRestState(); - getBackgroundBlurController().setSurfaceToLauncher(null); + getDepthController().setSurfaceToLauncher(null); // Workaround for b/78520668, explicitly trim memory once UI is hidden onTrimMemory(TRIM_MEMORY_UI_HIDDEN); @@ -2708,7 +2708,7 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, protected StateHandler[] createStateHandlers() { return new StateHandler[] { getAllAppsController(), getWorkspace(), - getBackgroundBlurController() }; + getDepthController() }; } public TouchController[] createTouchControllers() { @@ -2745,8 +2745,8 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, return Stream.of(APP_INFO, WIDGETS, INSTALL); } - public BackgroundBlurController getBackgroundBlurController() { - return mBackgroundBlurController; + public DepthController getDepthController() { + return mDepthController; } public static Launcher getLauncher(Context context) { diff --git a/src/com/android/launcher3/LauncherState.java b/src/com/android/launcher3/LauncherState.java index df71f16ec7..6ee82cd509 100644 --- a/src/com/android/launcher3/LauncherState.java +++ b/src/com/android/launcher3/LauncherState.java @@ -271,11 +271,13 @@ public abstract class LauncherState { } /** - * The amount of blur to apply to the background of either the app or Launcher surface in this - * state. + * The amount of blur and wallpaper zoom to apply to the background of either the app + * or Launcher surface in this state. Should be a number between 0 and 1, inclusive. + * + * 0 means completely zoomed in, without blurs. 1 is zoomed out, with blurs. */ - public int getBackgroundBlurRadius(Context context) { - return 0; + public float getDepth(Context context) { + return 0f; } public String getDescription(Launcher launcher) { diff --git a/src/com/android/launcher3/folder/FolderAnimationManager.java b/src/com/android/launcher3/folder/FolderAnimationManager.java index f72e67438b..b83609e680 100644 --- a/src/com/android/launcher3/folder/FolderAnimationManager.java +++ b/src/com/android/launcher3/folder/FolderAnimationManager.java @@ -21,7 +21,6 @@ import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY; import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW; import static com.android.launcher3.graphics.IconShape.getShape; import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound; -import static com.android.launcher3.uioverrides.BackgroundBlurController.BACKGROUND_BLUR; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; @@ -47,7 +46,6 @@ import com.android.launcher3.ShortcutAndWidgetContainer; import com.android.launcher3.Utilities; import com.android.launcher3.anim.PropertyResetListener; import com.android.launcher3.dragndrop.DragLayer; -import com.android.launcher3.uioverrides.BackgroundBlurController; import com.android.launcher3.util.Themes; import java.util.List; @@ -222,14 +220,6 @@ public class FolderAnimationManager { Animator z = getAnimator(mFolder, View.TRANSLATION_Z, -mFolder.getElevation(), 0); play(a, z, mIsOpening ? midDuration : 0, midDuration); - BackgroundBlurController blurController = mLauncher.getBackgroundBlurController(); - int stateBackgroundBlur = mLauncher.getStateManager().getState() - .getBackgroundBlurRadius(mLauncher); - int folderBackgroundBlurAdjustment = blurController.getFolderBackgroundBlurAdjustment(); - play(a, ObjectAnimator.ofInt(blurController, BACKGROUND_BLUR, mIsOpening - ? stateBackgroundBlur + folderBackgroundBlurAdjustment - : stateBackgroundBlur)); - // Store clip variables CellLayout cellLayout = mContent.getCurrentCellLayout(); boolean folderClipChildren = mFolder.getClipChildren(); diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/BackgroundBlurController.java b/src_ui_overrides/com/android/launcher3/uioverrides/DepthController.java similarity index 66% rename from src_ui_overrides/com/android/launcher3/uioverrides/BackgroundBlurController.java rename to src_ui_overrides/com/android/launcher3/uioverrides/DepthController.java index 75f99a9ee9..7ad85e22c3 100644 --- a/src_ui_overrides/com/android/launcher3/uioverrides/BackgroundBlurController.java +++ b/src_ui_overrides/com/android/launcher3/uioverrides/DepthController.java @@ -17,7 +17,7 @@ package com.android.launcher3.uioverrides; -import android.util.IntProperty; +import android.util.FloatProperty; import android.view.View; import com.android.launcher3.Launcher; @@ -27,26 +27,22 @@ import com.android.launcher3.anim.PendingAnimation; import com.android.launcher3.states.StateAnimationConfig; /** - * Controls the blur, for the Launcher surface only. + * Controls blur and wallpaper zoom, for the Launcher surface only. */ -public class BackgroundBlurController implements LauncherStateManager.StateHandler { +public class DepthController implements LauncherStateManager.StateHandler { - public static final IntProperty BACKGROUND_BLUR = - new IntProperty("backgroundBlur") { + public static final FloatProperty DEPTH = + new FloatProperty("depth") { @Override - public void setValue(BackgroundBlurController blurController, int blurRadius) {} + public void setValue(DepthController depthController, float depth) {} @Override - public Integer get(BackgroundBlurController blurController) { - return 0; + public Float get(DepthController depthController) { + return 0f; } }; - public BackgroundBlurController(Launcher l) {} - - public int getFolderBackgroundBlurAdjustment() { - return 0; - } + public DepthController(Launcher l) {} public void setSurfaceToLauncher(View v) {} From b6fe161f585a030a3397fe32abb07c4cddf787f4 Mon Sep 17 00:00:00 2001 From: Sunny Goyal Date: Wed, 18 Mar 2020 14:00:48 -0700 Subject: [PATCH 10/36] Adding additional logs to debug synchronous modification of update listeners Bug: 151665474 Change-Id: I6f2d900852bd8dc76249781ab15cb6c269dd7a7a --- src/com/android/launcher3/allapps/AllAppsStore.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/com/android/launcher3/allapps/AllAppsStore.java b/src/com/android/launcher3/allapps/AllAppsStore.java index c4b2f68c94..a6ef10a8f9 100644 --- a/src/com/android/launcher3/allapps/AllAppsStore.java +++ b/src/com/android/launcher3/allapps/AllAppsStore.java @@ -18,6 +18,7 @@ package com.android.launcher3.allapps; import static com.android.launcher3.AppInfo.COMPONENT_KEY_COMPARATOR; import static com.android.launcher3.AppInfo.EMPTY_ARRAY; +import android.util.Log; import android.view.View; import android.view.ViewGroup; @@ -55,6 +56,8 @@ public class AllAppsStore { private int mDeferUpdatesFlags = 0; private boolean mUpdatePending = false; + private boolean mListenerUpdateInProgress = false; + public AppInfo[] getApps() { return mApps; } @@ -99,10 +102,12 @@ public class AllAppsStore { mUpdatePending = true; return; } + mListenerUpdateInProgress = true; int count = mUpdateListeners.size(); for (int i = 0; i < count; i++) { mUpdateListeners.get(i).onAppsUpdated(); } + mListenerUpdateInProgress = false; } public void addUpdateListener(OnUpdateListener listener) { @@ -110,6 +115,9 @@ public class AllAppsStore { } public void removeUpdateListener(OnUpdateListener listener) { + if (mListenerUpdateInProgress) { + Log.e("AllAppsStore", "Trying to remove listener during update", new Exception()); + } mUpdateListeners.remove(listener); } From 42255d22a7bc08d4739c5521aaaedd5b90346d9c Mon Sep 17 00:00:00 2001 From: Tracy Zhou Date: Fri, 13 Mar 2020 00:38:11 -0700 Subject: [PATCH 11/36] Set default value of gridName to current grid name in GridOptionsProvider With grid preview, a grid name is passed in when requesting preview under different grid setting. With wallpaper preview, no such grid name will be passed in, and it should be set to the current. Bug: 145242344 Test: N/A Change-Id: I282cb5341b7f3756d41c4abd8d97f986abaa6d27 --- .../launcher3/uioverrides/PreviewSurfaceRenderer.java | 5 ++++- src/com/android/launcher3/InvariantDeviceProfile.java | 10 +++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/quickstep/src/com/android/launcher3/uioverrides/PreviewSurfaceRenderer.java b/quickstep/src/com/android/launcher3/uioverrides/PreviewSurfaceRenderer.java index 548223a8fe..c7cce0b563 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/PreviewSurfaceRenderer.java +++ b/quickstep/src/com/android/launcher3/uioverrides/PreviewSurfaceRenderer.java @@ -32,8 +32,11 @@ public class PreviewSurfaceRenderer { /** Handle a received surface view request. */ public static void render(Context context, Bundle bundle) { - final String gridName = bundle.getString("name"); + String gridName = bundle.getString("name"); bundle.remove("name"); + if (gridName == null) { + gridName = InvariantDeviceProfile.getCurrentGridName(context); + } final InvariantDeviceProfile idp = new InvariantDeviceProfile(context, gridName); MAIN_EXECUTOR.execute(() -> { diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java index 2ad84b9f9c..7414a88586 100644 --- a/src/com/android/launcher3/InvariantDeviceProfile.java +++ b/src/com/android/launcher3/InvariantDeviceProfile.java @@ -162,9 +162,7 @@ public class InvariantDeviceProfile { "PreviewContext is passed into this IDP constructor"); } - String gridName = Utilities.getPrefs(context).getBoolean(GRID_OPTIONS_PREFERENCE_KEY, false) - ? Utilities.getPrefs(context).getString(KEY_IDP_GRID_NAME, null) - : null; + String gridName = getCurrentGridName(context); initGrid(context, gridName); mConfigMonitor = new ConfigMonitor(context, APPLY_CONFIG_AT_RUNTIME.get() ? this::onConfigChanged : this::killProcess); @@ -188,6 +186,12 @@ public class InvariantDeviceProfile { initGrid(context, null, new Info(display)); } + public static String getCurrentGridName(Context context) { + return Utilities.getPrefs(context).getBoolean(GRID_OPTIONS_PREFERENCE_KEY, false) + ? Utilities.getPrefs(context).getString(KEY_IDP_GRID_NAME, null) + : null; + } + /** * Retrieve system defined or RRO overriden icon shape. */ From 739a66eb8c2e043fce7b0ad4b46c2d4b6d91388a Mon Sep 17 00:00:00 2001 From: Becky Qiu Date: Wed, 18 Mar 2020 12:34:10 -0700 Subject: [PATCH 12/36] [Overview Actions] Hide task view footers when it's modal. Demo: https://drive.google.com/a/google.com/file/d/1sXfHG0aMe7OzmIx7uZLAincvg6KizCLj/view?usp=sharing Test: local Bug: 151745339 Change-Id: Icf1263b87dbada2025a2d494bf1f2f067584f8c4 --- .../com/android/quickstep/views/TaskView.java | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java index c94b56cade..56e3632dcd 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java @@ -247,8 +247,17 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable { /** Updates UI based on whether the task is modal. */ public void updateUiForModalTask() { + boolean isOverlayModal = isTaskOverlayModal(); if (getRecentsView() != null) { - getRecentsView().updateUiForModalTask(this, isTaskOverlayModal()); + getRecentsView().updateUiForModalTask(this, isOverlayModal); + } + // Hide footers when overlay is modal. + if (isOverlayModal) { + for (FooterWrapper footer : mFooters) { + if (footer != null) { + footer.animateHide(); + } + } } } @@ -780,6 +789,22 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable { animator.setDuration(100); animator.start(); } + + void animateHide() { + ValueAnimator animator = ValueAnimator.ofFloat(0.0f, 1.0f); + animator.addUpdateListener(anim -> { + mFooterVerticalOffset = anim.getAnimatedFraction(); + updateFooterOffset(); + }); + animator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + removeView(mView); + } + }); + animator.setDuration(100); + animator.start(); + } } @Override From 351eaedc9191acf6cb5a9ed73c12e124db0f825d Mon Sep 17 00:00:00 2001 From: Samuel Fufa Date: Wed, 18 Mar 2020 16:22:57 -0700 Subject: [PATCH 13/36] Final strings for work profile and hybrid hotseat Bug: 151831282 Bug: 142753423 Change-Id: I11dd21c3854b8b33fc44b2e49ca0e62c24da3310 --- .../hybridhotseat/HotseatEduDialog.java | 17 ------------- quickstep/res/values/strings.xml | 25 ++++++++----------- res/layout/work_tab_footer.xml | 1 + res/values/strings.xml | 17 ++++++------- .../launcher3/views/WorkFooterContainer.java | 2 -- 5 files changed, 19 insertions(+), 43 deletions(-) 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 538b7f3952..7986c269bc 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 @@ -35,7 +35,6 @@ import com.android.launcher3.Insettable; import com.android.launcher3.Launcher; import com.android.launcher3.R; import com.android.launcher3.WorkspaceItemInfo; -import com.android.launcher3.WorkspaceLayoutManager; import com.android.launcher3.anim.Interpolators; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.logging.UserEventDispatcher; @@ -54,9 +53,6 @@ public class HotseatEduDialog extends AbstractSlideInView implements Insettable private static final int DEFAULT_CLOSE_DURATION = 200; protected static final int FINAL_SCRIM_BG_COLOR = 0x88000000; - // We don't migrate if user has more than SAME_PAGE_MAX_ROWS rows of item in their screen - private static final int SAME_PAGE_MAX_ROWS = 2; - private static final int MIGRATE_SAME_PAGE = 0; private static final int MIGRATE_NEW_PAGE = 1; private static final int MIGRATE_NO_MIGRATE = 2; @@ -228,19 +224,6 @@ public class HotseatEduDialog extends AbstractSlideInView implements Insettable if (FeatureFlags.HOTSEAT_MIGRATE_NEW_PAGE.get()) { mEduContent.setText(R.string.hotseat_edu_message_migrate_alt); mMigrationMode = MIGRATE_NEW_PAGE; - return; - } - CellLayout page = mLauncher.getWorkspace().getScreenWithId( - WorkspaceLayoutManager.FIRST_SCREEN_ID); - - int maxItemsOnPage = SAME_PAGE_MAX_ROWS * mLauncher.getDeviceProfile().inv.numColumns - + (FeatureFlags.QSB_ON_FIRST_SCREEN ? 1 : 0); - if (page.getShortcutsAndWidgets().getChildCount() > maxItemsOnPage - || !page.makeSpaceForHotseatMigration(false)) { - mMigrationMode = MIGRATE_NO_MIGRATE; - mEduContent.setText(R.string.hotseat_edu_message_no_migrate); - mEduHeading.setText(R.string.hotseat_edu_title_no_migrate); - mDismissBtn.setVisibility(GONE); } } diff --git a/quickstep/res/values/strings.xml b/quickstep/res/values/strings.xml index 90d42456d2..b55b042dda 100644 --- a/quickstep/res/values/strings.xml +++ b/quickstep/res/values/strings.xml @@ -68,30 +68,25 @@ - Get app suggestions based on your routines + Easily access your most-used apps - Tap to set up - + Pixel predicts apps you\’ll need next, right on your Home screen. Tap to set up. - Suggested apps replace the bottom row of apps - Your hotseat items will be moved up on the homescreen - Your hotseat items will be moved to the last page of your workspace + Get app suggestions on the bottom row of your Home screen - - - Suggested apps will be found at the bottom row of your home screen - Drag one or many apps off the bottom row of home screen to see app suggestions + 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. - Bottom row of apps moved up. - Bottom row of apps moved to last page. + Your hotseat items have been moved up to your homescreen + Your hotseat items have been moved to a folder - Bottom row won\'t be replaced. Manually drag apps for predictions. + Drag apps off the bottom row to see app suggestions - Got it + Get app suggestions - No thanks + No thanks diff --git a/res/layout/work_tab_footer.xml b/res/layout/work_tab_footer.xml index dbcdbdb88f..264e27375c 100644 --- a/res/layout/work_tab_footer.xml +++ b/res/layout/work_tab_footer.xml @@ -41,6 +41,7 @@ android:lines="1" android:minHeight="24dp" android:paddingEnd="12dp" + android:text="@string/work_profile_toggle_label" android:textSize="16sp"/> Work profile - Personal apps are private & can\'t be seen by IT + Personal data is separate & hidden from work apps - Work apps are badged & visible to IT + Work apps & data are visible to your IT admin Next Got it - - Work apps: On - - Work apps: Paused + + Work profile is paused + + Work apps can\’t send you notifications, use your battery, or access your location - Work apps are paused - You won\'t get any work notifications, and your IT admin can\'t see your location + + Pause work apps and notifications Failed: %1$s diff --git a/src/com/android/launcher3/views/WorkFooterContainer.java b/src/com/android/launcher3/views/WorkFooterContainer.java index 9ac82301eb..d86d0ffd89 100644 --- a/src/com/android/launcher3/views/WorkFooterContainer.java +++ b/src/com/android/launcher3/views/WorkFooterContainer.java @@ -112,8 +112,6 @@ public class WorkFooterContainer extends LinearLayout implements Insettable { boolean anyProfileQuietModeEnabled = UserCache.INSTANCE.get( getContext()).isAnyProfileQuietModeEnabled(); - mWorkModeLabel.setText(anyProfileQuietModeEnabled - ? R.string.work_mode_off_label : R.string.work_mode_on_label); mWorkModeLabel.setCompoundDrawablesWithIntrinsicBounds( anyProfileQuietModeEnabled ? R.drawable.ic_corp_off : R.drawable.ic_corp, 0, 0, 0); mWorkModeSwitch.refresh(); From 14b60d77b7a6d35237229b05f5996089d2bff825 Mon Sep 17 00:00:00 2001 From: vadimt Date: Mon, 2 Mar 2020 18:05:58 -0800 Subject: [PATCH 14/36] Unlocking device from the test This is a workaround for the infra not unlocking the device properly. I don't have access to may machine, so I've created this CL in Gerrit editor, didn't test, but it should work. Bug: 151613234 --- .../com/android/launcher3/ui/AbstractLauncherUiTest.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java index 3d12248493..535abbf341 100644 --- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java +++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java @@ -102,6 +102,7 @@ public abstract class AbstractLauncherUiTest { private static String sDetectedActivityLeak; private static boolean sActivityLeakReported; + private static final String SYSTEMUI_PACKAGE = "com.android.systemui"; protected LooperExecutor mMainThreadExecutor = MAIN_EXECUTOR; protected final UiDevice mDevice = UiDevice.getInstance(getInstrumentation()); @@ -213,8 +214,16 @@ public abstract class AbstractLauncherUiTest { return mDevice; } + private boolean hasSystemUiObject(String resId) { + return mDevice.hasObject(By.res(SYSTEMUI_PACKAGE, resId)); + } + @Before public void setUp() throws Exception { + if (hasSystemUiObject("keyguard_status_view")) { + mDevice.executeShellCommand("input keyevent 82"); + } + final String launcherPackageName = mDevice.getLauncherPackageName(); try { final Context context = InstrumentationRegistry.getContext(); From 42e7c503e1a9f3e8496698935d3a160e7fdd05ab Mon Sep 17 00:00:00 2001 From: Jon Miranda Date: Thu, 19 Mar 2020 11:58:58 -0700 Subject: [PATCH 15/36] Polish app open scrim animation. We used to hide the scrim animation immediately, now we fade it out along with the rest of the draglayer. Also uses staggered workspace animation when resuming launcher from other means ie. back gesture -> home Bug: 145242991 Change-Id: Ib2161746f79ae5eaceac02d1416aa9bd7ecfeac4 --- .../QuickstepAppTransitionManagerImpl.java | 106 +++++++----------- .../graphics/WorkspaceAndHotseatScrim.java | 8 -- 2 files changed, 38 insertions(+), 76 deletions(-) diff --git a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java index a366f081e2..c93a4ba566 100644 --- a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java +++ b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java @@ -69,7 +69,6 @@ import androidx.annotation.Nullable; import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener; import com.android.launcher3.allapps.AllAppsTransitionController; -import com.android.launcher3.anim.Interpolators; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.dragndrop.DragLayer; import com.android.launcher3.shortcuts.DeepShortcutView; @@ -387,18 +386,35 @@ public abstract class QuickstepAppTransitionManagerImpl extends LauncherAppTrans alpha.setInterpolator(LINEAR); launcherAnimator.play(alpha); - mDragLayer.setTranslationY(trans[0]); - ObjectAnimator transY = ObjectAnimator.ofFloat(mDragLayer, View.TRANSLATION_Y, trans); - transY.setInterpolator(AGGRESSIVE_EASE); - transY.setDuration(CONTENT_TRANSLATION_DURATION); - launcherAnimator.play(transY); + Workspace workspace = mLauncher.getWorkspace(); + View currentPage = ((CellLayout) workspace.getChildAt(workspace.getCurrentPage())) + .getShortcutsAndWidgets(); + View hotseat = mLauncher.getHotseat(); + View qsb = mLauncher.findViewById(R.id.search_container_all_apps); + + currentPage.setLayerType(View.LAYER_TYPE_HARDWARE, null); + hotseat.setLayerType(View.LAYER_TYPE_HARDWARE, null); + qsb.setLayerType(View.LAYER_TYPE_HARDWARE, null); + + launcherAnimator.play(ObjectAnimator.ofFloat(currentPage, View.TRANSLATION_Y, trans)); + launcherAnimator.play(ObjectAnimator.ofFloat(hotseat, View.TRANSLATION_Y, trans)); + launcherAnimator.play(ObjectAnimator.ofFloat(qsb, View.TRANSLATION_Y, trans)); - mDragLayer.getScrim().hideSysUiScrim(true); // Pause page indicator animations as they lead to layer trashing. mLauncher.getWorkspace().getPageIndicator().pauseAnimations(); - mDragLayer.setLayerType(View.LAYER_TYPE_HARDWARE, null); - endListener = this::resetContentView; + endListener = () -> { + currentPage.setTranslationY(0); + hotseat.setTranslationY(0); + qsb.setTranslationY(0); + + currentPage.setLayerType(View.LAYER_TYPE_NONE, null); + hotseat.setLayerType(View.LAYER_TYPE_NONE, null); + qsb.setLayerType(View.LAYER_TYPE_NONE, null); + + mDragLayerAlpha.setValue(1f); + mLauncher.getWorkspace().getPageIndicator().skipAnimationsToEnd(); + }; } return new Pair<>(launcherAnimator, endListener); } @@ -760,62 +776,6 @@ public abstract class QuickstepAppTransitionManagerImpl extends LauncherAppTrans return closingAnimator; } - /** - * Creates an animator that modifies Launcher as a result from - * {@link #createWallpaperOpenRunner}. - */ - private void createLauncherResumeAnimation(AnimatorSet anim) { - if (mLauncher.isInState(LauncherState.ALL_APPS)) { - Pair contentAnimator = - getLauncherContentAnimator(false /* isAppOpening */, - new float[] {-mContentTransY, 0}); - contentAnimator.first.setStartDelay(LAUNCHER_RESUME_START_DELAY); - anim.play(contentAnimator.first); - anim.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - contentAnimator.second.run(); - } - }); - } else { - AnimatorSet workspaceAnimator = new AnimatorSet(); - - mDragLayer.setTranslationY(-mWorkspaceTransY);; - workspaceAnimator.play(ObjectAnimator.ofFloat(mDragLayer, View.TRANSLATION_Y, - -mWorkspaceTransY, 0)); - - mDragLayerAlpha.setValue(0); - workspaceAnimator.play(ObjectAnimator.ofFloat( - mDragLayerAlpha, MultiValueAlpha.VALUE, 0, 1f)); - - workspaceAnimator.setStartDelay(LAUNCHER_RESUME_START_DELAY); - workspaceAnimator.setDuration(333); - workspaceAnimator.setInterpolator(Interpolators.DEACCEL_1_7); - - mDragLayer.getScrim().hideSysUiScrim(true); - - // Pause page indicator animations as they lead to layer trashing. - mLauncher.getWorkspace().getPageIndicator().pauseAnimations(); - mDragLayer.setLayerType(View.LAYER_TYPE_HARDWARE, null); - - workspaceAnimator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - resetContentView(); - } - }); - anim.play(workspaceAnimator); - } - } - - private void resetContentView() { - mLauncher.getWorkspace().getPageIndicator().skipAnimationsToEnd(); - mDragLayerAlpha.setValue(1f); - mDragLayer.setLayerType(View.LAYER_TYPE_NONE, null); - mDragLayer.setTranslationY(0f); - mDragLayer.getScrim().hideSysUiScrim(false); - } - private boolean hasControlRemoteAppTransitionPermission() { return mLauncher.checkSelfPermission(CONTROL_REMOTE_APP_TRANSITION_PERMISSION) == PackageManager.PERMISSION_GRANTED; @@ -888,15 +848,25 @@ public abstract class QuickstepAppTransitionManagerImpl extends LauncherAppTrans // Only register the content animation for cancellation when state changes mLauncher.getStateManager().setCurrentAnimation(anim); - if (mFromUnlock) { + if (mLauncher.isInState(LauncherState.ALL_APPS)) { + Pair contentAnimator = + getLauncherContentAnimator(false /* isAppOpening */, + new float[] {-mContentTransY, 0}); + contentAnimator.first.setStartDelay(LAUNCHER_RESUME_START_DELAY); + anim.play(contentAnimator.first); + anim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + contentAnimator.second.run(); + } + }); + } else { float velocityDpPerS = DynamicResource.provider(mLauncher) .getDimension(R.dimen.unlock_staggered_velocity_dp_per_s); float velocityPxPerS = TypedValue.applyDimension(COMPLEX_UNIT_DIP, velocityDpPerS, mLauncher.getResources().getDisplayMetrics()); anim.play(new StaggeredWorkspaceAnim(mLauncher, velocityPxPerS, false) .getAnimators()); - } else { - createLauncherResumeAnimation(anim); } } } diff --git a/src/com/android/launcher3/graphics/WorkspaceAndHotseatScrim.java b/src/com/android/launcher3/graphics/WorkspaceAndHotseatScrim.java index 2c7f89185c..b289c0b501 100644 --- a/src/com/android/launcher3/graphics/WorkspaceAndHotseatScrim.java +++ b/src/com/android/launcher3/graphics/WorkspaceAndHotseatScrim.java @@ -236,14 +236,6 @@ public class WorkspaceAndHotseatScrim extends Scrim { } } - public void hideSysUiScrim(boolean hideSysUiScrim) { - mHideSysUiScrim = hideSysUiScrim || (mTopScrim == null); - if (!hideSysUiScrim) { - mAnimateScrimOnNextDraw = true; - } - invalidate(); - } - private void setSysUiProgress(float progress) { if (progress != mSysUiProgress) { mSysUiProgress = progress; From 6feb4b638ea8d0e54fbc00ae4202fd00566d7370 Mon Sep 17 00:00:00 2001 From: Sunny Goyal Date: Thu, 19 Mar 2020 12:00:02 -0700 Subject: [PATCH 16/36] Fixing taskView is recycled with visibility gone when dismissed Bug: 151883828 Change-Id: I89d6ed78e1713b1e69e8c7075c638692902f5842 --- .../src/com/android/quickstep/views/RecentsView.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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 4917cbe17f..11a88aff4d 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 @@ -19,6 +19,7 @@ package com.android.quickstep.views; import static com.android.launcher3.BaseActivity.STATE_HANDLER_INVISIBILITY_FLAGS; import static com.android.launcher3.InvariantDeviceProfile.CHANGE_FLAG_ICON_PARAMS; import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY; +import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA; import static com.android.launcher3.LauncherState.BACKGROUND_APP; import static com.android.launcher3.Utilities.EDGE_NAV_BAR; import static com.android.launcher3.Utilities.squaredHypot; @@ -1162,7 +1163,9 @@ public abstract class RecentsView extends PagedView impl } private void addDismissedTaskAnimations(View taskView, long duration, PendingAnimation anim) { - anim.setViewAlpha(taskView, 0, ACCEL_2); + // Use setFloat instead of setViewAlpha as we want to keep the view visible even when it's + // alpha is set to 0 so that it can be recycled in the view pool properly + anim.setFloat(taskView, VIEW_ALPHA, 0, ACCEL_2); FloatProperty secondaryViewTranslate = mOrientationHandler.getSecondaryViewTranslate(); int secondaryTaskDimension = mOrientationHandler.getSecondaryDimension(taskView); From d8070756f1e9dbada59d2c8d896cd415686f18ad Mon Sep 17 00:00:00 2001 From: vadimt Date: Mon, 2 Mar 2020 18:05:58 -0800 Subject: [PATCH 17/36] Making sure the device doesn't lock during test execution Unlocking the device at the beginning of the test does help, but perhaps we should prevent locking during the test execution. Some failures still remain. Bug: 151613234 --- .../src/com/android/launcher3/ui/AbstractLauncherUiTest.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java index 535abbf341..f5dd995fc9 100644 --- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java +++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java @@ -220,8 +220,12 @@ public abstract class AbstractLauncherUiTest { @Before public void setUp() throws Exception { + mDevice.executeShellCommand("settings put global stay_on_while_plugged_in 3"); if (hasSystemUiObject("keyguard_status_view")) { + Log.d(TAG, "Before unlocking the phone"); mDevice.executeShellCommand("input keyevent 82"); + } else { + Log.d(TAG, "Phone isn't locked"); } final String launcherPackageName = mDevice.getLauncherPackageName(); From 445de0e579a95b2cef792ae1e12113b7f3f82fe6 Mon Sep 17 00:00:00 2001 From: Becky Qiu Date: Thu, 19 Mar 2020 15:58:42 -0700 Subject: [PATCH 18/36] [Overview Actions] Hide overview actions when swipe down to launch app. Test: demo video: https://drive.google.com/a/google.com/file/d/122DNdDhInp5KALSOcfNLhl19eDm4zKcf/view?usp=sharing Bug: 151928222 Change-Id: I837df884c78b297dbddf770e526340cb83530984 --- .../src/com/android/quickstep/views/RecentsView.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) 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 8322d8c754..681d822c1b 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 @@ -68,7 +68,6 @@ import android.view.HapticFeedbackConstants; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.MotionEvent; -import android.view.OrientationEventListener; import android.view.View; import android.view.ViewDebug; import android.view.ViewGroup; @@ -88,7 +87,6 @@ import com.android.launcher3.LauncherState; import com.android.launcher3.PagedView; import com.android.launcher3.R; import com.android.launcher3.Utilities; -import com.android.launcher3.anim.AnimationSuccessListener; import com.android.launcher3.anim.AnimatorPlaybackController; import com.android.launcher3.anim.PendingAnimation; import com.android.launcher3.anim.PendingAnimation.EndState; @@ -96,7 +94,6 @@ import com.android.launcher3.anim.PropertyListBuilder; import com.android.launcher3.anim.SpringProperty; import com.android.launcher3.compat.AccessibilityManagerCompat; import com.android.launcher3.config.FeatureFlags; -import com.android.launcher3.dragndrop.DragLayer; import com.android.launcher3.graphics.RotationMode; import com.android.launcher3.states.RotationHelper; import com.android.launcher3.touch.PagedOrientationHandler.CurveProperties; @@ -720,6 +717,9 @@ public abstract class RecentsView extends PagedView impl for (int i = 0; i < taskCount; i++) { getTaskViewAt(i).setFullscreenProgress(mFullscreenProgress); } + if (mActionsView != null) { + mActionsView.setVisibility(fullscreenProgress == 0 ? VISIBLE : INVISIBLE); + } } private void updateTaskStackListenerState() { @@ -2029,7 +2029,6 @@ public abstract class RecentsView extends PagedView impl void onEmptyMessageUpdated(boolean isEmpty); } - private static class PinnedStackAnimationListener extends IPinnedStackAnimationListener.Stub { private T mActivity; From d6f917f1824ed97e6f9a225fe1600a8162922d31 Mon Sep 17 00:00:00 2001 From: Tony Wickham Date: Thu, 19 Mar 2020 13:19:31 -0700 Subject: [PATCH 19/36] Support blacklisting live wallpapers from showing sysui scrims Add wallpaper changed broadcast receiver to BaseDragLayer, which checks if the new wallpaper is blacklisted and relays that to remove the scrims. Bug: 150144115 Change-Id: I55b7b98fdd419cd76532492461a872367efed67b --- .../quickstep/fallback/RecentsRootView.java | 3 +- res/values/config.xml | 3 + .../launcher3/dragndrop/DragLayer.java | 2 +- .../graphics/WorkspaceAndHotseatScrim.java | 11 ++-- .../launcher3/views/BaseDragLayer.java | 59 +++++++++++++++++++ 5 files changed, 72 insertions(+), 6 deletions(-) diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsRootView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsRootView.java index 2c5d6318d1..7f5ec9bc12 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsRootView.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsRootView.java @@ -81,7 +81,8 @@ public class RecentsRootView extends BaseDragLayer { if (!insets.equals(mInsets)) { super.setInsets(insets); } - setBackground(insets.top == 0 ? null + setBackground(insets.top == 0 || !mAllowSysuiScrims + ? null : Themes.getAttrDrawable(getContext(), R.attr.workspaceStatusBarScrim)); } diff --git a/res/values/config.xml b/res/values/config.xml index 6c239bd8a6..1675a986d3 100644 --- a/res/values/config.xml +++ b/res/values/config.xml @@ -176,4 +176,7 @@ @dimen/swipe_up_fling_min_visible_change @dimen/swipe_up_y_overshoot + + + diff --git a/src/com/android/launcher3/dragndrop/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java index 369bf283b1..9d07595f56 100644 --- a/src/com/android/launcher3/dragndrop/DragLayer.java +++ b/src/com/android/launcher3/dragndrop/DragLayer.java @@ -559,7 +559,7 @@ public class DragLayer extends BaseDragLayer { @Override public void setInsets(Rect insets) { super.setInsets(insets); - mWorkspaceScrim.onInsetsChanged(insets); + mWorkspaceScrim.onInsetsChanged(insets, mAllowSysuiScrims); mOverviewScrim.onInsetsChanged(insets); } diff --git a/src/com/android/launcher3/graphics/WorkspaceAndHotseatScrim.java b/src/com/android/launcher3/graphics/WorkspaceAndHotseatScrim.java index 2c7f89185c..3702ac9d82 100644 --- a/src/com/android/launcher3/graphics/WorkspaceAndHotseatScrim.java +++ b/src/com/android/launcher3/graphics/WorkspaceAndHotseatScrim.java @@ -194,10 +194,13 @@ public class WorkspaceAndHotseatScrim extends Scrim { return anim; } - public void onInsetsChanged(Rect insets) { - mDrawTopScrim = mTopScrim != null && insets.top > 0; - mDrawBottomScrim = mBottomMask != null && - !mLauncher.getDeviceProfile().isVerticalBarLayout(); + /** + * Determines whether to draw the top and/or bottom scrim based on new insets. + */ + public void onInsetsChanged(Rect insets, boolean allowSysuiScrims) { + mDrawTopScrim = allowSysuiScrims && mTopScrim != null && insets.top > 0; + mDrawBottomScrim = allowSysuiScrims && mBottomMask != null + && !mLauncher.getDeviceProfile().isVerticalBarLayout(); } @Override diff --git a/src/com/android/launcher3/views/BaseDragLayer.java b/src/com/android/launcher3/views/BaseDragLayer.java index 25748ae538..868c91d4ac 100644 --- a/src/com/android/launcher3/views/BaseDragLayer.java +++ b/src/com/android/launcher3/views/BaseDragLayer.java @@ -23,7 +23,11 @@ import static android.view.MotionEvent.ACTION_UP; import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs; import android.annotation.TargetApi; +import android.app.WallpaperInfo; +import android.app.WallpaperManager; +import android.content.ComponentName; import android.content.Context; +import android.content.Intent; import android.graphics.Insets; import android.graphics.Rect; import android.graphics.RectF; @@ -38,11 +42,15 @@ import android.view.WindowInsets; import android.view.accessibility.AccessibilityEvent; import android.widget.FrameLayout; +import androidx.annotation.Nullable; + import com.android.launcher3.AbstractFloatingView; import com.android.launcher3.InsettableFrameLayout; +import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.util.MultiValueAlpha; import com.android.launcher3.util.MultiValueAlpha.AlphaProperty; +import com.android.launcher3.util.SimpleBroadcastReceiver; import com.android.launcher3.util.TouchController; import java.io.PrintWriter; @@ -98,6 +106,10 @@ public abstract class BaseDragLayer protected final T mActivity; private final MultiValueAlpha mMultiValueAlpha; + private final WallpaperManager mWallpaperManager; + private final SimpleBroadcastReceiver mWallpaperChangeReceiver = + new SimpleBroadcastReceiver(this::onWallpaperChanged); + private final String[] mWallpapersWithoutSysuiScrims; // All the touch controllers for the view protected TouchController[] mControllers; @@ -108,10 +120,15 @@ public abstract class BaseDragLayer private TouchCompleteListener mTouchCompleteListener; + protected boolean mAllowSysuiScrims = true; + public BaseDragLayer(Context context, AttributeSet attrs, int alphaChannelCount) { super(context, attrs); mActivity = (T) ActivityContext.lookupContext(context); mMultiValueAlpha = new MultiValueAlpha(this, alphaChannelCount); + mWallpaperManager = context.getSystemService(WallpaperManager.class); + mWallpapersWithoutSysuiScrims = getResources().getStringArray( + R.array.live_wallpapers_remove_sysui_scrims); } /** @@ -517,4 +534,46 @@ public abstract class BaseDragLayer } return super.dispatchApplyWindowInsets(insets); } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + mWallpaperChangeReceiver.register(mActivity, Intent.ACTION_WALLPAPER_CHANGED); + onWallpaperChanged(null); + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + mActivity.unregisterReceiver(mWallpaperChangeReceiver); + } + + private void onWallpaperChanged(Intent unusedBroadcastIntent) { + WallpaperInfo newWallpaperInfo = mWallpaperManager.getWallpaperInfo(); + boolean oldAllowSysuiScrims = mAllowSysuiScrims; + mAllowSysuiScrims = computeAllowSysuiScrims(newWallpaperInfo); + if (mAllowSysuiScrims != oldAllowSysuiScrims) { + // Reapply insets so scrim can be removed or re-added if necessary. + setInsets(mInsets); + } + } + + /** + * Determines whether we can scrim the status bar and nav bar for the given wallpaper by + * checking against a list of live wallpapers that we don't show the scrims on. + */ + private boolean computeAllowSysuiScrims(@Nullable WallpaperInfo newWallpaperInfo) { + if (newWallpaperInfo == null) { + // New wallpaper is static, not live. Thus, blacklist isn't applicable. + return true; + } + ComponentName newWallpaper = newWallpaperInfo.getComponent(); + for (String wallpaperWithoutScrim : mWallpapersWithoutSysuiScrims) { + if (newWallpaper.equals(ComponentName.unflattenFromString(wallpaperWithoutScrim))) { + // New wallpaper is blacklisted from showing a scrim. + return false; + } + } + return true; + } } From 3e34dd3791b8aaa508bc270807af585826330965 Mon Sep 17 00:00:00 2001 From: Andy Wickham Date: Wed, 18 Mar 2020 21:47:46 +0000 Subject: [PATCH 20/36] Adds back gesture recognition to Sandbox. This is used to step through the "happy path" of the existing back tutorial steps (right edge and left edge). Unlike in the Pixel Tips app, the tutorial only continues if you perform the gesture from the correct edge of the screen. This also lays the groundwork to provide helpful tips if the expected gesture is performed incorrectly, although currently such gestures are just ignored. Demo: https://drive.google.com/open?id=12S42mZQITGzIWnj7mP1L__PCOgAQcjgp Bug: 148542211 Change-Id: Ib2e0b2ff7c021db48c96d58e1370fa2e93330912 --- .../layout/back_gesture_tutorial_fragment.xml | 2 - quickstep/res/values/colors.xml | 18 + .../BackGestureTutorialController.java | 29 +- .../BackGestureTutorialFragment.java | 33 +- .../interaction/EdgeBackGestureHandler.java | 274 +++++++ .../interaction/EdgeBackGesturePanel.java | 701 ++++++++++++++++++ .../interaction/GestureSandboxActivity.java | 23 +- .../launcher3/util/VibratorWrapper.java | 2 +- 8 files changed, 1053 insertions(+), 29 deletions(-) create mode 100644 quickstep/res/values/colors.xml create mode 100644 quickstep/src/com/android/quickstep/interaction/EdgeBackGestureHandler.java create mode 100644 quickstep/src/com/android/quickstep/interaction/EdgeBackGesturePanel.java diff --git a/quickstep/res/layout/back_gesture_tutorial_fragment.xml b/quickstep/res/layout/back_gesture_tutorial_fragment.xml index 294e46e431..d8c25bd4d0 100644 --- a/quickstep/res/layout/back_gesture_tutorial_fragment.xml +++ b/quickstep/res/layout/back_gesture_tutorial_fragment.xml @@ -16,9 +16,7 @@ - + + + #99000000 + \ No newline at end of file diff --git a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java index 3fe91a3a8b..5c2e9928a8 100644 --- a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java +++ b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java @@ -25,6 +25,7 @@ import android.widget.TextView; import com.android.launcher3.R; import com.android.quickstep.interaction.BackGestureTutorialFragment.TutorialStep; import com.android.quickstep.interaction.BackGestureTutorialFragment.TutorialType; +import com.android.quickstep.interaction.EdgeBackGestureHandler.BackGestureResult; import java.util.Optional; @@ -79,21 +80,33 @@ abstract class BackGestureTutorialController { mHandCoachingAnimation.stop(); } - void onGestureDetected() { - hideHandCoachingAnimation(); - - if (mTutorialStep == TutorialStep.CONFIRM) { + void onGestureAttempted(BackGestureResult result) { + if (mTutorialStep == TutorialStep.CONFIRM + && (result == BackGestureResult.BACK_COMPLETED_FROM_LEFT + || result == BackGestureResult.BACK_COMPLETED_FROM_RIGHT)) { mFragment.closeTutorial(); return; } - if (mTutorialTypeInfo.get().getTutorialType() == TutorialType.RIGHT_EDGE_BACK_NAVIGATION) { - mFragment.changeController(TutorialStep.ENGAGED, - TutorialType.LEFT_EDGE_BACK_NAVIGATION); + if (!mTutorialTypeInfo.isPresent()) { return; } - mFragment.changeController(TutorialStep.CONFIRM); + switch (mTutorialTypeInfo.get().getTutorialType()) { + case RIGHT_EDGE_BACK_NAVIGATION: + if (result == BackGestureResult.BACK_COMPLETED_FROM_RIGHT) { + hideHandCoachingAnimation(); + mFragment.changeController( + TutorialStep.ENGAGED, TutorialType.LEFT_EDGE_BACK_NAVIGATION); + } + break; + case LEFT_EDGE_BACK_NAVIGATION: + if (result == BackGestureResult.BACK_COMPLETED_FROM_LEFT) { + hideHandCoachingAnimation(); + mFragment.changeController(TutorialStep.CONFIRM); + } + break; + } } abstract Optional getTitleStringId(); diff --git a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialFragment.java b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialFragment.java index 54408ceb6e..593b6952dd 100644 --- a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialFragment.java +++ b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialFragment.java @@ -17,21 +17,26 @@ package com.android.quickstep.interaction; import android.content.ActivityNotFoundException; import android.content.Intent; +import android.graphics.Insets; import android.os.Bundle; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.view.WindowInsets; +import androidx.annotation.NonNull; import androidx.fragment.app.Fragment; import com.android.launcher3.R; +import com.android.quickstep.interaction.EdgeBackGestureHandler.BackGestureAttemptCallback; +import com.android.quickstep.interaction.EdgeBackGestureHandler.BackGestureResult; import java.net.URISyntaxException; import java.util.Optional; /** Shows the Back gesture interactive tutorial. */ -public class BackGestureTutorialFragment extends Fragment { +public class BackGestureTutorialFragment extends Fragment implements BackGestureAttemptCallback { private static final String LOG_TAG = "TutorialFragment"; private static final String KEY_TUTORIAL_STEP = "tutorialStep"; @@ -47,6 +52,7 @@ public class BackGestureTutorialFragment extends Fragment { private Optional mTutorialController = Optional.empty(); private View mRootView; private BackGestureTutorialHandAnimation mHandCoachingAnimation; + private EdgeBackGestureHandler mEdgeBackGestureHandler; public static BackGestureTutorialFragment newInstance( TutorialStep tutorialStep, TutorialType tutorialType) { @@ -64,17 +70,25 @@ public class BackGestureTutorialFragment extends Fragment { Bundle args = savedInstanceState != null ? savedInstanceState : getArguments(); mTutorialStep = (TutorialStep) args.getSerializable(KEY_TUTORIAL_STEP); mTutorialType = (TutorialType) args.getSerializable(KEY_TUTORIAL_TYPE); + mEdgeBackGestureHandler = new EdgeBackGestureHandler(getContext()); + mEdgeBackGestureHandler.registerBackGestureAttemptCallback(this); } @Override public View onCreateView( - LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + @NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { super.onCreateView(inflater, container, savedInstanceState); mRootView = inflater.inflate(R.layout.back_gesture_tutorial_fragment, container, /* attachToRoot= */ false); mRootView.findViewById(R.id.back_gesture_tutorial_fragment_close_button) .setOnClickListener(this::onCloseButtonClicked); + mRootView.setOnApplyWindowInsetsListener((view, insets) -> { + Insets systemInsets = insets.getInsets(WindowInsets.Type.systemBars()); + mEdgeBackGestureHandler.setInsets(systemInsets.left, systemInsets.right); + return insets; + }); + mRootView.setOnTouchListener(mEdgeBackGestureHandler); mHandCoachingAnimation = new BackGestureTutorialHandAnimation(getContext(), mRootView); return mRootView; @@ -92,6 +106,14 @@ public class BackGestureTutorialFragment extends Fragment { mHandCoachingAnimation.stop(); } + void onAttachedToWindow() { + mEdgeBackGestureHandler.setIsEnabled(true); + } + + void onDetachedFromWindow() { + mEdgeBackGestureHandler.setIsEnabled(false); + } + @Override public void onSaveInstanceState(Bundle savedInstanceState) { savedInstanceState.putSerializable(KEY_TUTORIAL_STEP, mTutorialStep); @@ -125,10 +147,9 @@ public class BackGestureTutorialFragment extends Fragment { this.mTutorialType = tutorialType; } - void onBackPressed() { - if (mTutorialController.isPresent()) { - mTutorialController.get().onGestureDetected(); - } + @Override + public void onBackGestureAttempted(BackGestureResult result) { + mTutorialController.ifPresent(controller -> controller.onGestureAttempted(result)); } void closeTutorial() { diff --git a/quickstep/src/com/android/quickstep/interaction/EdgeBackGestureHandler.java b/quickstep/src/com/android/quickstep/interaction/EdgeBackGestureHandler.java new file mode 100644 index 0000000000..04cd2f49d6 --- /dev/null +++ b/quickstep/src/com/android/quickstep/interaction/EdgeBackGestureHandler.java @@ -0,0 +1,274 @@ +/* + * 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.interaction; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.PixelFormat; +import android.graphics.Point; +import android.graphics.PointF; +import android.hardware.display.DisplayManager; +import android.hardware.display.DisplayManager.DisplayListener; +import android.os.Handler; +import android.os.Looper; +import android.os.SystemProperties; +import android.view.Display; +import android.view.MotionEvent; +import android.view.View; +import android.view.View.OnTouchListener; +import android.view.ViewConfiguration; +import android.view.WindowManager; +import android.view.WindowManager.LayoutParams; + +import com.android.launcher3.ResourceUtils; + +/** + * Utility class to handle edge swipes for back gestures. + * + * Forked from platform/frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java. + */ +public class EdgeBackGestureHandler implements DisplayListener, OnTouchListener { + + private static final String TAG = "EdgeBackGestureHandler"; + private static final int MAX_LONG_PRESS_TIMEOUT = SystemProperties.getInt( + "gestures.back_timeout", 250); + + private final Context mContext; + + private final Point mDisplaySize = new Point(); + private final int mDisplayId; + + // The edge width where touch down is allowed + private int mEdgeWidth; + // The bottom gesture area height + private int mBottomGestureHeight; + // The slop to distinguish between horizontal and vertical motion + private final float mTouchSlop; + // Duration after which we consider the event as longpress. + private final int mLongPressTimeout; + + private final PointF mDownPoint = new PointF(); + private boolean mThresholdCrossed = false; + private boolean mAllowGesture = false; + private boolean mIsEnabled; + private int mLeftInset; + private int mRightInset; + + private EdgeBackGesturePanel mEdgeBackPanel; + private BackGestureAttemptCallback mGestureCallback; + + private final EdgeBackGesturePanel.BackCallback mBackCallback = + new EdgeBackGesturePanel.BackCallback() { + @Override + public void triggerBack() { + if (mGestureCallback != null) { + mGestureCallback.onBackGestureAttempted(mEdgeBackPanel.getIsLeftPanel() + ? BackGestureResult.BACK_COMPLETED_FROM_LEFT + : BackGestureResult.BACK_COMPLETED_FROM_RIGHT); + } + } + + @Override + public void cancelBack() { + if (mGestureCallback != null) { + mGestureCallback.onBackGestureAttempted(mEdgeBackPanel.getIsLeftPanel() + ? BackGestureResult.BACK_CANCELLED_FROM_LEFT + : BackGestureResult.BACK_CANCELLED_FROM_RIGHT); + } + } + }; + + EdgeBackGestureHandler(Context context) { + final Resources res = context.getResources(); + mContext = context; + mDisplayId = context.getDisplay() == null + ? Display.DEFAULT_DISPLAY : context.getDisplay().getDisplayId(); + + mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); + mLongPressTimeout = Math.min(MAX_LONG_PRESS_TIMEOUT, + ViewConfiguration.getLongPressTimeout()); + + mBottomGestureHeight = + ResourceUtils.getNavbarSize(ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE, res); + mEdgeWidth = ResourceUtils.getNavbarSize("config_backGestureInset", res); + } + + void setIsEnabled(boolean isEnabled) { + if (isEnabled == mIsEnabled) { + return; + } + mIsEnabled = isEnabled; + + if (mEdgeBackPanel != null) { + mEdgeBackPanel.onDestroy(); + mEdgeBackPanel = null; + } + + if (!mIsEnabled) { + mContext.getSystemService(DisplayManager.class).unregisterDisplayListener(this); + } else { + updateDisplaySize(); + mContext.getSystemService(DisplayManager.class).registerDisplayListener(this, + new Handler(Looper.getMainLooper())); + + // Add a nav bar panel window. + mEdgeBackPanel = new EdgeBackGesturePanel(mContext); + mEdgeBackPanel.setBackCallback(mBackCallback); + mEdgeBackPanel.setLayoutParams(createLayoutParams()); + updateDisplaySize(); + } + } + + void registerBackGestureAttemptCallback(BackGestureAttemptCallback callback) { + mGestureCallback = callback; + } + + private WindowManager.LayoutParams createLayoutParams() { + Resources resources = mContext.getResources(); + WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams( + ResourceUtils.getNavbarSize("navigation_edge_panel_width", resources), + ResourceUtils.getNavbarSize("navigation_edge_panel_height", resources), + LayoutParams.TYPE_APPLICATION_PANEL, + WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE + | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL + | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH + | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN, + PixelFormat.TRANSLUCENT); + layoutParams.setTitle(TAG + mDisplayId); + layoutParams.windowAnimations = 0; + layoutParams.setFitInsetsTypes(0 /* types */); + return layoutParams; + } + + @Override + public boolean onTouch(View view, MotionEvent motionEvent) { + if (mIsEnabled) { + onMotionEvent(motionEvent); + return true; + } + return false; + } + + private boolean isWithinTouchRegion(int x, int y) { + // Disallow if too far from the edge + if (x > mEdgeWidth + mLeftInset && x < (mDisplaySize.x - mEdgeWidth - mRightInset)) { + return false; + } + + // Disallow if we are in the bottom gesture area + if (y >= (mDisplaySize.y - mBottomGestureHeight)) { + return false; + } + + return true; + } + + private void cancelGesture(MotionEvent ev) { + // Send action cancel to reset all the touch events + mAllowGesture = false; + MotionEvent cancelEv = MotionEvent.obtain(ev); + cancelEv.setAction(MotionEvent.ACTION_CANCEL); + mEdgeBackPanel.onMotionEvent(cancelEv); + cancelEv.recycle(); + } + + private void onMotionEvent(MotionEvent ev) { + int action = ev.getActionMasked(); + if (action == MotionEvent.ACTION_DOWN) { + boolean isOnLeftEdge = ev.getX() <= mEdgeWidth + mLeftInset; + mAllowGesture = isWithinTouchRegion((int) ev.getX(), (int) ev.getY()); + if (mAllowGesture) { + mEdgeBackPanel.setIsLeftPanel(isOnLeftEdge); + mEdgeBackPanel.onMotionEvent(ev); + + mDownPoint.set(ev.getX(), ev.getY()); + mThresholdCrossed = false; + } + } else if (mAllowGesture) { + if (!mThresholdCrossed) { + if (action == MotionEvent.ACTION_POINTER_DOWN) { + // We do not support multi touch for back gesture + cancelGesture(ev); + return; + } else if (action == MotionEvent.ACTION_MOVE) { + if ((ev.getEventTime() - ev.getDownTime()) > mLongPressTimeout) { + cancelGesture(ev); + return; + } + float dx = Math.abs(ev.getX() - mDownPoint.x); + float dy = Math.abs(ev.getY() - mDownPoint.y); + if (dy > dx && dy > mTouchSlop) { + cancelGesture(ev); + return; + + } else if (dx > dy && dx > mTouchSlop) { + mThresholdCrossed = true; + } + } + + } + + // forward touch + mEdgeBackPanel.onMotionEvent(ev); + } + + if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) { + if (!mAllowGesture && mGestureCallback != null) { + mGestureCallback.onBackGestureAttempted(BackGestureResult.BACK_NOT_STARTED); + } + } + } + + @Override + public void onDisplayAdded(int displayId) { } + + @Override + public void onDisplayRemoved(int displayId) { } + + @Override + public void onDisplayChanged(int displayId) { + if (displayId == mDisplayId) { + updateDisplaySize(); + } + } + + private void updateDisplaySize() { + mContext.getDisplay().getRealSize(mDisplaySize); + if (mEdgeBackPanel != null) { + mEdgeBackPanel.setDisplaySize(mDisplaySize); + } + } + + void setInsets(int leftInset, int rightInset) { + mLeftInset = leftInset; + mRightInset = rightInset; + } + + enum BackGestureResult { + UNKNOWN, + BACK_COMPLETED_FROM_LEFT, + BACK_COMPLETED_FROM_RIGHT, + BACK_CANCELLED_FROM_LEFT, + BACK_CANCELLED_FROM_RIGHT, + BACK_NOT_STARTED, + } + + /** Callback to let the UI react to attempted back gestures. */ + interface BackGestureAttemptCallback { + /** Called whenever any touch is completed. */ + void onBackGestureAttempted(BackGestureResult result); + } +} diff --git a/quickstep/src/com/android/quickstep/interaction/EdgeBackGesturePanel.java b/quickstep/src/com/android/quickstep/interaction/EdgeBackGesturePanel.java new file mode 100644 index 0000000000..34eeafc676 --- /dev/null +++ b/quickstep/src/com/android/quickstep/interaction/EdgeBackGesturePanel.java @@ -0,0 +1,701 @@ +/* + * Copyright (C) 2019 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.interaction; + +import android.animation.ValueAnimator; +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.Point; +import android.os.SystemClock; +import android.view.Gravity; +import android.view.MotionEvent; +import android.view.VelocityTracker; +import android.view.View; +import android.view.WindowManager; +import android.view.animation.Interpolator; +import android.view.animation.PathInterpolator; + +import androidx.core.math.MathUtils; +import androidx.dynamicanimation.animation.DynamicAnimation; +import androidx.dynamicanimation.animation.FloatPropertyCompat; +import androidx.dynamicanimation.animation.SpringAnimation; +import androidx.dynamicanimation.animation.SpringForce; + +import com.android.launcher3.R; +import com.android.launcher3.ResourceUtils; +import com.android.launcher3.anim.Interpolators; +import com.android.launcher3.util.VibratorWrapper; + +/** Forked from platform/frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarEdgePanel.java. */ +public class EdgeBackGesturePanel extends View { + + private static final String LOG_TAG = "EdgeBackGesturePanel"; + + private static final long DISAPPEAR_FADE_ANIMATION_DURATION_MS = 80; + private static final long DISAPPEAR_ARROW_ANIMATION_DURATION_MS = 100; + + /** + * The time required since the first vibration effect to automatically trigger a click + */ + private static final int GESTURE_DURATION_FOR_CLICK_MS = 400; + + /** + * The basic translation in dp where the arrow resides + */ + private static final int BASE_TRANSLATION_DP = 32; + + /** + * The length of the arrow leg measured from the center to the end + */ + private static final int ARROW_LENGTH_DP = 18; + + /** + * The angle measured from the xAxis, where the leg is when the arrow rests + */ + private static final int ARROW_ANGLE_WHEN_EXTENDED_DEGREES = 56; + + /** + * The angle that is added per 1000 px speed to the angle of the leg + */ + private static final int ARROW_ANGLE_ADDED_PER_1000_SPEED = 4; + + /** + * The maximum angle offset allowed due to speed + */ + private static final int ARROW_MAX_ANGLE_SPEED_OFFSET_DEGREES = 4; + + /** + * The thickness of the arrow. Adjusted to match the home handle (approximately) + */ + private static final float ARROW_THICKNESS_DP = 2.5f; + + /** + * The amount of rubber banding we do for the vertical translation + */ + private static final int RUBBER_BAND_AMOUNT = 15; + + /** + * The interpolator used to rubberband + */ + private static final Interpolator RUBBER_BAND_INTERPOLATOR = + new PathInterpolator(1.0f / 5.0f, 1.0f, 1.0f, 1.0f); + + /** + * The amount of rubber banding we do for the translation before base translation + */ + private static final int RUBBER_BAND_AMOUNT_APPEAR = 4; + + /** + * The interpolator used to rubberband the appearing of the arrow. + */ + private static final Interpolator RUBBER_BAND_INTERPOLATOR_APPEAR = + new PathInterpolator(1.0f / RUBBER_BAND_AMOUNT_APPEAR, 1.0f, 1.0f, 1.0f); + + private final WindowManager mWindowManager; + private BackCallback mBackCallback; + + /** + * The paint the arrow is drawn with + */ + private final Paint mPaint = new Paint(); + + private final float mDensity; + private final float mBaseTranslation; + private final float mArrowLength; + private final float mArrowThickness; + + /** + * The minimum delta needed in movement for the arrow to change direction / stop triggering back + */ + private final float mMinDeltaForSwitch; + // The closest to y = 0 that the arrow will be displayed. + private int mMinArrowPosition; + // The amount the arrow is shifted to avoid the finger. + private int mFingerOffset; + + private final float mSwipeThreshold; + private final Path mArrowPath = new Path(); + private final Point mDisplaySize = new Point(); + + private final SpringAnimation mAngleAnimation; + private final SpringAnimation mTranslationAnimation; + private final SpringAnimation mVerticalTranslationAnimation; + private final SpringForce mAngleAppearForce; + private final SpringForce mAngleDisappearForce; + private final ValueAnimator mArrowDisappearAnimation; + private final SpringForce mRegularTranslationSpring; + private final SpringForce mTriggerBackSpring; + + private VelocityTracker mVelocityTracker; + private int mArrowPaddingEnd; + private WindowManager.LayoutParams mLayoutParams; + + /** + * True if the panel is currently on the left of the screen + */ + private boolean mIsLeftPanel; + + private float mStartX; + private float mStartY; + private float mCurrentAngle; + /** + * The current translation of the arrow + */ + private float mCurrentTranslation; + /** + * Where the arrow will be in the resting position. + */ + private float mDesiredTranslation; + + private boolean mDragSlopPassed; + private boolean mArrowsPointLeft; + private float mMaxTranslation; + private boolean mTriggerBack; + private float mPreviousTouchTranslation; + private float mTotalTouchDelta; + private float mVerticalTranslation; + private float mDesiredVerticalTranslation; + private float mDesiredAngle; + private float mAngleOffset; + private float mDisappearAmount; + private long mVibrationTime; + private int mScreenSize; + + private final DynamicAnimation.OnAnimationEndListener mSetGoneEndListener = + new DynamicAnimation.OnAnimationEndListener() { + @Override + public void onAnimationEnd( + DynamicAnimation animation, boolean canceled, float value, float velocity) { + animation.removeEndListener(this); + if (!canceled) { + setVisibility(GONE); + } + } + }; + + private static final FloatPropertyCompat CURRENT_ANGLE = + new FloatPropertyCompat("currentAngle") { + @Override + public void setValue(EdgeBackGesturePanel object, float value) { + object.setCurrentAngle(value); + } + + @Override + public float getValue(EdgeBackGesturePanel object) { + return object.getCurrentAngle(); + } + }; + + private static final FloatPropertyCompat CURRENT_TRANSLATION = + new FloatPropertyCompat("currentTranslation") { + @Override + public void setValue(EdgeBackGesturePanel object, float value) { + object.setCurrentTranslation(value); + } + + @Override + public float getValue(EdgeBackGesturePanel object) { + return object.getCurrentTranslation(); + } + }; + + private static final FloatPropertyCompat CURRENT_VERTICAL_TRANSLATION = + new FloatPropertyCompat("verticalTranslation") { + + @Override + public void setValue(EdgeBackGesturePanel object, float value) { + object.setVerticalTranslation(value); + } + + @Override + public float getValue(EdgeBackGesturePanel object) { + return object.getVerticalTranslation(); + } + }; + + public EdgeBackGesturePanel(Context context) { + super(context); + + mWindowManager = context.getSystemService(WindowManager.class); + + mDensity = context.getResources().getDisplayMetrics().density; + + mBaseTranslation = dp(BASE_TRANSLATION_DP); + mArrowLength = dp(ARROW_LENGTH_DP); + mArrowThickness = dp(ARROW_THICKNESS_DP); + mMinDeltaForSwitch = dp(32); + + mPaint.setStrokeWidth(mArrowThickness); + mPaint.setStrokeCap(Paint.Cap.ROUND); + mPaint.setAntiAlias(true); + mPaint.setStyle(Paint.Style.STROKE); + mPaint.setStrokeJoin(Paint.Join.ROUND); + + mArrowDisappearAnimation = ValueAnimator.ofFloat(0.0f, 1.0f); + mArrowDisappearAnimation.setDuration(DISAPPEAR_ARROW_ANIMATION_DURATION_MS); + mArrowDisappearAnimation.setInterpolator(Interpolators.FAST_OUT_SLOW_IN); + mArrowDisappearAnimation.addUpdateListener(animation -> { + mDisappearAmount = (float) animation.getAnimatedValue(); + invalidate(); + }); + + mAngleAnimation = + new SpringAnimation(this, CURRENT_ANGLE); + mAngleAppearForce = new SpringForce() + .setStiffness(500) + .setDampingRatio(0.5f); + mAngleDisappearForce = new SpringForce() + .setStiffness(SpringForce.STIFFNESS_MEDIUM) + .setDampingRatio(SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY) + .setFinalPosition(90); + mAngleAnimation.setSpring(mAngleAppearForce).setMaxValue(90); + + mTranslationAnimation = + new SpringAnimation(this, CURRENT_TRANSLATION); + mRegularTranslationSpring = new SpringForce() + .setStiffness(SpringForce.STIFFNESS_MEDIUM) + .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY); + mTriggerBackSpring = new SpringForce() + .setStiffness(450) + .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY); + mTranslationAnimation.setSpring(mRegularTranslationSpring); + mVerticalTranslationAnimation = + new SpringAnimation(this, CURRENT_VERTICAL_TRANSLATION); + mVerticalTranslationAnimation.setSpring( + new SpringForce() + .setStiffness(SpringForce.STIFFNESS_MEDIUM) + .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY)); + mPaint.setColor(context.getColor(R.color.back_arrow_color_dark)); + loadDimens(); + updateArrowDirection(); + + mSwipeThreshold = ResourceUtils.getDimenByName( + "navigation_edge_action_drag_threshold", context.getResources(), 16 /* defaultValue */); + setVisibility(GONE); + } + + void onDestroy() { + mWindowManager.removeView(this); + } + + @Override + public boolean hasOverlappingRendering() { + return false; + } + + @SuppressLint("RtlHardcoded") + void setIsLeftPanel(boolean isLeftPanel) { + mIsLeftPanel = isLeftPanel; + mLayoutParams.gravity = mIsLeftPanel + ? (Gravity.LEFT | Gravity.TOP) + : (Gravity.RIGHT | Gravity.TOP); + } + + boolean getIsLeftPanel() { + return mIsLeftPanel; + } + + void setDisplaySize(Point displaySize) { + mDisplaySize.set(displaySize.x, displaySize.y); + mScreenSize = Math.min(mDisplaySize.x, mDisplaySize.y); + } + + void setBackCallback(BackCallback callback) { + mBackCallback = callback; + } + + void setLayoutParams(WindowManager.LayoutParams layoutParams) { + mLayoutParams = layoutParams; + mWindowManager.addView(this, mLayoutParams); + } + + private float getCurrentAngle() { + return mCurrentAngle; + } + + private float getCurrentTranslation() { + return mCurrentTranslation; + } + + void onMotionEvent(MotionEvent event) { + if (mVelocityTracker == null) { + mVelocityTracker = VelocityTracker.obtain(); + } + mVelocityTracker.addMovement(event); + switch (event.getActionMasked()) { + case MotionEvent.ACTION_DOWN: + mDragSlopPassed = false; + resetOnDown(); + mStartX = event.getX(); + mStartY = event.getY(); + setVisibility(VISIBLE); + updatePosition(event.getY()); + mWindowManager.updateViewLayout(this, mLayoutParams); + break; + case MotionEvent.ACTION_MOVE: + handleMoveEvent(event); + break; + case MotionEvent.ACTION_UP: + if (mTriggerBack) { + triggerBack(); + } else { + cancelBack(); + } + mVelocityTracker.recycle(); + mVelocityTracker = null; + break; + case MotionEvent.ACTION_CANCEL: + cancelBack(); + mVelocityTracker.recycle(); + mVelocityTracker = null; + break; + } + } + + @Override + protected void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + updateArrowDirection(); + loadDimens(); + } + + @Override + protected void onDraw(Canvas canvas) { + float pointerPosition = mCurrentTranslation - mArrowThickness / 2.0f; + canvas.save(); + canvas.translate( + mIsLeftPanel ? pointerPosition : getWidth() - pointerPosition, + (getHeight() * 0.5f) + mVerticalTranslation); + + // Let's calculate the position of the end based on the angle + float x = (polarToCartX(mCurrentAngle) * mArrowLength); + float y = (polarToCartY(mCurrentAngle) * mArrowLength); + Path arrowPath = calculatePath(x, y); + + canvas.drawPath(arrowPath, mPaint); + canvas.restore(); + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + mMaxTranslation = getWidth() - mArrowPaddingEnd; + } + + private void loadDimens() { + Resources res = getResources(); + mArrowPaddingEnd = ResourceUtils.getDimenByName("navigation_edge_panel_padding", res, 8); + mMinArrowPosition = ResourceUtils.getDimenByName("navigation_edge_arrow_min_y", res, 64); + mFingerOffset = ResourceUtils.getDimenByName("navigation_edge_finger_offset", res, 48); + } + + private void updateArrowDirection() { + // Both panels arrow point the same way + mArrowsPointLeft = getLayoutDirection() == LAYOUT_DIRECTION_LTR; + invalidate(); + } + + private float getStaticArrowWidth() { + return polarToCartX(ARROW_ANGLE_WHEN_EXTENDED_DEGREES) * mArrowLength; + } + + private float polarToCartX(float angleInDegrees) { + return (float) Math.cos(Math.toRadians(angleInDegrees)); + } + + private float polarToCartY(float angleInDegrees) { + return (float) Math.sin(Math.toRadians(angleInDegrees)); + } + + private Path calculatePath(float x, float y) { + if (!mArrowsPointLeft) { + x = -x; + } + float extent = lerp(1.0f, 0.75f, mDisappearAmount); + x = x * extent; + y = y * extent; + mArrowPath.reset(); + mArrowPath.moveTo(x, y); + mArrowPath.lineTo(0, 0); + mArrowPath.lineTo(x, -y); + return mArrowPath; + } + + private static float lerp(float start, float stop, float amount) { + return start + (stop - start) * amount; + } + + private void triggerBack() { + if (mBackCallback != null) { + mBackCallback.triggerBack(); + } + + if (mVelocityTracker == null) { + mVelocityTracker = VelocityTracker.obtain(); + } + mVelocityTracker.computeCurrentVelocity(1000); + // Only do the extra translation if we're not already flinging + boolean isSlow = Math.abs(mVelocityTracker.getXVelocity()) < 500; + if (isSlow + || SystemClock.uptimeMillis() - mVibrationTime >= GESTURE_DURATION_FOR_CLICK_MS) { + VibratorWrapper.INSTANCE.get(getContext()).vibrate(VibratorWrapper.EFFECT_CLICK); + } + + // Let's also snap the angle a bit + if (mAngleOffset > -4) { + mAngleOffset = Math.max(-8, mAngleOffset - 8); + updateAngle(true /* animated */); + } + + // Finally, after the translation, animate back and disappear the arrow + Runnable translationEnd = () -> { + // let's snap it back + mAngleOffset = Math.max(0, mAngleOffset + 8); + updateAngle(true /* animated */); + + mTranslationAnimation.setSpring(mTriggerBackSpring); + // Translate the arrow back a bit to make for a nice transition + setDesiredTranslation(mDesiredTranslation - dp(32), true /* animated */); + animate().alpha(0f).setDuration(DISAPPEAR_FADE_ANIMATION_DURATION_MS) + .withEndAction(() -> setVisibility(GONE)); + mArrowDisappearAnimation.start(); + }; + if (mTranslationAnimation.isRunning()) { + mTranslationAnimation.addEndListener(new DynamicAnimation.OnAnimationEndListener() { + @Override + public void onAnimationEnd(DynamicAnimation animation, boolean canceled, + float value, + float velocity) { + animation.removeEndListener(this); + if (!canceled) { + translationEnd.run(); + } + } + }); + } else { + translationEnd.run(); + } + } + + private void cancelBack() { + if (mBackCallback != null) { + mBackCallback.cancelBack(); + } + + if (mTranslationAnimation.isRunning()) { + mTranslationAnimation.addEndListener(mSetGoneEndListener); + } else { + setVisibility(GONE); + } + } + + private void resetOnDown() { + animate().cancel(); + mAngleAnimation.cancel(); + mTranslationAnimation.cancel(); + mVerticalTranslationAnimation.cancel(); + mArrowDisappearAnimation.cancel(); + mAngleOffset = 0; + mTranslationAnimation.setSpring(mRegularTranslationSpring); + // Reset the arrow to the side + setTriggerBack(false /* triggerBack */, false /* animated */); + setDesiredTranslation(0, false /* animated */); + setCurrentTranslation(0); + updateAngle(false /* animate */); + mPreviousTouchTranslation = 0; + mTotalTouchDelta = 0; + mVibrationTime = 0; + setDesiredVerticalTransition(0, false /* animated */); + } + + private void handleMoveEvent(MotionEvent event) { + float x = event.getX(); + float y = event.getY(); + float touchTranslation = Math.abs(x - mStartX); + float yOffset = y - mStartY; + float delta = touchTranslation - mPreviousTouchTranslation; + if (Math.abs(delta) > 0) { + if (Math.signum(delta) == Math.signum(mTotalTouchDelta)) { + mTotalTouchDelta += delta; + } else { + mTotalTouchDelta = delta; + } + } + mPreviousTouchTranslation = touchTranslation; + + // Apply a haptic on drag slop passed + if (!mDragSlopPassed && touchTranslation > mSwipeThreshold) { + mDragSlopPassed = true; + VibratorWrapper.INSTANCE.get(getContext()).vibrate(VibratorWrapper.EFFECT_CLICK); + mVibrationTime = SystemClock.uptimeMillis(); + + // Let's show the arrow and animate it in! + mDisappearAmount = 0.0f; + setAlpha(1f); + // And animate it go to back by default! + setTriggerBack(true /* triggerBack */, true /* animated */); + } + + // Let's make sure we only go to the baseextend and apply rubberbanding afterwards + if (touchTranslation > mBaseTranslation) { + float diff = touchTranslation - mBaseTranslation; + float progress = MathUtils.clamp(diff / (mScreenSize - mBaseTranslation), 0, 1); + progress = RUBBER_BAND_INTERPOLATOR.getInterpolation(progress) + * (mMaxTranslation - mBaseTranslation); + touchTranslation = mBaseTranslation + progress; + } else { + float diff = mBaseTranslation - touchTranslation; + float progress = MathUtils.clamp(diff / mBaseTranslation, 0, 1); + progress = RUBBER_BAND_INTERPOLATOR_APPEAR.getInterpolation(progress) + * (mBaseTranslation / RUBBER_BAND_AMOUNT_APPEAR); + touchTranslation = mBaseTranslation - progress; + } + // By default we just assume the current direction is kept + boolean triggerBack = mTriggerBack; + + // First lets see if we had continuous motion in one direction for a while + if (Math.abs(mTotalTouchDelta) > mMinDeltaForSwitch) { + triggerBack = mTotalTouchDelta > 0; + } + + // Then, let's see if our velocity tells us to change direction + mVelocityTracker.computeCurrentVelocity(1000); + float xVelocity = mVelocityTracker.getXVelocity(); + float yVelocity = mVelocityTracker.getYVelocity(); + float velocity = (float) Math.hypot(xVelocity, yVelocity); + mAngleOffset = Math.min(velocity / 1000 * ARROW_ANGLE_ADDED_PER_1000_SPEED, + ARROW_MAX_ANGLE_SPEED_OFFSET_DEGREES) * Math.signum(xVelocity); + if (mIsLeftPanel && mArrowsPointLeft || !mIsLeftPanel && !mArrowsPointLeft) { + mAngleOffset *= -1; + } + + // Last if the direction in Y is bigger than X * 2 we also abort + if (Math.abs(yOffset) > Math.abs(x - mStartX) * 2) { + triggerBack = false; + } + setTriggerBack(triggerBack, true /* animated */); + + if (!mTriggerBack) { + touchTranslation = 0; + } else if (mIsLeftPanel && mArrowsPointLeft + || (!mIsLeftPanel && !mArrowsPointLeft)) { + // If we're on the left we should move less, because the arrow is facing the other + // direction + touchTranslation -= getStaticArrowWidth(); + } + setDesiredTranslation(touchTranslation, true /* animated */); + updateAngle(true /* animated */); + + float maxYOffset = getHeight() / 2.0f - mArrowLength; + float progress = + MathUtils.clamp(Math.abs(yOffset) / (maxYOffset * RUBBER_BAND_AMOUNT), 0, 1); + float verticalTranslation = RUBBER_BAND_INTERPOLATOR.getInterpolation(progress) + * maxYOffset * Math.signum(yOffset); + setDesiredVerticalTransition(verticalTranslation, true /* animated */); + } + + private void updatePosition(float touchY) { + float position = touchY - mFingerOffset; + position = Math.max(position, mMinArrowPosition); + position -= mLayoutParams.height / 2.0f; + mLayoutParams.y = MathUtils.clamp((int) position, 0, mDisplaySize.y); + } + + private void setDesiredVerticalTransition(float verticalTranslation, boolean animated) { + if (mDesiredVerticalTranslation != verticalTranslation) { + mDesiredVerticalTranslation = verticalTranslation; + if (!animated) { + setVerticalTranslation(verticalTranslation); + } else { + mVerticalTranslationAnimation.animateToFinalPosition(verticalTranslation); + } + invalidate(); + } + } + + private void setVerticalTranslation(float verticalTranslation) { + mVerticalTranslation = verticalTranslation; + invalidate(); + } + + private float getVerticalTranslation() { + return mVerticalTranslation; + } + + private void setDesiredTranslation(float desiredTranslation, boolean animated) { + if (mDesiredTranslation != desiredTranslation) { + mDesiredTranslation = desiredTranslation; + if (!animated) { + setCurrentTranslation(desiredTranslation); + } else { + mTranslationAnimation.animateToFinalPosition(desiredTranslation); + } + } + } + + private void setCurrentTranslation(float currentTranslation) { + mCurrentTranslation = currentTranslation; + invalidate(); + } + + private void setTriggerBack(boolean triggerBack, boolean animated) { + if (mTriggerBack != triggerBack) { + mTriggerBack = triggerBack; + mAngleAnimation.cancel(); + updateAngle(animated); + // Whenever the trigger back state changes the existing translation animation should be + // cancelled + mTranslationAnimation.cancel(); + } + } + + private void updateAngle(boolean animated) { + float newAngle = mTriggerBack ? ARROW_ANGLE_WHEN_EXTENDED_DEGREES + mAngleOffset : 90; + if (newAngle != mDesiredAngle) { + if (!animated) { + setCurrentAngle(newAngle); + } else { + mAngleAnimation.setSpring(mTriggerBack ? mAngleAppearForce : mAngleDisappearForce); + mAngleAnimation.animateToFinalPosition(newAngle); + } + mDesiredAngle = newAngle; + } + } + + private void setCurrentAngle(float currentAngle) { + mCurrentAngle = currentAngle; + invalidate(); + } + + private float dp(float dp) { + return mDensity * dp; + } + + /** Callback to let the gesture handler react to the detected back gestures. */ + interface BackCallback { + /** Indicates that a Back gesture was recognized. */ + void triggerBack(); + + /** Indicates that the gesture was cancelled. */ + void cancelBack(); + } +} diff --git a/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java b/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java index 8081ad7ce1..48153664cd 100644 --- a/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java +++ b/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java @@ -30,12 +30,11 @@ import com.android.quickstep.interaction.BackGestureTutorialFragment.TutorialSte import com.android.quickstep.interaction.BackGestureTutorialFragment.TutorialType; import java.util.List; -import java.util.Optional; /** Shows the gesture interactive sandbox in full screen mode. */ public class GestureSandboxActivity extends FragmentActivity { - Optional mFragment = Optional.empty(); + private BackGestureTutorialFragment mFragment; @Override protected void onCreate(Bundle savedInstanceState) { @@ -43,10 +42,10 @@ public class GestureSandboxActivity extends FragmentActivity { requestWindowFeature(Window.FEATURE_NO_TITLE); setContentView(R.layout.back_gesture_tutorial_activity); - mFragment = Optional.of(BackGestureTutorialFragment.newInstance(TutorialStep.ENGAGED, - TutorialType.RIGHT_EDGE_BACK_NAVIGATION)); + mFragment = BackGestureTutorialFragment.newInstance( + TutorialStep.ENGAGED, TutorialType.RIGHT_EDGE_BACK_NAVIGATION); getSupportFragmentManager().beginTransaction() - .add(R.id.back_gesture_tutorial_fragment_container, mFragment.get()) + .add(R.id.back_gesture_tutorial_fragment_container, mFragment) .commit(); } @@ -54,6 +53,13 @@ public class GestureSandboxActivity extends FragmentActivity { public void onAttachedToWindow() { super.onAttachedToWindow(); disableSystemGestures(); + mFragment.onAttachedToWindow(); + } + + @Override + public void onDetachedFromWindow() { + super.onDetachedFromWindow(); + mFragment.onDetachedFromWindow(); } @Override @@ -64,13 +70,6 @@ public class GestureSandboxActivity extends FragmentActivity { } } - @Override - public void onBackPressed() { - if (mFragment.isPresent()) { - mFragment.get().onBackPressed(); - } - } - private void hideSystemUI() { getWindow().getDecorView().setSystemUiVisibility( View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY diff --git a/src/com/android/launcher3/util/VibratorWrapper.java b/src/com/android/launcher3/util/VibratorWrapper.java index 04741a165b..b0defd445a 100644 --- a/src/com/android/launcher3/util/VibratorWrapper.java +++ b/src/com/android/launcher3/util/VibratorWrapper.java @@ -39,7 +39,7 @@ public class VibratorWrapper { public static final MainThreadInitializedObject INSTANCE = new MainThreadInitializedObject<>(VibratorWrapper::new); - private static final VibrationEffect EFFECT_CLICK = + public static final VibrationEffect EFFECT_CLICK = createPredefined(VibrationEffect.EFFECT_CLICK); /** From 82bbdaceae1d8ee0432567a96e4f5422a91a95cd Mon Sep 17 00:00:00 2001 From: Samuel Fufa Date: Mon, 9 Mar 2020 18:24:47 -0700 Subject: [PATCH 21/36] Migrate hotseat items into a folder If feature flag HOTSEAT_MIGRATE_TO_FOLDER is enabled, this moves hotseat items into a folder in the workspace. If not, it moves the whole hotseat to the first workspace page that can host the hotseat. Bug: 151099421 Test: Manual Change-Id: I49f6a22a0ada2c4cf237ca91a323a46346a11a59 --- .../res/layout/arrow_toast.xml | 1 + .../com/android/launcher3/ArrowTipView.java | 151 +++++++++++++++ .../appprediction/AllAppsTipView.java | 142 ++------------ .../hybridhotseat/HotseatEduController.java | 180 +++++++++++++++--- .../hybridhotseat/HotseatEduDialog.java | 47 ++--- .../HotseatPredictionController.java | 14 +- .../launcher3/AbstractFloatingView.java | 5 +- src/com/android/launcher3/CellLayout.java | 12 +- .../launcher3/config/FeatureFlags.java | 7 +- 9 files changed, 366 insertions(+), 193 deletions(-) create mode 100644 quickstep/recents_ui_overrides/src/com/android/launcher3/ArrowTipView.java diff --git a/quickstep/recents_ui_overrides/res/layout/arrow_toast.xml b/quickstep/recents_ui_overrides/res/layout/arrow_toast.xml index b0f2b4bf8c..980bb5a805 100644 --- a/quickstep/recents_ui_overrides/res/layout/arrow_toast.xml +++ b/quickstep/recents_ui_overrides/res/layout/arrow_toast.xml @@ -34,6 +34,7 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" + android:gravity="center" android:layout_gravity="center_vertical" android:textColor="@android:color/white" android:textSize="16sp"/> diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/ArrowTipView.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/ArrowTipView.java new file mode 100644 index 0000000000..a5ea523a27 --- /dev/null +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/ArrowTipView.java @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2008 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; + +import android.content.Context; +import android.graphics.CornerPathEffect; +import android.graphics.Paint; +import android.graphics.drawable.ShapeDrawable; +import android.os.Handler; +import android.util.TypedValue; +import android.view.Gravity; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.widget.LinearLayout; +import android.widget.TextView; + +import androidx.core.content.ContextCompat; + +import com.android.launcher3.anim.Interpolators; +import com.android.launcher3.dragndrop.DragLayer; +import com.android.launcher3.graphics.TriangleShape; + +/** + * A base class for arrow tip view in launcher + */ +public class ArrowTipView extends AbstractFloatingView { + + private static final long AUTO_CLOSE_TIMEOUT_MILLIS = 10 * 1000; + private static final long SHOW_DELAY_MS = 200; + private static final long SHOW_DURATION_MS = 300; + private static final long HIDE_DURATION_MS = 100; + + protected final Launcher mLauncher; + private final Handler mHandler = new Handler(); + private Runnable mOnClosed; + + public ArrowTipView(Context context) { + super(context, null, 0); + mLauncher = Launcher.getLauncher(context); + init(context); + } + + @Override + public boolean onControllerInterceptTouchEvent(MotionEvent ev) { + if (ev.getAction() == MotionEvent.ACTION_DOWN) { + close(true); + } + return false; + } + + @Override + protected void handleClose(boolean animate) { + if (mIsOpen) { + if (animate) { + animate().alpha(0f) + .withLayer() + .setStartDelay(0) + .setDuration(HIDE_DURATION_MS) + .setInterpolator(Interpolators.ACCEL) + .withEndAction(() -> mLauncher.getDragLayer().removeView(this)) + .start(); + } else { + animate().cancel(); + mLauncher.getDragLayer().removeView(this); + } + if (mOnClosed != null) mOnClosed.run(); + mIsOpen = false; + } + } + + @Override + public void logActionCommand(int command) { + } + + @Override + protected boolean isOfType(int type) { + return (type & TYPE_ON_BOARD_POPUP) != 0; + } + + private void init(Context context) { + inflate(context, R.layout.arrow_toast, this); + setOrientation(LinearLayout.VERTICAL); + View dismissButton = findViewById(R.id.dismiss); + dismissButton.setOnClickListener(view -> { + handleClose(true); + }); + + View arrowView = findViewById(R.id.arrow); + ViewGroup.LayoutParams arrowLp = arrowView.getLayoutParams(); + ShapeDrawable arrowDrawable = new ShapeDrawable(TriangleShape.create( + arrowLp.width, arrowLp.height, false)); + Paint arrowPaint = arrowDrawable.getPaint(); + TypedValue typedValue = new TypedValue(); + context.getTheme().resolveAttribute(android.R.attr.colorAccent, typedValue, true); + arrowPaint.setColor(ContextCompat.getColor(getContext(), typedValue.resourceId)); + // The corner path effect won't be reflected in the shadow, but shouldn't be noticeable. + arrowPaint.setPathEffect(new CornerPathEffect( + context.getResources().getDimension(R.dimen.arrow_toast_corner_radius))); + arrowView.setBackground(arrowDrawable); + + mIsOpen = true; + + mHandler.postDelayed(() -> handleClose(true), AUTO_CLOSE_TIMEOUT_MILLIS); + } + + /** + * Show Tip with specified string and Y location + */ + public ArrowTipView show(String text, int top) { + ((TextView) findViewById(R.id.text)).setText(text); + mLauncher.getDragLayer().addView(this); + + DragLayer.LayoutParams params = (DragLayer.LayoutParams) getLayoutParams(); + params.gravity = Gravity.CENTER_HORIZONTAL; + params.leftMargin = mLauncher.getDeviceProfile().workspacePadding.left; + params.rightMargin = mLauncher.getDeviceProfile().workspacePadding.right; + post(() -> setY(top - getHeight())); + setAlpha(0); + animate() + .alpha(1f) + .withLayer() + .setStartDelay(SHOW_DELAY_MS) + .setDuration(SHOW_DURATION_MS) + .setInterpolator(Interpolators.DEACCEL) + .start(); + return this; + } + + /** + * Register a callback fired when toast is hidden + */ + public ArrowTipView setOnClosedCallback(Runnable runnable) { + mOnClosed = runnable; + return this; + } +} diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/AllAppsTipView.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/AllAppsTipView.java index 0ae7435907..b3bb850806 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/AllAppsTipView.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/AllAppsTipView.java @@ -16,133 +16,30 @@ package com.android.launcher3.appprediction; +import static com.android.launcher3.AbstractFloatingView.TYPE_DISCOVERY_BOUNCE; +import static com.android.launcher3.AbstractFloatingView.TYPE_ON_BOARD_POPUP; import static com.android.launcher3.LauncherState.ALL_APPS; import static com.android.quickstep.logging.UserEventDispatcherExtension.ALL_APPS_PREDICTION_TIPS; -import android.content.Context; -import android.graphics.CornerPathEffect; -import android.graphics.Paint; -import android.graphics.drawable.ShapeDrawable; -import android.os.Handler; import android.os.UserManager; -import android.util.AttributeSet; -import android.util.TypedValue; -import android.view.Gravity; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewGroup; -import android.widget.LinearLayout; -import android.widget.TextView; - -import androidx.core.content.ContextCompat; import com.android.launcher3.AbstractFloatingView; +import com.android.launcher3.ArrowTipView; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherState; import com.android.launcher3.LauncherStateManager; import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.allapps.FloatingHeaderView; -import com.android.launcher3.anim.Interpolators; -import com.android.launcher3.dragndrop.DragLayer; -import com.android.launcher3.graphics.TriangleShape; import com.android.systemui.shared.system.LauncherEventUtil; /** - * All apps tip view aligned just above prediction apps, shown to users that enter all apps for the + * ArrowTip helper aligned just above prediction apps, shown to users that enter all apps for the * first time. */ -public class AllAppsTipView extends AbstractFloatingView { +public class AllAppsTipView { private static final String ALL_APPS_TIP_SEEN = "launcher.all_apps_tip_seen"; - private static final long AUTO_CLOSE_TIMEOUT_MILLIS = 10 * 1000; - private static final long SHOW_DELAY_MS = 200; - private static final long SHOW_DURATION_MS = 300; - private static final long HIDE_DURATION_MS = 100; - - private final Launcher mLauncher; - private final Handler mHandler = new Handler(); - - private AllAppsTipView(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - private AllAppsTipView(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - setOrientation(LinearLayout.VERTICAL); - - mLauncher = Launcher.getLauncher(context); - - init(context); - } - - @Override - public boolean onControllerInterceptTouchEvent(MotionEvent ev) { - if (ev.getAction() == MotionEvent.ACTION_DOWN) { - close(true); - } - return false; - } - - @Override - protected void handleClose(boolean animate) { - if (mIsOpen) { - if (animate) { - animate().alpha(0f) - .withLayer() - .setStartDelay(0) - .setDuration(HIDE_DURATION_MS) - .setInterpolator(Interpolators.ACCEL) - .withEndAction(() -> mLauncher.getDragLayer().removeView(this)) - .start(); - } else { - animate().cancel(); - mLauncher.getDragLayer().removeView(this); - } - mLauncher.getSharedPrefs().edit().putBoolean(ALL_APPS_TIP_SEEN, true).apply(); - mIsOpen = false; - } - } - - @Override - public void logActionCommand(int command) { - } - - @Override - protected boolean isOfType(int type) { - return (type & TYPE_ON_BOARD_POPUP) != 0; - } - - private void init(Context context) { - inflate(context, R.layout.arrow_toast, this); - - TextView textView = findViewById(R.id.text); - textView.setText(R.string.all_apps_prediction_tip); - - View dismissButton = findViewById(R.id.dismiss); - dismissButton.setOnClickListener(view -> { - mLauncher.getUserEventDispatcher().logActionTip( - LauncherEventUtil.DISMISS, ALL_APPS_PREDICTION_TIPS); - handleClose(true); - }); - - View arrowView = findViewById(R.id.arrow); - ViewGroup.LayoutParams arrowLp = arrowView.getLayoutParams(); - ShapeDrawable arrowDrawable = new ShapeDrawable(TriangleShape.create( - arrowLp.width, arrowLp.height, false)); - Paint arrowPaint = arrowDrawable.getPaint(); - TypedValue typedValue = new TypedValue(); - context.getTheme().resolveAttribute(android.R.attr.colorAccent, typedValue, true); - arrowPaint.setColor(ContextCompat.getColor(getContext(), typedValue.resourceId)); - // The corner path effect won't be reflected in the shadow, but shouldn't be noticeable. - arrowPaint.setPathEffect(new CornerPathEffect( - context.getResources().getDimension(R.dimen.arrow_toast_corner_radius))); - arrowView.setBackground(arrowDrawable); - - mIsOpen = true; - - mHandler.postDelayed(() -> handleClose(true), AUTO_CLOSE_TIMEOUT_MILLIS); - } private static boolean showAllAppsTipIfNecessary(Launcher launcher) { FloatingHeaderView floatingHeaderView = launcher.getAppsView().getFloatingHeaderView(); @@ -156,28 +53,15 @@ public class AllAppsTipView extends AbstractFloatingView { return false; } - AllAppsTipView allAppsTipView = new AllAppsTipView(launcher.getAppsView().getContext(), - null); - launcher.getDragLayer().addView(allAppsTipView); + int[] coords = new int[2]; + floatingHeaderView.findFixedRowByType(PredictionRowView.class).getLocationOnScreen(coords); + ArrowTipView arrowTipView = new ArrowTipView(launcher).setOnClosedCallback(() -> { + launcher.getSharedPrefs().edit().putBoolean(ALL_APPS_TIP_SEEN, true).apply(); + launcher.getUserEventDispatcher().logActionTip(LauncherEventUtil.DISMISS, + ALL_APPS_PREDICTION_TIPS); + }); + arrowTipView.show(launcher.getString(R.string.all_apps_prediction_tip), coords[1]); - DragLayer.LayoutParams params = (DragLayer.LayoutParams) allAppsTipView.getLayoutParams(); - params.gravity = Gravity.CENTER_HORIZONTAL; - - int top = floatingHeaderView.findFixedRowByType(PredictionRowView.class).getTop(); - allAppsTipView.setY(top - launcher.getResources().getDimensionPixelSize( - R.dimen.all_apps_tip_bottom_margin)); - - allAppsTipView.setAlpha(0); - allAppsTipView.animate() - .alpha(1f) - .withLayer() - .setStartDelay(SHOW_DELAY_MS) - .setDuration(SHOW_DURATION_MS) - .setInterpolator(Interpolators.DEACCEL) - .start(); - - launcher.getUserEventDispatcher().logActionTip( - LauncherEventUtil.VISIBLE, ALL_APPS_PREDICTION_TIPS); return true; } 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 a07cd1d82c..da588173af 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 @@ -23,23 +23,28 @@ import android.content.Intent; import android.content.res.Configuration; import android.os.Build; import android.view.View; -import android.view.ViewGroup; import androidx.core.app.NotificationCompat; import com.android.launcher3.CellLayout; +import com.android.launcher3.FolderInfo; +import com.android.launcher3.Hotseat; +import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.ItemInfo; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherSettings; import com.android.launcher3.R; import com.android.launcher3.Workspace; import com.android.launcher3.WorkspaceItemInfo; -import com.android.launcher3.WorkspaceLayoutManager; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.uioverrides.QuickstepLauncher; 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 java.util.ArrayDeque; +import java.util.ArrayList; import java.util.List; /** @@ -52,48 +57,179 @@ public class HotseatEduController { private static final int ONBOARDING_NOTIFICATION_ID = 7641; private final Launcher mLauncher; + private final NotificationManager mNotificationManager; + private final Notification mNotification; private List mPredictedApps; private HotseatEduDialog mActiveDialog; - private final NotificationManager mNotificationManager; - private final Notification mNotification; + private ArrayList mNewItems = new ArrayList<>(); + private IntArray mNewScreens = null; + private Runnable mOnOnboardingComplete; - HotseatEduController(Launcher launcher) { + HotseatEduController(Launcher launcher, Runnable runnable) { mLauncher = launcher; + mOnOnboardingComplete = runnable; mNotificationManager = mLauncher.getSystemService(NotificationManager.class); createNotificationChannel(); mNotification = createNotification(); } - boolean migrate() { - Workspace workspace = mLauncher.getWorkspace(); - CellLayout firstScreen = workspace.getScreenWithId(WorkspaceLayoutManager.FIRST_SCREEN_ID); - int toPage = Workspace.FIRST_SCREEN_ID; - int toRow = mLauncher.getDeviceProfile().inv.numRows - 1; - if (FeatureFlags.HOTSEAT_MIGRATE_NEW_PAGE.get()) { - toPage = workspace.getScreenIdForPageIndex(workspace.getPageCount()); - toRow = 0; - } else if (!firstScreen.makeSpaceForHotseatMigration(true)) { - return false; + /** + * Checks what type of migration should be used and migrates hotseat + */ + void migrate() { + if (FeatureFlags.HOTSEAT_MIGRATE_TO_FOLDER.get()) { + migrateToFolder(); + } else { + migrateHotseatWhole(); } - ViewGroup hotseatVG = mLauncher.getHotseat().getShortcutsAndWidgets(); - for (int i = 0; i < hotseatVG.getChildCount(); i++) { - View child = hotseatVG.getChildAt(i); + } + + /** + * This migration places all non folder items in the hotseat into a folder and then moves + * all folders in the hotseat to a workspace page that has enough empty spots. + * + * @return pageId that has accepted the items. + */ + private int migrateToFolder() { + ArrayDeque folders = new ArrayDeque<>(); + + ArrayList putIntoFolder = new ArrayList<>(); + + //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); + if (view == null) continue; + ItemInfo info = (ItemInfo) view.getTag(); + if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) { + folders.add((FolderInfo) info); + } else if (info instanceof WorkspaceItemInfo) { + putIntoFolder.add((WorkspaceItemInfo) info); + } + } + + // create a temp folder and add non folder items to it + if (!putIntoFolder.isEmpty()) { + ItemInfo firstItem = putIntoFolder.get(0); + FolderInfo folderInfo = new FolderInfo(); + folderInfo.title = ""; + mLauncher.getModelWriter().addItemToDatabase(folderInfo, firstItem.container, + firstItem.screenId, firstItem.cellX, firstItem.cellY); + folderInfo.contents.addAll(putIntoFolder); + for (int i = 0; i < folderInfo.contents.size(); i++) { + ItemInfo item = folderInfo.contents.get(i); + item.rank = i; + mLauncher.getModelWriter().moveItemInDatabase(item, folderInfo.id, 0, + item.cellX, item.cellY); + } + folders.add(folderInfo); + } + mNewItems.addAll(folders); + + return placeFoldersInWorkspace(folders); + } + + private int placeFoldersInWorkspace(ArrayDeque folders) { + if (folders.isEmpty()) return 0; + + Workspace workspace = mLauncher.getWorkspace(); + InvariantDeviceProfile idp = mLauncher.getDeviceProfile().inv; + + GridOccupancy[] occupancyList = new GridOccupancy[workspace.getChildCount()]; + for (int i = 0; i < occupancyList.length; i++) { + occupancyList[i] = ((CellLayout) workspace.getChildAt(i)).cloneGridOccupancy(); + } + //scan every screen to find available spots to place folders + int occupancyIndex = 0; + int[] itemXY = new int[2]; + while (occupancyIndex < occupancyList.length && !folders.isEmpty()) { + GridOccupancy occupancy = occupancyList[occupancyIndex]; + if (occupancy.findVacantCell(itemXY, 1, 1)) { + FolderInfo info = folders.poll(); + mLauncher.getModelWriter().moveItemInDatabase(info, + LauncherSettings.Favorites.CONTAINER_DESKTOP, + workspace.getScreenIdForPageIndex(occupancyIndex), itemXY[0], itemXY[1]); + occupancy.markCells(info, true); + } else { + occupancyIndex++; + } + } + if (folders.isEmpty()) return workspace.getScreenIdForPageIndex(occupancyIndex); + int screenId = LauncherSettings.Settings.call(mLauncher.getContentResolver(), + LauncherSettings.Settings.METHOD_NEW_SCREEN_ID) + .getInt(LauncherSettings.Settings.EXTRA_VALUE); + // if all screens are full and we still have folders left, put those on a new page + FolderInfo folderInfo; + int col = 0; + while ((folderInfo = folders.poll()) != null) { + mLauncher.getModelWriter().moveItemInDatabase(folderInfo, + LauncherSettings.Favorites.CONTAINER_DESKTOP, screenId, col++, + idp.numRows - 1); + } + mNewScreens = IntArray.wrap(screenId); + return workspace.getPageCount(); + } + + /** + * This migration option attempts to move the entire hotseat up to the first workspace that + * has space to host items. If no such page is found, it moves items to a new page. + * + * @return pageId where items are migrated + */ + private int migrateHotseatWhole() { + Workspace workspace = mLauncher.getWorkspace(); + Hotseat hotseatVG = mLauncher.getHotseat(); + + int pageId = -1; + int toRow = 0; + for (int i = 0; i < workspace.getPageCount(); i++) { + CellLayout target = workspace.getScreenWithId(workspace.getScreenIdForPageIndex(i)); + if (target.makeSpaceForHotseatMigration(true)) { + toRow = mLauncher.getDeviceProfile().inv.numRows - 1; + pageId = i; + break; + } + } + if (pageId == -1) { + pageId = LauncherSettings.Settings.call(mLauncher.getContentResolver(), + LauncherSettings.Settings.METHOD_NEW_SCREEN_ID) + .getInt(LauncherSettings.Settings.EXTRA_VALUE); + } + for (int i = 0; i < mLauncher.getDeviceProfile().inv.numHotseatIcons; i++) { + View child = hotseatVG.getChildAt(i, 0); + if (child == null || child.getTag() == null) continue; ItemInfo tag = (ItemInfo) child.getTag(); mLauncher.getModelWriter().moveItemInDatabase(tag, - LauncherSettings.Favorites.CONTAINER_DESKTOP, toPage, tag.screenId, toRow); + LauncherSettings.Favorites.CONTAINER_DESKTOP, pageId, i, toRow); + mNewItems.add(tag); } - return true; + return pageId; } + void removeNotification() { mNotificationManager.cancel(ONBOARDING_NOTIFICATION_ID); } void finishOnboarding() { - mLauncher.getModel().rebindCallbacks(); + mLauncher.getHotseat().removeAllViewsInLayout(); + if (!mNewItems.isEmpty()) { + int lastPage = mNewItems.get(mNewItems.size() - 1).screenId; + ArrayList animated = new ArrayList<>(); + ArrayList nonAnimated = new ArrayList<>(); + + for (ItemInfo info : mNewItems) { + if (info.screenId == lastPage) { + animated.add(info); + } else { + nonAnimated.add(info); + } + } + mLauncher.bindAppsAdded(mNewScreens, nonAnimated, animated); + } + mOnOnboardingComplete.run(); + destroy(); mLauncher.getSharedPrefs().edit().putBoolean(KEY_HOTSEAT_EDU_SEEN, true).apply(); - removeNotification(); } void setPredictedApps(List predictedApps) { 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 7986c269bc..bcce168b95 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 @@ -29,6 +29,7 @@ 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; @@ -53,20 +54,15 @@ public class HotseatEduDialog extends AbstractSlideInView implements Insettable private static final int DEFAULT_CLOSE_DURATION = 200; protected static final int FINAL_SCRIM_BG_COLOR = 0x88000000; - private static final int MIGRATE_SAME_PAGE = 0; - private static final int MIGRATE_NEW_PAGE = 1; - private static final int MIGRATE_NO_MIGRATE = 2; + // we use this value to keep track of migration logs as we experiment with different migrations + private static final int MIGRATION_EXPERIMENT_IDENTIFIER = 1; private final Rect mInsets = new Rect(); private View mHotseatWrapper; private CellLayout mSampleHotseat; - private TextView mEduHeading; - private TextView mEduContent; private Button mDismissBtn; - private int mMigrationMode = MIGRATE_SAME_PAGE; - public void setHotseatEduController(HotseatEduController hotseatEduController) { mHotseatEduController = hotseatEduController; } @@ -89,8 +85,6 @@ public class HotseatEduDialog extends AbstractSlideInView implements Insettable super.onFinishInflate(); mHotseatWrapper = findViewById(R.id.hotseat_wrapper); mSampleHotseat = findViewById(R.id.sample_prediction); - mEduHeading = findViewById(R.id.hotseat_edu_heading); - mEduContent = findViewById(R.id.hotseat_edu_content); DeviceProfile grid = mLauncher.getDeviceProfile(); Rect padding = grid.getHotseatLayoutPadding(); @@ -105,25 +99,30 @@ public class HotseatEduDialog extends AbstractSlideInView implements Insettable mDismissBtn = findViewById(R.id.no_thanks); mDismissBtn.setOnClickListener(this::onDismiss); + // update ui to reflect which migration method is going to be used + if (FeatureFlags.HOTSEAT_MIGRATE_TO_FOLDER.get()) { + ((TextView) findViewById(R.id.hotseat_edu_content)).setText( + R.string.hotseat_edu_message_migrate_alt); + } } private void onAccept(View v) { - if (mMigrationMode == MIGRATE_NO_MIGRATE || !mHotseatEduController.migrate()) { - onDismiss(v); - return; - } + mHotseatEduController.migrate(); handleClose(true); mHotseatEduController.finishOnboarding(); - logUserAction(true); - int toastStringRes = mMigrationMode == MIGRATE_SAME_PAGE + //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) { - Toast.makeText(getContext(), R.string.hotseat_no_migration, Toast.LENGTH_LONG).show(); + int top = mLauncher.getHotseat().getTop(); + new ArrowTipView(mLauncher).show(mLauncher.getString(R.string.hotseat_no_migration), top); mHotseatEduController.finishOnboarding(); - logUserAction(false); + logUserAction(false, -1); handleClose(true); } @@ -155,7 +154,7 @@ public class HotseatEduDialog extends AbstractSlideInView implements Insettable mLauncher.getDeviceProfile().hotseatBarSizePx + insets.bottom; } - private void logUserAction(boolean migrated) { + private void logUserAction(boolean migrated, int pageIndex) { LauncherLogProto.Action action = new LauncherLogProto.Action(); LauncherLogProto.Target target = new LauncherLogProto.Target(); action.type = LauncherLogProto.Action.Type.TOUCH; @@ -164,8 +163,9 @@ public class HotseatEduDialog extends AbstractSlideInView implements Insettable target.tipType = LauncherLogProto.TipType.HYBRID_HOTSEAT; target.controlType = migrated ? LauncherLogProto.ControlType.HYBRID_HOTSEAT_ACCEPTED : HYBRID_HOTSEAT_CANCELED; + target.rank = MIGRATION_EXPERIMENT_IDENTIFIER; // encoding migration type on pageIndex - target.pageIndex = mMigrationMode; + target.pageIndex = pageIndex; LauncherLogProto.LauncherEvent event = newLauncherEvent(action, target); UserEventDispatcher.newInstance(getContext()).dispatchUserEvent(event, null); } @@ -218,15 +218,6 @@ public class HotseatEduDialog extends AbstractSlideInView implements Insettable } } - @Override - protected void attachToContainer() { - super.attachToContainer(); - if (FeatureFlags.HOTSEAT_MIGRATE_NEW_PAGE.get()) { - mEduContent.setText(R.string.hotseat_edu_message_migrate_alt); - mMigrationMode = MIGRATE_NEW_PAGE; - } - } - /** * Opens User education dialog with a list of suggested apps */ 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 2cdcd20732..d82e9f0115 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 @@ -94,7 +94,6 @@ public class HotseatPredictionController implements DragController.DragListener, private static final String BUNDLE_KEY_WORKSPACE = "workspace_apps"; private static final String PREDICTION_CLIENT = "hotseat"; - private DropTarget.DragObject mDragObject; private int mHotSeatItemsCount; private int mPredictedSpotsCount = 0; @@ -115,6 +114,7 @@ public class HotseatPredictionController implements DragController.DragListener, private List mOutlineDrawings = new ArrayList<>(); + private final View.OnLongClickListener mPredictionLongClickListener = v -> { if (!ItemLongClickListener.canStartDrag(mLauncher)) return false; if (mLauncher.getWorkspace().isSwitchingState()) return false; @@ -276,12 +276,10 @@ public class HotseatPredictionController implements DragController.DragListener, .build()); mAppPredictor.registerPredictionUpdates(mLauncher.getMainExecutor(), this::setPredictedApps); + setPauseUIUpdate(false); if (!isReady()) { - if (mHotseatEduController != null) { - mHotseatEduController.destroy(); - } - mHotseatEduController = new HotseatEduController(mLauncher); + mHotseatEduController = new HotseatEduController(mLauncher, this::createPredictor); } mAppPredictor.requestPredictionUpdate(); } @@ -327,7 +325,7 @@ public class HotseatPredictionController implements DragController.DragListener, mComponentKeyMappers.add(new ComponentKeyMapper(key, mDynamicItemCache)); } predictionLog.append("]"); - FileLog.d(TAG, predictionLog.toString()); + if (false) FileLog.d(TAG, predictionLog.toString()); updateDependencies(); if (isReady()) { fillGapsWithPrediction(); @@ -488,7 +486,6 @@ public class HotseatPredictionController implements DragController.DragListener, } } - @Override public void onDragEnd() { if (mDragObject == null) { @@ -564,7 +561,8 @@ public class HotseatPredictionController implements DragController.DragListener, } @Override - public void reapplyItemInfo(ItemInfoWithIcon info) {} + public void reapplyItemInfo(ItemInfoWithIcon info) { + } @Override public void onDropCompleted(View target, DropTarget.DragObject d, boolean success) { diff --git a/src/com/android/launcher3/AbstractFloatingView.java b/src/com/android/launcher3/AbstractFloatingView.java index 12b5fc1afa..bed8278bc7 100644 --- a/src/com/android/launcher3/AbstractFloatingView.java +++ b/src/com/android/launcher3/AbstractFloatingView.java @@ -180,7 +180,10 @@ public abstract class AbstractFloatingView extends LinearLayout implements Touch return null; } - protected static T getOpenView( + /** + * Returns a view matching FloatingViewType + */ + public static T getOpenView( ActivityContext activity, @FloatingViewType int type) { BaseDragLayer dragLayer = activity.getDragLayer(); if (dragLayer == null) return null; diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java index 4742bbc766..f2d07f2b11 100644 --- a/src/com/android/launcher3/CellLayout.java +++ b/src/com/android/launcher3/CellLayout.java @@ -2766,7 +2766,6 @@ public class CellLayout extends ViewGroup implements Transposable { * Finds solution to accept hotseat migration to cell layout. commits solution if commitConfig */ public boolean makeSpaceForHotseatMigration(boolean commitConfig) { - if (FeatureFlags.HOTSEAT_MIGRATE_NEW_PAGE.get()) return false; int[] cellPoint = new int[2]; int[] directionVector = new int[]{0, -1}; cellToPoint(0, mCountY, cellPoint); @@ -2776,12 +2775,23 @@ public class CellLayout extends ViewGroup implements Transposable { if (commitConfig) { copySolutionToTempState(configuration, null); commitTempPlacement(); + // undo marking cells occupied since there is actually nothing being placed yet. + mOccupied.markCells(0, mCountY - 1, mCountX, 1, false); } return true; } return false; } + /** + * returns a copy of cell layout's grid occupancy + */ + public GridOccupancy cloneGridOccupancy() { + GridOccupancy occupancy = new GridOccupancy(mCountX, mCountY); + mOccupied.copyTo(occupancy); + return occupancy; + } + public boolean isRegionVacant(int x, int y, int spanX, int spanY) { return mOccupied.isRegionVacant(x, y, spanX, spanY); } diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java index 6292167237..4df3b0a7ce 100644 --- a/src/com/android/launcher3/config/FeatureFlags.java +++ b/src/com/android/launcher3/config/FeatureFlags.java @@ -120,12 +120,11 @@ public final class FeatureFlags { "ASSISTANT_GIVES_LAUNCHER_FOCUS", false, "Allow Launcher to handle nav bar gestures while Assistant is running over it"); - public static final BooleanFlag ENABLE_HYBRID_HOTSEAT = getDebugFlag( + public static final BooleanFlag ENABLE_HYBRID_HOTSEAT = new DeviceFlag( "ENABLE_HYBRID_HOTSEAT", false, "Fill gaps in hotseat with predicted apps"); - public static final BooleanFlag HOTSEAT_MIGRATE_NEW_PAGE = getDebugFlag( - "HOTSEAT_MIGRATE_NEW_PAGE", false, - "Migrates hotseat to a new workspace page instead of same page"); + public static final BooleanFlag HOTSEAT_MIGRATE_TO_FOLDER = new DeviceFlag( + "HOTSEAT_MIGRATE_TO_FOLDER", false, "Should move hotseat items into a folder"); public static final BooleanFlag ENABLE_DEEP_SHORTCUT_ICON_CACHE = getDebugFlag( "ENABLE_DEEP_SHORTCUT_ICON_CACHE", true, "R/W deep shortcut in IconCache"); From 45c3b10448ed32bd18883b132310b7e3d7c7f301 Mon Sep 17 00:00:00 2001 From: Samuel Fufa Date: Thu, 19 Mar 2020 16:34:24 -0700 Subject: [PATCH 22/36] Work profile accessibility fixes - Remove TextView and use work switches text - Allow talkback to read recyclerview overlay Bug: 151884799 Bug: 151803591 Bug: 151274763 Bug: 151585835 Test: Manual Change-Id: Ieee0d37d0244eb12ecb923d710cbbd6a86281062 --- ...rk_tab_footer.xml => work_mode_switch.xml} | 45 +++--- .../allapps/AllAppsContainerView.java | 37 +++-- .../launcher3/allapps/WorkModeSwitch.java | 63 ++++++++- .../launcher3/views/WorkFooterContainer.java | 133 ------------------ .../com/android/launcher3/ui/WorkTabTest.java | 8 +- 5 files changed, 97 insertions(+), 189 deletions(-) rename res/layout/{work_tab_footer.xml => work_mode_switch.xml} (51%) delete mode 100644 src/com/android/launcher3/views/WorkFooterContainer.java diff --git a/res/layout/work_tab_footer.xml b/res/layout/work_mode_switch.xml similarity index 51% rename from res/layout/work_tab_footer.xml rename to res/layout/work_mode_switch.xml index 264e27375c..9cb7ce8b64 100644 --- a/res/layout/work_tab_footer.xml +++ b/res/layout/work_mode_switch.xml @@ -13,39 +13,26 @@ See the License for the specific language governing permissions and limitations under the License. --> - - - - - - \ No newline at end of file + android:paddingTop="@dimen/all_apps_work_profile_tab_footer_padding" +/> diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java index e085ff00bc..10a3060f5e 100644 --- a/src/com/android/launcher3/allapps/AllAppsContainerView.java +++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java @@ -62,7 +62,6 @@ import com.android.launcher3.util.MultiValueAlpha.AlphaProperty; import com.android.launcher3.util.Themes; import com.android.launcher3.views.RecyclerViewFastScroller; import com.android.launcher3.views.SpringRelativeLayout; -import com.android.launcher3.views.WorkFooterContainer; import java.util.ArrayList; @@ -91,7 +90,7 @@ public class AllAppsContainerView extends SpringRelativeLayout implements DragSo private AllAppsPagedView mViewPager; private FloatingHeaderView mHeader; - private WorkFooterContainer mWorkFooterContainer; + private WorkModeSwitch mWorkModeSwitch; private SpannableStringBuilder mSearchQueryBuilder = null; @@ -156,8 +155,8 @@ public class AllAppsContainerView extends SpringRelativeLayout implements DragSo return mMultiValueAlpha.getProperty(index); } - public WorkFooterContainer getWorkFooterContainer() { - return mWorkFooterContainer; + public WorkModeSwitch getWorkModeSwitch() { + return mWorkModeSwitch; } @@ -195,7 +194,7 @@ public class AllAppsContainerView extends SpringRelativeLayout implements DragSo } private void resetWorkProfile() { - mWorkFooterContainer.refresh(); + mWorkModeSwitch.refresh(); mAH[AdapterHolder.WORK].setupOverlay(); mAH[AdapterHolder.WORK].applyPadding(); } @@ -410,9 +409,9 @@ public class AllAppsContainerView extends SpringRelativeLayout implements DragSo } else { mAH[AdapterHolder.MAIN].setup(findViewById(R.id.apps_list_view), null); mAH[AdapterHolder.WORK].recyclerView = null; - if (mWorkFooterContainer != null) { - ((ViewGroup) mWorkFooterContainer.getParent()).removeView(mWorkFooterContainer); - mWorkFooterContainer = null; + if (mWorkModeSwitch != null) { + ((ViewGroup) mWorkModeSwitch.getParent()).removeView(mWorkModeSwitch); + mWorkModeSwitch = null; } } setupHeader(); @@ -422,14 +421,11 @@ public class AllAppsContainerView extends SpringRelativeLayout implements DragSo } private void setupWorkToggle() { - mWorkFooterContainer = (WorkFooterContainer) mLauncher.getLayoutInflater().inflate( - R.layout.work_tab_footer, findViewById(R.id.work_toggle_container)); - mWorkFooterContainer.setLayoutParams( - new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.WRAP_CONTENT)); - this.addView(mWorkFooterContainer); - mWorkFooterContainer.setInsets(mInsets); - mWorkFooterContainer.post(() -> mAH[AdapterHolder.WORK].applyPadding()); + mWorkModeSwitch = (WorkModeSwitch) mLauncher.getLayoutInflater().inflate( + R.layout.work_mode_switch, this, false); + this.addView(mWorkModeSwitch); + mWorkModeSwitch.setInsets(mInsets); + mWorkModeSwitch.post(() -> mAH[AdapterHolder.WORK].applyPadding()); } private void replaceRVContainer(boolean showTabs) { @@ -469,8 +465,8 @@ public class AllAppsContainerView extends SpringRelativeLayout implements DragSo findViewById(R.id.tab_work) .setOnClickListener((View view) -> mViewPager.snapToPage(AdapterHolder.WORK)); } - if (mWorkFooterContainer != null) { - mWorkFooterContainer.setWorkTabVisible(pos == AdapterHolder.WORK); + if (mWorkModeSwitch != null) { + mWorkModeSwitch.setWorkTabVisible(pos == AdapterHolder.WORK); } } @@ -648,6 +644,8 @@ public class AllAppsContainerView extends SpringRelativeLayout implements DragSo if (!mIsWork || recyclerView == null) return; boolean workDisabled = UserCache.INSTANCE.get(mLauncher).isAnyProfileQuietModeEnabled(); if (mWorkDisabled == workDisabled) return; + recyclerView.setContentDescription( + workDisabled ? mLauncher.getString(R.string.work_apps_paused_title) : null); if (workDisabled) { appsList.updateItemFilter((info, cn) -> false); recyclerView.addAutoSizedOverlay( @@ -662,8 +660,7 @@ public class AllAppsContainerView extends SpringRelativeLayout implements DragSo void applyPadding() { if (recyclerView != null) { int bottomOffset = - mWorkFooterContainer != null && mIsWork ? mWorkFooterContainer.getHeight() - : 0; + mWorkModeSwitch != null && mIsWork ? mWorkModeSwitch.getHeight() : 0; recyclerView.setPadding(padding.left, padding.top, padding.right, padding.bottom + bottomOffset); } diff --git a/src/com/android/launcher3/allapps/WorkModeSwitch.java b/src/com/android/launcher3/allapps/WorkModeSwitch.java index aadb297446..f935e4d40c 100644 --- a/src/com/android/launcher3/allapps/WorkModeSwitch.java +++ b/src/com/android/launcher3/allapps/WorkModeSwitch.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018 The Android Open Source Project + * 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. @@ -15,7 +15,13 @@ */ package com.android.launcher3.allapps; +import static com.android.launcher3.util.PackageManagerHelper.hasShortcutsPermission; + +import android.animation.ObjectAnimator; +import android.animation.PropertyValuesHolder; import android.content.Context; +import android.content.pm.PackageManager; +import android.graphics.Rect; import android.os.AsyncTask; import android.os.Process; import android.os.UserHandle; @@ -24,28 +30,45 @@ import android.util.AttributeSet; import android.view.MotionEvent; import android.widget.Switch; +import com.android.launcher3.Insettable; +import com.android.launcher3.Launcher; +import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.pm.UserCache; import java.lang.ref.WeakReference; -public class WorkModeSwitch extends Switch { +/** + * Work profile toggle switch shown at the bottom of AllApps work tab + */ +public class WorkModeSwitch extends Switch implements Insettable { + + private Rect mInsets = new Rect(); + protected ObjectAnimator mOpenCloseAnimator; + public WorkModeSwitch(Context context) { super(context); + init(); } public WorkModeSwitch(Context context, AttributeSet attrs) { super(context, attrs); + init(); } public WorkModeSwitch(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); + init(); + } + + private void init() { + mOpenCloseAnimator = ObjectAnimator.ofPropertyValuesHolder(this); } @Override public void setChecked(boolean checked) { - // No-op, do not change the checked state until broadcast is received. + } @Override @@ -55,14 +78,23 @@ public class WorkModeSwitch extends Switch { private void setCheckedInternal(boolean checked) { super.setChecked(checked); + setCompoundDrawablesWithIntrinsicBounds( + checked ? R.drawable.ic_corp : R.drawable.ic_corp_off, 0, 0, 0); } public void refresh() { + if (!shouldShowWorkSwitch()) return; UserCache userManager = UserCache.INSTANCE.get(getContext()); setCheckedInternal(!userManager.isAnyProfileQuietModeEnabled()); setEnabled(true); } + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + super.onLayout(changed, l, t, r, b); + this.setVisibility(shouldShowWorkSwitch() ? VISIBLE : GONE); + } + @Override public boolean onTouchEvent(MotionEvent ev) { return ev.getActionMasked() == MotionEvent.ACTION_MOVE || super.onTouchEvent(ev); @@ -72,6 +104,24 @@ public class WorkModeSwitch extends Switch { new SetQuietModeEnabledAsyncTask(enabled, new WeakReference<>(this)).execute(); } + @Override + public void setInsets(Rect insets) { + int bottomInset = insets.bottom - mInsets.bottom; + mInsets.set(insets); + setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(), + getPaddingBottom() + bottomInset); + } + + /** + * Animates in/out work profile toggle panel based on the tab user is on + */ + public void setWorkTabVisible(boolean workTabVisible) { + if (!shouldShowWorkSwitch()) return; + + mOpenCloseAnimator.setValues(PropertyValuesHolder.ofFloat(ALPHA, workTabVisible ? 1 : 0)); + mOpenCloseAnimator.start(); + } + private static final class SetQuietModeEnabledAsyncTask extends AsyncTask { @@ -122,4 +172,11 @@ public class WorkModeSwitch extends Switch { } } } + + private boolean shouldShowWorkSwitch() { + Launcher launcher = Launcher.getLauncher(getContext()); + return Utilities.ATLEAST_P && (hasShortcutsPermission(launcher) + || launcher.checkSelfPermission("android.permission.MODIFY_QUIET_MODE") + == PackageManager.PERMISSION_GRANTED); + } } diff --git a/src/com/android/launcher3/views/WorkFooterContainer.java b/src/com/android/launcher3/views/WorkFooterContainer.java deleted file mode 100644 index d86d0ffd89..0000000000 --- a/src/com/android/launcher3/views/WorkFooterContainer.java +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright (C) 2017 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.views; - -import static com.android.launcher3.util.PackageManagerHelper.hasShortcutsPermission; - -import android.animation.ObjectAnimator; -import android.animation.PropertyValuesHolder; -import android.content.Context; -import android.content.pm.PackageManager; -import android.graphics.Rect; -import android.util.AttributeSet; -import android.view.View; -import android.widget.LinearLayout; -import android.widget.TextView; - -import com.android.launcher3.Insettable; -import com.android.launcher3.Launcher; -import com.android.launcher3.R; -import com.android.launcher3.Utilities; -import com.android.launcher3.allapps.WorkModeSwitch; -import com.android.launcher3.pm.UserCache; - -/** - * Container to show work footer in all-apps. - */ -public class WorkFooterContainer extends LinearLayout implements Insettable { - private Rect mInsets = new Rect(); - - private WorkModeSwitch mWorkModeSwitch; - private TextView mWorkModeLabel; - - protected final ObjectAnimator mOpenCloseAnimator; - - public WorkFooterContainer(Context context) { - this(context, null, 0); - } - - public WorkFooterContainer(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public WorkFooterContainer(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - mOpenCloseAnimator = ObjectAnimator.ofPropertyValuesHolder(this); - } - - @Override - protected void onLayout(boolean changed, int l, int t, int r, int b) { - super.onLayout(changed, l, t, r, b); - updateTranslation(); - this.setVisibility(shouldShowWorkFooter() ? VISIBLE : GONE); - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - mWorkModeSwitch = findViewById(R.id.work_mode_toggle); - mWorkModeLabel = findViewById(R.id.work_mode_label); - } - - @Override - public void offsetTopAndBottom(int offset) { - super.offsetTopAndBottom(offset); - updateTranslation(); - } - - private void updateTranslation() { - if (getParent() instanceof View) { - View parent = (View) getParent(); - int availableBot = parent.getHeight() - parent.getPaddingBottom(); - setTranslationY(Math.max(0, availableBot - getBottom())); - } - } - - @Override - public void setInsets(Rect insets) { - int bottomInset = insets.bottom - mInsets.bottom; - mInsets.set(insets); - setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(), - getPaddingBottom() + bottomInset); - } - - /** - * Animates in/out work profile toggle panel based on the tab user is on - */ - public void setWorkTabVisible(boolean workTabVisible) { - if (!shouldShowWorkFooter()) return; - - mOpenCloseAnimator.setValues(PropertyValuesHolder.ofFloat(ALPHA, workTabVisible ? 1 : 0)); - mOpenCloseAnimator.start(); - } - - /** - * Refreshes views based on current work profile enabled status - */ - public void refresh() { - if (!shouldShowWorkFooter()) return; - boolean anyProfileQuietModeEnabled = UserCache.INSTANCE.get( - getContext()).isAnyProfileQuietModeEnabled(); - - mWorkModeLabel.setCompoundDrawablesWithIntrinsicBounds( - anyProfileQuietModeEnabled ? R.drawable.ic_corp_off : R.drawable.ic_corp, 0, 0, 0); - mWorkModeSwitch.refresh(); - } - - /** - * Returns work mode switch - */ - public WorkModeSwitch getWorkModeSwitch() { - return mWorkModeSwitch; - } - - private boolean shouldShowWorkFooter() { - Launcher launcher = Launcher.getLauncher(getContext()); - return Utilities.ATLEAST_P && (hasShortcutsPermission(launcher) - || launcher.checkSelfPermission("android.permission.MODIFY_QUIET_MODE") - == PackageManager.PERMISSION_GRANTED); - } -} diff --git a/tests/src/com/android/launcher3/ui/WorkTabTest.java b/tests/src/com/android/launcher3/ui/WorkTabTest.java index db2d97478a..7e80e5d96e 100644 --- a/tests/src/com/android/launcher3/ui/WorkTabTest.java +++ b/tests/src/com/android/launcher3/ui/WorkTabTest.java @@ -31,9 +31,9 @@ import androidx.test.runner.AndroidJUnit4; import com.android.launcher3.R; import com.android.launcher3.allapps.AllAppsContainerView; import com.android.launcher3.allapps.AllAppsPagedView; +import com.android.launcher3.allapps.WorkModeSwitch; import com.android.launcher3.dragndrop.DragLayer; import com.android.launcher3.views.WorkEduView; -import com.android.launcher3.views.WorkFooterContainer; import org.junit.After; import org.junit.Before; @@ -87,7 +87,7 @@ public class WorkTabTest extends AbstractLauncherUiTest { executeOnLauncher(launcher -> launcher.getStateManager().goToState(ALL_APPS)); waitForState("Launcher internal state didn't switch to All Apps", () -> ALL_APPS); getOnceNotNull("Apps view did not bind", - launcher -> launcher.getAppsView().getWorkFooterContainer(), 60000); + launcher -> launcher.getAppsView().getWorkModeSwitch(), 60000); UserManager userManager = getFromLauncher(l -> l.getSystemService(UserManager.class)); assertEquals(2, userManager.getUserProfiles().size()); @@ -102,10 +102,10 @@ public class WorkTabTest extends AbstractLauncherUiTest { assertTrue(userManager.isQuietModeEnabled(workProfile)); executeOnLauncher(launcher -> { - WorkFooterContainer wf = launcher.getAppsView().getWorkFooterContainer(); + WorkModeSwitch wf = launcher.getAppsView().getWorkModeSwitch(); ((AllAppsPagedView) launcher.getAppsView().getContentView()).snapToPageImmediately( AllAppsContainerView.AdapterHolder.WORK); - wf.getWorkModeSwitch().toggle(); + wf.toggle(); }); waitForLauncherCondition("Work toggle did not work", l -> l.getSystemService(UserManager.class).isQuietModeEnabled(workProfile)); From 7f6baf9f60387ff3ee8c8ee7982edb1cba7a316d Mon Sep 17 00:00:00 2001 From: Samuel Fufa Date: Fri, 20 Mar 2020 13:25:46 -0700 Subject: [PATCH 23/36] [Flake test] add state checks for WorkTabTest#testWorkEduIntermittent Bug: 151768149 Test: labtest Change-Id: I676175629c60662763b26ce24eba4a361784fbeb --- tests/src/com/android/launcher3/ui/WorkTabTest.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/src/com/android/launcher3/ui/WorkTabTest.java b/tests/src/com/android/launcher3/ui/WorkTabTest.java index db2d97478a..c0d8bfc73d 100644 --- a/tests/src/com/android/launcher3/ui/WorkTabTest.java +++ b/tests/src/com/android/launcher3/ui/WorkTabTest.java @@ -16,6 +16,7 @@ package com.android.launcher3.ui; import static com.android.launcher3.LauncherState.ALL_APPS; +import static com.android.launcher3.LauncherState.NORMAL; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @@ -158,9 +159,11 @@ public class WorkTabTest extends AbstractLauncherUiTest { // dismiss personal edu mDevice.pressHome(); + waitForState("Launcher did not go home", () -> NORMAL); // open work tab executeOnLauncher(launcher -> launcher.getStateManager().goToState(ALL_APPS)); + waitForState("Launcher did not switch to all apps", () -> ALL_APPS); executeOnLauncher(launcher -> { AllAppsPagedView pagedView = (AllAppsPagedView) launcher.getAppsView().getContentView(); pagedView.setCurrentPage(WORK_PAGE); From 1b4247d9cd6c2f2d10338a0441683bcf362bd267 Mon Sep 17 00:00:00 2001 From: vadimt Date: Fri, 20 Mar 2020 14:25:16 -0700 Subject: [PATCH 24/36] Disable battery defender to avoid locking during the test Bug: 151613234 Change-Id: Id323025c1a3bb53cc39839aa2295fcd25436e68e --- tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java index f5dd995fc9..551b5337fa 100644 --- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java +++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java @@ -220,6 +220,7 @@ public abstract class AbstractLauncherUiTest { @Before public void setUp() throws Exception { + mDevice.executeShellCommand("setprop vendor.battery.defender.disable 1"); mDevice.executeShellCommand("settings put global stay_on_while_plugged_in 3"); if (hasSystemUiObject("keyguard_status_view")) { Log.d(TAG, "Before unlocking the phone"); From 650869973a8758ec3f08db8be8e4acc4ee2e940a Mon Sep 17 00:00:00 2001 From: Adam Cohen Date: Wed, 19 Feb 2020 08:40:49 -0800 Subject: [PATCH 25/36] Refactor / Cleanup / Simplify a bunch of dnd related rendering / animation Addresses: => Fix folder icon drop location mapping (was very far off) => Fix BubbleTextView drop animation shifted by a few pixels => Get rid multiple unnecessary calls to setDragMode(none), was breaking some of the reorder flow; still some issues to address here. => Fix folder icon parallax (didn't work when the folder had a dot) Test: manual Change-Id: I5959cf341996f75d30212353ec096ed25bf40ea5 --- src/com/android/launcher3/BubbleTextView.java | 25 +++- src/com/android/launcher3/CellLayout.java | 115 ++++++++++-------- src/com/android/launcher3/DropTarget.java | 5 + .../launcher3/ShortcutAndWidgetContainer.java | 64 +++++----- src/com/android/launcher3/Workspace.java | 59 ++++----- .../launcher3/dragndrop/DragController.java | 5 +- .../launcher3/dragndrop/DragLayer.java | 51 +++----- .../launcher3/dragndrop/DraggableView.java | 57 +++++++++ .../dragndrop/FolderAdaptiveIcon.java | 72 +++++------ .../android/launcher3/folder/FolderIcon.java | 18 ++- .../graphics/DragPreviewProvider.java | 86 +++---------- .../popup/PopupContainerWithArrow.java | 4 +- .../launcher3/views/FloatingIconView.java | 26 +--- .../widget/LauncherAppWidgetHostView.java | 18 ++- .../widget/NavigableAppWidgetHostView.java | 15 ++- .../widget/PendingItemDragHelper.java | 9 +- 16 files changed, 355 insertions(+), 274 deletions(-) create mode 100644 src/com/android/launcher3/dragndrop/DraggableView.java diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java index e6f8a85e52..21a8fd406e 100644 --- a/src/com/android/launcher3/BubbleTextView.java +++ b/src/com/android/launcher3/BubbleTextView.java @@ -46,6 +46,7 @@ import android.widget.TextView; import com.android.launcher3.Launcher.OnResumeCallback; import com.android.launcher3.accessibility.LauncherAccessibilityDelegate; import com.android.launcher3.dot.DotInfo; +import com.android.launcher3.dragndrop.DraggableView; import com.android.launcher3.folder.FolderIcon; import com.android.launcher3.graphics.IconPalette; import com.android.launcher3.graphics.IconShape; @@ -65,7 +66,7 @@ import java.text.NumberFormat; * too aggressive. */ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, OnResumeCallback, - IconLabelDotView { + IconLabelDotView, DraggableView { private static final int DISPLAY_WORKSPACE = 0; private static final int DISPLAY_ALL_APPS = 1; @@ -103,7 +104,7 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, private final ActivityContext mActivity; private Drawable mIcon; - private final boolean mCenterVertically; + private boolean mCenterVertically; private final int mDisplay; @@ -701,4 +702,24 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, public int getIconSize() { return mIconSize; } + + @Override + public int getViewType() { + return DRAGGABLE_ICON; + } + + @Override + public void getVisualDragBounds(Rect bounds) { + DeviceProfile grid = mActivity.getDeviceProfile(); + BubbleTextView.getIconBounds(this, bounds, grid.iconSizePx); + } + + @Override + public void prepareDrawDragView() { + if (getIcon() instanceof FastBitmapDrawable) { + FastBitmapDrawable icon = (FastBitmapDrawable) getIcon(); + icon.setScale(1f); + } + setForceHideDot(true); + } } diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java index e3eb38792a..6c562cfac2 100644 --- a/src/com/android/launcher3/CellLayout.java +++ b/src/com/android/launcher3/CellLayout.java @@ -58,6 +58,7 @@ import com.android.launcher3.accessibility.WorkspaceAccessibilityHelper; import com.android.launcher3.anim.Interpolators; import com.android.launcher3.anim.PropertyListBuilder; import com.android.launcher3.config.FeatureFlags; +import com.android.launcher3.dragndrop.DraggableView; import com.android.launcher3.folder.PreviewBackground; import com.android.launcher3.graphics.DragPreviewProvider; import com.android.launcher3.graphics.RotationMode; @@ -105,6 +106,11 @@ public class CellLayout extends ViewGroup implements Transposable { @Thunk final int[] mTmpPoint = new int[2]; @Thunk final int[] mTempLocation = new int[2]; + // Used to visualize / debug the Grid of the CellLayout + private static final boolean VISUALIZE_GRID = false; + private Rect mVisualizeGridRect = new Rect(); + private Paint mVisualizeGridPaint = new Paint(); + private GridOccupancy mOccupied; private GridOccupancy mTmpOccupied; @@ -483,6 +489,37 @@ public class CellLayout extends ViewGroup implements Transposable { mFolderLeaveBehind.drawLeaveBehind(canvas); canvas.restore(); } + + if (VISUALIZE_GRID) { + visualizeGrid(canvas); + } + } + + protected void visualizeGrid(Canvas canvas) { + mVisualizeGridRect.set(0, 0, mCellWidth, mCellHeight); + mVisualizeGridPaint.setStrokeWidth(4); + + for (int i = 0; i < mCountX; i++) { + for (int j = 0; j < mCountY; j++) { + canvas.save(); + + int transX = i * mCellWidth; + int transY = j * mCellHeight; + + canvas.translate(getPaddingLeft() + transX, getPaddingTop() + transY); + + mVisualizeGridPaint.setStyle(Paint.Style.FILL); + mVisualizeGridPaint.setColor(Color.argb(80, 255, 100, 100)); + + canvas.drawRect(mVisualizeGridRect, mVisualizeGridPaint); + + mVisualizeGridPaint.setStyle(Paint.Style.STROKE); + mVisualizeGridPaint.setColor(Color.argb(255, 255, 100, 100)); + + canvas.drawRect(mVisualizeGridRect, mVisualizeGridPaint); + canvas.restore(); + } + } } @Override @@ -949,8 +986,8 @@ public class CellLayout extends ViewGroup implements Transposable { return false; } - void visualizeDropLocation(View v, DragPreviewProvider outlineProvider, int cellX, int cellY, - int spanX, int spanY, boolean resize, DropTarget.DragObject dragObject) { + void visualizeDropLocation(DraggableView v, DragPreviewProvider outlineProvider, int cellX, int + cellY, int spanX, int spanY, boolean resize, DropTarget.DragObject dragObject) { final int oldDragCellX = mDragCell[0]; final int oldDragCellY = mDragCell[1]; @@ -960,9 +997,6 @@ public class CellLayout extends ViewGroup implements Transposable { Bitmap dragOutline = outlineProvider.generatedDragOutline; if (cellX != oldDragCellX || cellY != oldDragCellY) { - Point dragOffset = dragObject.dragView.getDragVisualizeOffset(); - Rect dragRegion = dragObject.dragView.getDragRegion(); - mDragCell[0] = cellX; mDragCell[1] = cellY; @@ -971,50 +1005,27 @@ public class CellLayout extends ViewGroup implements Transposable { mDragOutlineCurrent = (oldIndex + 1) % mDragOutlines.length; Rect r = mDragOutlines[mDragOutlineCurrent]; + cellToRect(cellX, cellY, spanX, spanY, r); + int left = r.left; + int top = r.top; + + int width = dragOutline.getWidth(); + int height = dragOutline.getHeight(); + if (resize) { - cellToRect(cellX, cellY, spanX, spanY, r); - if (v instanceof LauncherAppWidgetHostView) { - DeviceProfile profile = mActivity.getWallpaperDeviceProfile(); - Utilities.shrinkRect(r, profile.appWidgetScale.x, profile.appWidgetScale.y); - } - } else { - // Find the top left corner of the rect the object will occupy - final int[] topLeft = mTmpPoint; - cellToPoint(cellX, cellY, topLeft); - - int left = topLeft[0]; - int top = topLeft[1]; - - if (v != null && dragOffset == null) { - // When drawing the drag outline, it did not account for margin offsets - // added by the view's parent. - MarginLayoutParams lp = (MarginLayoutParams) v.getLayoutParams(); - left += lp.leftMargin; - top += lp.topMargin; - - // Offsets due to the size difference between the View and the dragOutline. - // There is a size difference to account for the outer blur, which may lie - // outside the bounds of the view. - top += ((mCellHeight * spanY) - dragOutline.getHeight()) / 2; - // We center about the x axis - left += ((mCellWidth * spanX) - dragOutline.getWidth()) / 2; - } else { - if (dragOffset != null && dragRegion != null) { - // Center the drag region *horizontally* in the cell and apply a drag - // outline offset - left += dragOffset.x + ((mCellWidth * spanX) - dragRegion.width()) / 2; - int cHeight = getShortcutsAndWidgets().getCellContentHeight(); - int cellPaddingY = (int) Math.max(0, ((mCellHeight - cHeight) / 2f)); - top += dragOffset.y + cellPaddingY; - } else { - // Center the drag outline in the cell - left += ((mCellWidth * spanX) - dragOutline.getWidth()) / 2; - top += ((mCellHeight * spanY) - dragOutline.getHeight()) / 2; - } - } - r.set(left, top, left + dragOutline.getWidth(), top + dragOutline.getHeight()); + width = r.width(); + height = r.height(); } + if (v != null && v.getViewType() == DraggableView.DRAGGABLE_ICON) { + left += ((mCellWidth * spanX) - dragOutline.getWidth()) / 2; + int cHeight = getShortcutsAndWidgets().getCellContentHeight(); + int cellPaddingY = (int) Math.max(0, ((mCellHeight - cHeight) / 2f)); + top += cellPaddingY; + } + + r.set(left, top, left + width, top + height); + Utilities.scaleRectAboutCenter(r, mChildScale); mDragOutlineAnims[mDragOutlineCurrent].setTag(dragOutline); mDragOutlineAnims[mDragOutlineCurrent].animateIn(); @@ -1900,7 +1911,7 @@ public class CellLayout extends ViewGroup implements Transposable { // This method starts or changes the reorder preview animations private void beginOrAdjustReorderPreviewAnimations(ItemConfiguration solution, - View dragView, int delay, int mode) { + View dragView, int mode) { int childCount = mShortcutsAndWidgets.getChildCount(); for (int i = 0; i < childCount; i++) { View child = mShortcutsAndWidgets.getChildAt(i); @@ -1967,6 +1978,8 @@ public class CellLayout extends ViewGroup implements Transposable { this.child = child; this.mode = mode; + + // TODO issue! setInitialAnimationValues(false); finalScale = (mChildScale - (CHILD_DIVIDEND / child.getWidth())) * initScale; finalDeltaX = initDeltaX; @@ -2162,6 +2175,8 @@ public class CellLayout extends ViewGroup implements Transposable { */ private void getDirectionVectorForDrop(int dragViewCenterX, int dragViewCenterY, int spanX, int spanY, View dragView, int[] resultDirection) { + + //TODO(adamcohen) b/151776141 use the items visual center for the direction vector int[] targetDestination = new int[2]; findNearestArea(dragViewCenterX, dragViewCenterY, spanX, spanY, targetDestination); @@ -2272,7 +2287,7 @@ public class CellLayout extends ViewGroup implements Transposable { setItemPlacementDirty(false); } else { beginOrAdjustReorderPreviewAnimations(swapSolution, dragView, - REORDER_ANIMATION_DURATION, ReorderPreviewAnimation.MODE_PREVIEW); + ReorderPreviewAnimation.MODE_PREVIEW); } mShortcutsAndWidgets.requestLayout(); } @@ -2326,7 +2341,7 @@ public class CellLayout extends ViewGroup implements Transposable { if (mode == MODE_SHOW_REORDER_HINT) { if (finalSolution != null) { - beginOrAdjustReorderPreviewAnimations(finalSolution, dragView, 0, + beginOrAdjustReorderPreviewAnimations(finalSolution, dragView, ReorderPreviewAnimation.MODE_HINT); result[0] = finalSolution.cellX; result[1] = finalSolution.cellY; @@ -2366,7 +2381,7 @@ public class CellLayout extends ViewGroup implements Transposable { setItemPlacementDirty(false); } else { beginOrAdjustReorderPreviewAnimations(finalSolution, dragView, - REORDER_ANIMATION_DURATION, ReorderPreviewAnimation.MODE_PREVIEW); + ReorderPreviewAnimation.MODE_PREVIEW); } } } else { diff --git a/src/com/android/launcher3/DropTarget.java b/src/com/android/launcher3/DropTarget.java index a32fd1239f..43ec5a6949 100644 --- a/src/com/android/launcher3/DropTarget.java +++ b/src/com/android/launcher3/DropTarget.java @@ -23,6 +23,7 @@ import com.android.launcher3.accessibility.DragViewStateAnnouncer; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.dragndrop.DragOptions; import com.android.launcher3.dragndrop.DragView; +import com.android.launcher3.dragndrop.DraggableView; import com.android.launcher3.folder.FolderNameProvider; /** @@ -72,6 +73,10 @@ public interface DropTarget { public FolderNameProvider folderNameProvider; + /** The source view (ie. icon, widget etc.) that is being dragged and which the + * DragView represents. May be an actual View class or a virtual stand-in */ + public DraggableView originalView = null; + public DragObject(Context context) { if (FeatureFlags.FOLDER_NAME_SUGGEST.get()) { folderNameProvider = FolderNameProvider.newInstance(context); diff --git a/src/com/android/launcher3/ShortcutAndWidgetContainer.java b/src/com/android/launcher3/ShortcutAndWidgetContainer.java index 1bd82633eb..c07dd9d3f8 100644 --- a/src/com/android/launcher3/ShortcutAndWidgetContainer.java +++ b/src/com/android/launcher3/ShortcutAndWidgetContainer.java @@ -145,38 +145,46 @@ public class ShortcutAndWidgetContainer extends ViewGroup { final View child = getChildAt(i); if (child.getVisibility() != GONE) { CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams(); - - if (child instanceof LauncherAppWidgetHostView) { - LauncherAppWidgetHostView lahv = (LauncherAppWidgetHostView) child; - - // Scale and center the widget to fit within its cells. - DeviceProfile profile = mActivity.getDeviceProfile(); - float scaleX = profile.appWidgetScale.x; - float scaleY = profile.appWidgetScale.y; - - lahv.setScaleToFit(Math.min(scaleX, scaleY)); - lahv.setTranslationForCentering(-(lp.width - (lp.width * scaleX)) / 2.0f, - -(lp.height - (lp.height * scaleY)) / 2.0f); - } - - int childLeft = lp.x; - int childTop = lp.y; - child.layout(childLeft, childTop, childLeft + lp.width, childTop + lp.height); - - if (lp.dropped) { - lp.dropped = false; - - final int[] cellXY = mTmpCellXY; - getLocationOnScreen(cellXY); - mWallpaperManager.sendWallpaperCommand(getWindowToken(), - WallpaperManager.COMMAND_DROP, - cellXY[0] + childLeft + lp.width / 2, - cellXY[1] + childTop + lp.height / 2, 0, null); - } + layoutChild(child); } } } + /** + * Core logic to layout a child for this ViewGroup. + */ + public void layoutChild(View child) { + CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams(); + if (child instanceof LauncherAppWidgetHostView) { + LauncherAppWidgetHostView lahv = (LauncherAppWidgetHostView) child; + + // Scale and center the widget to fit within its cells. + DeviceProfile profile = mActivity.getDeviceProfile(); + float scaleX = profile.appWidgetScale.x; + float scaleY = profile.appWidgetScale.y; + + lahv.setScaleToFit(Math.min(scaleX, scaleY)); + lahv.setTranslationForCentering(-(lp.width - (lp.width * scaleX)) / 2.0f, + -(lp.height - (lp.height * scaleY)) / 2.0f); + } + + int childLeft = lp.x; + int childTop = lp.y; + child.layout(childLeft, childTop, childLeft + lp.width, childTop + lp.height); + + if (lp.dropped) { + lp.dropped = false; + + final int[] cellXY = mTmpCellXY; + getLocationOnScreen(cellXY); + mWallpaperManager.sendWallpaperCommand(getWindowToken(), + WallpaperManager.COMMAND_DROP, + cellXY[0] + childLeft + lp.width / 2, + cellXY[1] + childTop + lp.height / 2, 0, null); + } + } + + @Override public boolean onInterceptTouchEvent(MotionEvent ev) { if (ev.getAction() == ACTION_DOWN && getAlpha() == 0) { diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java index ead6018505..d5517765a6 100644 --- a/src/com/android/launcher3/Workspace.java +++ b/src/com/android/launcher3/Workspace.java @@ -70,6 +70,7 @@ import com.android.launcher3.dragndrop.DragController; import com.android.launcher3.dragndrop.DragLayer; import com.android.launcher3.dragndrop.DragOptions; import com.android.launcher3.dragndrop.DragView; +import com.android.launcher3.dragndrop.DraggableView; import com.android.launcher3.dragndrop.SpringLoadedDragController; import com.android.launcher3.folder.Folder; import com.android.launcher3.folder.FolderIcon; @@ -81,7 +82,6 @@ import com.android.launcher3.icons.BitmapRenderer; import com.android.launcher3.logging.UserEventDispatcher; import com.android.launcher3.pageindicators.WorkspacePageIndicator; import com.android.launcher3.popup.PopupContainerWithArrow; -import com.android.launcher3.shortcuts.ShortcutDragPreviewProvider; import com.android.launcher3.states.StateAnimationConfig; import com.android.launcher3.touch.WorkspaceTouchListener; import com.android.launcher3.userevent.nano.LauncherLogProto.Action; @@ -1465,12 +1465,17 @@ public class Workspace extends PagedView + "View: " + child + " tag: " + child.getTag(); throw new IllegalStateException(msg); } - beginDragShared(child, source, (ItemInfo) dragObject, + beginDragShared(child, null, source, (ItemInfo) dragObject, new DragPreviewProvider(child), options); } - public DragView beginDragShared(View child, DragSource source, ItemInfo dragObject, - DragPreviewProvider previewProvider, DragOptions dragOptions) { + /** + * Core functionality for beginning a drag operation for an item that will be dropped within + * the workspace + */ + public DragView beginDragShared(View child, DraggableView draggableView, DragSource source, + ItemInfo dragObject, DragPreviewProvider previewProvider, DragOptions dragOptions) { + float iconScale = 1f; if (child instanceof BubbleTextView) { Drawable icon = ((BubbleTextView) child).getIcon(); @@ -1479,41 +1484,36 @@ public class Workspace extends PagedView } } + // Clear the pressed state if necessary child.clearFocus(); child.setPressed(false); + if (child instanceof BubbleTextView) { + BubbleTextView icon = (BubbleTextView) child; + icon.clearPressedBackground(); + } + mOutlineProvider = previewProvider; // The drag bitmap follows the touch point around on the screen final Bitmap b = previewProvider.createDragBitmap(); int halfPadding = previewProvider.previewPadding / 2; - float scale = previewProvider.getScaleAndPosition(b, mTempXY); int dragLayerX = mTempXY[0]; int dragLayerY = mTempXY[1]; - DeviceProfile grid = mLauncher.getDeviceProfile(); Point dragVisualizeOffset = null; - Rect dragRect = null; - if (child instanceof BubbleTextView) { - dragRect = new Rect(); - BubbleTextView.getIconBounds(child, dragRect, grid.iconSizePx); + Rect dragRect = new Rect(); + + if (draggableView == null && child instanceof DraggableView) { + draggableView = (DraggableView) child; + } + + if (draggableView != null) { + draggableView.getVisualDragBounds(dragRect); dragLayerY += dragRect.top; - // Note: The dragRect is used to calculate drag layer offsets, but the - // dragVisualizeOffset in addition to the dragRect (the size) to position the outline. - dragVisualizeOffset = new Point(- halfPadding, halfPadding); - } else if (child instanceof FolderIcon) { - int previewSize = grid.folderIconSizePx; - dragVisualizeOffset = new Point(- halfPadding, halfPadding - child.getPaddingTop()); - dragRect = new Rect(0, child.getPaddingTop(), child.getWidth(), previewSize); - } else if (previewProvider instanceof ShortcutDragPreviewProvider) { dragVisualizeOffset = new Point(- halfPadding, halfPadding); } - // Clear the pressed state if necessary - if (child instanceof BubbleTextView) { - BubbleTextView icon = (BubbleTextView) child; - icon.clearPressedBackground(); - } if (child.getParent() instanceof ShortcutAndWidgetContainer) { mDragSourceInternal = (ShortcutAndWidgetContainer) child.getParent(); @@ -1524,13 +1524,13 @@ public class Workspace extends PagedView .showForIcon((BubbleTextView) child); if (popupContainer != null) { dragOptions.preDragCondition = popupContainer.createPreDragCondition(); - mLauncher.getUserEventDispatcher().resetElapsedContainerMillis("dragging started"); } } - DragView dv = mDragController.startDrag(b, dragLayerX, dragLayerY, source, - dragObject, dragVisualizeOffset, dragRect, scale * iconScale, scale, dragOptions); + DragView dv = mDragController.startDrag(b, draggableView, dragLayerX, dragLayerY, source, + dragObject, dragVisualizeOffset, dragRect, scale * iconScale, + scale, dragOptions); dv.setIntrinsicIconScaleFactor(dragOptions.intrinsicIconScaleFactor); return dv; } @@ -2203,7 +2203,7 @@ public class Workspace extends PagedView item.spanY, child, mTargetCell); if (!nearestDropOccupied) { - mDragTargetLayout.visualizeDropLocation(child, mOutlineProvider, + mDragTargetLayout.visualizeDropLocation(d.originalView, mOutlineProvider, mTargetCell[0], mTargetCell[1], item.spanX, item.spanY, false, d); } else if ((mDragMode == DRAG_MODE_NONE || mDragMode == DRAG_MODE_REORDER) && !mReorderAlarm.alarmPending() && (mLastReorderX != reorderX || @@ -2297,7 +2297,8 @@ public class Workspace extends PagedView private void manageFolderFeedback(CellLayout targetLayout, int[] targetCell, float distance, DragObject dragObject) { if (distance > mMaxDistanceForFolderCreation) { - if (mDragMode != DRAG_MODE_NONE) { + if ((mDragMode == DRAG_MODE_ADD_TO_FOLDER + || mDragMode == DRAG_MODE_CREATE_FOLDER)) { setDragMode(DRAG_MODE_NONE); } return; @@ -2413,7 +2414,7 @@ public class Workspace extends PagedView } boolean resize = resultSpan[0] != spanX || resultSpan[1] != spanY; - mDragTargetLayout.visualizeDropLocation(child, mOutlineProvider, + mDragTargetLayout.visualizeDropLocation(dragObject.originalView, mOutlineProvider, mTargetCell[0], mTargetCell[1], resultSpan[0], resultSpan[1], resize, dragObject); } } diff --git a/src/com/android/launcher3/dragndrop/DragController.java b/src/com/android/launcher3/dragndrop/DragController.java index de7bc6d0e2..ffaada1379 100644 --- a/src/com/android/launcher3/dragndrop/DragController.java +++ b/src/com/android/launcher3/dragndrop/DragController.java @@ -140,6 +140,8 @@ public class DragController implements DragDriver.EventListener, TouchController * * @param b The bitmap to display as the drag image. It will be re-scaled to the * enlarged size. + * @param originalView The source view (ie. icon, widget etc.) that is being dragged + * and which the DragView represents * @param dragLayerX The x position in the DragLayer of the left-top of the bitmap. * @param dragLayerY The y position in the DragLayer of the left-top of the bitmap. * @param source An object representing where the drag originated @@ -147,7 +149,7 @@ public class DragController implements DragDriver.EventListener, TouchController * @param dragRegion Coordinates within the bitmap b for the position of item being dragged. * Makes dragging feel more precise, e.g. you can clip out a transparent border */ - public DragView startDrag(Bitmap b, int dragLayerX, int dragLayerY, + public DragView startDrag(Bitmap b, DraggableView originalView, int dragLayerX, int dragLayerY, DragSource source, ItemInfo dragInfo, Point dragOffset, Rect dragRegion, float initialDragViewScale, float dragViewScaleOnDrop, DragOptions options) { if (PROFILE_DRAWING_DURING_DRAG) { @@ -173,6 +175,7 @@ public class DragController implements DragDriver.EventListener, TouchController mLastDropTarget = null; mDragObject = new DropTarget.DragObject(mLauncher.getApplicationContext()); + mDragObject.originalView = originalView; mIsInPreDrag = mOptions.preDragCondition != null && !mOptions.preDragCondition.shouldStartDrag(0); diff --git a/src/com/android/launcher3/dragndrop/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java index 369bf283b1..409a1d5434 100644 --- a/src/com/android/launcher3/dragndrop/DragLayer.java +++ b/src/com/android/launcher3/dragndrop/DragLayer.java @@ -41,7 +41,6 @@ import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; import android.view.animation.Interpolator; import android.widget.FrameLayout; -import android.widget.TextView; import com.android.launcher3.AbstractFloatingView; import com.android.launcher3.CellLayout; @@ -52,7 +51,6 @@ 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.folder.FolderIcon; import com.android.launcher3.graphics.OverviewScrim; import com.android.launcher3.graphics.RotationMode; import com.android.launcher3.graphics.WorkspaceAndHotseatScrim; @@ -90,6 +88,8 @@ 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; @@ -254,59 +254,46 @@ public class DragLayer extends BaseDragLayer { public void animateViewIntoPosition(DragView dragView, final View child, int duration, View anchorView) { + ShortcutAndWidgetContainer parentChildren = (ShortcutAndWidgetContainer) child.getParent(); CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams(); parentChildren.measureChild(child); + parentChildren.layoutChild(child); - Rect r = new Rect(); - getViewRectRelativeToSelf(dragView, r); + getViewRectRelativeToSelf(dragView, mTmpRect); + final int fromX = mTmpRect.left; + final int fromY = mTmpRect.top; float coord[] = new float[2]; float childScale = child.getScaleX(); + coord[0] = lp.x + (child.getMeasuredWidth() * (1 - childScale) / 2); coord[1] = lp.y + (child.getMeasuredHeight() * (1 - childScale) / 2); // Since the child hasn't necessarily been laid out, we force the lp to be updated with // the correct coordinates (above) and use these to determine the final location float scale = getDescendantCoordRelativeToSelf((View) child.getParent(), coord); + // We need to account for the scale of the child itself, as the above only accounts for // for the scale in parents. scale *= childScale; int toX = Math.round(coord[0]); int toY = Math.round(coord[1]); float toScale = scale; - if (child instanceof TextView) { - TextView tv = (TextView) child; - // Account for the source scale of the icon (ie. from AllApps to Workspace, in which - // the workspace may have smaller icon bounds). - toScale = scale / dragView.getIntrinsicIconScaleFactor(); - // The child may be scaled (always about the center of the view) so to account for it, - // we have to offset the position by the scaled size. Once we do that, we can center - // the drag view about the scaled child view. - // padding will remain constant (does not scale with size) - toY += tv.getPaddingTop(); - toY -= dragView.getMeasuredHeight() * (1 - toScale) / 2; - if (dragView.getDragVisualizeOffset() != null) { - toY -= Math.round(toScale * dragView.getDragVisualizeOffset().y); - } + if (child instanceof DraggableView) { + DraggableView d = (DraggableView) child; + d.getVisualDragBounds(mTmpRect); - toX -= (dragView.getMeasuredWidth() - Math.round(scale * child.getMeasuredWidth())) / 2; - } else if (child instanceof FolderIcon) { - // Account for holographic blur padding on the drag view - toY += Math.round(scale * (child.getPaddingTop() - dragView.getDragRegionTop())); - toY -= scale * dragView.getBlurSizeOutline() / 2; - toY -= (1 - scale) * dragView.getMeasuredHeight() / 2; - // Center in the x coordinate about the target's drawable - toX -= (dragView.getMeasuredWidth() - Math.round(scale * child.getMeasuredWidth())) / 2; - } else { - toY -= (Math.round(scale * (dragView.getHeight() - child.getMeasuredHeight()))) / 2; - toX -= (Math.round(scale * (dragView.getMeasuredWidth() - - child.getMeasuredWidth()))) / 2; + // 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; } - final int fromX = r.left; - final int fromY = r.top; child.setVisibility(INVISIBLE); Runnable onCompleteRunnable = () -> child.setVisibility(VISIBLE); animateViewIntoPosition(dragView, fromX, fromY, toX, toY, 1, 1, 1, toScale, toScale, diff --git a/src/com/android/launcher3/dragndrop/DraggableView.java b/src/com/android/launcher3/dragndrop/DraggableView.java new file mode 100644 index 0000000000..df9990278a --- /dev/null +++ b/src/com/android/launcher3/dragndrop/DraggableView.java @@ -0,0 +1,57 @@ +/* + * 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.dragndrop; + +import android.graphics.Rect; + +/** + * Interface defining methods required for drawing and previewing DragViews, drag previews, and + * related animations + */ +public interface DraggableView { + int DRAGGABLE_ICON = 0; + int DRAGGABLE_WIDGET = 1; + + /** + * Static ctr for a simple instance which just returns the view type. + */ + static DraggableView ofType(int type) { + return () -> type; + } + + /** + * Certain handling of DragViews depend only on whether this is an Icon Type item or a Widget + * Type item. + * + * @return DRAGGABLE_ICON or DRAGGABLE_WIDGET as appropriate + */ + int getViewType(); + + /** + * Before rendering as a DragView bitmap, some views need a preparation step. + */ + default void prepareDrawDragView() { } + + /** + * If an actual View subclass, this method returns the rectangle (within the View's coordinates) + * of the visual region that should get dragged. This is used to extract exactly that element + * as well as to offset that element as appropriate for various animations + * + * @param bounds Visual bounds in the views coordinates will be written here. + */ + default void getVisualDragBounds(Rect bounds) { } +} diff --git a/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java b/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java index 0bb3fbac52..ea1fbdb583 100644 --- a/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java +++ b/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java @@ -20,10 +20,10 @@ import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import android.annotation.TargetApi; import android.graphics.Bitmap; -import android.graphics.Canvas; import android.graphics.Matrix; import android.graphics.Path; import android.graphics.Point; +import android.graphics.Rect; import android.graphics.drawable.AdaptiveIconDrawable; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; @@ -33,7 +33,6 @@ import android.util.Log; import androidx.annotation.Nullable; import com.android.launcher3.Launcher; -import com.android.launcher3.R; import com.android.launcher3.folder.FolderIcon; import com.android.launcher3.folder.PreviewBackground; import com.android.launcher3.graphics.ShiftedBitmapDrawable; @@ -50,6 +49,7 @@ public class FolderAdaptiveIcon extends AdaptiveIconDrawable { private final Drawable mBadge; private final Path mMask; private final ConstantState mConstantState; + private static final Rect sTmpRect = new Rect(); private FolderAdaptiveIcon(Drawable bg, Drawable fg, Drawable badge, Path mask) { super(bg, fg); @@ -72,23 +72,14 @@ public class FolderAdaptiveIcon extends AdaptiveIconDrawable { public static @Nullable FolderAdaptiveIcon createFolderAdaptiveIcon( Launcher launcher, int folderId, Point dragViewSize) { Preconditions.assertNonUiThread(); - int margin = launcher.getResources() - .getDimensionPixelSize(R.dimen.blur_size_medium_outline); - - // Allocate various bitmaps on the background thread, because why not! - int width = dragViewSize.x - margin; - int height = dragViewSize.y - margin; - if (width <= 0 || height <= 0) { - return null; - } - final Bitmap badge = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); // Create the actual drawable on the UI thread to avoid race conditions with // FolderIcon draw pass try { return MAIN_EXECUTOR.submit(() -> { FolderIcon icon = launcher.findFolderIcon(folderId); - return icon == null ? null : createDrawableOnUiThread(icon, badge, dragViewSize); + return icon == null ? null : createDrawableOnUiThread(icon, dragViewSize); + }).get(); } catch (Exception e) { Log.e(TAG, "Unable to create folder icon", e); @@ -96,49 +87,50 @@ public class FolderAdaptiveIcon extends AdaptiveIconDrawable { } } - /** - * Initializes various bitmaps on the UI thread and returns the final drawable. - */ private static FolderAdaptiveIcon createDrawableOnUiThread(FolderIcon icon, - Bitmap badgeBitmap, Point dragViewSize) { + Point dragViewSize) { Preconditions.assertUIThread(); - float margin = icon.getResources().getDimension(R.dimen.blur_size_medium_outline) / 2; - Canvas c = new Canvas(); + icon.getPreviewBounds(sTmpRect); + PreviewBackground bg = icon.getFolderBackground(); - // Initialize badge - c.setBitmap(badgeBitmap); - bg.drawShadow(c); - bg.drawBackgroundStroke(c); - icon.drawDot(c); + // assume square + assert (dragViewSize.x == dragViewSize.y); + final int previewSize = sTmpRect.width(); - // Initialize preview - final float sizeScaleFactor = 1 + 2 * AdaptiveIconDrawable.getExtraInsetFraction(); - final int previewWidth = (int) (dragViewSize.x * sizeScaleFactor); - final int previewHeight = (int) (dragViewSize.y * sizeScaleFactor); + final int margin = (dragViewSize.x - previewSize) / 2; + final float previewShiftX = -sTmpRect.left + margin; + final float previewShiftY = -sTmpRect.top + margin; - final float shiftFactor = AdaptiveIconDrawable.getExtraInsetFraction() / sizeScaleFactor; - final float previewShiftX = shiftFactor * previewWidth; - final float previewShiftY = shiftFactor * previewHeight; - - Bitmap previewBitmap = BitmapRenderer.createHardwareBitmap(previewWidth, previewHeight, + // Initialize badge, which consists of the outline stroke, shadow and dot; these + // must be rendered above the foreground + Bitmap badgeBmp = BitmapRenderer.createHardwareBitmap(dragViewSize.x, dragViewSize.y, (canvas) -> { - int count = canvas.save(); + canvas.save(); canvas.translate(previewShiftX, previewShiftY); - icon.getPreviewItemManager().draw(canvas); - canvas.restoreToCount(count); + bg.drawShadow(canvas); + bg.drawBackgroundStroke(canvas); + icon.drawDot(canvas); + canvas.restore(); }); // Initialize mask Path mask = new Path(); Matrix m = new Matrix(); - m.setTranslate(margin, margin); + m.setTranslate(previewShiftX, previewShiftY); bg.getClipPath().transform(m, mask); - ShiftedBitmapDrawable badge = new ShiftedBitmapDrawable(badgeBitmap, margin, margin); - ShiftedBitmapDrawable foreground = new ShiftedBitmapDrawable(previewBitmap, - margin - previewShiftX, margin - previewShiftY); + Bitmap previewBitmap = BitmapRenderer.createHardwareBitmap(dragViewSize.x, dragViewSize.y, + (canvas) -> { + canvas.save(); + canvas.translate(previewShiftX, previewShiftY); + icon.getPreviewItemManager().draw(canvas); + canvas.restore(); + }); + + ShiftedBitmapDrawable badge = new ShiftedBitmapDrawable(badgeBmp, 0, 0); + ShiftedBitmapDrawable foreground = new ShiftedBitmapDrawable(previewBitmap, 0, 0); return new FolderAdaptiveIcon(new ColorDrawable(bg.getBgColor()), foreground, badge, mask); } diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java index ab1ff102c6..f0d18ae63c 100644 --- a/src/com/android/launcher3/folder/FolderIcon.java +++ b/src/com/android/launcher3/folder/FolderIcon.java @@ -63,6 +63,7 @@ import com.android.launcher3.dot.FolderDotInfo; import com.android.launcher3.dragndrop.BaseItemDragListener; import com.android.launcher3.dragndrop.DragLayer; import com.android.launcher3.dragndrop.DragView; +import com.android.launcher3.dragndrop.DraggableView; import com.android.launcher3.icons.DotRenderer; import com.android.launcher3.touch.ItemClickHandler; import com.android.launcher3.util.Executors; @@ -78,7 +79,8 @@ import java.util.function.Predicate; /** * An icon that can appear on in the workspace representing an {@link Folder}. */ -public class FolderIcon extends FrameLayout implements FolderListener, IconLabelDotView { +public class FolderIcon extends FrameLayout implements FolderListener, IconLabelDotView, + DraggableView { @Thunk ActivityContext mActivity; @Thunk Folder mFolder; @@ -230,6 +232,16 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel mBackground.getBounds(outBounds); } + @Override + public int getViewType() { + return DRAGGABLE_ICON; + } + + @Override + public void getVisualDragBounds(Rect bounds) { + getPreviewBounds(bounds); + } + public float getBackgroundStrokeWidth() { return mBackground.getStrokeWidth(); } @@ -525,6 +537,10 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel invalidate(); } + public boolean getIconVisible() { + return mBackgroundIsVisible; + } + public PreviewBackground getFolderBackground() { return mBackground; } diff --git a/src/com/android/launcher3/graphics/DragPreviewProvider.java b/src/com/android/launcher3/graphics/DragPreviewProvider.java index 94d30f69c3..ed9dfbbd79 100644 --- a/src/com/android/launcher3/graphics/DragPreviewProvider.java +++ b/src/com/android/launcher3/graphics/DragPreviewProvider.java @@ -30,14 +30,12 @@ import android.graphics.drawable.Drawable; import android.view.View; import com.android.launcher3.BubbleTextView; -import com.android.launcher3.FastBitmapDrawable; import com.android.launcher3.Launcher; import com.android.launcher3.R; import com.android.launcher3.config.FeatureFlags; -import com.android.launcher3.folder.FolderIcon; +import com.android.launcher3.dragndrop.DraggableView; import com.android.launcher3.icons.BitmapRenderer; import com.android.launcher3.widget.LauncherAppWidgetHostView; -import com.android.launcher3.widget.PendingAppWidgetHostView; import java.nio.ByteBuffer; @@ -53,7 +51,7 @@ public class DragPreviewProvider { // The padding added to the drag view during the preview generation. public final int previewPadding; - protected final int blurSizeOutline; + public final int blurSizeOutline; private OutlineGeneratorCallback mOutlineGeneratorCallback; public Bitmap generatedDragOutline; @@ -66,56 +64,25 @@ public class DragPreviewProvider { mView = view; blurSizeOutline = context.getResources().getDimensionPixelSize(R.dimen.blur_size_medium_outline); - - if (mView instanceof BubbleTextView) { - Drawable d = ((BubbleTextView) mView).getIcon(); - Rect bounds = getDrawableBounds(d); - previewPadding = blurSizeOutline - bounds.left - bounds.top; - } else { - previewPadding = blurSizeOutline; - } + previewPadding = blurSizeOutline; } /** * Draws the {@link #mView} into the given {@param destCanvas}. */ protected void drawDragView(Canvas destCanvas, float scale) { - destCanvas.save(); + int saveCount = destCanvas.save(); destCanvas.scale(scale, scale); - if (mView instanceof BubbleTextView) { - Drawable d = ((BubbleTextView) mView).getIcon(); - Rect bounds = getDrawableBounds(d); - destCanvas.translate(blurSizeOutline / 2 - bounds.left, - blurSizeOutline / 2 - bounds.top); - if (d instanceof FastBitmapDrawable) { - ((FastBitmapDrawable) d).setScale(1); - } - d.draw(destCanvas); - } else { - final Rect clipRect = mTempRect; - mView.getDrawingRect(clipRect); - - boolean textVisible = false; - if (mView instanceof FolderIcon) { - // For FolderIcons the text can bleed into the icon area, and so we need to - // hide the text completely (which can't be achieved by clipping). - if (((FolderIcon) mView).getTextVisible()) { - ((FolderIcon) mView).setTextVisible(false); - textVisible = true; - } - } - destCanvas.translate(-mView.getScrollX() + blurSizeOutline / 2, - -mView.getScrollY() + blurSizeOutline / 2); - destCanvas.clipRect(clipRect); + if (mView instanceof DraggableView) { + DraggableView dv = (DraggableView) mView; + dv.prepareDrawDragView(); + dv.getVisualDragBounds(mTempRect); + destCanvas.translate(blurSizeOutline / 2 - mTempRect.left, + blurSizeOutline / 2 - mTempRect.top); mView.draw(destCanvas); - - // Restore text visibility of FolderIcon if necessary - if (textVisible) { - ((FolderIcon) mView).setTextVisible(true); - } } - destCanvas.restore(); + destCanvas.restoreToCount(saveCount); } /** @@ -123,28 +90,15 @@ public class DragPreviewProvider { * Responsibility for the bitmap is transferred to the caller. */ public Bitmap createDragBitmap() { - int width = mView.getWidth(); - int height = mView.getHeight(); - - if (mView instanceof BubbleTextView) { - Drawable d = ((BubbleTextView) mView).getIcon(); - Rect bounds = getDrawableBounds(d); - width = bounds.width(); - height = bounds.height(); - } else if (mView instanceof LauncherAppWidgetHostView) { - float scale = ((LauncherAppWidgetHostView) mView).getScaleToFit(); - width = (int) (mView.getWidth() * scale); - height = (int) (mView.getHeight() * scale); - - if (mView instanceof PendingAppWidgetHostView) { - // Use hardware renderer as the icon for the pending app widget may be a hw bitmap - return BitmapRenderer.createHardwareBitmap(width + blurSizeOutline, - height + blurSizeOutline, (c) -> drawDragView(c, scale)); - } else { - // Use software renderer for widgets as we know that they already work - return BitmapRenderer.createSoftwareBitmap(width + blurSizeOutline, - height + blurSizeOutline, (c) -> drawDragView(c, scale)); - } + int width = 0; + int height = 0; + if (mView instanceof DraggableView) { + ((DraggableView) mView).getVisualDragBounds(mTempRect); + width = mTempRect.width(); + height = mTempRect.height(); + } else { + width = mView.getWidth(); + height = mView.getHeight(); } return BitmapRenderer.createHardwareBitmap(width + blurSizeOutline, diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java index 5af5ebb4f5..9bac259efe 100644 --- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java +++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java @@ -59,6 +59,7 @@ import com.android.launcher3.dot.DotInfo; import com.android.launcher3.dragndrop.DragController; import com.android.launcher3.dragndrop.DragOptions; import com.android.launcher3.dragndrop.DragView; +import com.android.launcher3.dragndrop.DraggableView; import com.android.launcher3.notification.NotificationInfo; import com.android.launcher3.notification.NotificationItemView; import com.android.launcher3.notification.NotificationKeyData; @@ -662,7 +663,8 @@ public class PopupContainerWithArrow extends Arr iconShift.x = mIconLastTouchPos.x - sv.getIconCenter().x; iconShift.y = mIconLastTouchPos.y - mLauncher.getDeviceProfile().iconSizePx; - DragView dv = mLauncher.getWorkspace().beginDragShared(sv.getIconView(), + DraggableView draggableView = DraggableView.ofType(DraggableView.DRAGGABLE_ICON); + DragView dv = mLauncher.getWorkspace().beginDragShared(sv.getIconView(), draggableView, mContainer, sv.getFinalInfo(), new ShortcutDragPreviewProvider(sv.getIconView(), iconShift), new DragOptions()); diff --git a/src/com/android/launcher3/views/FloatingIconView.java b/src/com/android/launcher3/views/FloatingIconView.java index fa625ed930..d41bb86949 100644 --- a/src/com/android/launcher3/views/FloatingIconView.java +++ b/src/com/android/launcher3/views/FloatingIconView.java @@ -67,7 +67,6 @@ import com.android.launcher3.dragndrop.DragLayer; import com.android.launcher3.dragndrop.FolderAdaptiveIcon; import com.android.launcher3.folder.FolderIcon; import com.android.launcher3.graphics.IconShape; -import com.android.launcher3.graphics.ShiftedBitmapDrawable; import com.android.launcher3.icons.LauncherIcons; import com.android.launcher3.popup.SystemShortcut; import com.android.launcher3.shortcuts.DeepShortcutView; @@ -412,9 +411,8 @@ public class FloatingIconView extends View implements drawable = originalView.getBackground(); } } else { - boolean isFolderIcon = originalView instanceof FolderIcon; - int width = isFolderIcon ? originalView.getWidth() : (int) pos.width(); - int height = isFolderIcon ? originalView.getHeight() : (int) pos.height(); + int width = (int) pos.width(); + int height = (int) pos.height(); if (supportsAdaptiveIcons) { drawable = getFullDrawable(l, info, width, height, sTmpObjArray); if (drawable instanceof AdaptiveIconDrawable) { @@ -499,20 +497,7 @@ public class FloatingIconView extends View implements } } - if (isFolderIcon) { - ((FolderIcon) originalView).getPreviewBounds(sTmpRect); - float bgStroke = ((FolderIcon) originalView).getBackgroundStrokeWidth(); - if (mForeground instanceof ShiftedBitmapDrawable) { - ShiftedBitmapDrawable sbd = (ShiftedBitmapDrawable) mForeground; - sbd.setShiftX(sbd.getShiftX() - sTmpRect.left - bgStroke); - sbd.setShiftY(sbd.getShiftY() - sTmpRect.top - bgStroke); - } - if (mBadge instanceof ShiftedBitmapDrawable) { - ShiftedBitmapDrawable sbd = (ShiftedBitmapDrawable) mBadge; - sbd.setShiftX(sbd.getShiftX() - sTmpRect.left - bgStroke); - sbd.setShiftY(sbd.getShiftY() - sTmpRect.top - bgStroke); - } - } else { + if (!isFolderIcon) { Utilities.scaleRectAboutCenter(mStartRevealRect, IconShape.getNormalizationScale()); } @@ -615,8 +600,9 @@ public class FloatingIconView extends View implements @WorkerThread @SuppressWarnings("WrongThread") private static int getOffsetForIconBounds(Launcher l, Drawable drawable, RectF position) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O || - !(drawable instanceof AdaptiveIconDrawable)) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O + || !(drawable instanceof AdaptiveIconDrawable) + || (drawable instanceof FolderAdaptiveIcon)) { return 0; } int blurSizeOutline = diff --git a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java index f3fd7ca4da..c1310e38fb 100644 --- a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java +++ b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java @@ -20,6 +20,7 @@ import android.appwidget.AppWidgetProviderInfo; import android.content.Context; import android.content.res.Configuration; import android.graphics.PointF; +import android.graphics.Rect; import android.os.Handler; import android.os.SystemClock; import android.util.SparseBooleanArray; @@ -44,6 +45,7 @@ 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; import com.android.launcher3.util.Executors; import com.android.launcher3.util.Themes; import com.android.launcher3.views.BaseDragLayer.TouchCompleteListener; @@ -52,7 +54,7 @@ import com.android.launcher3.views.BaseDragLayer.TouchCompleteListener; * {@inheritDoc} */ public class LauncherAppWidgetHostView extends NavigableAppWidgetHostView - implements TouchCompleteListener, View.OnLongClickListener { + implements TouchCompleteListener, View.OnLongClickListener, DraggableView { // Related to the auto-advancing of widgets private static final long ADVANCE_INTERVAL = 20000; @@ -412,4 +414,18 @@ public class LauncherAppWidgetHostView extends NavigableAppWidgetHostView } return false; } + + @Override + public int getViewType() { + return DRAGGABLE_WIDGET; + } + + @Override + public void getVisualDragBounds(Rect bounds) { + int x = (int) (1 - getScaleToFit()) * getMeasuredWidth() / 2; + int y = (int) (1 - getScaleToFit()) * getMeasuredWidth() / 2; + int width = (int) getScaleToFit() * getMeasuredWidth(); + int height = (int) getScaleToFit() * getMeasuredHeight(); + bounds.set(x, y , x + width, y + height); + } } diff --git a/src/com/android/launcher3/widget/NavigableAppWidgetHostView.java b/src/com/android/launcher3/widget/NavigableAppWidgetHostView.java index 68b1595eed..104ad779d6 100644 --- a/src/com/android/launcher3/widget/NavigableAppWidgetHostView.java +++ b/src/com/android/launcher3/widget/NavigableAppWidgetHostView.java @@ -24,12 +24,15 @@ import android.view.View; import android.view.ViewDebug; import android.view.ViewGroup; +import com.android.launcher3.dragndrop.DraggableView; + import java.util.ArrayList; /** * Extension of AppWidgetHostView with support for controlled keyboard navigation. */ -public abstract class NavigableAppWidgetHostView extends AppWidgetHostView { +public abstract class NavigableAppWidgetHostView extends AppWidgetHostView + implements DraggableView { @ViewDebug.ExportedProperty(category = "launcher") private boolean mChildrenFocused; @@ -133,4 +136,14 @@ public abstract class NavigableAppWidgetHostView extends AppWidgetHostView { // The host view's background changes when selected, to indicate the focus is inside. setSelected(childIsFocused); } + + @Override + public int getViewType() { + return DRAGGABLE_WIDGET; + } + + @Override + public void getVisualDragBounds(Rect bounds) { + bounds.set(0, 0 , getMeasuredWidth(), getMeasuredHeight()); + } } diff --git a/src/com/android/launcher3/widget/PendingItemDragHelper.java b/src/com/android/launcher3/widget/PendingItemDragHelper.java index 662e627d99..3c112740cf 100644 --- a/src/com/android/launcher3/widget/PendingItemDragHelper.java +++ b/src/com/android/launcher3/widget/PendingItemDragHelper.java @@ -32,6 +32,7 @@ import com.android.launcher3.LauncherAppState; import com.android.launcher3.PendingAddItemInfo; import com.android.launcher3.R; import com.android.launcher3.dragndrop.DragOptions; +import com.android.launcher3.dragndrop.DraggableView; import com.android.launcher3.dragndrop.LivePreviewWidgetCell; import com.android.launcher3.graphics.DragPreviewProvider; import com.android.launcher3.icons.LauncherIcons; @@ -79,6 +80,8 @@ public class PendingItemDragHelper extends DragPreviewProvider { mEstimatedCellSize = launcher.getWorkspace().estimateItemSize(mAddInfo); + DraggableView draggableView; + if (mAddInfo instanceof PendingAddWidgetInfo) { PendingAddWidgetInfo createWidgetInfo = (PendingAddWidgetInfo) mAddInfo; @@ -110,6 +113,7 @@ public class PendingItemDragHelper extends DragPreviewProvider { dragOffset = null; dragRegion = null; + draggableView = DraggableView.ofType(DraggableView.DRAGGABLE_WIDGET); } else { PendingAddShortcutInfo createShortcutInfo = (PendingAddShortcutInfo) mAddInfo; Drawable icon = createShortcutInfo.activityInfo.getFullResIcon(app.getIconCache()); @@ -136,6 +140,7 @@ public class PendingItemDragHelper extends DragPreviewProvider { dragRegion.top = (mEstimatedCellSize[1] - iconSize - dp.iconTextSizePx - dp.iconDrawablePaddingPx) / 2; dragRegion.bottom = dragRegion.top + iconSize; + draggableView = DraggableView.ofType(DraggableView.DRAGGABLE_ICON); } // Since we are not going through the workspace for starting the drag, set drag related @@ -148,8 +153,8 @@ public class PendingItemDragHelper extends DragPreviewProvider { + (int) ((scale * preview.getHeight() - preview.getHeight()) / 2); // Start the drag - launcher.getDragController().startDrag(preview, dragLayerX, dragLayerY, source, mAddInfo, - dragOffset, dragRegion, scale, scale, options); + launcher.getDragController().startDrag(preview, draggableView, dragLayerX, dragLayerY, + source, mAddInfo, dragOffset, dragRegion, scale, scale, options); } @Override From 81feb2c5477b52ecb8976e5b79e5aa2081b0ff82 Mon Sep 17 00:00:00 2001 From: Tony Wickham Date: Fri, 20 Mar 2020 12:50:04 -0700 Subject: [PATCH 26/36] Don't draw bottom scrim in fully gestural mode The scrim exists primarily to protect nav bar buttons which don't exist in this mode. Change-Id: Ic6de7d54f27f135eecb116ac09714c91c05b88da --- .../graphics/WorkspaceAndHotseatScrim.java | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/src/com/android/launcher3/graphics/WorkspaceAndHotseatScrim.java b/src/com/android/launcher3/graphics/WorkspaceAndHotseatScrim.java index 3fb2bf6a29..7b7ab5e18b 100644 --- a/src/com/android/launcher3/graphics/WorkspaceAndHotseatScrim.java +++ b/src/com/android/launcher3/graphics/WorkspaceAndHotseatScrim.java @@ -40,12 +40,14 @@ import android.graphics.drawable.Drawable; import android.util.DisplayMetrics; import android.util.FloatProperty; import android.view.View; +import android.view.WindowInsets; import androidx.core.graphics.ColorUtils; import com.android.launcher3.CellLayout; import com.android.launcher3.R; import com.android.launcher3.ResourceUtils; +import com.android.launcher3.Utilities; import com.android.launcher3.Workspace; import com.android.launcher3.uioverrides.WallpaperColorInfo; import com.android.launcher3.util.Themes; @@ -198,9 +200,22 @@ public class WorkspaceAndHotseatScrim extends Scrim { * Determines whether to draw the top and/or bottom scrim based on new insets. */ public void onInsetsChanged(Rect insets, boolean allowSysuiScrims) { - mDrawTopScrim = allowSysuiScrims && mTopScrim != null && insets.top > 0; - mDrawBottomScrim = allowSysuiScrims && mBottomMask != null - && !mLauncher.getDeviceProfile().isVerticalBarLayout(); + mDrawTopScrim = allowSysuiScrims + && mTopScrim != null + && insets.top > 0; + mDrawBottomScrim = allowSysuiScrims + && mBottomMask != null + && !mLauncher.getDeviceProfile().isVerticalBarLayout() + && hasBottomNavButtons(); + } + + private boolean hasBottomNavButtons() { + if (Utilities.ATLEAST_Q && mLauncher.getRootView() != null + && mLauncher.getRootView().getRootWindowInsets() != null) { + WindowInsets windowInsets = mLauncher.getRootView().getRootWindowInsets(); + return windowInsets.getTappableElementInsets().bottom > 0; + } + return true; } @Override From 330fde4aaf6bd7194d9f0fca790b08cdcf3d0b6d Mon Sep 17 00:00:00 2001 From: vadimt Date: Fri, 20 Mar 2020 18:55:53 -0700 Subject: [PATCH 27/36] Adding more checks around unlocking the phone Bug: 151613234 Change-Id: I045306cce2ce85d6895c9f050ebb4d2bd15ba232 --- .../src/com/android/launcher3/ui/AbstractLauncherUiTest.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java index 551b5337fa..d4b2ee26f9 100644 --- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java +++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java @@ -220,7 +220,9 @@ public abstract class AbstractLauncherUiTest { @Before public void setUp() throws Exception { + Log.d(TAG, "Before disabling battery defender"); mDevice.executeShellCommand("setprop vendor.battery.defender.disable 1"); + Log.d(TAG, "Before enabling stay awake"); mDevice.executeShellCommand("settings put global stay_on_while_plugged_in 3"); if (hasSystemUiObject("keyguard_status_view")) { Log.d(TAG, "Before unlocking the phone"); @@ -228,6 +230,9 @@ public abstract class AbstractLauncherUiTest { } else { Log.d(TAG, "Phone isn't locked"); } + Assert.assertTrue("Keyguard still visible", + mDevice.wait(Until.gone(By.res(SYSTEMUI_PACKAGE, "keyguard_status_view")), 10000)); + Log.d(TAG, "Keyguard is not visible"); final String launcherPackageName = mDevice.getLauncherPackageName(); try { From 1b373657a490f00039c34d872708e0e62a8f454f Mon Sep 17 00:00:00 2001 From: Samuel Fufa Date: Fri, 20 Mar 2020 16:00:23 -0700 Subject: [PATCH 28/36] Animate Work paused overlay Please see bug for video preview Bug: 151408501 Test: Manual Change-Id: Ic96bdc438b7c897c89261764dad16124006d7138 --- .../allapps/AllAppsContainerView.java | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java index 10a3060f5e..dd0212a359 100644 --- a/src/com/android/launcher3/allapps/AllAppsContainerView.java +++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java @@ -38,6 +38,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.StringRes; import androidx.dynamicanimation.animation.DynamicAnimation; +import androidx.recyclerview.widget.DefaultItemAnimator; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; @@ -610,6 +611,7 @@ public class AllAppsContainerView extends SpringRelativeLayout implements DragSo final Rect padding = new Rect(); AllAppsRecyclerView recyclerView; boolean verticalFadingEdge; + private View mOverlay; boolean mWorkDisabled; @@ -646,13 +648,20 @@ public class AllAppsContainerView extends SpringRelativeLayout implements DragSo if (mWorkDisabled == workDisabled) return; recyclerView.setContentDescription( workDisabled ? mLauncher.getString(R.string.work_apps_paused_title) : null); + View overlayView = getOverlayView(); + recyclerView.setItemAnimator(new DefaultItemAnimator()); if (workDisabled) { + overlayView.setAlpha(0); appsList.updateItemFilter((info, cn) -> false); - recyclerView.addAutoSizedOverlay( - mLauncher.getLayoutInflater().inflate(R.layout.work_apps_paused, null)); + recyclerView.addAutoSizedOverlay(overlayView); + overlayView.animate().alpha(1).withEndAction( + () -> recyclerView.setItemAnimator(null)).start(); } else if (mInfoMatcher != null) { appsList.updateItemFilter(mInfoMatcher); - recyclerView.clearAutoSizedOverlays(); + overlayView.animate().alpha(0).withEndAction(() -> { + recyclerView.setItemAnimator(null); + recyclerView.clearAutoSizedOverlays(); + }).start(); } mWorkDisabled = workDisabled; } @@ -671,5 +680,12 @@ public class AllAppsContainerView extends SpringRelativeLayout implements DragSo mAH[AdapterHolder.MAIN].recyclerView.setVerticalFadingEdgeEnabled(!mUsingTabs && verticalFadingEdge); } + + private View getOverlayView() { + if (mOverlay == null) { + mOverlay = mLauncher.getLayoutInflater().inflate(R.layout.work_apps_paused, null); + } + return mOverlay; + } } } From 7500c241ac17da04523c31f87860b8a1a89ad375 Mon Sep 17 00:00:00 2001 From: vadimt Date: Sat, 21 Mar 2020 13:04:15 -0700 Subject: [PATCH 29/36] Increasing wait time till unlock Bug: 151613234 Change-Id: I47e0a87551cebf31fa309b9a65b2ecff809a7f00 --- tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java index d4b2ee26f9..5c9fe83561 100644 --- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java +++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java @@ -231,7 +231,7 @@ public abstract class AbstractLauncherUiTest { Log.d(TAG, "Phone isn't locked"); } Assert.assertTrue("Keyguard still visible", - mDevice.wait(Until.gone(By.res(SYSTEMUI_PACKAGE, "keyguard_status_view")), 10000)); + mDevice.wait(Until.gone(By.res(SYSTEMUI_PACKAGE, "keyguard_status_view")), 60000)); Log.d(TAG, "Keyguard is not visible"); final String launcherPackageName = mDevice.getLauncherPackageName(); From 7a9f1628f2dad51e9a1f7e5e18aacb528ef0d48f Mon Sep 17 00:00:00 2001 From: vadimt Date: Sat, 21 Mar 2020 15:09:39 -0700 Subject: [PATCH 30/36] Repeating unlocking the phone several times Bug: 151613234 Change-Id: Id5cbc247d7827fa31c2011d817b2964c1486daeb --- .../launcher3/ui/AbstractLauncherUiTest.java | 34 ++++++++++++------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java index 5c9fe83561..7cd656e811 100644 --- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java +++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java @@ -18,7 +18,6 @@ package com.android.launcher3.ui; import static androidx.test.InstrumentationRegistry.getInstrumentation; import static com.android.launcher3.WorkspaceLayoutManager.FIRST_SCREEN_ID; -import static com.android.launcher3.tapl.LauncherInstrumentation.ContainerType; import static com.android.launcher3.ui.TaplTestsLauncher3.getAppPackageName; import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; @@ -57,6 +56,7 @@ import com.android.launcher3.LauncherStateManager; import com.android.launcher3.Utilities; import com.android.launcher3.model.AppLaunchTracker; import com.android.launcher3.tapl.LauncherInstrumentation; +import com.android.launcher3.tapl.LauncherInstrumentation.ContainerType; import com.android.launcher3.tapl.TestHelpers; import com.android.launcher3.testcomponent.TestCommandReceiver; import com.android.launcher3.testing.TestProtocol; @@ -224,14 +224,14 @@ public abstract class AbstractLauncherUiTest { mDevice.executeShellCommand("setprop vendor.battery.defender.disable 1"); Log.d(TAG, "Before enabling stay awake"); mDevice.executeShellCommand("settings put global stay_on_while_plugged_in 3"); - if (hasSystemUiObject("keyguard_status_view")) { + for (int i = 0; i < 10 && hasSystemUiObject("keyguard_status_view"); ++i) { Log.d(TAG, "Before unlocking the phone"); mDevice.executeShellCommand("input keyevent 82"); - } else { - Log.d(TAG, "Phone isn't locked"); + mDevice.waitForIdle(); } Assert.assertTrue("Keyguard still visible", - mDevice.wait(Until.gone(By.res(SYSTEMUI_PACKAGE, "keyguard_status_view")), 60000)); + mDevice.wait( + Until.gone(By.res(SYSTEMUI_PACKAGE, "keyguard_status_view")), 60000)); Log.d(TAG, "Keyguard is not visible"); final String launcherPackageName = mDevice.getLauncherPackageName(); @@ -294,7 +294,8 @@ public abstract class AbstractLauncherUiTest { protected void resetLoaderState() { try { mMainThreadExecutor.execute( - () -> LauncherAppState.getInstance(mTargetContext).getModel().forceReload()); + () -> LauncherAppState.getInstance( + mTargetContext).getModel().forceReload()); } catch (Throwable t) { throw new IllegalArgumentException(t); } @@ -308,7 +309,8 @@ public abstract class AbstractLauncherUiTest { ContentResolver resolver = mTargetContext.getContentResolver(); int screenId = FIRST_SCREEN_ID; // Update the screen id counter for the provider. - LauncherSettings.Settings.call(resolver, LauncherSettings.Settings.METHOD_NEW_SCREEN_ID); + LauncherSettings.Settings.call(resolver, + LauncherSettings.Settings.METHOD_NEW_SCREEN_ID); if (screenId > FIRST_SCREEN_ID) { screenId = FIRST_SCREEN_ID; @@ -322,7 +324,8 @@ public abstract class AbstractLauncherUiTest { item.screenId = screenId; item.onAddToDatabase(writer); writer.put(LauncherSettings.Favorites._ID, item.id); - resolver.insert(LauncherSettings.Favorites.CONTENT_URI, writer.getValues(mTargetContext)); + resolver.insert(LauncherSettings.Favorites.CONTENT_URI, + writer.getValues(mTargetContext)); resetLoaderState(); // Launch the home activity @@ -353,7 +356,8 @@ public abstract class AbstractLauncherUiTest { }); } - // Cannot be used in TaplTests between a Tapl call injecting a gesture and a tapl call expecting + // Cannot be used in TaplTests between a Tapl call injecting a gesture and a tapl call + // expecting // the results of that gesture because the wait can hide flakeness. protected void waitForState(String message, Supplier state) { waitForLauncherCondition(message, @@ -366,7 +370,8 @@ public abstract class AbstractLauncherUiTest { // Cannot be used in TaplTests after injecting any gesture using Tapl because this can hide // flakiness. - protected void waitForLauncherCondition(String message, Function condition) { + protected void waitForLauncherCondition(String + message, Function condition) { waitForLauncherCondition(message, condition, DEFAULT_ACTIVITY_TIMEOUT); } @@ -442,7 +447,8 @@ public abstract class AbstractLauncherUiTest { public Intent blockingGetExtraIntent() throws InterruptedException { Intent intent = blockingGetIntent(); - return intent == null ? null : (Intent) intent.getParcelableExtra(Intent.EXTRA_INTENT); + return intent == null ? null : (Intent) intent.getParcelableExtra( + Intent.EXTRA_INTENT); } } @@ -469,7 +475,8 @@ public abstract class AbstractLauncherUiTest { if (newTask) { intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); } else { - intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK | Intent.FLAG_ACTIVITY_NEW_DOCUMENT); + intent.addFlags( + Intent.FLAG_ACTIVITY_MULTIPLE_TASK | Intent.FLAG_ACTIVITY_NEW_DOCUMENT); } getInstrumentation().getTargetContext().startActivity(intent); assertTrue("App didn't start: " + selector, @@ -506,7 +513,8 @@ public abstract class AbstractLauncherUiTest { protected boolean isInState(Supplier state) { if (!TestHelpers.isInLauncherProcess()) return true; - return getFromLauncher(launcher -> launcher.getStateManager().getState() == state.get()); + return getFromLauncher( + launcher -> launcher.getStateManager().getState() == state.get()); } protected int getAllAppsScroll(Launcher launcher) { From d25023b05eadb2e907031889f50e3fd87506d20c Mon Sep 17 00:00:00 2001 From: Pinyao Ting Date: Thu, 19 Mar 2020 12:28:24 -0700 Subject: [PATCH 31/36] Parse share-targets defined by non-main activities Some system apps do not have any launcher icons but want to publish sharing shortcuts. As a fix, all activities are parsed for their XML resource, which implies some of these shortcut may not hold an activity. This CL removes that assumption from launcher side. Bug: 136353429 Test: Manual test Change-Id: I2994da17fe078b2fab61a614c1326fddd5fd8571 --- src/com/android/launcher3/model/BgDataModel.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/com/android/launcher3/model/BgDataModel.java b/src/com/android/launcher3/model/BgDataModel.java index 32fce0b5a3..fdfcef1c28 100644 --- a/src/com/android/launcher3/model/BgDataModel.java +++ b/src/com/android/launcher3/model/BgDataModel.java @@ -424,7 +424,8 @@ public class BgDataModel { // Now add the new shortcuts to the map. for (ShortcutInfo shortcut : shortcuts) { boolean shouldShowInContainer = shortcut.isEnabled() - && (shortcut.isDeclaredInManifest() || shortcut.isDynamic()); + && (shortcut.isDeclaredInManifest() || shortcut.isDynamic()) + && shortcut.getActivity() != null; if (shouldShowInContainer) { ComponentKey targetComponent = new ComponentKey(shortcut.getActivity(), shortcut.getUserHandle()); From 48486048821f13f2cdae2f496273973b1c2b3db4 Mon Sep 17 00:00:00 2001 From: Jon Miranda Date: Mon, 23 Mar 2020 07:39:29 -0700 Subject: [PATCH 32/36] Polish badge animation on app open/close. Create new ClipIconView so that the adaptive icon foreground/background can get clipped properly, and the badge gets drawn separately on top of it. Bug: 142105172 Change-Id: Ie5d65c20f845d9219fd01daa383f273dde0e096c --- .../android/launcher3/views/ClipIconView.java | 349 ++++++++++++++++++ .../launcher3/views/FloatingIconView.java | 319 +++------------- 2 files changed, 398 insertions(+), 270 deletions(-) create mode 100644 src/com/android/launcher3/views/ClipIconView.java diff --git a/src/com/android/launcher3/views/ClipIconView.java b/src/com/android/launcher3/views/ClipIconView.java new file mode 100644 index 0000000000..478141ae3c --- /dev/null +++ b/src/com/android/launcher3/views/ClipIconView.java @@ -0,0 +1,349 @@ +/* + * 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.views; + +import static com.android.launcher3.Utilities.mapToRange; +import static com.android.launcher3.anim.Interpolators.LINEAR; +import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ValueAnimator; +import android.annotation.TargetApi; +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Outline; +import android.graphics.Path; +import android.graphics.Rect; +import android.graphics.RectF; +import android.graphics.drawable.AdaptiveIconDrawable; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewOutlineProvider; + +import androidx.annotation.Nullable; +import androidx.dynamicanimation.animation.FloatPropertyCompat; +import androidx.dynamicanimation.animation.SpringAnimation; +import androidx.dynamicanimation.animation.SpringForce; + +import com.android.launcher3.DeviceProfile; +import com.android.launcher3.InsettableFrameLayout.LayoutParams; +import com.android.launcher3.Launcher; +import com.android.launcher3.R; +import com.android.launcher3.Utilities; +import com.android.launcher3.dragndrop.FolderAdaptiveIcon; +import com.android.launcher3.graphics.IconShape; + +/** + * A view used to draw both layers of an {@link AdaptiveIconDrawable}. + * Supports springing just the foreground layer. + * Supports clipping the icon to/from its icon shape. + */ +@TargetApi(Build.VERSION_CODES.Q) +public class ClipIconView extends View implements ClipPathView { + + private static final Rect sTmpRect = new Rect(); + + // We spring the foreground drawable relative to the icon's movement in the DragLayer. + // We then use these two factor values to scale the movement of the fg within this view. + private static final int FG_TRANS_X_FACTOR = 60; + private static final int FG_TRANS_Y_FACTOR = 75; + + private static final FloatPropertyCompat mFgTransYProperty = + new FloatPropertyCompat("ClipIconViewFgTransY") { + @Override + public float getValue(ClipIconView view) { + return view.mFgTransY; + } + + @Override + public void setValue(ClipIconView view, float transY) { + view.mFgTransY = transY; + view.invalidate(); + } + }; + + private static final FloatPropertyCompat mFgTransXProperty = + new FloatPropertyCompat("ClipIconViewFgTransX") { + @Override + public float getValue(ClipIconView view) { + return view.mFgTransX; + } + + @Override + public void setValue(ClipIconView view, float transX) { + view.mFgTransX = transX; + view.invalidate(); + } + }; + + private final Launcher mLauncher; + private final int mBlurSizeOutline; + private final boolean mIsRtl; + + private @Nullable Drawable mForeground; + private @Nullable Drawable mBackground; + + private boolean mIsVerticalBarLayout = false; + private boolean mIsAdaptiveIcon = false; + + private ValueAnimator mRevealAnimator; + + private final Rect mStartRevealRect = new Rect(); + private final Rect mEndRevealRect = new Rect(); + private Path mClipPath; + private float mTaskCornerRadius; + + private final Rect mOutline = new Rect(); + private final Rect mFinalDrawableBounds = new Rect(); + + private final SpringAnimation mFgSpringY; + private float mFgTransY; + private final SpringAnimation mFgSpringX; + private float mFgTransX; + + public ClipIconView(Context context) { + this(context, null); + } + + public ClipIconView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public ClipIconView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + mLauncher = Launcher.getLauncher(context); + mBlurSizeOutline = getResources().getDimensionPixelSize( + R.dimen.blur_size_medium_outline); + mIsRtl = Utilities.isRtl(getResources()); + + mFgSpringX = new SpringAnimation(this, mFgTransXProperty) + .setSpring(new SpringForce() + .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY) + .setStiffness(SpringForce.STIFFNESS_LOW)); + mFgSpringY = new SpringAnimation(this, mFgTransYProperty) + .setSpring(new SpringForce() + .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY) + .setStiffness(SpringForce.STIFFNESS_LOW)); + } + + void update(RectF rect, float progress, float shapeProgressStart, float cornerRadius, + boolean isOpening, float scale, float minSize, LayoutParams parentLp) { + DeviceProfile dp = mLauncher.getDeviceProfile(); + float dX = mIsRtl + ? rect.left - (dp.widthPx - parentLp.getMarginStart() - parentLp.width) + : rect.left - parentLp.getMarginStart(); + float dY = rect.top - parentLp.topMargin; + + // shapeRevealProgress = 1 when progress = shapeProgressStart + SHAPE_PROGRESS_DURATION + float toMax = isOpening ? 1 / SHAPE_PROGRESS_DURATION : 1f; + float shapeRevealProgress = Utilities.boundToRange(mapToRange( + Math.max(shapeProgressStart, progress), shapeProgressStart, 1f, 0, toMax, + LINEAR), 0, 1); + + if (mIsVerticalBarLayout) { + mOutline.right = (int) (rect.width() / scale); + } else { + mOutline.bottom = (int) (rect.height() / scale); + } + + mTaskCornerRadius = cornerRadius / scale; + if (mIsAdaptiveIcon) { + if (!isOpening && progress >= shapeProgressStart) { + if (mRevealAnimator == null) { + mRevealAnimator = (ValueAnimator) IconShape.getShape().createRevealAnimator( + this, mStartRevealRect, mOutline, mTaskCornerRadius, !isOpening); + mRevealAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mRevealAnimator = null; + } + }); + mRevealAnimator.start(); + // We pause here so we can set the current fraction ourselves. + mRevealAnimator.pause(); + } + mRevealAnimator.setCurrentFraction(shapeRevealProgress); + } + + float drawableScale = (mIsVerticalBarLayout ? mOutline.width() : mOutline.height()) + / minSize; + setBackgroundDrawableBounds(drawableScale); + if (isOpening) { + // Center align foreground + int height = mFinalDrawableBounds.height(); + int width = mFinalDrawableBounds.width(); + int diffY = mIsVerticalBarLayout ? 0 + : (int) (((height * drawableScale) - height) / 2); + int diffX = mIsVerticalBarLayout ? (int) (((width * drawableScale) - width) / 2) + : 0; + sTmpRect.set(mFinalDrawableBounds); + sTmpRect.offset(diffX, diffY); + mForeground.setBounds(sTmpRect); + } else { + // Spring the foreground relative to the icon's movement within the DragLayer. + int diffX = (int) (dX / dp.availableWidthPx * FG_TRANS_X_FACTOR); + int diffY = (int) (dY / dp.availableHeightPx * FG_TRANS_Y_FACTOR); + + mFgSpringX.animateToFinalPosition(diffX); + mFgSpringY.animateToFinalPosition(diffY); + } + } + invalidate(); + invalidateOutline(); + } + + private void setBackgroundDrawableBounds(float scale) { + sTmpRect.set(mFinalDrawableBounds); + Utilities.scaleRectAboutCenter(sTmpRect, scale); + // Since the drawable is at the top of the view, we need to offset to keep it centered. + if (mIsVerticalBarLayout) { + sTmpRect.offsetTo((int) (mFinalDrawableBounds.left * scale), sTmpRect.top); + } else { + sTmpRect.offsetTo(sTmpRect.left, (int) (mFinalDrawableBounds.top * scale)); + } + mBackground.setBounds(sTmpRect); + } + + protected void endReveal() { + if (mRevealAnimator != null) { + mRevealAnimator.end(); + } + } + + void setIcon(@Nullable Drawable drawable, int iconOffset, LayoutParams lp, boolean isOpening) { + mIsAdaptiveIcon = drawable instanceof AdaptiveIconDrawable; + if (mIsAdaptiveIcon) { + boolean isFolderIcon = drawable instanceof FolderAdaptiveIcon; + + AdaptiveIconDrawable adaptiveIcon = (AdaptiveIconDrawable) drawable; + Drawable background = adaptiveIcon.getBackground(); + if (background == null) { + background = new ColorDrawable(Color.TRANSPARENT); + } + mBackground = background; + Drawable foreground = adaptiveIcon.getForeground(); + if (foreground == null) { + foreground = new ColorDrawable(Color.TRANSPARENT); + } + mForeground = foreground; + + final int originalHeight = lp.height; + final int originalWidth = lp.width; + + int blurMargin = mBlurSizeOutline / 2; + mFinalDrawableBounds.set(0, 0, originalWidth, originalHeight); + + if (!isFolderIcon) { + mFinalDrawableBounds.inset(iconOffset - blurMargin, iconOffset - blurMargin); + } + mForeground.setBounds(mFinalDrawableBounds); + mBackground.setBounds(mFinalDrawableBounds); + + mStartRevealRect.set(0, 0, originalWidth, originalHeight); + + if (!isFolderIcon) { + Utilities.scaleRectAboutCenter(mStartRevealRect, IconShape.getNormalizationScale()); + } + + float aspectRatio = mLauncher.getDeviceProfile().aspectRatio; + if (mIsVerticalBarLayout) { + lp.width = (int) Math.max(lp.width, lp.height * aspectRatio); + } else { + lp.height = (int) Math.max(lp.height, lp.width * aspectRatio); + } + + int left = mIsRtl + ? mLauncher.getDeviceProfile().widthPx - lp.getMarginStart() - lp.width + : lp.leftMargin; + layout(left, lp.topMargin, left + lp.width, lp.topMargin + lp.height); + + float scale = Math.max((float) lp.height / originalHeight, + (float) lp.width / originalWidth); + float bgDrawableStartScale; + if (isOpening) { + bgDrawableStartScale = 1f; + mOutline.set(0, 0, originalWidth, originalHeight); + } else { + bgDrawableStartScale = scale; + mOutline.set(0, 0, lp.width, lp.height); + } + setBackgroundDrawableBounds(bgDrawableStartScale); + mEndRevealRect.set(0, 0, lp.width, lp.height); + setOutlineProvider(new ViewOutlineProvider() { + @Override + public void getOutline(View view, Outline outline) { + outline.setRoundRect(mOutline, mTaskCornerRadius); + } + }); + setClipToOutline(true); + } else { + setBackground(drawable); + setClipToOutline(false); + } + + invalidate(); + invalidateOutline(); + } + + @Override + public void setClipPath(Path clipPath) { + mClipPath = clipPath; + invalidate(); + } + + @Override + public void draw(Canvas canvas) { + int count = canvas.save(); + if (mClipPath != null) { + canvas.clipPath(mClipPath); + } + super.draw(canvas); + if (mBackground != null) { + mBackground.draw(canvas); + } + if (mForeground != null) { + int count2 = canvas.save(); + canvas.translate(mFgTransX, mFgTransY); + mForeground.draw(canvas); + canvas.restoreToCount(count2); + } + canvas.restoreToCount(count); + } + + void recycle() { + setBackground(null); + mIsAdaptiveIcon = false; + mForeground = null; + mBackground = null; + mClipPath = null; + mFinalDrawableBounds.setEmpty(); + if (mRevealAnimator != null) { + mRevealAnimator.cancel(); + } + mRevealAnimator = null; + mTaskCornerRadius = 0; + mOutline.setEmpty(); + mFgTransY = 0; + mFgSpringX.cancel(); + mFgTransX = 0; + mFgSpringY.cancel(); + } +} diff --git a/src/com/android/launcher3/views/FloatingIconView.java b/src/com/android/launcher3/views/FloatingIconView.java index d41bb86949..3e2560f13c 100644 --- a/src/com/android/launcher3/views/FloatingIconView.java +++ b/src/com/android/launcher3/views/FloatingIconView.java @@ -18,8 +18,6 @@ package com.android.launcher3.views; import static com.android.launcher3.LauncherAnimUtils.DRAWABLE_ALPHA; import static com.android.launcher3.Utilities.getBadge; import static com.android.launcher3.Utilities.getFullDrawable; -import static com.android.launcher3.Utilities.mapToRange; -import static com.android.launcher3.anim.Interpolators.LINEAR; import static com.android.launcher3.config.FeatureFlags.ADAPTIVE_ICON_WINDOW_ANIM; import static com.android.launcher3.states.RotationHelper.REQUEST_LOCK; import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; @@ -28,17 +26,12 @@ import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; -import android.animation.ValueAnimator; import android.annotation.TargetApi; import android.content.Context; import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Outline; -import android.graphics.Path; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.drawable.AdaptiveIconDrawable; -import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.os.Build; import android.os.CancellationSignal; @@ -46,19 +39,17 @@ import android.util.AttributeSet; import android.util.Log; import android.view.View; import android.view.ViewGroup; -import android.view.ViewOutlineProvider; import android.view.ViewTreeObserver.OnGlobalLayoutListener; +import android.widget.FrameLayout; import android.widget.ImageView; import androidx.annotation.Nullable; import androidx.annotation.UiThread; import androidx.annotation.WorkerThread; -import androidx.dynamicanimation.animation.FloatPropertyCompat; -import androidx.dynamicanimation.animation.SpringAnimation; -import androidx.dynamicanimation.animation.SpringForce; import com.android.launcher3.BubbleTextView; -import com.android.launcher3.InsettableFrameLayout.LayoutParams; +import com.android.launcher3.DeviceProfile; +import com.android.launcher3.InsettableFrameLayout; import com.android.launcher3.ItemInfo; import com.android.launcher3.Launcher; import com.android.launcher3.R; @@ -66,7 +57,6 @@ import com.android.launcher3.Utilities; import com.android.launcher3.dragndrop.DragLayer; import com.android.launcher3.dragndrop.FolderAdaptiveIcon; import com.android.launcher3.folder.FolderIcon; -import com.android.launcher3.graphics.IconShape; import com.android.launcher3.icons.LauncherIcons; import com.android.launcher3.popup.SystemShortcut; import com.android.launcher3.shortcuts.DeepShortcutView; @@ -75,8 +65,8 @@ import com.android.launcher3.shortcuts.DeepShortcutView; * A view that is created to look like another view with the purpose of creating fluid animations. */ @TargetApi(Build.VERSION_CODES.Q) -public class FloatingIconView extends View implements - Animator.AnimatorListener, ClipPathView, OnGlobalLayoutListener { +public class FloatingIconView extends FrameLayout implements + Animator.AnimatorListener, OnGlobalLayoutListener { private static final String TAG = FloatingIconView.class.getSimpleName(); @@ -85,81 +75,34 @@ public class FloatingIconView extends View implements public static final float SHAPE_PROGRESS_DURATION = 0.10f; private static final int FADE_DURATION_MS = 200; - private static final Rect sTmpRect = new Rect(); private static final RectF sTmpRectF = new RectF(); private static final Object[] sTmpObjArray = new Object[1]; - // We spring the foreground drawable relative to the icon's movement in the DragLayer. - // We then use these two factor values to scale the movement of the fg within this view. - private static final int FG_TRANS_X_FACTOR = 60; - private static final int FG_TRANS_Y_FACTOR = 75; - - private static final FloatPropertyCompat mFgTransYProperty - = new FloatPropertyCompat("FloatingViewFgTransY") { - @Override - public float getValue(FloatingIconView view) { - return view.mFgTransY; - } - - @Override - public void setValue(FloatingIconView view, float transY) { - view.mFgTransY = transY; - view.invalidate(); - } - }; - - private static final FloatPropertyCompat mFgTransXProperty - = new FloatPropertyCompat("FloatingViewFgTransX") { - @Override - public float getValue(FloatingIconView view) { - return view.mFgTransX; - } - - @Override - public void setValue(FloatingIconView view, float transX) { - view.mFgTransX = transX; - view.invalidate(); - } - }; - private Runnable mEndRunnable; private CancellationSignal mLoadIconSignal; private final Launcher mLauncher; - private final int mBlurSizeOutline; private final boolean mIsRtl; private boolean mIsVerticalBarLayout = false; - private boolean mIsAdaptiveIcon = false; private boolean mIsOpening; private IconLoadResult mIconLoadResult; + private ClipIconView mClipIconView; private @Nullable Drawable mBadge; - private @Nullable Drawable mForeground; - private @Nullable Drawable mBackground; + private float mRotation; - private ValueAnimator mRevealAnimator; - private final Rect mStartRevealRect = new Rect(); - private final Rect mEndRevealRect = new Rect(); - private Path mClipPath; - private float mTaskCornerRadius; private View mOriginalIcon; private RectF mPositionOut; private Runnable mOnTargetChangeRunnable; - private final Rect mOutline = new Rect(); private final Rect mFinalDrawableBounds = new Rect(); private AnimatorSet mFadeAnimatorSet; private ListenerView mListenerView; - private final SpringAnimation mFgSpringY; - private float mFgTransY; - private final SpringAnimation mFgSpringX; - private float mFgTransX; - public FloatingIconView(Context context) { this(context, null); } @@ -171,19 +114,11 @@ public class FloatingIconView extends View implements public FloatingIconView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); mLauncher = Launcher.getLauncher(context); - mBlurSizeOutline = getResources().getDimensionPixelSize( - R.dimen.blur_size_medium_outline); mIsRtl = Utilities.isRtl(getResources()); mListenerView = new ListenerView(context, attrs); - - mFgSpringX = new SpringAnimation(this, mFgTransXProperty) - .setSpring(new SpringForce() - .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY) - .setStiffness(SpringForce.STIFFNESS_LOW)); - mFgSpringY = new SpringAnimation(this, mFgTransYProperty) - .setSpring(new SpringForce() - .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY) - .setStiffness(SpringForce.STIFFNESS_LOW)); + mClipIconView = new ClipIconView(context, attrs); + addView(mClipIconView); + setWillNotDraw(false); } @Override @@ -212,10 +147,12 @@ public class FloatingIconView extends View implements float cornerRadius, boolean isOpening) { setAlpha(alpha); - LayoutParams lp = (LayoutParams) getLayoutParams(); + InsettableFrameLayout.LayoutParams lp = + (InsettableFrameLayout.LayoutParams) getLayoutParams(); + + DeviceProfile dp = mLauncher.getDeviceProfile(); float dX = mIsRtl - ? rect.left - - (mLauncher.getDeviceProfile().widthPx - lp.getMarginStart() - lp.width) + ? rect.left - (dp.widthPx - lp.getMarginStart() - lp.width) : rect.left - lp.getMarginStart(); float dY = rect.top - lp.topMargin; setTranslationX(dX); @@ -226,69 +163,15 @@ public class FloatingIconView extends View implements float scaleY = rect.height() / minSize; float scale = Math.max(1f, Math.min(scaleX, scaleY)); + mClipIconView.update(rect, progress, shapeProgressStart, cornerRadius, isOpening, scale, + minSize, lp); + setPivotX(0); setPivotY(0); setScaleX(scale); setScaleY(scale); - // shapeRevealProgress = 1 when progress = shapeProgressStart + SHAPE_PROGRESS_DURATION - float toMax = isOpening ? 1 / SHAPE_PROGRESS_DURATION : 1f; - float shapeRevealProgress = Utilities.boundToRange(mapToRange( - Math.max(shapeProgressStart, progress), shapeProgressStart, 1f, 0, toMax, - LINEAR), 0, 1); - - if (mIsVerticalBarLayout) { - mOutline.right = (int) (rect.width() / scale); - } else { - mOutline.bottom = (int) (rect.height() / scale); - } - - mTaskCornerRadius = cornerRadius / scale; - if (mIsAdaptiveIcon) { - if (!isOpening && progress >= shapeProgressStart) { - if (mRevealAnimator == null) { - mRevealAnimator = (ValueAnimator) IconShape.getShape().createRevealAnimator( - this, mStartRevealRect, mOutline, mTaskCornerRadius, !isOpening); - mRevealAnimator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - mRevealAnimator = null; - } - }); - mRevealAnimator.start(); - // We pause here so we can set the current fraction ourselves. - mRevealAnimator.pause(); - } - mRevealAnimator.setCurrentFraction(shapeRevealProgress); - } - - float drawableScale = (mIsVerticalBarLayout ? mOutline.width() : mOutline.height()) - / minSize; - setBackgroundDrawableBounds(drawableScale); - if (isOpening) { - // Center align foreground - int height = mFinalDrawableBounds.height(); - int width = mFinalDrawableBounds.width(); - int diffY = mIsVerticalBarLayout ? 0 - : (int) (((height * drawableScale) - height) / 2); - int diffX = mIsVerticalBarLayout ? (int) (((width * drawableScale) - width) / 2) - : 0; - sTmpRect.set(mFinalDrawableBounds); - sTmpRect.offset(diffX, diffY); - mForeground.setBounds(sTmpRect); - } else { - // Spring the foreground relative to the icon's movement within the DragLayer. - int diffX = (int) (dX / mLauncher.getDeviceProfile().availableWidthPx - * FG_TRANS_X_FACTOR); - int diffY = (int) (dY / mLauncher.getDeviceProfile().availableHeightPx - * FG_TRANS_Y_FACTOR); - - mFgSpringX.animateToFinalPosition(diffX); - mFgSpringY.animateToFinalPosition(diffY); - } - } invalidate(); - invalidateOutline(); } @Override @@ -300,9 +183,7 @@ public class FloatingIconView extends View implements mEndRunnable.run(); } else { // End runnable also ends the reveal animator, so we manually handle it here. - if (mRevealAnimator != null) { - mRevealAnimator.end(); - } + mClipIconView.endReveal(); } } @@ -314,23 +195,25 @@ public class FloatingIconView extends View implements */ private void matchPositionOf(Launcher launcher, View v, boolean isOpening, RectF positionOut) { float rotation = getLocationBoundsForView(launcher, v, isOpening, positionOut); - final LayoutParams lp = new LayoutParams( + final InsettableFrameLayout.LayoutParams lp = new InsettableFrameLayout.LayoutParams( Math.round(positionOut.width()), Math.round(positionOut.height())); updatePosition(rotation, positionOut, lp); setLayoutParams(lp); + + mClipIconView.setLayoutParams(new FrameLayout.LayoutParams(lp.width, lp.height)); } - private void updatePosition(float rotation, RectF position, LayoutParams lp) { + private void updatePosition(float rotation, RectF pos, InsettableFrameLayout.LayoutParams lp) { mRotation = rotation; - mPositionOut.set(position); + mPositionOut.set(pos); lp.ignoreInsets = true; // Position the floating view exactly on top of the original - lp.topMargin = Math.round(position.top); + lp.topMargin = Math.round(pos.top); if (mIsRtl) { - lp.setMarginStart(Math.round(mLauncher.getDeviceProfile().widthPx - position.right)); + lp.setMarginStart(Math.round(mLauncher.getDeviceProfile().widthPx - pos.right)); } else { - lp.setMarginStart(Math.round(position.left)); + lp.setMarginStart(Math.round(pos.left)); } // Set the properties here already to make sure they are available when running the first // animation frame. @@ -449,97 +332,42 @@ public class FloatingIconView extends View implements /** * Sets the drawables of the {@param originalView} onto this view. * - * @param originalView The View that the FloatingIconView will replace. * @param drawable The drawable of the original view. * @param badge The badge of the original view. * @param iconOffset The amount of offset needed to match this view with the original view. */ @UiThread - private void setIcon(View originalView, @Nullable Drawable drawable, @Nullable Drawable badge, - int iconOffset) { + private void setIcon(@Nullable Drawable drawable, @Nullable Drawable badge, int iconOffset) { + final InsettableFrameLayout.LayoutParams lp = + (InsettableFrameLayout.LayoutParams) getLayoutParams(); mBadge = badge; - - mIsAdaptiveIcon = drawable instanceof AdaptiveIconDrawable; - if (mIsAdaptiveIcon) { - boolean isFolderIcon = drawable instanceof FolderAdaptiveIcon; - - AdaptiveIconDrawable adaptiveIcon = (AdaptiveIconDrawable) drawable; - Drawable background = adaptiveIcon.getBackground(); - if (background == null) { - background = new ColorDrawable(Color.TRANSPARENT); - } - mBackground = background; - Drawable foreground = adaptiveIcon.getForeground(); - if (foreground == null) { - foreground = new ColorDrawable(Color.TRANSPARENT); - } - mForeground = foreground; - - final LayoutParams lp = (LayoutParams) getLayoutParams(); + mClipIconView.setIcon(drawable, iconOffset, lp, mIsOpening); + if (drawable instanceof AdaptiveIconDrawable) { final int originalHeight = lp.height; final int originalWidth = lp.width; - int blurMargin = mBlurSizeOutline / 2; mFinalDrawableBounds.set(0, 0, originalWidth, originalHeight); - if (!isFolderIcon) { - mFinalDrawableBounds.inset(iconOffset - blurMargin, iconOffset - blurMargin); - } - mForeground.setBounds(mFinalDrawableBounds); - mBackground.setBounds(mFinalDrawableBounds); - - mStartRevealRect.set(0, 0, originalWidth, originalHeight); - - if (mBadge != null) { - mBadge.setBounds(mStartRevealRect); - if (!mIsOpening && !isFolderIcon) { - DRAWABLE_ALPHA.set(mBadge, 0); - } - } - - if (!isFolderIcon) { - Utilities.scaleRectAboutCenter(mStartRevealRect, - IconShape.getNormalizationScale()); - } - float aspectRatio = mLauncher.getDeviceProfile().aspectRatio; if (mIsVerticalBarLayout) { lp.width = (int) Math.max(lp.width, lp.height * aspectRatio); } else { lp.height = (int) Math.max(lp.height, lp.width * aspectRatio); } + setLayoutParams(lp); - int left = mIsRtl - ? mLauncher.getDeviceProfile().widthPx - lp.getMarginStart() - lp.width - : lp.leftMargin; - layout(left, lp.topMargin, left + lp.width, lp.topMargin + lp.height); + final LayoutParams clipViewLp = (LayoutParams) mClipIconView.getLayoutParams(); + final int clipViewOgHeight = clipViewLp.height; + final int clipViewOgWidth = clipViewLp.width; + clipViewLp.width = lp.width; + clipViewLp.height = lp.height; + mClipIconView.setLayoutParams(clipViewLp); - float scale = Math.max((float) lp.height / originalHeight, - (float) lp.width / originalWidth); - float bgDrawableStartScale; - if (mIsOpening) { - bgDrawableStartScale = 1f; - mOutline.set(0, 0, originalWidth, originalHeight); - } else { - bgDrawableStartScale = scale; - mOutline.set(0, 0, lp.width, lp.height); + if (mBadge != null) { + mBadge.setBounds(0, 0, clipViewOgWidth, clipViewOgHeight); } - setBackgroundDrawableBounds(bgDrawableStartScale); - mEndRevealRect.set(0, 0, lp.width, lp.height); - setOutlineProvider(new ViewOutlineProvider() { - @Override - public void getOutline(View view, Outline outline) { - outline.setRoundRect(mOutline, mTaskCornerRadius); - } - }); - setClipToOutline(true); - } else { - setBackground(drawable); - setClipToOutline(false); } - invalidate(); - invalidateOutline(); } /** @@ -556,7 +384,7 @@ public class FloatingIconView extends View implements synchronized (mIconLoadResult) { if (mIconLoadResult.isIconLoaded) { - setIcon(originalView, mIconLoadResult.drawable, mIconLoadResult.badge, + setIcon(mIconLoadResult.drawable, mIconLoadResult.badge, mIconLoadResult.iconOffset); hideOriginalView(originalView); } else { @@ -565,7 +393,7 @@ public class FloatingIconView extends View implements return; } - setIcon(originalView, mIconLoadResult.drawable, mIconLoadResult.badge, + setIcon(mIconLoadResult.drawable, mIconLoadResult.badge, mIconLoadResult.iconOffset); setVisibility(VISIBLE); @@ -585,18 +413,6 @@ public class FloatingIconView extends View implements } } - private void setBackgroundDrawableBounds(float scale) { - sTmpRect.set(mFinalDrawableBounds); - Utilities.scaleRectAboutCenter(sTmpRect, scale); - // Since the drawable is at the top of the view, we need to offset to keep it centered. - if (mIsVerticalBarLayout) { - sTmpRect.offsetTo((int) (mFinalDrawableBounds.left * scale), sTmpRect.top); - } else { - sTmpRect.offsetTo(sTmpRect.left, (int) (mFinalDrawableBounds.top * scale)); - } - mBackground.setBounds(sTmpRect); - } - @WorkerThread @SuppressWarnings("WrongThread") private static int getOffsetForIconBounds(Launcher l, Drawable drawable, RectF position) { @@ -626,29 +442,11 @@ public class FloatingIconView extends View implements } @Override - public void setClipPath(Path clipPath) { - mClipPath = clipPath; - invalidate(); - } - - @Override - public void draw(Canvas canvas) { + protected void dispatchDraw(Canvas canvas) { int count = canvas.save(); canvas.rotate(mRotation, mFinalDrawableBounds.exactCenterX(), mFinalDrawableBounds.exactCenterY()); - if (mClipPath != null) { - canvas.clipPath(mClipPath); - } - super.draw(canvas); - if (mBackground != null) { - mBackground.draw(canvas); - } - if (mForeground != null) { - int count2 = canvas.save(); - canvas.translate(mFgTransX, mFgTransY); - mForeground.draw(canvas); - canvas.restoreToCount(count2); - } + super.dispatchDraw(canvas); if (mBadge != null) { mBadge.draw(canvas); } @@ -692,7 +490,8 @@ public class FloatingIconView extends View implements float rotation = getLocationBoundsForView(mLauncher, mOriginalIcon, mIsOpening, sTmpRectF); if (rotation != mRotation || !sTmpRectF.equals(mPositionOut)) { - updatePosition(rotation, sTmpRectF, (LayoutParams) getLayoutParams()); + updatePosition(rotation, sTmpRectF, + (InsettableFrameLayout.LayoutParams) getLayoutParams()); if (mOnTargetChangeRunnable != null) { mOnTargetChangeRunnable.run(); } @@ -808,12 +607,6 @@ public class FloatingIconView extends View implements } }); - if (mBadge != null) { - ObjectAnimator badgeFade = ObjectAnimator.ofInt(mBadge, DRAWABLE_ALPHA, 255); - badgeFade.addUpdateListener(valueAnimator -> invalidate()); - fade.play(badgeFade); - } - if (originalView instanceof IconLabelDotView) { IconLabelDotView view = (IconLabelDotView) originalView; fade.addListener(new AnimatorListenerAdapter() { @@ -855,21 +648,12 @@ public class FloatingIconView extends View implements setScaleX(1); setScaleY(1); setAlpha(1); - setBackground(null); if (mLoadIconSignal != null) { mLoadIconSignal.cancel(); } mLoadIconSignal = null; mEndRunnable = null; - mIsAdaptiveIcon = false; - mForeground = null; - mBackground = null; - mClipPath = null; mFinalDrawableBounds.setEmpty(); - if (mRevealAnimator != null) { - mRevealAnimator.cancel(); - } - mRevealAnimator = null; if (mFadeAnimatorSet != null) { mFadeAnimatorSet.cancel(); } @@ -878,15 +662,10 @@ public class FloatingIconView extends View implements mListenerView.setListener(null); mOriginalIcon = null; mOnTargetChangeRunnable = null; - mTaskCornerRadius = 0; - mOutline.setEmpty(); - mFgTransY = 0; - mFgSpringX.cancel(); - mFgTransX = 0; - mFgSpringY.cancel(); mBadge = null; sTmpObjArray[0] = null; mIconLoadResult = null; + mClipIconView.recycle(); } private static class IconLoadResult { @@ -897,7 +676,7 @@ public class FloatingIconView extends View implements Runnable onIconLoaded; boolean isIconLoaded; - public IconLoadResult(ItemInfo itemInfo) { + IconLoadResult(ItemInfo itemInfo) { this.itemInfo = itemInfo; } } From 4d9ad3a19620abfc18de7c855aebd3a7e7f9a2ca Mon Sep 17 00:00:00 2001 From: Hyunyoung Song Date: Fri, 13 Mar 2020 11:04:34 -0700 Subject: [PATCH 33/36] Cleanup LoggerUtils Change-Id: Ibeccb61b25236ee905f61bdb5ea2255f778d4b05 --- .../launcher3/logging/LoggerUtils.java | 92 ------------------- 1 file changed, 92 deletions(-) diff --git a/src/com/android/launcher3/logging/LoggerUtils.java b/src/com/android/launcher3/logging/LoggerUtils.java index a9d10d775d..1b70fde88c 100644 --- a/src/com/android/launcher3/logging/LoggerUtils.java +++ b/src/com/android/launcher3/logging/LoggerUtils.java @@ -15,8 +15,6 @@ */ package com.android.launcher3.logging; -import static com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType.NAVBAR; - import android.util.ArrayMap; import android.util.SparseArray; import android.view.View; @@ -27,12 +25,9 @@ import com.android.launcher3.ItemInfo; import com.android.launcher3.LauncherSettings; import com.android.launcher3.userevent.nano.LauncherLogExtensions.TargetExtension; import com.android.launcher3.userevent.nano.LauncherLogProto.Action; -import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType; -import com.android.launcher3.userevent.nano.LauncherLogProto.ControlType; import com.android.launcher3.userevent.nano.LauncherLogProto.ItemType; import com.android.launcher3.userevent.nano.LauncherLogProto.LauncherEvent; import com.android.launcher3.userevent.nano.LauncherLogProto.Target; -import com.android.launcher3.userevent.nano.LauncherLogProto.TipType; import com.android.launcher3.util.InstantAppResolver; import java.lang.reflect.Field; @@ -70,93 +65,6 @@ public class LoggerUtils { return result != null ? result : UNKNOWN; } - public static String getActionStr(Action action) { - String str = ""; - switch (action.type) { - case Action.Type.TOUCH: - str += getFieldName(action.touch, Action.Touch.class); - if (action.touch == Action.Touch.SWIPE || action.touch == Action.Touch.FLING) { - str += " direction=" + getFieldName(action.dir, Action.Direction.class); - } - break; - case Action.Type.COMMAND: - str += getFieldName(action.command, Action.Command.class); - break; - default: return getFieldName(action.type, Action.Type.class); - } - if (action.touch == Action.Touch.SWIPE || action.touch == Action.Touch.FLING || - (action.command == Action.Command.BACK && action.dir != Action.Direction.NONE)) { - str += " direction=" + getFieldName(action.dir, Action.Direction.class); - } - return str; - } - - public static String getTargetStr(Target t) { - if (t == null) { - return ""; - } - String str = ""; - switch (t.type) { - case Target.Type.ITEM: - str = getItemStr(t); - break; - case Target.Type.CONTROL: - str = getFieldName(t.controlType, ControlType.class); - break; - case Target.Type.CONTAINER: - str = getFieldName(t.containerType, ContainerType.class); - if (t.containerType == ContainerType.WORKSPACE || - t.containerType == ContainerType.HOTSEAT || - t.containerType == NAVBAR) { - str += " id=" + t.pageIndex; - } else if (t.containerType == ContainerType.FOLDER) { - str += " grid(" + t.gridX + "," + t.gridY + ")"; - } - break; - default: - str += "UNKNOWN TARGET TYPE"; - } - - if (t.spanX != 1 || t.spanY != 1) { - str += " span(" + t.spanX + "," + t.spanY + ")"; - } - - if (t.tipType != TipType.DEFAULT_NONE) { - str += " " + getFieldName(t.tipType, TipType.class); - } - - return str; - } - - private static String getItemStr(Target t) { - String typeStr = getFieldName(t.itemType, ItemType.class); - if (t.packageNameHash != 0) { - typeStr += ", packageHash=" + t.packageNameHash; - } - if (t.componentHash != 0) { - typeStr += ", componentHash=" + t.componentHash; - } - if (t.intentHash != 0) { - typeStr += ", intentHash=" + t.intentHash; - } - if (t.itemType == ItemType.FOLDER_ICON) { - typeStr += ", grid(" + t.gridX + "," + t.gridY + ")"; - } else if ((t.packageNameHash != 0 || t.componentHash != 0 || t.intentHash != 0) - && t.itemType != ItemType.TASK) { - typeStr += - ", isWorkApp=" + t.isWorkApp + ", predictiveRank=" + t.predictedRank + ", grid(" - + t.gridX + "," + t.gridY + "), span(" + t.spanX + "," + t.spanY - + "), pageIdx=" + t.pageIndex; - } - if (t.searchQueryLength != 0) { - typeStr += ", searchQueryLength=" + t.searchQueryLength; - } - if (t.itemType == ItemType.TASK) { - typeStr += ", pageIdx=" + t.pageIndex; - } - return typeStr; - } - public static Target newItemTarget(int itemType) { Target t = newTarget(Target.Type.ITEM); t.itemType = itemType; From 7fb01cc6b353f407d590c71580616f5b47b10ec9 Mon Sep 17 00:00:00 2001 From: Sunny Goyal Date: Mon, 23 Mar 2020 14:14:49 -0700 Subject: [PATCH 34/36] Restring the item rank to valid range since the folder contents change during drag operation Bug: 152193591 Change-Id: I71a3c44e84fe8c3bf787ad28c2f2fad48f0ad70a --- src/com/android/launcher3/FolderInfo.java | 2 +- src/com/android/launcher3/folder/Folder.java | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/com/android/launcher3/FolderInfo.java b/src/com/android/launcher3/FolderInfo.java index 336e423373..b75a5e7987 100644 --- a/src/com/android/launcher3/FolderInfo.java +++ b/src/com/android/launcher3/FolderInfo.java @@ -79,7 +79,7 @@ public class FolderInfo extends ItemInfo { * Add an app or shortcut for a specified rank. */ public void add(WorkspaceItemInfo item, int rank, boolean animate) { - rank = Utilities.boundToRange(rank, 0, contents.size() + 1); + rank = Utilities.boundToRange(rank, 0, contents.size()); contents.add(rank, item); for (int i = 0; i < mListeners.size(); i++) { mListeners.get(i).onAdd(item, rank); diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java index 14fa1f4478..365e76fa2b 100644 --- a/src/com/android/launcher3/folder/Folder.java +++ b/src/com/android/launcher3/folder/Folder.java @@ -81,6 +81,7 @@ import com.android.launcher3.OnAlarmListener; import com.android.launcher3.PagedView; import com.android.launcher3.R; import com.android.launcher3.ShortcutAndWidgetContainer; +import com.android.launcher3.Utilities; import com.android.launcher3.Workspace; import com.android.launcher3.Workspace.ItemOperator; import com.android.launcher3.WorkspaceItemInfo; @@ -962,6 +963,7 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo View icon = (mCurrentDragView != null && mCurrentDragView.getTag() == info) ? mCurrentDragView : mContent.createNewView(info); ArrayList views = getIconsInReadingOrder(); + info.rank = Utilities.boundToRange(info.rank, 0, views.size()); views.add(info.rank, icon); mContent.arrangeChildren(views); mItemsInvalidated = true; From d684eddd9f47e97931c8960e11e4434c58270150 Mon Sep 17 00:00:00 2001 From: thiruram Date: Mon, 23 Mar 2020 13:26:44 -0700 Subject: [PATCH 35/36] [DO NOT MERGE] Adds basic smart-folder logging. This would allow collecting trimmed down version of smart-folder logs from QPR devices. Change-Id: Ida332bd969774cabcfd72def5bed0e6409d6c8e1 --- protos/launcher_log.proto | 3 +- src/com/android/launcher3/folder/Folder.java | 41 +++++++++++++++++++ .../launcher3/logging/LoggerUtils.java | 2 +- 3 files changed, 44 insertions(+), 2 deletions(-) diff --git a/protos/launcher_log.proto b/protos/launcher_log.proto index 055ade58eb..fd36d4ba53 100644 --- a/protos/launcher_log.proto +++ b/protos/launcher_log.proto @@ -137,7 +137,8 @@ message Action { AUTOMATED = 1; COMMAND = 2; TIP = 3; - // SOFT_KEYBOARD, HARD_KEYBOARD, ASSIST + SOFT_KEYBOARD = 4; + // HARD_KEYBOARD, ASSIST } enum Touch { diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java index 0bd2c9af7f..f483d78f4a 100644 --- a/src/com/android/launcher3/folder/Folder.java +++ b/src/com/android/launcher3/folder/Folder.java @@ -17,6 +17,8 @@ package com.android.launcher3.folder; import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY; +import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP; +import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT; import static com.android.launcher3.LauncherState.NORMAL; import static com.android.launcher3.compat.AccessibilityManagerCompat.sendCustomAccessibilityEvent; @@ -74,6 +76,7 @@ import com.android.launcher3.dragndrop.DragLayer; import com.android.launcher3.dragndrop.DragOptions; import com.android.launcher3.logging.LoggerUtils; import com.android.launcher3.pageindicators.PageIndicatorDots; +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; import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType; @@ -1340,6 +1343,9 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo if (hasFocus) { startEditingFolderName(); } else { + if (isEditingName()) { + logEditFolderLabel(); + } mFolderName.dispatchBackKey(); } } @@ -1517,4 +1523,39 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo super.draw(canvas); } } + + private void logEditFolderLabel() { + LauncherLogProto.LauncherEvent ev = new LauncherLogProto.LauncherEvent(); + LauncherLogProto.Action action = new LauncherLogProto.Action(); + action.type = LauncherLogProto.Action.Type.SOFT_KEYBOARD; + ev.action = action; + + LauncherLogProto.Target edittext_target = new LauncherLogProto.Target(); + edittext_target.type = LauncherLogProto.Target.Type.ITEM; + edittext_target.itemType = LauncherLogProto.ItemType.EDITTEXT; + + LauncherLogProto.Target folder_target = new LauncherLogProto.Target(); + folder_target.type = LauncherLogProto.Target.Type.CONTAINER; + folder_target.containerType = LauncherLogProto.ContainerType.FOLDER; + folder_target.pageIndex = mInfo.screenId; + folder_target.gridX = mInfo.cellX; + folder_target.gridY = mInfo.cellY; + folder_target.cardinality = mInfo.contents.size(); + + LauncherLogProto.Target parent_target = new LauncherLogProto.Target(); + parent_target.type = LauncherLogProto.Target.Type.CONTAINER; + switch (mInfo.container) { + case CONTAINER_HOTSEAT: + parent_target.containerType = LauncherLogProto.ContainerType.HOTSEAT; + break; + case CONTAINER_DESKTOP: + parent_target.containerType = LauncherLogProto.ContainerType.WORKSPACE; + break; + default: + Log.e(TAG, String.format("Expected container to be either %s or %s but found %s.", + CONTAINER_HOTSEAT, CONTAINER_DESKTOP, mInfo.container)); + } + ev.srcTarget = new LauncherLogProto.Target[]{edittext_target, folder_target, parent_target}; + mLauncher.getUserEventDispatcher().dispatchUserEvent(ev, null); + } } diff --git a/src/com/android/launcher3/logging/LoggerUtils.java b/src/com/android/launcher3/logging/LoggerUtils.java index 598792abce..925b7ba286 100644 --- a/src/com/android/launcher3/logging/LoggerUtils.java +++ b/src/com/android/launcher3/logging/LoggerUtils.java @@ -109,7 +109,7 @@ public class LoggerUtils { t.containerType == NAVBAR) { str += " id=" + t.pageIndex; } else if (t.containerType == ContainerType.FOLDER) { - str += " grid(" + t.gridX + "," + t.gridY + ")"; + str += "[PageIndex=" + t.pageIndex + ", grid(" + t.gridX + "," + t.gridY + ")]"; } break; default: From 12ca8b940da6c989fd47bdf7d1fbbd05bec2acec Mon Sep 17 00:00:00 2001 From: Andy Wickham Date: Tue, 24 Mar 2020 01:03:52 +0000 Subject: [PATCH 36/36] Fixes ag/10573640 on qt-future-dev branch. Original qt-future-dev change was ag/10601877. The change needed to be in UiFactory, not RecentsUiFactory. Fixes: 146593239 Test: Installed NexusLauncherDebug on a QD4A build and tried the repo steps. Also built other launcher variants. Merged-In: Ib9c85de2f83f99d1ef53fb17fde5d0b3c514849a Change-Id: I802e2b0069a19ca62a08325bb6d0de5275c43c9b --- .../android/launcher3/uioverrides/RecentsUiFactory.java | 8 -------- .../src/com/android/launcher3/uioverrides/UiFactory.java | 8 +++++++- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsUiFactory.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsUiFactory.java index 4d935e1157..cac170c68c 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsUiFactory.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsUiFactory.java @@ -19,7 +19,6 @@ package com.android.launcher3.uioverrides; import static com.android.launcher3.LauncherState.NORMAL; import static com.android.launcher3.LauncherState.OVERVIEW; import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON; -import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS; import android.content.Context; import android.graphics.Rect; @@ -49,7 +48,6 @@ import com.android.quickstep.SysUINavigationMode; import com.android.quickstep.SysUINavigationMode.Mode; import com.android.quickstep.TouchInteractionService; import com.android.quickstep.views.RecentsView; -import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.WindowManagerWrapper; import java.util.ArrayList; @@ -210,12 +208,6 @@ public abstract class RecentsUiFactory { } } - /** Closes system windows. */ - public static void closeSystemWindows() { - ActivityManagerWrapper.getInstance() - .closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS); - } - private static final class LauncherTaskViewController extends TaskViewTouchController { diff --git a/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java b/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java index eb58b9425f..0790cf6b03 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java +++ b/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java @@ -28,6 +28,7 @@ import static com.android.launcher3.allapps.DiscoveryBounce.HOME_BOUNCE_COUNT; import static com.android.launcher3.allapps.DiscoveryBounce.HOME_BOUNCE_SEEN; import static com.android.launcher3.allapps.DiscoveryBounce.SHELF_BOUNCE_COUNT; import static com.android.launcher3.allapps.DiscoveryBounce.SHELF_BOUNCE_SEEN; +import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS; import android.animation.AnimatorSet; import android.animation.ValueAnimator; @@ -58,6 +59,7 @@ import com.android.quickstep.SysUINavigationMode.Mode; import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener; import com.android.quickstep.util.RemoteFadeOutAnimationListener; import com.android.systemui.shared.system.ActivityCompat; +import com.android.systemui.shared.system.ActivityManagerWrapper; import java.io.ByteArrayOutputStream; import java.io.PrintWriter; @@ -252,5 +254,9 @@ public class UiFactory extends RecentsUiFactory { return persons == null ? Utilities.EMPTY_PERSON_ARRAY : persons; } - public static void closeSystemWindows() {} + /** Closes system windows. */ + public static void closeSystemWindows() { + ActivityManagerWrapper.getInstance() + .closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS); + } }