Updating taskbar icon alignment state

Icon alignment is only tied to Launcher paused/resumed state
Creating two separate states for this:
  1) Launcher paused/resumed
  2) Active gesture interaction (live-titles can affect paused state)

Removing state handler dependency on taskbar visibility

Bug: 190170303
Bug: 187353581
Bug: 187919439

Test: Manual
Change-Id: Ia97cdf43cec1d9213f5dc2af8d66258b34c57514
This commit is contained in:
Sunny Goyal 2021-06-15 14:49:28 -07:00
parent e7cf240e0c
commit 5cf86b263e
9 changed files with 177 additions and 143 deletions

View File

@ -15,27 +15,29 @@
*/
package com.android.launcher3.taskbar;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.LauncherState.HOTSEAT_ICONS;
import static com.android.launcher3.taskbar.TaskbarViewController.ALPHA_INDEX_HOME;
import static com.android.launcher3.taskbar.TaskbarViewController.ALPHA_INDEX_LAUNCHER_STATE;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.graphics.Rect;
import android.view.MotionEvent;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.launcher3.BaseQuickstepLauncher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.QuickstepTransitionManager;
import com.android.launcher3.R;
import com.android.launcher3.anim.AnimatorListeners;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.util.MultiValueAlpha;
import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
import com.android.quickstep.AnimatedFloat;
import com.android.quickstep.RecentsAnimationCallbacks;
import com.android.quickstep.RecentsAnimationCallbacks.RecentsAnimationListener;
import com.android.quickstep.RecentsAnimationController;
import com.android.systemui.shared.recents.model.ThumbnailData;
/**
@ -51,12 +53,19 @@ public class LauncherTaskbarUIController extends TaskbarUIController {
final TaskbarDragLayer mTaskbarDragLayer;
final TaskbarView mTaskbarView;
private final AnimatedFloat mIconAlignmentForResumedState =
new AnimatedFloat(this::onIconAlignmentRatioChanged);
private final AnimatedFloat mIconAlignmentForGestureState =
new AnimatedFloat(this::onIconAlignmentRatioChanged);
private AnimatedFloat mTaskbarBackgroundAlpha;
private AlphaProperty mIconAlphaForHome;
private @Nullable Animator mAnimator;
private boolean mIsAnimatingToLauncher;
private TaskbarKeyguardController mKeyguardController;
private LauncherState mTargetStateOverride = null;
private TaskbarControllers mControllers;
public LauncherTaskbarUIController(
BaseQuickstepLauncher launcher, TaskbarActivityContext context) {
mContext = context;
@ -67,7 +76,6 @@ public class LauncherTaskbarUIController extends TaskbarUIController {
mTaskbarStateHandler = mLauncher.getTaskbarStateHandler();
mHotseatController = new TaskbarHotseatController(
mLauncher, mTaskbarView::updateHotseatItems);
}
@Override
@ -77,21 +85,17 @@ public class LauncherTaskbarUIController extends TaskbarUIController {
MultiValueAlpha taskbarIconAlpha = taskbarControllers.taskbarViewController
.getTaskbarIconAlpha();
mIconAlphaForHome = taskbarIconAlpha.getProperty(ALPHA_INDEX_HOME);
mTaskbarStateHandler.setAnimationController(taskbarIconAlpha.getProperty(
ALPHA_INDEX_LAUNCHER_STATE));
mControllers = taskbarControllers;
mHotseatController.init();
setTaskbarViewVisible(!mLauncher.hasBeenResumed());
mLauncher.setTaskbarUIController(this);
mKeyguardController = taskbarControllers.taskbarKeyguardController;
onLauncherResumedOrPaused(mLauncher.hasBeenResumed());
mIconAlignmentForResumedState.finishAnimation();
}
@Override
protected void onDestroy() {
if (mAnimator != null) {
// End this first, in case it relies on properties that are about to be cleaned up.
mAnimator.end();
}
mTaskbarStateHandler.setAnimationController(null);
mHotseatController.cleanup();
setTaskbarViewVisible(true);
mLauncher.getHotseat().setIconsAlpha(1f);
@ -100,7 +104,7 @@ public class LauncherTaskbarUIController extends TaskbarUIController {
@Override
protected boolean isTaskbarTouchable() {
return !mIsAnimatingToLauncher;
return !mIsAnimatingToLauncher && mTargetStateOverride == null;
}
@Override
@ -128,63 +132,82 @@ public class LauncherTaskbarUIController extends TaskbarUIController {
}
}
long duration = QuickstepTransitionManager.CONTENT_ALPHA_DURATION;
if (mAnimator != null) {
mAnimator.cancel();
}
if (isResumed) {
mAnimator = createAnimToLauncher(mLauncher.getStateManager().getState(), duration);
} else {
mAnimator = createAnimToApp(duration);
}
mAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mAnimator = null;
}
});
mAnimator.start();
ObjectAnimator anim = mIconAlignmentForResumedState.animateToValue(
getCurrentIconAlignmentRatio(), isResumed ? 1 : 0)
.setDuration(QuickstepTransitionManager.CONTENT_ALPHA_DURATION);
anim.addListener(AnimatorListeners.forEndCallback(() -> mIsAnimatingToLauncher = false));
anim.start();
mIsAnimatingToLauncher = isResumed;
}
/**
* Create Taskbar animation when going from an app to Launcher.
* Create Taskbar animation when going from an app to Launcher as part of recents transition.
* @param toState If known, the state we will end up in when reaching Launcher.
* TODO: Move this and createAnimToApp to TaskbarStateHandler using the BACKGROUND state
* @param callbacks callbacks to track the recents animation lifecycle. The state change is
* automatically reset once the recents animation finishes
*/
public Animator createAnimToLauncher(@NonNull LauncherState toState, long duration) {
PendingAnimation anim = new PendingAnimation(duration);
mTaskbarStateHandler.setState(toState, anim);
anim.setFloat(mTaskbarBackgroundAlpha, AnimatedFloat.VALUE, 0, LINEAR);
mTaskbarView.alignIconsWithLauncher(mLauncher.getDeviceProfile(), anim);
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
mIsAnimatingToLauncher = true;
}
public Animator createAnimToLauncher(@NonNull LauncherState toState,
@NonNull RecentsAnimationCallbacks callbacks,
long duration) {
ObjectAnimator animator = mIconAlignmentForGestureState
.animateToValue(mIconAlignmentForGestureState.value, 1)
.setDuration(duration);
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mIsAnimatingToLauncher = false;
setTaskbarViewVisible(false);
mTargetStateOverride = null;
}
});
return anim.buildAnim();
}
private Animator createAnimToApp(long duration) {
PendingAnimation anim = new PendingAnimation(duration);
anim.setFloat(mTaskbarBackgroundAlpha, AnimatedFloat.VALUE, 1, LINEAR);
anim.addListener(AnimatorListeners.forEndCallback(mTaskbarView.resetIconPosition(anim)));
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
setTaskbarViewVisible(true);
mTargetStateOverride = toState;
}
});
return anim.buildAnim();
callbacks.addListener(new RecentsAnimationListener() {
@Override
public void onRecentsAnimationCanceled(ThumbnailData thumbnailData) {
endGestureStateOverride();
}
@Override
public void onRecentsAnimationFinished(RecentsAnimationController controller) {
endGestureStateOverride();
}
private void endGestureStateOverride() {
callbacks.removeListener(this);
mIconAlignmentForGestureState
.animateToValue(mIconAlignmentForGestureState.value, 0)
.start();
}
});
return animator;
}
private float getCurrentIconAlignmentRatio() {
return Math.max(mIconAlignmentForResumedState.value, mIconAlignmentForGestureState.value);
}
private void onIconAlignmentRatioChanged() {
if (mControllers == null) {
return;
}
float alignment = getCurrentIconAlignmentRatio();
mControllers.taskbarViewController.setLauncherIconAlignment(
alignment, mLauncher.getDeviceProfile());
mTaskbarBackgroundAlpha.updateValue(1 - alignment);
LauncherState state = mTargetStateOverride != null ? mTargetStateOverride
: mLauncher.getStateManager().getState();
if ((state.getVisibleElements(mLauncher) & HOTSEAT_ICONS) != 0) {
// If the hotseat icons are visible, then switch taskbar in last frame
setTaskbarViewVisible(alignment < 1);
} else {
mLauncher.getHotseat().setIconsAlpha(1);
mIconAlphaForHome.setValue(1 - alignment);
}
}
/**

View File

@ -17,7 +17,6 @@ package com.android.launcher3.taskbar;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
import static com.android.systemui.shared.system.WindowManagerWrapper.ITYPE_BOTTOM_TAPPABLE_ELEMENT;
@ -93,6 +92,7 @@ public class TaskbarActivityContext extends ContextThemeWrapper implements Activ
private final ViewCache mViewCache = new ViewCache();
private final boolean mIsSafeModeEnabled;
private boolean mIsDestroyed = false;
public TaskbarActivityContext(Context windowContext, DeviceProfile dp,
TaskbarNavButtonController buttonController) {
@ -208,6 +208,7 @@ public class TaskbarActivityContext extends ContextThemeWrapper implements Activ
* Called when this instance of taskbar is no longer needed
*/
public void onDestroy() {
mIsDestroyed = true;
setUIController(TaskbarUIController.DEFAULT);
mControllers.onDestroy();
mWindowManager.removeViewImmediate(mDragLayer);
@ -252,7 +253,7 @@ public class TaskbarActivityContext extends ContextThemeWrapper implements Activ
* Updates the TaskbarContainer height (pass deviceProfile.taskbarSize to reset).
*/
public void setTaskbarWindowHeight(int height) {
if (mWindowLayoutParams.height == height) {
if (mWindowLayoutParams.height == height || mIsDestroyed) {
return;
}
if (height != MATCH_PARENT) {

View File

@ -18,45 +18,33 @@ package com.android.launcher3.taskbar;
import static com.android.launcher3.LauncherState.TASKBAR;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import androidx.annotation.Nullable;
import com.android.launcher3.BaseQuickstepLauncher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.anim.PropertySetter;
import com.android.launcher3.statemanager.StateManager;
import com.android.launcher3.states.StateAnimationConfig;
import com.android.launcher3.util.MultiValueAlpha;
import com.android.quickstep.AnimatedFloat;
import com.android.quickstep.SystemUiProxy;
/**
* StateHandler to animate Taskbar according to Launcher's state machine. Does nothing if Taskbar
* isn't present (i.e. {@link #setAnimationController} is never called).
* StateHandler to animate Taskbar according to Launcher's state machine.
*/
public class TaskbarStateHandler implements StateManager.StateHandler<LauncherState> {
private final BaseQuickstepLauncher mLauncher;
// Contains Taskbar-related properties we should aniamte. If null, don't do anything.
private @Nullable MultiValueAlpha.AlphaProperty mTaskbarAlpha = null;
private AnimatedFloat mNavbarButtonAlpha = new AnimatedFloat(this::updateNavbarButtonAlpha);
public TaskbarStateHandler(BaseQuickstepLauncher launcher) {
mLauncher = launcher;
}
public void setAnimationController(MultiValueAlpha.AlphaProperty taskbarAlpha) {
mTaskbarAlpha = taskbarAlpha;
// Reapply state.
setState(mLauncher.getStateManager().getState());
updateNavbarButtonAlpha();
}
@Override
public void setState(LauncherState state) {
setState(state, PropertySetter.NO_ANIM_PROPERTY_SETTER);
// Force update the alpha in case it was not initialized properly
updateNavbarButtonAlpha();
}
@Override
@ -69,12 +57,7 @@ public class TaskbarStateHandler implements StateManager.StateHandler<LauncherSt
* Sets the provided state
*/
public void setState(LauncherState toState, PropertySetter setter) {
if (mTaskbarAlpha == null) {
return;
}
boolean isTaskbarVisible = (toState.getVisibleElements(mLauncher) & TASKBAR) != 0;
setter.setFloat(mTaskbarAlpha, MultiValueAlpha.VALUE, isTaskbarVisible ? 1f : 0f, LINEAR);
// Make the nav bar visible in states that taskbar isn't visible.
// TODO: We should draw our own handle instead of showing the nav bar.
float navbarButtonAlpha = isTaskbarVisible ? 0f : 1f;

View File

@ -15,11 +15,6 @@
*/
package com.android.launcher3.taskbar;
import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
@ -34,10 +29,8 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Insettable;
import com.android.launcher3.R;
import com.android.launcher3.anim.PropertySetter;
import com.android.launcher3.folder.FolderIcon;
import com.android.launcher3.model.data.FolderInfo;
import com.android.launcher3.model.data.ItemInfo;
@ -105,51 +98,6 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
mIconLongClickListener = mControllerCallbacks.getOnLongClickListener();
}
/**
* Aligns the icons in the taskbar to that of Launcher.
*/
public void alignIconsWithLauncher(DeviceProfile launcherDp, PropertySetter setter) {
Rect hotseatPadding = launcherDp.getHotseatLayoutPadding(getContext());
float scaleUp = ((float) launcherDp.iconSizePx)
/ mActivityContext.getDeviceProfile().iconSizePx;
int hotseatCellSize =
(launcherDp.availableWidthPx - hotseatPadding.left - hotseatPadding.right)
/ launcherDp.numShownHotseatIcons;
int offsetY = launcherDp.getTaskbarOffsetY();
setter.setFloat(this, VIEW_TRANSLATE_Y, -offsetY, LINEAR);
mActivityContext.setTaskbarWindowHeight(
mActivityContext.getDeviceProfile().taskbarSize + offsetY);
int count = getChildCount();
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
ItemInfo info = (ItemInfo) child.getTag();
setter.setFloat(child, SCALE_PROPERTY, scaleUp, LINEAR);
float childCenter = (child.getLeft() + child.getRight()) / 2;
float hotseatIconCenter = hotseatPadding.left + hotseatCellSize * info.screenId
+ hotseatCellSize / 2;
setter.setFloat(child, VIEW_TRANSLATE_X, hotseatIconCenter - childCenter, LINEAR);
}
}
/**
* Aligns the icons in the taskbar to that of Launcher.
* @return a callback to be executed at the end of the setter
*/
public Runnable resetIconPosition(PropertySetter setter) {
int count = getChildCount();
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
setter.setFloat(child, SCALE_PROPERTY, 1, LINEAR);
setter.setFloat(child, VIEW_TRANSLATE_X, 0, LINEAR);
}
setter.setFloat(this, VIEW_TRANSLATE_Y, 0, LINEAR);
return () -> mActivityContext.setTaskbarWindowHeight(
mActivityContext.getDeviceProfile().taskbarSize);
}
private void removeAndRecycle(View view) {
removeView(view);
view.setOnClickListener(null);
@ -195,6 +143,7 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
// so if the info changes we need to reinflate. This should only happen if a new
// folder is dragged to the position that another folder previously existed.
removeAndRecycle(hotseatView);
hotseatView = null;
} else {
// View found
break;

View File

@ -15,19 +15,29 @@
*/
package com.android.launcher3.taskbar;
import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import android.graphics.Rect;
import android.view.View;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.util.MultiValueAlpha;
/**
* Handles properties/data collection, then passes the results to TaskbarView to render.
*/
public class TaskbarViewController {
private static final Runnable NO_OP = () -> { };
public static final int ALPHA_INDEX_HOME = 0;
public static final int ALPHA_INDEX_LAUNCHER_STATE = 1;
public static final int ALPHA_INDEX_IME = 2;
public static final int ALPHA_INDEX_KEYGUARD = 3;
public static final int ALPHA_INDEX_IME = 1;
public static final int ALPHA_INDEX_KEYGUARD = 2;
private final TaskbarActivityContext mActivity;
private final TaskbarView mTaskbarView;
@ -36,10 +46,15 @@ public class TaskbarViewController {
// Initialized in init.
private TaskbarControllers mControllers;
// Animation to align icons with Launcher, created lazily. This allows the controller to be
// active only during the animation and does not need to worry about layout changes.
private AnimatorPlaybackController mIconAlignControllerLazy = null;
private Runnable mOnControllerPreCreateCallback = NO_OP;
public TaskbarViewController(TaskbarActivityContext activity, TaskbarView taskbarView) {
mActivity = activity;
mTaskbarView = taskbarView;
mTaskbarIconAlpha = new MultiValueAlpha(mTaskbarView, 4);
mTaskbarIconAlpha = new MultiValueAlpha(mTaskbarView, 3);
mTaskbarIconAlpha.setUpdateVisibility(true);
}
@ -71,6 +86,60 @@ public class TaskbarViewController {
mTaskbarView.setClickAndLongClickListenersForIcon(icon);
}
/**
* Sets the taskbar icon alignment relative to Launcher hotseat icons
* @param alignmentRatio [0, 1]
* 0 => not aligned
* 1 => fully aligned
*/
public void setLauncherIconAlignment(float alignmentRatio, DeviceProfile launcherDp) {
if (mIconAlignControllerLazy == null) {
mIconAlignControllerLazy = createIconAlignmentController(launcherDp);
}
mIconAlignControllerLazy.setPlayFraction(alignmentRatio);
if (alignmentRatio <= 0 || alignmentRatio >= 1) {
// Cleanup lazy controller so that it is created again in next animation
mIconAlignControllerLazy = null;
}
}
/**
* Creates an animation for aligning the taskbar icons with the provided Launcher device profile
*/
private AnimatorPlaybackController createIconAlignmentController(DeviceProfile launcherDp) {
mOnControllerPreCreateCallback.run();
PendingAnimation setter = new PendingAnimation(100);
Rect hotseatPadding = launcherDp.getHotseatLayoutPadding(mActivity);
float scaleUp = ((float) launcherDp.iconSizePx) / mActivity.getDeviceProfile().iconSizePx;
int hotseatCellSize =
(launcherDp.availableWidthPx - hotseatPadding.left - hotseatPadding.right)
/ launcherDp.numShownHotseatIcons;
int offsetY = launcherDp.getTaskbarOffsetY();
setter.setFloat(mTaskbarView, VIEW_TRANSLATE_Y, -offsetY, LINEAR);
int collapsedHeight = mActivity.getDeviceProfile().taskbarSize;
int expandedHeight = collapsedHeight + offsetY;
setter.addOnFrameListener(anim -> mActivity.setTaskbarWindowHeight(
anim.getAnimatedFraction() > 0 ? expandedHeight : collapsedHeight));
int count = mTaskbarView.getChildCount();
for (int i = 0; i < count; i++) {
View child = mTaskbarView.getChildAt(i);
ItemInfo info = (ItemInfo) child.getTag();
setter.setFloat(child, SCALE_PROPERTY, scaleUp, LINEAR);
float childCenter = (child.getLeft() + child.getRight()) / 2;
float hotseatIconCenter = hotseatPadding.left + hotseatCellSize * info.screenId
+ hotseatCellSize / 2;
setter.setFloat(child, VIEW_TRANSLATE_X, hotseatIconCenter - childCenter, LINEAR);
}
AnimatorPlaybackController controller = setter.createPlaybackController();
mOnControllerPreCreateCallback = () -> controller.setPlayFraction(0);
return controller;
}
/**
* Callbacks for {@link TaskbarView} to interact with its controller.
*/

View File

@ -1107,7 +1107,8 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
mActivityRestartListener);
mParallelRunningAnim = mActivityInterface.getParallelAnimationToLauncher(
mGestureState.getEndTarget(), duration);
mGestureState.getEndTarget(), duration,
mTaskAnimationManager.getCurrentCallbacks());
if (mParallelRunningAnim != null) {
mParallelRunningAnim.start();
}

View File

@ -346,7 +346,8 @@ public abstract class BaseActivityInterface<STATE_TYPE extends BaseState<STATE_T
* an optional additional animation with the same duration.
*/
public @Nullable Animator getParallelAnimationToLauncher(
GestureState.GestureEndTarget endTarget, long duration) {
GestureState.GestureEndTarget endTarget, long duration,
RecentsAnimationCallbacks callbacks) {
if (endTarget == RECENTS) {
ACTIVITY_TYPE activity = getCreatedActivity();
if (activity == null) {

View File

@ -284,14 +284,15 @@ public final class LauncherActivityInterface extends
@Override
public @Nullable Animator getParallelAnimationToLauncher(GestureEndTarget endTarget,
long duration) {
long duration, RecentsAnimationCallbacks callbacks) {
LauncherTaskbarUIController uiController = getTaskbarController();
Animator superAnimator = super.getParallelAnimationToLauncher(endTarget, duration);
if (uiController == null) {
Animator superAnimator = super.getParallelAnimationToLauncher(
endTarget, duration, callbacks);
if (uiController == null || callbacks == null) {
return superAnimator;
}
LauncherState toState = stateFromGestureEndTarget(endTarget);
Animator taskbarAnimator = uiController.createAnimToLauncher(toState, duration);
Animator taskbarAnimator = uiController.createAnimToLauncher(toState, callbacks, duration);
if (superAnimator == null) {
return taskbarAnimator;
} else {

View File

@ -28,6 +28,7 @@ import android.os.Bundle;
import android.os.SystemProperties;
import android.util.Log;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import com.android.launcher3.Utilities;
@ -261,6 +262,11 @@ public class TaskAnimationManager implements RecentsAnimationCallbacks.RecentsAn
mLastAppearedTaskTarget = null;
}
@Nullable
public RecentsAnimationCallbacks getCurrentCallbacks() {
return mCallbacks;
}
public void dump() {
// TODO
}