diff --git a/quickstep/AndroidManifest-launcher.xml b/quickstep/AndroidManifest-launcher.xml index a24a588b88..7d7054f5a5 100644 --- a/quickstep/AndroidManifest-launcher.xml +++ b/quickstep/AndroidManifest-launcher.xml @@ -57,6 +57,7 @@ android:enabled="true"> + diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java index 2239102c4d..95d6dd02d5 100644 --- a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java +++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java @@ -92,6 +92,9 @@ import com.android.systemui.shared.system.RemoteAnimationTargetCompat; import com.android.systemui.unfold.UnfoldTransitionFactory; import com.android.systemui.unfold.UnfoldTransitionProgressProvider; import com.android.systemui.unfold.config.UnfoldTransitionConfig; +import com.android.systemui.unfold.system.ActivityManagerActivityTypeProvider; +import com.android.systemui.unfold.system.DeviceStateManagerFoldProvider; +import com.android.systemui.unfold.config.ResourceUnfoldTransitionConfig; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -343,15 +346,17 @@ public abstract class BaseQuickstepLauncher extends Launcher { } private void initUnfoldTransitionProgressProvider() { - final UnfoldTransitionConfig config = UnfoldTransitionFactory.createConfig(this); + final UnfoldTransitionConfig config = new ResourceUnfoldTransitionConfig(); if (config.isEnabled()) { mUnfoldTransitionProgressProvider = UnfoldTransitionFactory.createUnfoldTransitionProgressProvider( - this, + /* context= */ this, config, ProxyScreenStatusProvider.INSTANCE, - getSystemService(DeviceStateManager.class), - getSystemService(ActivityManager.class), + new DeviceStateManagerFoldProvider( + getSystemService(DeviceStateManager.class), /* context */this), + new ActivityManagerActivityTypeProvider( + getSystemService(ActivityManager.class)), getSystemService(SensorManager.class), getMainThreadHandler(), getMainExecutor(), diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java index a3e8b5ccfd..ca30e72609 100644 --- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java +++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java @@ -19,6 +19,7 @@ import static com.android.launcher3.taskbar.TaskbarLauncherStateController.FLAG_ import static com.android.systemui.shared.system.WindowManagerWrapper.ITYPE_EXTRA_NAVIGATION_BAR; import android.animation.Animator; +import android.animation.AnimatorSet; import android.annotation.ColorInt; import android.os.RemoteException; import android.util.Log; @@ -28,6 +29,7 @@ import android.view.TaskTransitionSpec; import android.view.WindowManagerGlobal; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import com.android.launcher3.BaseQuickstepLauncher; @@ -139,6 +141,24 @@ public class LauncherTaskbarUIController extends TaskbarUIController { mControllers.taskbarStashController.onLongPressToUnstashTaskbar(); } + /** + * Adds the Launcher resume animator to the given animator set. + * + * This should be used to run a Launcher resume animation whose progress matches a + * swipe progress. + * + * @param placeholderDuration a placeholder duration to be used to ensure all full-length + * sub-animations are properly coordinated. This duration should not + * actually be used since this animation tracks a swipe progress. + */ + protected void addLauncherResumeAnimation(AnimatorSet animation, int placeholderDuration) { + animation.play(onLauncherResumedOrPaused( + /* isResumed= */ true, + /* fromInit= */ false, + /* startAnimation= */ false, + placeholderDuration)); + } + /** * Should be called from onResume() and onPause(), and animates the Taskbar accordingly. */ @@ -147,9 +167,19 @@ public class LauncherTaskbarUIController extends TaskbarUIController { } private void onLauncherResumedOrPaused(boolean isResumed, boolean fromInit) { + onLauncherResumedOrPaused( + isResumed, + fromInit, + /* startAnimation= */ true, + QuickstepTransitionManager.CONTENT_ALPHA_DURATION); + } + + @Nullable + private Animator onLauncherResumedOrPaused( + boolean isResumed, boolean fromInit, boolean startAnimation, int duration) { if (mKeyguardController.isScreenOff()) { if (!isResumed) { - return; + return null; } else { // Resuming implicitly means device unlocked mKeyguardController.setScreenOn(); @@ -157,8 +187,7 @@ public class LauncherTaskbarUIController extends TaskbarUIController { } mTaskbarLauncherStateController.updateStateForFlag(FLAG_RESUMED, isResumed); - mTaskbarLauncherStateController.applyState( - fromInit ? 0 : QuickstepTransitionManager.CONTENT_ALPHA_DURATION); + return mTaskbarLauncherStateController.applyState(fromInit ? 0 : duration, startAnimation); } /** diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java index 2f322199f3..ed1001cead 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java @@ -29,6 +29,7 @@ import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_N import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED; import android.animation.AnimatorSet; +import android.animation.ValueAnimator; import android.app.ActivityOptions; import android.content.ActivityNotFoundException; import android.content.Context; @@ -60,6 +61,8 @@ import com.android.launcher3.BubbleTextView; import com.android.launcher3.DeviceProfile; import com.android.launcher3.LauncherSettings.Favorites; import com.android.launcher3.R; +import com.android.launcher3.anim.AnimatorPlaybackController; +import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.dot.DotInfo; import com.android.launcher3.folder.Folder; import com.android.launcher3.folder.FolderIcon; @@ -734,6 +737,45 @@ public class TaskbarActivityContext extends BaseTaskbarContext { return mIsNavBarForceVisible; } + /** + * Displays a single frame of the Launcher start from SUW animation. + * + * This animation is a combination of the Launcher resume animation, which animates the hotseat + * icons into position, the Taskbar unstash to hotseat animation, which animates the Taskbar + * stash bar into the hotseat icons, and an override to prevent showing the Taskbar all apps + * button. + * + * This should be used to run a Taskbar unstash to hotseat animation whose progress matches a + * swipe progress. + * + * @param duration a placeholder duration to be used to ensure all full-length + * sub-animations are properly coordinated. This duration should not actually + * be used since this animation tracks a swipe progress. + */ + protected AnimatorPlaybackController createLauncherStartFromSuwAnim(int duration) { + AnimatorSet fullAnimation = new AnimatorSet(); + fullAnimation.setDuration(duration); + + TaskbarUIController uiController = mControllers.uiController; + if (uiController instanceof LauncherTaskbarUIController) { + ((LauncherTaskbarUIController) uiController).addLauncherResumeAnimation( + fullAnimation, duration); + } + mControllers.taskbarStashController.addUnstashToHotseatAnimation(fullAnimation, duration); + + if (!FeatureFlags.ENABLE_ALL_APPS_BUTTON_IN_HOTSEAT.get()) { + ValueAnimator alphaOverride = ValueAnimator.ofFloat(0, 1); + alphaOverride.setDuration(duration); + alphaOverride.addUpdateListener(a -> { + // Override the alpha updates in the icon alignment animation. + mControllers.taskbarViewController.getAllAppsButtonView().setAlpha(0); + }); + fullAnimation.play(alphaOverride); + } + + return AnimatorPlaybackController.wrap(fullAnimation, duration); + } + /** * Called when we determine the touchable region. * diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java index 2e37170952..ef7bab9832 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java @@ -39,6 +39,7 @@ import androidx.annotation.Nullable; import com.android.launcher3.BaseQuickstepLauncher; import com.android.launcher3.DeviceProfile; import com.android.launcher3.LauncherAppState; +import com.android.launcher3.anim.AnimatorPlaybackController; import com.android.launcher3.statemanager.StatefulActivity; import com.android.launcher3.taskbar.unfold.NonDestroyableScopedUnfoldTransitionProgressProvider; import com.android.launcher3.util.DisplayController; @@ -182,6 +183,17 @@ public class TaskbarManager { } } + /** + * Displays a frame of the first Launcher reveal animation. + * + * This should be used to run a first Launcher reveal animation whose progress matches a swipe + * progress. + */ + public AnimatorPlaybackController createLauncherStartFromSuwAnim(int duration) { + return mTaskbarActivityContext == null + ? null : mTaskbarActivityContext.createLauncherStartFromSuwAnim(duration); + } + /** * Called when the user is unlocked */ diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java index 7d95743bc9..fc9f9d002b 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java @@ -35,6 +35,8 @@ import android.view.WindowInsets; import com.android.launcher3.DeviceProfile; import com.android.launcher3.Utilities; +import com.android.launcher3.anim.AnimatorListeners; +import com.android.launcher3.taskbar.allapps.TaskbarAllAppsSlideInView; import com.android.launcher3.testing.TestProtocol; import com.android.launcher3.util.MultiValueAlpha.AlphaProperty; import com.android.quickstep.AnimatedFloat; @@ -367,13 +369,34 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba return false; } + /** + * Adds the Taskbar unstash to Hotseat animator to the animator set. + * + * This should be used to run a Taskbar unstash to Hotseat animation whose progress matches a + * swipe progress. + * + * @param placeholderDuration a placeholder duration to be used to ensure all full-length + * sub-animations are properly coordinated. This duration should not + * actually be used since this animation tracks a swipe progress. + */ + protected void addUnstashToHotseatAnimation(AnimatorSet animation, int placeholderDuration) { + createAnimToIsStashed( + /* isStashed= */ false, + placeholderDuration, + /* startDelay= */ 0, + /* animateBg= */ false); + animation.play(mAnimator); + } + /** * Create a stash animation and save to {@link #mAnimator}. * @param isStashed whether it's a stash animation or an unstash animation * @param duration duration of the animation * @param startDelay how many milliseconds to delay the animation after starting it. + * @param animateBg whether the taskbar's background should be animated */ - private void createAnimToIsStashed(boolean isStashed, long duration, long startDelay) { + private void createAnimToIsStashed( + boolean isStashed, long duration, long startDelay, boolean animateBg) { if (mAnimator != null) { mAnimator.cancel(); } @@ -408,10 +431,14 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba secondHalfDurationScale = 0.5f; final float stashTranslation = (mUnstashedHeight - mStashedHeight) / 2f; - fullLengthAnimatorSet.playTogether( - mTaskbarBackgroundOffset.animateToValue(1), - mIconTranslationYForStash.animateToValue(stashTranslation) - ); + fullLengthAnimatorSet.play(mIconTranslationYForStash.animateToValue(stashTranslation)); + if (animateBg) { + fullLengthAnimatorSet.play(mTaskbarBackgroundOffset.animateToValue(1)); + } else { + fullLengthAnimatorSet.addListener(AnimatorListeners.forEndCallback( + () -> mTaskbarBackgroundOffset.updateValue(1))); + } + firstHalfAnimatorSet.playTogether( mIconAlphaForStash.animateToValue(0), mIconScaleForStash.animateToValue(STASHED_TASKBAR_SCALE) @@ -424,10 +451,15 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba secondHalfDurationScale = 0.75f; fullLengthAnimatorSet.playTogether( - mTaskbarBackgroundOffset.animateToValue(0), mIconScaleForStash.animateToValue(1), - mIconTranslationYForStash.animateToValue(0) - ); + mIconTranslationYForStash.animateToValue(0)); + if (animateBg) { + fullLengthAnimatorSet.play(mTaskbarBackgroundOffset.animateToValue(0)); + } else { + fullLengthAnimatorSet.addListener(AnimatorListeners.forEndCallback( + () -> mTaskbarBackgroundOffset.updateValue(0))); + } + firstHalfAnimatorSet.playTogether( mTaskbarStashedHandleAlpha.animateToValue(0) ); @@ -728,7 +760,7 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba mIsStashed = isStashed; // This sets mAnimator. - createAnimToIsStashed(mIsStashed, duration, startDelay); + createAnimToIsStashed(mIsStashed, duration, startDelay, /* animateBg= */ true); if (start) { mAnimator.start(); } diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java index 5db495db43..3dd7932a9a 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java @@ -258,21 +258,21 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar setter.addOnFrameListener(anim -> mActivity.setTaskbarWindowHeight( anim.getAnimatedFraction() > 0 ? expandedHeight : collapsedHeight)); - int count = mTaskbarView.getChildCount(); - for (int i = 0; i < count; i++) { + for (int i = 0; i < mTaskbarView.getChildCount(); i++) { View child = mTaskbarView.getChildAt(i); - int positionInHotseat = -1; - boolean isRtl = Utilities.isRtl(child.getResources()); + int positionInHotseat; if (FeatureFlags.ENABLE_ALL_APPS_IN_TASKBAR.get() - && ((isRtl && i == 0) || (!isRtl && i == count - 1))) { + && child == mTaskbarView.getAllAppsButtonView()) { // Note that there is no All Apps button in the hotseat, this position is only used // as its convenient for animation purposes. - positionInHotseat = isRtl + positionInHotseat = Utilities.isRtl(child.getResources()) ? -1 : mActivity.getDeviceProfile().numShownHotseatIcons; - setter.setViewAlpha(child, 0, LINEAR); + if (!FeatureFlags.ENABLE_ALL_APPS_BUTTON_IN_HOTSEAT.get()) { + setter.setViewAlpha(child, 0, LINEAR); + } } else if (child.getTag() instanceof ItemInfo) { positionInHotseat = ((ItemInfo) child.getTag()).screenId; } else { diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java index f2583fb2c0..d7ee3cbc9d 100644 --- a/quickstep/src/com/android/quickstep/TouchInteractionService.java +++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java @@ -51,6 +51,7 @@ import android.os.Bundle; import android.os.IBinder; import android.os.Looper; import android.os.SystemClock; +import android.os.SystemProperties; import android.util.Log; import android.view.Choreographer; import android.view.InputEvent; @@ -127,6 +128,9 @@ public class TouchInteractionService extends Service private static final String TAG = "TouchInteractionService"; + private static final boolean BUBBLES_HOME_GESTURE_ENABLED = + SystemProperties.getBoolean("persist.wm.debug.bubbles_home_gesture", false); + private static final String KEY_BACK_NOTIFICATION_COUNT = "backNotificationCount"; private static final String NOTIFY_ACTION_BACK = "com.android.quickstep.action.BACK_GESTURE"; private static final String HAS_ENABLED_QUICKSTEP_ONCE = "launcher.has_enabled_quickstep_once"; @@ -698,16 +702,30 @@ public class TouchInteractionService extends Service base = new TaskbarStashInputConsumer(this, base, mInputMonitorCompat, tac); } - // If Bubbles is expanded, use the overlay input consumer, which will close Bubbles - // instead of going all the way home when a swipe up is detected. - // Notification panel can be expanded on top of expanded bubbles. Bubbles remain - // expanded in the back. Make sure swipe up is not passed to bubbles in this case. - if ((mDeviceState.isBubblesExpanded() && !mDeviceState.isNotificationPanelExpanded()) - || mDeviceState.isSystemUiDialogShowing()) { + if (mDeviceState.isBubblesExpanded()) { + if (BUBBLES_HOME_GESTURE_ENABLED) { + // Bubbles can handle home gesture itself. + base = getDefaultInputConsumer(); + } else { + // If Bubbles is expanded, use the overlay input consumer, which will close + // Bubbles instead of going all the way home when a swipe up is detected. + // Notification panel can be expanded on top of expanded bubbles. Bubbles remain + // expanded in the back. Make sure swipe up is not passed to bubbles in this + // case. + if (!mDeviceState.isNotificationPanelExpanded()) { + base = new SysUiOverlayInputConsumer( + getBaseContext(), mDeviceState, mInputMonitorCompat); + } + } + } + + if (mDeviceState.isSystemUiDialogShowing()) { base = new SysUiOverlayInputConsumer( getBaseContext(), mDeviceState, mInputMonitorCompat); } + + if (mDeviceState.isScreenPinningActive()) { // Note: we only allow accessibility to wrap this, and it replaces the previous // base input consumer (which should be NO_OP anyway since topTaskLocked == true). diff --git a/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java b/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java index db19c452a7..a379aada4a 100644 --- a/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java +++ b/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java @@ -17,6 +17,7 @@ package com.android.quickstep.interaction; import static com.android.launcher3.Utilities.mapBoundToRange; import static com.android.launcher3.Utilities.mapRange; +import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN; import static com.android.launcher3.anim.Interpolators.LINEAR; import android.animation.Animator; @@ -54,6 +55,8 @@ import androidx.core.graphics.ColorUtils; import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.R; import com.android.launcher3.Utilities; +import com.android.launcher3.anim.AnimatorPlaybackController; +import com.android.launcher3.util.Executors; import com.android.quickstep.AnimatedFloat; import com.android.quickstep.GestureState; import com.android.quickstep.TouchInteractionService.TISBinder; @@ -77,6 +80,8 @@ public class AllSetActivity extends Activity { private static final float HINT_BOTTOM_FACTOR = 1 - .94f; + private static final int MAX_SWIPE_DURATION = 350; + private TISBindHelper mTISBindHelper; private TISBinder mBinder; @@ -89,6 +94,8 @@ public class AllSetActivity extends Activity { private LottieAnimationView mAnimatedBackground; private Animator.AnimatorListener mBackgroundAnimatorListener; + private AnimatorPlaybackController mLauncherStartAnim = null; + @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -136,6 +143,10 @@ public class AllSetActivity extends Activity { startBackgroundAnimation(); } + private void runOnUiHelperThread(Runnable runnable) { + Executors.UI_HELPER_EXECUTOR.execute(runnable); + } + private void startBackgroundAnimation() { if (Utilities.ATLEAST_S && mVibrator != null && mVibrator.areAllPrimitivesSupported( VibrationEffect.Composition.PRIMITIVE_THUD)) { @@ -144,22 +155,22 @@ public class AllSetActivity extends Activity { new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animation) { - mVibrator.vibrate(getVibrationEffect()); + runOnUiHelperThread(() -> mVibrator.vibrate(getVibrationEffect())); } @Override public void onAnimationRepeat(Animator animation) { - mVibrator.vibrate(getVibrationEffect()); + runOnUiHelperThread(() -> mVibrator.vibrate(getVibrationEffect())); } @Override public void onAnimationEnd(Animator animation) { - mVibrator.cancel(); + runOnUiHelperThread(mVibrator::cancel); } @Override public void onAnimationCancel(Animator animation) { - mVibrator.cancel(); + runOnUiHelperThread(mVibrator::cancel); } }; } @@ -232,11 +243,20 @@ public class AllSetActivity extends Activity { private void onSwipeProgressUpdate() { mBackground.setProgress(mSwipeProgress.value); - float alpha = Utilities.mapBoundToRange(mSwipeProgress.value, 0, HINT_BOTTOM_FACTOR, - 1, 0, LINEAR); + float alpha = Utilities.mapBoundToRange( + mSwipeProgress.value, 0, HINT_BOTTOM_FACTOR, 1, 0, LINEAR); mContentView.setAlpha(alpha); mContentView.setTranslationY((alpha - 1) * mSwipeUpShift); + if (mLauncherStartAnim == null) { + mLauncherStartAnim = mBinder.getTaskbarManager().createLauncherStartFromSuwAnim( + MAX_SWIPE_DURATION); + } + if (mLauncherStartAnim != null) { + mLauncherStartAnim.setPlayFraction(Utilities.mapBoundToRange( + mSwipeProgress.value, 0, 1, 0, 1, FAST_OUT_SLOW_IN)); + } + if (alpha == 0f) { mAnimatedBackground.pauseAnimation(); } else if (!mAnimatedBackground.isAnimating()) { diff --git a/res/values-sw600dp-land/dimens.xml b/res/values-sw600dp-land/dimens.xml index dce09e3668..63970cddc6 100644 --- a/res/values-sw600dp-land/dimens.xml +++ b/res/values-sw600dp-land/dimens.xml @@ -16,6 +16,9 @@ --> + + 3600dp + 44dp diff --git a/res/values-sw600dp/config.xml b/res/values-sw600dp/config.xml new file mode 100644 index 0000000000..072b92d5f2 --- /dev/null +++ b/res/values-sw600dp/config.xml @@ -0,0 +1,21 @@ + + + + + + 550 + + diff --git a/res/values-sw600dp/dimens.xml b/res/values-sw600dp/dimens.xml index eb347f2874..d69e7777b0 100644 --- a/res/values-sw600dp/dimens.xml +++ b/res/values-sw600dp/dimens.xml @@ -15,6 +15,9 @@ --> + + 3000dp + -1000dp diff --git a/res/values-sw720dp-land/dimens.xml b/res/values-sw720dp-land/dimens.xml index 439ea9322c..235631d6fa 100644 --- a/res/values-sw720dp-land/dimens.xml +++ b/res/values-sw720dp-land/dimens.xml @@ -15,6 +15,9 @@ --> + + 5300dp + 21.93dp 29.33dp diff --git a/res/values-sw720dp/dimens.xml b/res/values-sw720dp/dimens.xml index fad8c95039..7b2ed8bc65 100644 --- a/res/values-sw720dp/dimens.xml +++ b/res/values-sw720dp/dimens.xml @@ -15,6 +15,9 @@ --> + + 3400dp + 28dp @@ -28,6 +31,7 @@ 24dp 20dp 32dp + 32dp 110dp 48dp diff --git a/res/values/config.xml b/res/values/config.xml index 5ecd9299c7..9aa1f03734 100644 --- a/res/values/config.xml +++ b/res/values/config.xml @@ -1,3 +1,18 @@ + + false @@ -25,6 +40,9 @@ 50 + + 750 + diff --git a/res/values/dimens.xml b/res/values/dimens.xml index 098c69465d..dd99a7a5a7 100644 --- a/res/values/dimens.xml +++ b/res/values/dimens.xml @@ -24,6 +24,8 @@ 7dp 8dp + + 24dp 16dp 10.77dp @@ -58,13 +60,9 @@ 56dp 20dp - 36dp + 32dp 16dp - - 10sp - 1sp - 13dp 24dp @@ -99,6 +97,13 @@ 58dp -26dp + + 500dp + 400dp + 250dp + + 1500dp + 300dp 48dp @@ -229,7 +234,9 @@ 8dp 16dp 8dp - 22dp + 28dp + 0dp + 28dp 30dp diff --git a/src/com/android/launcher3/ButtonDropTarget.java b/src/com/android/launcher3/ButtonDropTarget.java index 0b07c952bb..8da4f053df 100644 --- a/src/com/android/launcher3/ButtonDropTarget.java +++ b/src/com/android/launcher3/ButtonDropTarget.java @@ -24,6 +24,7 @@ import android.content.Context; import android.content.res.Resources; import android.graphics.Rect; import android.graphics.drawable.Drawable; +import android.text.InputType; import android.text.TextUtils; import android.util.AttributeSet; import android.view.LayoutInflater; @@ -49,6 +50,8 @@ public abstract class ButtonDropTarget extends TextView private static final int[] sTempCords = new int[2]; private static final int DRAG_VIEW_DROP_DURATION = 285; private static final float DRAG_VIEW_HOVER_OVER_OPACITY = 0.65f; + private static final int MAX_LINES_TEXT_MULTI_LINE = 2; + private static final int MAX_LINES_TEXT_SINGLE_LINE = 1; public static final int TOOLTIP_DEFAULT = 0; public static final int TOOLTIP_LEFT = 1; @@ -72,6 +75,8 @@ public abstract class ButtonDropTarget extends TextView protected CharSequence mText; protected Drawable mDrawable; private boolean mTextVisible = true; + private boolean mIconVisible = true; + private boolean mTextMultiLine = true; private PopupWindow mToolTip; private int mToolTipLocation; @@ -109,8 +114,7 @@ public abstract class ButtonDropTarget extends TextView // drawableLeft and drawableStart. mDrawable = getContext().getDrawable(resId).mutate(); mDrawable.setTintList(getTextColors()); - centerIcon(); - setCompoundDrawablesRelative(mDrawable, null, null, null); + updateIconVisibility(); } public void setDropTargetBar(DropTargetBar dropTargetBar) { @@ -306,13 +310,49 @@ public abstract class ButtonDropTarget extends TextView if (mTextVisible != isVisible || !TextUtils.equals(newText, getText())) { mTextVisible = isVisible; setText(newText); - centerIcon(); - setCompoundDrawablesRelative(mDrawable, null, null, null); - int drawablePadding = mTextVisible ? mDrawablePadding : 0; - setCompoundDrawablePadding(drawablePadding); + updateIconVisibility(); } } + /** + * Display button text over multiple lines when isMultiLine is true, single line otherwise. + */ + public void setTextMultiLine(boolean isMultiLine) { + if (mTextMultiLine != isMultiLine) { + mTextMultiLine = isMultiLine; + setSingleLine(!isMultiLine); + setMaxLines(isMultiLine ? MAX_LINES_TEXT_MULTI_LINE : MAX_LINES_TEXT_SINGLE_LINE); + int inputType = InputType.TYPE_CLASS_TEXT; + if (isMultiLine) { + inputType |= InputType.TYPE_TEXT_FLAG_MULTI_LINE; + + } + setInputType(inputType); + } + } + + protected boolean isTextMultiLine() { + return mTextMultiLine; + } + + /** + * Sets the button icon visible when isVisible is true, hides it otherwise. + */ + public void setIconVisible(boolean isVisible) { + if (mIconVisible != isVisible) { + mIconVisible = isVisible; + updateIconVisibility(); + } + } + + private void updateIconVisibility() { + if (mIconVisible) { + centerIcon(); + } + setCompoundDrawablesRelative(mIconVisible ? mDrawable : null, null, null, null); + setCompoundDrawablePadding(mIconVisible && mTextVisible ? mDrawablePadding : 0); + } + @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); @@ -324,40 +364,6 @@ public abstract class ButtonDropTarget extends TextView hideTooltip(); } - - /** - * Reduce the size of the text until it fits or reaches a minimum. - * - * The minimum size is defined by {@code R.dimen.button_drop_target_min_text_size} and - * it diminishes by intervals defined by - * {@code R.dimen.button_drop_target_resize_text_increment} - * This functionality is very similar to the option - * {@link TextView#setAutoSizeTextTypeWithDefaults(int)} but can't be used in this view because - * the layout width is {@code WRAP_CONTENT}. - * - * @param availableWidth Available width in the button to fit the text, used in - * {@code ButtonDropTarget#isTextTruncated(int)} - * @return The biggest text size in SP that makes the text fit or if the text can't fit returns - * the min available value - */ - public float resizeTextToFit(int availableWidth) { - float minSize = Utilities.pxToSp(getResources() - .getDimensionPixelSize(R.dimen.button_drop_target_min_text_size)); - float step = Utilities.pxToSp(getResources() - .getDimensionPixelSize(R.dimen.button_drop_target_resize_text_increment)); - float textSize = Utilities.pxToSp(getTextSize()); - - while (textSize > minSize) { - if (isTextTruncated(availableWidth)) { - textSize -= step; - setTextSize(textSize); - } else { - return textSize; - } - } - return minSize; - } - public boolean isTextTruncated(int availableWidth) { availableWidth -= (getPaddingLeft() + getPaddingRight() + mDrawable.getIntrinsicWidth() + getCompoundDrawablePadding()); diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java index be180a61cb..f028d3c0d1 100644 --- a/src/com/android/launcher3/DeviceProfile.java +++ b/src/com/android/launcher3/DeviceProfile.java @@ -108,6 +108,7 @@ public class DeviceProfile { public float workspaceSpringLoadShrunkTop; public float workspaceSpringLoadShrunkBottom; public final int workspaceSpringLoadedBottomSpace; + public final int workspaceSpringLoadedMinNextPageVisiblePx; private final int extraSpace; public int workspaceTopPadding; @@ -214,6 +215,8 @@ public class DeviceProfile { public int dropTargetHorizontalPaddingPx; public int dropTargetVerticalPaddingPx; public int dropTargetGapPx; + public int dropTargetButtonWorkspaceEdgeGapPx; + public int dropTargetButtonScreenEdgeGapPx; // Insets private final Rect mInsets = new Rect(); @@ -343,9 +346,15 @@ public class DeviceProfile { dropTargetVerticalPaddingPx = res.getDimensionPixelSize( R.dimen.drop_target_button_drawable_vertical_padding); dropTargetGapPx = res.getDimensionPixelSize(R.dimen.drop_target_button_gap); + dropTargetButtonWorkspaceEdgeGapPx = res.getDimensionPixelSize( + R.dimen.drop_target_button_workspace_edge_gap); + dropTargetButtonScreenEdgeGapPx = res.getDimensionPixelSize( + R.dimen.drop_target_button_screen_edge_gap); workspaceSpringLoadedBottomSpace = res.getDimensionPixelSize(R.dimen.dynamic_grid_min_spring_loaded_space); + workspaceSpringLoadedMinNextPageVisiblePx = res.getDimensionPixelSize( + R.dimen.dynamic_grid_spring_loaded_min_next_space_visible); workspaceCellPaddingXPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_cell_padding_x); @@ -501,7 +510,7 @@ public class DeviceProfile { */ private int calculateQsbWidth() { if (isQsbInline) { - int columns = isTwoPanels ? inv.numColumns * 2 : inv.numColumns; + int columns = getPanelCount() * inv.numColumns; return getIconToIconWidthForColumns(columns) - iconSizePx * numShownHotseatIcons - hotseatBorderSpace * numShownHotseatIcons; @@ -954,13 +963,6 @@ public class DeviceProfile { return workspaceSpringLoadShrunkBottom; } - /** - * Gets the minimum visible amount of the next workspace page when in the spring-loaded state. - */ - private float getWorkspaceSpringLoadedMinimumNextPageVisible() { - return getCellSize().x / 2f; - } - /** * Gets the scale of the workspace for the spring-loaded edit state. */ @@ -972,8 +974,7 @@ public class DeviceProfile { // Reduce scale if next pages would not be visible after scaling the workspace int workspaceWidth = availableWidthPx; float scaledWorkspaceWidth = workspaceWidth * scale; - float maxAvailableWidth = - workspaceWidth - (2 * getWorkspaceSpringLoadedMinimumNextPageVisible()); + float maxAvailableWidth = workspaceWidth - (2 * workspaceSpringLoadedMinNextPageVisiblePx); if (scaledWorkspaceWidth > maxAvailableWidth) { scale *= maxAvailableWidth / scaledWorkspaceWidth; } @@ -1412,11 +1413,19 @@ public class DeviceProfile { writer.println(prefix + pxToDpStr("dropTargetBarSizePx", dropTargetBarSizePx)); writer.println( prefix + pxToDpStr("dropTargetBarBottomMarginPx", dropTargetBarBottomMarginPx)); + writer.println(prefix + pxToDpStr("dropTargetButtonWorkspaceEdgeGapPx", + dropTargetButtonWorkspaceEdgeGapPx)); + writer.println(prefix + pxToDpStr("dropTargetButtonScreenEdgeGapPx", + dropTargetButtonScreenEdgeGapPx)); writer.println( prefix + pxToDpStr("workspaceSpringLoadShrunkTop", workspaceSpringLoadShrunkTop)); writer.println(prefix + pxToDpStr("workspaceSpringLoadShrunkBottom", workspaceSpringLoadShrunkBottom)); + writer.println(prefix + pxToDpStr("workspaceSpringLoadedBottomSpace", + workspaceSpringLoadedBottomSpace)); + writer.println(prefix + pxToDpStr("workspaceSpringLoadedMinNextPageVisiblePx", + workspaceSpringLoadedMinNextPageVisiblePx)); writer.println( prefix + pxToDpStr("getWorkspaceSpringLoadScale()", getWorkspaceSpringLoadScale())); } diff --git a/src/com/android/launcher3/DropTargetBar.java b/src/com/android/launcher3/DropTargetBar.java index 2e3f26c7d0..6a8ba1bec4 100644 --- a/src/com/android/launcher3/DropTargetBar.java +++ b/src/com/android/launcher3/DropTargetBar.java @@ -39,8 +39,6 @@ import com.android.launcher3.dragndrop.DragController.DragListener; import com.android.launcher3.dragndrop.DragOptions; import com.android.launcher3.testing.TestProtocol; -import java.util.Arrays; - /* * The top bar containing various drop targets: Delete/App Info/Uninstall. */ @@ -53,6 +51,8 @@ public class DropTargetBar extends FrameLayout private final Runnable mFadeAnimationEndRunnable = () -> updateVisibility(DropTargetBar.this); + private final Launcher mLauncher; + @ViewDebug.ExportedProperty(category = "launcher") protected boolean mDeferOnDragEnd; @@ -60,16 +60,19 @@ public class DropTargetBar extends FrameLayout protected boolean mVisible = false; private ButtonDropTarget[] mDropTargets; + private ButtonDropTarget[] mTempTargets; private ViewPropertyAnimator mCurrentAnimation; private boolean mIsVertical = true; public DropTargetBar(Context context, AttributeSet attrs) { super(context, attrs); + mLauncher = Launcher.getLauncher(context); } public DropTargetBar(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); + mLauncher = Launcher.getLauncher(context); } @Override @@ -80,12 +83,13 @@ public class DropTargetBar extends FrameLayout mDropTargets[i] = (ButtonDropTarget) getChildAt(i); mDropTargets[i].setDropTargetBar(this); } + mTempTargets = new ButtonDropTarget[getChildCount()]; } @Override public void setInsets(Rect insets) { FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams(); - DeviceProfile grid = Launcher.getLauncher(getContext()).getDeviceProfile(); + DeviceProfile grid = mLauncher.getDeviceProfile(); mIsVertical = grid.isVerticalBarLayout(); lp.leftMargin = insets.left; @@ -116,10 +120,15 @@ public class DropTargetBar extends FrameLayout lp.height = grid.dropTargetBarSizePx; lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.TOP; + DeviceProfile dp = mLauncher.getDeviceProfile(); + int horizontalPadding = dp.dropTargetHorizontalPaddingPx; + int verticalPadding = dp.dropTargetVerticalPaddingPx; setLayoutParams(lp); for (ButtonDropTarget button : mDropTargets) { button.setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.dropTargetTextSizePx); button.setToolTipLocation(tooltipLocation); + button.setPadding(horizontalPadding, verticalPadding, horizontalPadding, + verticalPadding); } } @@ -135,36 +144,83 @@ public class DropTargetBar extends FrameLayout protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int width = MeasureSpec.getSize(widthMeasureSpec); int height = MeasureSpec.getSize(heightMeasureSpec); + int heightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY); - int visibleCount = getVisibleButtonsCount(); - if (visibleCount > 0) { - int availableWidth = width / visibleCount; - boolean textVisible = true; - boolean textResized = false; - float textSize = mDropTargets[0].getTextSize(); - for (ButtonDropTarget button : mDropTargets) { - if (button.getVisibility() == GONE) { - continue; - } - if (button.isTextTruncated(availableWidth)) { - textSize = Math.min(textSize, button.resizeTextToFit(availableWidth)); - textResized = true; - } - textVisible = textVisible && !button.isTextTruncated(availableWidth); - } + int visibleCount = getVisibleButtons(mTempTargets); + if (visibleCount == 1) { + int widthSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST); - if (textResized) { - for (ButtonDropTarget button : mDropTargets) { - button.setTextSize(textSize); - } - } + ButtonDropTarget firstButton = mTempTargets[0]; + firstButton.setTextVisible(true); + firstButton.setIconVisible(true); + firstButton.measure(widthSpec, heightSpec); + } else if (visibleCount == 2) { + DeviceProfile dp = mLauncher.getDeviceProfile(); + int verticalPadding = dp.dropTargetVerticalPaddingPx; + int horizontalPadding = dp.dropTargetHorizontalPaddingPx; - int widthSpec = MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.AT_MOST); - int heightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY); - for (ButtonDropTarget button : mDropTargets) { - if (button.getVisibility() != GONE) { - button.setTextVisible(textVisible); - button.measure(widthSpec, heightSpec); + ButtonDropTarget firstButton = mTempTargets[0]; + firstButton.setTextVisible(true); + firstButton.setIconVisible(true); + + ButtonDropTarget secondButton = mTempTargets[1]; + secondButton.setTextVisible(true); + secondButton.setIconVisible(true); + secondButton.setTextMultiLine(false); + // Reset second button padding in case it was previously changed to multi-line text. + secondButton.setPadding(horizontalPadding, verticalPadding, horizontalPadding, + verticalPadding); + + if (dp.isTwoPanels) { + // Both buttons for two panel fit to the width of one Cell Layout (less + // half of the center gap between the buttons). + float scale = dp.getWorkspaceSpringLoadScale(); + int scaledPanelWidth = (int) (dp.getCellLayoutWidth() * scale); + int halfButtonGap = dp.dropTargetGapPx / 2; + scaledPanelWidth -= halfButtonGap / 2; + + int widthSpec = MeasureSpec.makeMeasureSpec(scaledPanelWidth, MeasureSpec.AT_MOST); + firstButton.measure(widthSpec, heightSpec); + secondButton.measure(widthSpec, heightSpec); + } else { + int availableWidth; + int buttonGap = dp.dropTargetGapPx; + if (mIsVertical) { + // Both buttons plus the button gap do not display past the edge of the + // scaled workspace, less a pre-defined gap from the edge of the workspace. + float scale = dp.getWorkspaceSpringLoadScale(); + int panelWidth = (int) (dp.getCellLayoutWidth() * scale); + availableWidth = Math.min( + panelWidth - (2 * dp.dropTargetButtonWorkspaceEdgeGapPx), width); + } else { + // Both buttons plus the button gap display up to a pre-defined margin of + // the unscaled workspace edge. + availableWidth = Math.min( + dp.availableWidthPx - (2 * dp.dropTargetButtonScreenEdgeGapPx), + width); + } + int widthSpec = MeasureSpec.makeMeasureSpec(availableWidth - buttonGap, + MeasureSpec.AT_MOST); + + // First button's width is at most the drop target bar's total width less the button + // gap. + firstButton.measure(widthSpec, heightSpec); + + int usedWidth = firstButton.getMeasuredWidth() + buttonGap; + int remainingWidth = availableWidth - usedWidth; + widthSpec = MeasureSpec.makeMeasureSpec(remainingWidth, MeasureSpec.AT_MOST); + secondButton.measure(widthSpec, heightSpec); + + // Remove both icons and put the second button's text on two lines if text is + // truncated on phones. We assume first button's text is never truncated, so it + // remains single-line. + if (secondButton.isTextTruncated(remainingWidth) && !mIsVertical) { + firstButton.setIconVisible(false); + secondButton.setIconVisible(false); + secondButton.setTextMultiLine(true); + secondButton.setPadding(secondButton.getPaddingLeft(), + secondButton.getPaddingTop() / 2, secondButton.getPaddingRight(), + secondButton.getPaddingBottom() / 2); } } } @@ -173,98 +229,79 @@ public class DropTargetBar extends FrameLayout @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - int visibleCount = getVisibleButtonsCount(); + int visibleCount = getVisibleButtons(mTempTargets); if (visibleCount == 0) { return; } - Launcher launcher = Launcher.getLauncher(getContext()); - Workspace workspace = launcher.getWorkspace(); - DeviceProfile dp = launcher.getDeviceProfile(); - int buttonHorizontalPadding = dp.dropTargetHorizontalPaddingPx; - int buttonVerticalPadding = dp.dropTargetVerticalPaddingPx; + DeviceProfile dp = mLauncher.getDeviceProfile(); int barCenter = (right - left) / 2; - - ButtonDropTarget[] visibleButtons = Arrays.stream(mDropTargets) - .filter(b -> b.getVisibility() != GONE) - .toArray(ButtonDropTarget[]::new); - Arrays.stream(visibleButtons).forEach( - b -> b.setPadding(buttonHorizontalPadding, buttonVerticalPadding, - buttonHorizontalPadding, buttonVerticalPadding)); + if (mIsVertical) { + // Center vertical bar over scaled workspace, accounting for hotseat offset. + float scale = dp.getWorkspaceSpringLoadScale(); + Workspace ws = mLauncher.getWorkspace(); + int workspaceCenter = (ws.getLeft() + ws.getRight()) / 2; + int cellLayoutCenter = ((dp.getInsets().left + dp.workspacePadding.left) + (dp.widthPx + - dp.getInsets().right - dp.workspacePadding.right)) / 2; + int cellLayoutCenterOffset = (int) ((cellLayoutCenter - workspaceCenter) * scale); + barCenter = workspaceCenter + cellLayoutCenterOffset; + } if (visibleCount == 1) { - ButtonDropTarget button = visibleButtons[0]; + ButtonDropTarget button = mTempTargets[0]; button.layout(barCenter - (button.getMeasuredWidth() / 2), 0, barCenter + (button.getMeasuredWidth() / 2), button.getMeasuredHeight()); } else if (visibleCount == 2) { int buttonGap = dp.dropTargetGapPx; if (dp.isTwoPanels) { - ButtonDropTarget leftButton = visibleButtons[0]; + ButtonDropTarget leftButton = mTempTargets[0]; leftButton.layout(barCenter - leftButton.getMeasuredWidth() - (buttonGap / 2), 0, barCenter - (buttonGap / 2), leftButton.getMeasuredHeight()); - ButtonDropTarget rightButton = visibleButtons[1]; + ButtonDropTarget rightButton = mTempTargets[1]; rightButton.layout(barCenter + (buttonGap / 2), 0, - barCenter + rightButton.getMeasuredWidth() + (buttonGap / 2), + barCenter + (buttonGap / 2) + rightButton.getMeasuredWidth(), rightButton.getMeasuredHeight()); - } else if (dp.isTablet) { - int numberOfMargins = visibleCount - 1; - int buttonWidths = Arrays.stream(mDropTargets) - .filter(b -> b.getVisibility() != GONE) - .mapToInt(ButtonDropTarget::getMeasuredWidth) - .sum(); - int totalWidth = buttonWidths + (numberOfMargins * buttonGap); - int buttonsStartMargin = barCenter - (totalWidth / 2); - - int start = buttonsStartMargin; - for (ButtonDropTarget button : visibleButtons) { - int margin = (start != buttonsStartMargin) ? buttonGap : 0; - button.layout(start + margin, 0, start + margin + button.getMeasuredWidth(), - button.getMeasuredHeight()); - start += button.getMeasuredWidth() + margin; - } - } else if (mIsVertical) { - // Center buttons over workspace, not screen. - int verticalCenter = (workspace.getRight() - workspace.getLeft()) / 2; - ButtonDropTarget leftButton = visibleButtons[0]; - leftButton.layout(verticalCenter - leftButton.getMeasuredWidth() - (buttonGap / 2), - 0, verticalCenter - (buttonGap / 2), leftButton.getMeasuredHeight()); - - ButtonDropTarget rightButton = visibleButtons[1]; - rightButton.layout(verticalCenter + (buttonGap / 2), 0, - verticalCenter + rightButton.getMeasuredWidth() + (buttonGap / 2), - rightButton.getMeasuredHeight()); - } else if (dp.isPhone) { - // Buttons aligned to outer edges of scaled workspace. - float scale = dp.getWorkspaceSpringLoadScale(); - - int workspaceWidth = (int) (launcher.getWorkspace().getNormalChildWidth() * scale); - int start = barCenter - (workspaceWidth / 2); - int end = barCenter + (workspaceWidth / 2); - - ButtonDropTarget leftButton = visibleButtons[0]; - ButtonDropTarget rightButton = visibleButtons[1]; - - // If the text within the buttons is too long, the buttons can overlap - int overlap = start + leftButton.getMeasuredWidth() + rightButton.getMeasuredWidth() - - end; - if (overlap > 0) { - end += overlap; + } else { + int start; + int end; + if (mIsVertical) { + // Scaled CellLayout width is assumed to not exceed the bounds of left/right. + float scale = dp.getWorkspaceSpringLoadScale(); + int panelWidth = (int) (dp.getCellLayoutWidth() * scale); + start = barCenter - (panelWidth / 2) + dp.dropTargetButtonWorkspaceEdgeGapPx; + end = barCenter + (panelWidth / 2) - dp.dropTargetButtonWorkspaceEdgeGapPx; + } else { + start = Math.max(dp.dropTargetButtonScreenEdgeGapPx, left); + end = Math.min(dp.availableWidthPx - dp.dropTargetButtonScreenEdgeGapPx, right); } - leftButton.layout(start, 0, start + leftButton.getMeasuredWidth(), + ButtonDropTarget leftButton = mTempTargets[0]; + ButtonDropTarget rightButton = mTempTargets[1]; + + int leftButtonWidth = leftButton.getMeasuredWidth(); + int rightButtonWidth = rightButton.getMeasuredWidth(); + int buttonPlusGapWidth = leftButtonWidth + buttonGap + rightButtonWidth; + + int extraSpace = end - start - buttonPlusGapWidth; + start = (start - left) + (extraSpace / 2); + + leftButton.layout(start, 0, start + leftButtonWidth, leftButton.getMeasuredHeight()); - rightButton.layout(end - rightButton.getMeasuredWidth(), 0, end, + + int rightButtonStart = start + leftButtonWidth + buttonGap; + rightButton.layout(rightButtonStart, 0, rightButtonStart + rightButtonWidth, rightButton.getMeasuredHeight()); } } } - private int getVisibleButtonsCount() { + private int getVisibleButtons(ButtonDropTarget[] outVisibleButtons) { int visibleCount = 0; - for (ButtonDropTarget buttons : mDropTargets) { - if (buttons.getVisibility() != GONE) { + for (ButtonDropTarget button : mDropTargets) { + if (button.getVisibility() != GONE) { + outVisibleButtons[visibleCount] = button; visibleCount++; } } diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java index 95a8a2a9c8..4d33eae01c 100644 --- a/src/com/android/launcher3/PagedView.java +++ b/src/com/android/launcher3/PagedView.java @@ -28,6 +28,8 @@ import static com.android.launcher3.touch.PagedOrientationHandler.VIEW_SCROLL_TO import android.animation.LayoutTransition; import android.annotation.SuppressLint; import android.content.Context; +import android.content.res.Configuration; +import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Rect; @@ -78,27 +80,19 @@ public abstract class PagedView extends ViewGrou public static final int INVALID_PAGE = -1; protected static final ComputePageScrollsLogic SIMPLE_SCROLL_LOGIC = (v) -> v.getVisibility() != GONE; - public static final int PAGE_SNAP_ANIMATION_DURATION = 750; - private static final float RETURN_TO_ORIGINAL_PAGE_THRESHOLD = 0.33f; // The page is moved more than halfway, automatically move to the next page on touch up. private static final float SIGNIFICANT_MOVE_THRESHOLD = 0.4f; private static final float MAX_SCROLL_PROGRESS = 1.0f; - // The following constants need to be scaled based on density. The scaled versions will be - // assigned to the corresponding member variables below. - private static final int FLING_THRESHOLD_VELOCITY = 500; - private static final int EASY_FLING_THRESHOLD_VELOCITY = 400; - private static final int MIN_SNAP_VELOCITY = 1500; - private static final int MIN_FLING_VELOCITY = 250; - private boolean mFreeScroll = false; - protected final int mFlingThresholdVelocity; - protected final int mEasyFlingThresholdVelocity; - protected final int mMinFlingVelocity; - protected final int mMinSnapVelocity; + private int mFlingThresholdVelocity; + private int mEasyFlingThresholdVelocity; + private int mMinFlingVelocity; + private int mMinSnapVelocity; + private int mPageSnapAnimationDuration; protected boolean mFirstLayout = true; @@ -192,11 +186,7 @@ public abstract class PagedView extends ViewGrou mPageSlop = configuration.getScaledPagingTouchSlop(); mMaximumVelocity = configuration.getScaledMaximumFlingVelocity(); - float density = getResources().getDisplayMetrics().density; - mFlingThresholdVelocity = (int) (FLING_THRESHOLD_VELOCITY * density); - mEasyFlingThresholdVelocity = (int) (EASY_FLING_THRESHOLD_VELOCITY * density); - mMinFlingVelocity = (int) (MIN_FLING_VELOCITY * density); - mMinSnapVelocity = (int) (MIN_SNAP_VELOCITY * density); + updateVelocityValues(); initEdgeEffect(); setDefaultFocusHighlightEnabled(false); @@ -628,6 +618,22 @@ public abstract class PagedView extends ViewGrou - mInsets.left - mInsets.right; } + private void updateVelocityValues() { + Resources res = getResources(); + mFlingThresholdVelocity = res.getDimensionPixelSize(R.dimen.fling_threshold_velocity); + mEasyFlingThresholdVelocity = + res.getDimensionPixelSize(R.dimen.easy_fling_threshold_velocity); + mMinFlingVelocity = res.getDimensionPixelSize(R.dimen.min_fling_velocity); + mMinSnapVelocity = res.getDimensionPixelSize(R.dimen.min_page_snap_velocity); + mPageSnapAnimationDuration = res.getInteger(R.integer.config_pageSnapAnimationDuration); + } + + @Override + protected void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + updateVelocityValues(); + } + @Override public void requestLayout() { mIsLayoutValid = false; @@ -1616,7 +1622,7 @@ public abstract class PagedView extends ViewGrou } protected void snapToDestination() { - snapToPage(getDestinationPage(), PAGE_SNAP_ANIMATION_DURATION); + snapToPage(getDestinationPage(), mPageSnapAnimationDuration); } // We want the duration of the page snap animation to be influenced by the distance that @@ -1640,7 +1646,7 @@ public abstract class PagedView extends ViewGrou if (Math.abs(velocity) < mMinFlingVelocity) { // If the velocity is low enough, then treat this more as an automatic page advance // as opposed to an apparent physical response to flinging - return snapToPage(whichPage, PAGE_SNAP_ANIMATION_DURATION); + return snapToPage(whichPage, mPageSnapAnimationDuration); } // Here we compute a "distance" that will be used in the computation of the overall @@ -1663,11 +1669,11 @@ public abstract class PagedView extends ViewGrou } public boolean snapToPage(int whichPage) { - return snapToPage(whichPage, PAGE_SNAP_ANIMATION_DURATION); + return snapToPage(whichPage, mPageSnapAnimationDuration); } public boolean snapToPageImmediately(int whichPage) { - return snapToPage(whichPage, PAGE_SNAP_ANIMATION_DURATION, true); + return snapToPage(whichPage, mPageSnapAnimationDuration, true); } public boolean snapToPage(int whichPage, int duration) { diff --git a/src/com/android/launcher3/allapps/BaseAllAppsContainerView.java b/src/com/android/launcher3/allapps/BaseAllAppsContainerView.java index 95c67ddaa8..fc527976ab 100644 --- a/src/com/android/launcher3/allapps/BaseAllAppsContainerView.java +++ b/src/com/android/launcher3/allapps/BaseAllAppsContainerView.java @@ -326,7 +326,7 @@ public abstract class BaseAllAppsContainerView currentWorkspaceItems = new ArrayList<>(); diff --git a/src/com/android/launcher3/model/BgDataModel.java b/src/com/android/launcher3/model/BgDataModel.java index d52537e074..de23c4b31f 100644 --- a/src/com/android/launcher3/model/BgDataModel.java +++ b/src/com/android/launcher3/model/BgDataModel.java @@ -31,6 +31,7 @@ import android.text.TextUtils; import android.util.ArraySet; import android.util.Log; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.launcher3.LauncherSettings; @@ -469,6 +470,7 @@ public class BgDataModel { * or an empty IntSet * @param orderedScreenIds All the page ids to be bound */ + @NonNull default IntSet getPagesToBindSynchronously(IntArray orderedScreenIds) { return new IntSet(); } diff --git a/src/com/android/launcher3/model/ModelUtils.java b/src/com/android/launcher3/model/ModelUtils.java index df6768d582..422af43e19 100644 --- a/src/com/android/launcher3/model/ModelUtils.java +++ b/src/com/android/launcher3/model/ModelUtils.java @@ -51,7 +51,7 @@ public class ModelUtils { * specified screen. */ public static void filterCurrentWorkspaceItems( - IntSet currentScreenIds, + final IntSet currentScreenIds, ArrayList allWorkspaceItems, ArrayList currentScreenItems, ArrayList otherScreenItems) { diff --git a/src/com/android/launcher3/secondarydisplay/SecondaryDragLayer.java b/src/com/android/launcher3/secondarydisplay/SecondaryDragLayer.java index e906c951db..c79d70dacc 100644 --- a/src/com/android/launcher3/secondarydisplay/SecondaryDragLayer.java +++ b/src/com/android/launcher3/secondarydisplay/SecondaryDragLayer.java @@ -120,23 +120,20 @@ public class SecondaryDragLayer extends BaseDragLayer int maxWidth = grid.allAppsCellWidthPx * grid.numShownAllAppsColumns + horizontalPadding; - int appsWidth = Math.min(width, maxWidth); + int appsWidth = Math.min(width - getPaddingLeft() - getPaddingRight(), maxWidth); int maxHeight = grid.allAppsCellHeightPx * grid.numShownAllAppsColumns + verticalPadding; - int appsHeight = Math.min(height, maxHeight); + int appsHeight = Math.min(height - getPaddingTop() - getPaddingBottom(), maxHeight); mAppsView.measure( makeMeasureSpec(appsWidth, EXACTLY), makeMeasureSpec(appsHeight, EXACTLY)); - } else if (child == mAllAppsButton) { int appsButtonSpec = makeMeasureSpec(grid.iconSizePx, EXACTLY); mAllAppsButton.measure(appsButtonSpec, appsButtonSpec); - } else if (child == mWorkspace) { measureChildWithMargins(mWorkspace, widthMeasureSpec, 0, heightMeasureSpec, grid.iconSizePx + grid.edgeMarginPx); - } else { measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0); } diff --git a/src/com/android/launcher3/states/RotationHelper.java b/src/com/android/launcher3/states/RotationHelper.java index 8b425daa5e..38b62d466e 100644 --- a/src/com/android/launcher3/states/RotationHelper.java +++ b/src/com/android/launcher3/states/RotationHelper.java @@ -109,7 +109,7 @@ public class RotationHelper implements OnSharedPreferenceChangeListener, @Override public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String s) { - if (mDestroyed) return; + if (mDestroyed || mIgnoreAutoRotateSettings) return; boolean wasRotationEnabled = mHomeRotationEnabled; mHomeRotationEnabled = mSharedPrefs.getBoolean(ALLOW_ROTATION_PREFERENCE_KEY, getAllowRotationDefaultValue(mActivity.getDeviceProfile())); diff --git a/src/com/android/launcher3/widget/AddItemWidgetsBottomSheet.java b/src/com/android/launcher3/widget/AddItemWidgetsBottomSheet.java index d2d569f79f..9442734646 100644 --- a/src/com/android/launcher3/widget/AddItemWidgetsBottomSheet.java +++ b/src/com/android/launcher3/widget/AddItemWidgetsBottomSheet.java @@ -18,7 +18,6 @@ package com.android.launcher3.widget; import static com.android.launcher3.Utilities.ATLEAST_R; import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN; -import static com.android.launcher3.widget.BaseWidgetSheet.MAX_WIDTH_SCALE_FOR_LARGER_SCREEN; import android.animation.PropertyValuesHolder; import android.annotation.SuppressLint; @@ -106,7 +105,10 @@ public class AddItemWidgetsBottomSheet extends AbstractSlideInView 0) { + if (deviceProfile.isTablet) { + int margin = deviceProfile.allAppsLeftRightMargin; + widthUsed = Math.max(2 * margin, 2 * (mInsets.left + mInsets.right)); + } else if (mInsets.bottom > 0) { widthUsed = mInsets.left + mInsets.right; } else { Rect padding = deviceProfile.workspacePadding; @@ -114,18 +116,8 @@ public class AddItemWidgetsBottomSheet extends AbstractSlideInView PopupDataProvider.PopupDataChangeListener, Insettable { /** The default number of cells that can fit horizontally in a widget sheet. */ protected static final int DEFAULT_MAX_HORIZONTAL_SPANS = 4; - /** - * The maximum scale, [0, 1], of the device screen width that the widgets picker can consume - * on large screen devices. - */ - protected static final float MAX_WIDTH_SCALE_FOR_LARGER_SCREEN = 0.89f; protected static final String KEY_WIDGETS_EDUCATION_TIP_SEEN = "launcher.widgets_education_tip_seen"; @@ -70,10 +69,15 @@ public abstract class BaseWidgetSheet extends AbstractSlideInView private int mContentHorizontalMarginInPx; + protected int mNavBarScrimHeight; + private final Paint mNavBarScrimPaint; + public BaseWidgetSheet(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); mContentHorizontalMarginInPx = getResources().getDimensionPixelSize( R.dimen.widget_list_horizontal_margin); + mNavBarScrimPaint = new Paint(); + mNavBarScrimPaint.setColor(Themes.getAttrColor(context, R.attr.allAppsNavBarScrimColor)); } protected int getScrimColor(Context context) { @@ -83,6 +87,9 @@ public abstract class BaseWidgetSheet extends AbstractSlideInView @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); + WindowInsets windowInsets = WindowManagerProxy.INSTANCE.get(getContext()) + .normalizeWindowInsets(getContext(), getRootWindowInsets(), new Rect()); + mNavBarScrimHeight = getNavBarScrimHeight(windowInsets); mActivityContext.getPopupDataProvider().setChangeListener(this); } @@ -136,6 +143,30 @@ public abstract class BaseWidgetSheet extends AbstractSlideInView } } + private int getNavBarScrimHeight(WindowInsets insets) { + if (Utilities.ATLEAST_Q) { + return insets.getTappableElementInsets().bottom; + } else { + return insets.getStableInsetBottom(); + } + } + + @Override + public WindowInsets onApplyWindowInsets(WindowInsets insets) { + mNavBarScrimHeight = getNavBarScrimHeight(insets); + return super.onApplyWindowInsets(insets); + } + + @Override + protected void dispatchDraw(Canvas canvas) { + super.dispatchDraw(canvas); + + if (mNavBarScrimHeight > 0) { + canvas.drawRect(0, getHeight() - mNavBarScrimHeight, getWidth(), getHeight(), + mNavBarScrimPaint); + } + } + /** Called when the horizontal margin of the content view has changed. */ protected abstract void onContentHorizontalMarginChanged(int contentHorizontalMarginInPx); @@ -147,7 +178,10 @@ public abstract class BaseWidgetSheet extends AbstractSlideInView protected void doMeasure(int widthMeasureSpec, int heightMeasureSpec) { DeviceProfile deviceProfile = mActivityContext.getDeviceProfile(); int widthUsed; - if (mInsets.bottom > 0) { + if (deviceProfile.isTablet) { + int margin = deviceProfile.allAppsLeftRightMargin; + widthUsed = Math.max(2 * margin, 2 * (mInsets.left + mInsets.right)); + } else if (mInsets.bottom > 0) { widthUsed = mInsets.left + mInsets.right; } else { Rect padding = deviceProfile.workspacePadding; @@ -155,15 +189,6 @@ public abstract class BaseWidgetSheet extends AbstractSlideInView 2 * (mInsets.left + mInsets.right)); } - if (deviceProfile.isTablet || deviceProfile.isTwoPanels) { - // In large screen devices, we restrict the width of the widgets picker to show part of - // the home screen. Let's ensure the minimum width used is at least the minimum width - // that isn't taken by the widgets picker. - int minUsedWidth = (int) (deviceProfile.availableWidthPx - * (1 - MAX_WIDTH_SCALE_FOR_LARGER_SCREEN)); - widthUsed = Math.max(widthUsed, minUsedWidth); - } - measureChildWithMargins(mContent, widthMeasureSpec, widthUsed, heightMeasureSpec, deviceProfile.bottomSheetTopPadding); setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), diff --git a/src/com/android/launcher3/widget/WidgetsBottomSheet.java b/src/com/android/launcher3/widget/WidgetsBottomSheet.java index b152ddc2d9..bf521cc7eb 100644 --- a/src/com/android/launcher3/widget/WidgetsBottomSheet.java +++ b/src/com/android/launcher3/widget/WidgetsBottomSheet.java @@ -247,10 +247,12 @@ public class WidgetsBottomSheet extends BaseWidgetSheet { @Override public void setInsets(Rect insets) { super.setInsets(insets); + int bottomPadding = Math.max(insets.bottom, mNavBarScrimHeight); mContent.setPadding(mContent.getPaddingStart(), - mContent.getPaddingTop(), mContent.getPaddingEnd(), insets.bottom); - if (insets.bottom > 0) { + mContent.getPaddingTop(), mContent.getPaddingEnd(), + bottomPadding); + if (bottomPadding > 0) { setupNavBarColor(); } else { clearNavBarColor(); diff --git a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java index 341cb5c93e..a49cdc005a 100644 --- a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java +++ b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java @@ -328,15 +328,15 @@ public class WidgetsFullSheet extends BaseWidgetSheet @Override public void setInsets(Rect insets) { super.setInsets(insets); - - setBottomPadding(mAdapters.get(AdapterHolder.PRIMARY).mWidgetsRecyclerView, insets.bottom); - setBottomPadding(mAdapters.get(AdapterHolder.SEARCH).mWidgetsRecyclerView, insets.bottom); + int bottomPadding = Math.max(insets.bottom, mNavBarScrimHeight); + setBottomPadding(mAdapters.get(AdapterHolder.PRIMARY).mWidgetsRecyclerView, bottomPadding); + setBottomPadding(mAdapters.get(AdapterHolder.SEARCH).mWidgetsRecyclerView, bottomPadding); if (mHasWorkProfile) { - setBottomPadding(mAdapters.get(AdapterHolder.WORK).mWidgetsRecyclerView, insets.bottom); + setBottomPadding(mAdapters.get(AdapterHolder.WORK).mWidgetsRecyclerView, bottomPadding); } - ((MarginLayoutParams) mNoWidgetsView.getLayoutParams()).bottomMargin = insets.bottom; + ((MarginLayoutParams) mNoWidgetsView.getLayoutParams()).bottomMargin = bottomPadding; - if (insets.bottom > 0) { + if (bottomPadding > 0) { setupNavBarColor(); } else { clearNavBarColor(); diff --git a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java index 15e8f681e6..a3b05f6868 100644 --- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java +++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java @@ -48,11 +48,13 @@ import com.android.launcher3.tapl.HomeAppIconMenuItem; import com.android.launcher3.tapl.Widgets; import com.android.launcher3.tapl.Workspace; import com.android.launcher3.util.TestUtil; +import com.android.launcher3.util.rule.ScreenRecordRule; import com.android.launcher3.util.rule.ScreenRecordRule.ScreenRecord; import com.android.launcher3.widget.picker.WidgetsFullSheet; import com.android.launcher3.widget.picker.WidgetsRecyclerView; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -66,6 +68,10 @@ public class TaplTestsLauncher3 extends AbstractLauncherUiTest { private static final String DUMMY_APP_NAME = "Aardwolf"; private static final String MAPS_APP_NAME = "Maps"; private static final String STORE_APP_NAME = "Play Store"; + private static final String GMAIL_APP_NAME = "Gmail"; + + @Rule + public ScreenRecordRule mScreenRecordRule = new ScreenRecordRule(); @Before public void setUp() throws Exception { @@ -374,28 +380,23 @@ public class TaplTestsLauncher3 extends AbstractLauncherUiTest { @Test @PortraitLandscape - public void testDragToFolder() throws Exception { - final HomeAppIcon playStoreIcon = createShortcutIfNotExist("Play Store", 0, 1); - final HomeAppIcon gmailIcon = createShortcutIfNotExist("Gmail", 1, 1); + @ScreenRecord + public void testDragToFolder() { + // TODO: add the use case to drag an icon to an existing folder. Currently it either fails + // on tablets or phones due to difference in resolution. + final HomeAppIcon playStoreIcon = createShortcutIfNotExist(STORE_APP_NAME, 0, 1); + final HomeAppIcon gmailIcon = createShortcutInCenterIfNotExist(GMAIL_APP_NAME); FolderIcon folderIcon = gmailIcon.dragToIcon(playStoreIcon); - Folder folder = folderIcon.open(); - folder.getAppIcon("Play Store"); - folder.getAppIcon("Gmail"); + folder.getAppIcon(STORE_APP_NAME); + folder.getAppIcon(GMAIL_APP_NAME); Workspace workspace = folder.close(); - assertNull("Gmail should be moved to a folder.", - workspace.tryGetWorkspaceAppIcon("Gmail")); - assertNull("Play Store should be moved to a folder.", - workspace.tryGetWorkspaceAppIcon("Play Store")); - - final HomeAppIcon youTubeIcon = createShortcutInCenterIfNotExist("YouTube"); - - folderIcon = youTubeIcon.dragToIcon(folderIcon); - folder = folderIcon.open(); - folder.getAppIcon("YouTube"); - folder.close(); + assertNull(STORE_APP_NAME + " should be moved to a folder.", + workspace.tryGetWorkspaceAppIcon(STORE_APP_NAME)); + assertNull(GMAIL_APP_NAME + " should be moved to a folder.", + workspace.tryGetWorkspaceAppIcon(GMAIL_APP_NAME)); } @Test diff --git a/tests/tapl/com/android/launcher3/tapl/Launchable.java b/tests/tapl/com/android/launcher3/tapl/Launchable.java index 252627238b..91ab1bddb0 100644 --- a/tests/tapl/com/android/launcher3/tapl/Launchable.java +++ b/tests/tapl/com/android/launcher3/tapl/Launchable.java @@ -105,7 +105,6 @@ public abstract class Launchable { expectLongClickEvents); } - return dragStartCenter; } diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java index 6fcd7d8de6..9d25b1ba90 100644 --- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java +++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java @@ -1552,11 +1552,11 @@ public final class LauncherInstrumentation { // vx0: initial speed at the x-dimension, set as twice the avg speed // dx: the constant deceleration at the x-dimension - double vx0 = 2 * (to.x - from.x) / duration; + double vx0 = 2.0 * (to.x - from.x) / duration; double dx = vx0 / duration; // vy0: initial speed at the y-dimension, set as twice the avg speed // dy: the constant deceleration at the y-dimension - double vy0 = 2 * (to.y - from.y) / duration; + double vy0 = 2.0 * (to.y - from.y) / duration; double dy = vy0 / duration; for (long i = 0; i < steps; ++i) {