From ae72b46b2cc4b92fd341698b0e695d903379b7e3 Mon Sep 17 00:00:00 2001 From: Tony Wickham Date: Wed, 10 Mar 2021 16:18:40 -0800 Subject: [PATCH] Replace taskbar hotseat with real hotseat when folder is open - Seamlessly show real hotseat and hide taskbar hotseat, while keeping rest of taskbar visible - Update MultiValueAlpha to allow for taking max alpha instead of blending, and use that for Hotseat - Fix folder open bounds on home screen when taskbar is present Test: Open folder from taskbar on home, can drag out items Bug: 182079330 Bug: 171917176 Change-Id: I7c1983e3219b1341cf233260f0ccac9051c4dc14 --- quickstep/res/layout/taskbar.xml | 3 +- .../launcher3/BaseQuickstepLauncher.java | 4 + .../launcher3/taskbar/TaskbarController.java | 85 +++++++++++++++---- .../launcher3/taskbar/TaskbarView.java | 22 ++++- .../launcher3/AbstractFloatingView.java | 3 + src/com/android/launcher3/DeviceProfile.java | 3 +- src/com/android/launcher3/Hotseat.java | 17 ++++ .../WorkspaceStateTransitionAnimation.java | 5 +- .../launcher3/util/MultiValueAlpha.java | 43 ++++++++-- 9 files changed, 158 insertions(+), 27 deletions(-) diff --git a/quickstep/res/layout/taskbar.xml b/quickstep/res/layout/taskbar.xml index b124b33290..84e23043b0 100644 --- a/quickstep/res/layout/taskbar.xml +++ b/quickstep/res/layout/taskbar.xml @@ -25,7 +25,6 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@color/taskbar_background" - android:gravity="center" - android:animateLayoutChanges="true"/> + android:gravity="center"/> \ No newline at end of file diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java index 641e108be5..ff47eae20c 100644 --- a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java +++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java @@ -315,6 +315,10 @@ public abstract class BaseQuickstepLauncher extends Launcher @Override public void onDragLayerHierarchyChanged() { onLauncherStateOrFocusChanged(); + + if (mTaskbarController != null) { + mTaskbarController.onLauncherDragLayerHierarchyChanged(); + } } @Override diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java index 544835c317..d96cf7c854 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java @@ -19,6 +19,8 @@ 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 com.android.launcher3.AbstractFloatingView.TYPE_ALL; +import static com.android.launcher3.AbstractFloatingView.TYPE_REPLACE_TASKBAR_WITH_HOTSEAT; import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY; import static com.android.launcher3.anim.Interpolators.LINEAR; import static com.android.systemui.shared.system.WindowManagerWrapper.ITYPE_BOTTOM_TAPPABLE_ELEMENT; @@ -40,6 +42,7 @@ import androidx.annotation.Nullable; import com.android.launcher3.AbstractFloatingView; import com.android.launcher3.BaseQuickstepLauncher; +import com.android.launcher3.Hotseat; import com.android.launcher3.LauncherState; import com.android.launcher3.QuickstepAppTransitionManagerImpl; import com.android.launcher3.R; @@ -144,22 +147,13 @@ public class TaskbarController { ActivityManagerWrapper.getInstance().startActivityFromRecents(task.key, ActivityOptions.makeBasic()); } else if (tag instanceof FolderInfo) { - FolderIcon folderIcon = (FolderIcon) view; - Folder folder = folderIcon.getFolder(); - - setTaskbarWindowFullscreen(true); - - mTaskbarContainerView.post(() -> { - folder.animateOpen(); - - folder.iterateOverItems((itemInfo, itemView) -> { - itemView.setOnClickListener(getItemOnClickListener()); - itemView.setOnLongClickListener(getItemOnLongClickListener()); - // To play haptic when dragging, like other Taskbar items do. - itemView.setHapticFeedbackEnabled(true); - return false; - }); - }); + if (mLauncher.hasBeenResumed()) { + FolderInfo folderInfo = (FolderInfo) tag; + onClickedOnFolderFromHome(folderInfo); + } else { + FolderIcon folderIcon = (FolderIcon) view; + onClickedOnFolderInApp(folderIcon); + } } else { ItemClickHandler.INSTANCE.onClick(view); } @@ -169,6 +163,34 @@ public class TaskbarController { }; } + // Open the real folder in Launcher. + private void onClickedOnFolderFromHome(FolderInfo folderInfo) { + alignRealHotseatWithTaskbar(); + + FolderIcon folderIcon = (FolderIcon) mLauncher.getHotseat() + .getFirstItemMatch((info, v) -> info == folderInfo); + folderIcon.post(folderIcon::performClick); + } + + // Open the Taskbar folder, and handle clicks on folder items. + private void onClickedOnFolderInApp(FolderIcon folderIcon) { + Folder folder = folderIcon.getFolder(); + + setTaskbarWindowFullscreen(true); + + mTaskbarContainerView.post(() -> { + folder.animateOpen(); + + folder.iterateOverItems((itemInfo, itemView) -> { + itemView.setOnClickListener(getItemOnClickListener()); + itemView.setOnLongClickListener(getItemOnLongClickListener()); + // To play haptic when dragging, like other Taskbar items do. + itemView.setHapticFeedbackEnabled(true); + return false; + }); + }); + } + @Override public View.OnLongClickListener getItemOnLongClickListener() { return view -> { @@ -306,6 +328,7 @@ public class TaskbarController { mAnimator = createAnimToLauncher(null, duration); } else { mAnimator = createAnimToApp(duration); + replaceTaskbarWithHotseatOrViceVersa(); } mAnimator.addListener(new AnimatorListenerAdapter() { @Override @@ -355,6 +378,7 @@ public class TaskbarController { @Override public void onAnimationStart(Animator animation) { mTaskbarView.updateHotseatItemsVisibility(); + setReplaceTaskbarWithHotseat(false); } }); return anim.buildAnim(); @@ -451,6 +475,35 @@ public class TaskbarController { mTaskbarView.getHeight() - hotseatBounds.bottom); } + /** + * A view was added or removed from DragLayer, check if we need to hide our hotseat copy and + * show the real one instead. + */ + public void onLauncherDragLayerHierarchyChanged() { + replaceTaskbarWithHotseatOrViceVersa(); + } + + private void replaceTaskbarWithHotseatOrViceVersa() { + boolean replaceTaskbarWithHotseat = AbstractFloatingView.getTopOpenViewWithType(mLauncher, + TYPE_ALL & TYPE_REPLACE_TASKBAR_WITH_HOTSEAT) != null; + if (!mLauncher.hasBeenResumed()) { + replaceTaskbarWithHotseat = false; + } + setReplaceTaskbarWithHotseat(replaceTaskbarWithHotseat); + } + + private void setReplaceTaskbarWithHotseat(boolean replaceTaskbarWithHotseat) { + Hotseat hotseat = mLauncher.getHotseat(); + if (replaceTaskbarWithHotseat) { + alignRealHotseatWithTaskbar(); + hotseat.getReplaceTaskbarAlpha().setValue(1f); + mTaskbarView.setHotseatViewsHidden(true); + } else { + hotseat.getReplaceTaskbarAlpha().setValue(0f); + mTaskbarView.setHotseatViewsHidden(false); + } + } + private float getTaskbarScaleOnHome() { return 1f / mTaskbarContainerView.getTaskbarActivityContext().getTaskbarIconScale(); } diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java index a729e7799f..1d762e9696 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java @@ -15,6 +15,7 @@ */ package com.android.launcher3.taskbar; +import android.animation.LayoutTransition; import android.content.Context; import android.content.res.Resources; import android.graphics.Canvas; @@ -63,6 +64,7 @@ public class TaskbarView extends LinearLayout implements FolderIcon.FolderIconPa private TaskbarController.TaskbarViewCallbacks mControllerCallbacks; // Initialized in init(). + private LayoutTransition mLayoutTransition; private int mHotseatStartIndex; private int mHotseatEndIndex; private View mHotseatRecentsDivider; @@ -76,6 +78,7 @@ public class TaskbarView extends LinearLayout implements FolderIcon.FolderIconPa private boolean mIsDraggingItem; // Only non-null when the corresponding Folder is open. private @Nullable FolderIcon mLeaveBehindFolderIcon; + private boolean mIsHotseatHidden; public TaskbarView(@NonNull Context context) { this(context, null); @@ -107,6 +110,9 @@ public class TaskbarView extends LinearLayout implements FolderIcon.FolderIconPa } protected void init(int numHotseatIcons, int numRecentIcons) { + mLayoutTransition = new LayoutTransition(); + setLayoutTransitionsEnabled(true); + mHotseatStartIndex = 0; mHotseatEndIndex = mHotseatStartIndex + numHotseatIcons - 1; updateHotseatItems(new ItemInfo[numHotseatIcons]); @@ -119,6 +125,10 @@ public class TaskbarView extends LinearLayout implements FolderIcon.FolderIconPa updateRecentTasks(new Task[numRecentIcons]); } + private void setLayoutTransitionsEnabled(boolean enabled) { + setLayoutTransition(enabled ? mLayoutTransition : null); + } + protected void cleanup() { removeAllViews(); } @@ -206,9 +216,19 @@ public class TaskbarView extends LinearLayout implements FolderIcon.FolderIconPa } } + /** + * Hides or shows the hotseat items immediately (without layout transitions). + */ + protected void setHotseatViewsHidden(boolean hidden) { + mIsHotseatHidden = hidden; + setLayoutTransitionsEnabled(false); + updateHotseatItemsVisibility(); + setLayoutTransitionsEnabled(true); + } + private void updateHotseatItemVisibility(View hotseatView) { if (hotseatView.getTag() != null) { - hotseatView.setVisibility(VISIBLE); + hotseatView.setVisibility(mIsHotseatHidden ? INVISIBLE : VISIBLE); } else { int oldVisibility = hotseatView.getVisibility(); int newVisibility = mControllerCallbacks.getEmptyHotseatViewVisibility(); diff --git a/src/com/android/launcher3/AbstractFloatingView.java b/src/com/android/launcher3/AbstractFloatingView.java index 95cdbdd147..e263c7af55 100644 --- a/src/com/android/launcher3/AbstractFloatingView.java +++ b/src/com/android/launcher3/AbstractFloatingView.java @@ -98,6 +98,9 @@ public abstract class AbstractFloatingView extends LinearLayout implements Touch public static final int TYPE_HIDE_BACK_BUTTON = TYPE_ON_BOARD_POPUP | TYPE_DISCOVERY_BOUNCE | TYPE_SNACKBAR | TYPE_WIDGET_RESIZE_FRAME | TYPE_LISTENER; + // When these types of floating views are open, hide the taskbar hotseat and show the real one. + public static final int TYPE_REPLACE_TASKBAR_WITH_HOTSEAT = TYPE_FOLDER | TYPE_ACTION_POPUP; + public static final int TYPE_ACCESSIBLE = TYPE_ALL & ~TYPE_DISCOVERY_BOUNCE & ~TYPE_LISTENER & ~TYPE_ALL_APPS_EDU; diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java index 2440854028..fb7a99f433 100644 --- a/src/com/android/launcher3/DeviceProfile.java +++ b/src/com/android/launcher3/DeviceProfile.java @@ -708,10 +708,11 @@ public class DeviceProfile { mInsets.top + availableHeightPx); } else { // Folders should only appear below the drop target bar and above the hotseat + int hotseatTop = isTaskbarPresent ? taskbarSize : hotseatBarSizePx; return new Rect(mInsets.left + edgeMarginPx, mInsets.top + dropTargetBarSizePx + edgeMarginPx, mInsets.left + availableWidthPx - edgeMarginPx, - mInsets.top + availableHeightPx - hotseatBarSizePx + mInsets.top + availableHeightPx - hotseatTop - workspacePageIndicatorHeight - edgeMarginPx); } } diff --git a/src/com/android/launcher3/Hotseat.java b/src/com/android/launcher3/Hotseat.java index b2112ad83a..e5b75c1382 100644 --- a/src/com/android/launcher3/Hotseat.java +++ b/src/com/android/launcher3/Hotseat.java @@ -29,6 +29,7 @@ import android.widget.FrameLayout; import androidx.annotation.Nullable; import com.android.launcher3.config.FeatureFlags; +import com.android.launcher3.util.MultiValueAlpha; import java.util.function.Consumer; @@ -37,6 +38,10 @@ import java.util.function.Consumer; */ public class Hotseat extends CellLayout implements Insettable { + private static final int ALPHA_INDEX_STATE = 0; + private static final int ALPHA_INDEX_REPLACE_TASKBAR = 1; + private static final int NUM_ALPHA_CHANNELS = 2; + @ViewDebug.ExportedProperty(category = "launcher") private boolean mHasVerticalHotseat; private Workspace mWorkspace; @@ -44,6 +49,8 @@ public class Hotseat extends CellLayout implements Insettable { @Nullable private Consumer mOnVisibilityAggregatedCallback; + private final MultiValueAlpha mMultiValueAlpha; + public Hotseat(Context context) { this(context, null); } @@ -54,6 +61,8 @@ public class Hotseat extends CellLayout implements Insettable { public Hotseat(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); + mMultiValueAlpha = new MultiValueAlpha(this, NUM_ALPHA_CHANNELS, MultiValueAlpha.Mode.MAX); + mMultiValueAlpha.setUpdateVisibility(true); } /** @@ -174,4 +183,12 @@ public class Hotseat extends CellLayout implements Insettable { public View getFirstItemMatch(Workspace.ItemOperator itemOperator) { return mWorkspace.getFirstMatch(new CellLayout[] { this }, itemOperator); } + + public MultiValueAlpha.AlphaProperty getStateAlpha() { + return mMultiValueAlpha.getProperty(ALPHA_INDEX_STATE); + } + + public MultiValueAlpha.AlphaProperty getReplaceTaskbarAlpha() { + return mMultiValueAlpha.getProperty(ALPHA_INDEX_REPLACE_TASKBAR); + } } diff --git a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java index 660eeabbdc..d6d2f733c3 100644 --- a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java +++ b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java @@ -55,6 +55,7 @@ import com.android.launcher3.graphics.SysUiScrim; import com.android.launcher3.graphics.WorkspaceDragScrim; import com.android.launcher3.states.StateAnimationConfig; import com.android.launcher3.util.DynamicResource; +import com.android.launcher3.util.MultiValueAlpha; import com.android.systemui.plugins.ResourceProvider; /** @@ -143,8 +144,8 @@ public class WorkspaceStateTransitionAnimation { } float hotseatIconsAlpha = (elements & HOTSEAT_ICONS) != 0 ? 1 : 0; - propertySetter.setViewAlpha(hotseat, hotseatIconsAlpha, - config.getInterpolator(ANIM_HOTSEAT_FADE, fadeInterpolator)); + propertySetter.setFloat(hotseat.getStateAlpha(), MultiValueAlpha.VALUE, + hotseatIconsAlpha, config.getInterpolator(ANIM_HOTSEAT_FADE, fadeInterpolator)); float workspacePageIndicatorAlpha = (elements & WORKSPACE_PAGE_INDICATOR) != 0 ? 1 : 0; propertySetter.setViewAlpha(mLauncher.getWorkspace().getPageIndicator(), workspacePageIndicatorAlpha, fadeInterpolator); diff --git a/src/com/android/launcher3/util/MultiValueAlpha.java b/src/com/android/launcher3/util/MultiValueAlpha.java index 5be95292b4..c79b1f6c99 100644 --- a/src/com/android/launcher3/util/MultiValueAlpha.java +++ b/src/com/android/launcher3/util/MultiValueAlpha.java @@ -42,16 +42,49 @@ public class MultiValueAlpha { } }; + /** + * Determines how each alpha should factor into the final alpha. + */ + public enum Mode { + BLEND(1f) { + @Override + public float calculateNewAlpha(float currentAlpha, float otherAlpha) { + return currentAlpha * otherAlpha; + } + }, + + MAX(0f) { + @Override + public float calculateNewAlpha(float currentAlpha, float otherAlpha) { + return Math.max(currentAlpha, otherAlpha); + } + }; + + Mode(float startAlpha) { + mStartAlpha = startAlpha; + } + + protected final float mStartAlpha; + protected abstract float calculateNewAlpha(float currentAlpha, float otherAlpha); + } + private final View mView; private final AlphaProperty[] mMyProperties; + private final Mode mMode; private int mValidMask; // Whether we should change from INVISIBLE to VISIBLE and vice versa at low alpha values. private boolean mUpdateVisibility; public MultiValueAlpha(View view, int size) { + this(view, size, Mode.BLEND); + } + + public MultiValueAlpha(View view, int size, Mode mode) { mView = view; mMyProperties = new AlphaProperty[size]; + mMode = mode; + mView.setAlpha(mMode.mStartAlpha); mValidMask = 0; for (int i = 0; i < size; i++) { @@ -79,9 +112,9 @@ public class MultiValueAlpha { private final int mMyMask; - private float mValue = 1; + private float mValue = mMode.mStartAlpha; // Factor of all other alpha channels, only valid if mMyMask is present in mValidMask. - private float mOthers = 1; + private float mOthers = mMode.mStartAlpha; AlphaProperty(int myMask) { mMyMask = myMask; @@ -94,10 +127,10 @@ public class MultiValueAlpha { if ((mValidMask & mMyMask) == 0) { // Our cache value is not correct, recompute it. - mOthers = 1; + mOthers = mMode.mStartAlpha; for (AlphaProperty prop : mMyProperties) { if (prop != this) { - mOthers *= prop.mValue; + mOthers = mMode.calculateNewAlpha(mOthers, prop.mValue); } } } @@ -107,7 +140,7 @@ public class MultiValueAlpha { mValidMask = mMyMask; mValue = value; - mView.setAlpha(mOthers * mValue); + mView.setAlpha(mMode.calculateNewAlpha(mOthers, mValue)); if (mUpdateVisibility) { AlphaUpdateListener.updateVisibility(mView); }