Fix bug with double-relayout of Overview Actions View

This CL refactors Overview Actions View so that a separate view, R.id.group_action_buttons, is used for buttons related to grouped tasks. This also changes the way visibility is handled with actions buttons -- instead of all buttons living on one bar and being toggled on and off, sometimes the whole bar is toggled on and off (to change from single task actions to group actions and vice versa).

This prevents the same view from having its visibility changed twice in in one layout and causing the wrong visibility to be rendered.

Fixes: 333844287
Test: Manual
Flag: ACONFIG com.android.wm.shell.enable_app_pairs NEXTFOOD
Change-Id: I1b6be4637ac8c8d424e7633c437fca376ecdd757
This commit is contained in:
Jeremy Sim
2024-04-29 18:59:37 -07:00
parent c81ffaf821
commit 41eadd1a91
9 changed files with 144 additions and 114 deletions

View File

@@ -121,6 +121,17 @@
android:layout_weight="1"
android:visibility="gone" />
</LinearLayout>
<!-- Unused. Included only for compatibility with parent class. -->
<LinearLayout
android:id="@+id/group_action_buttons"
android:layout_width="match_parent"
android:layout_height="@dimen/overview_actions_height"
android:layout_gravity="top|center_horizontal"
android:orientation="horizontal"
android:visibility="gone">
<Button
android:id="@+id/action_save_app_pair"
style="@style/GoOverviewActionButton"
@@ -128,8 +139,8 @@
android:layout_height="wrap_content"
android:drawableStart="@drawable/ic_save_app_pair_up_down"
android:text="@string/action_save_app_pair"
android:theme="@style/ThemeControlHighlightWorkspaceColor"
android:visibility="gone" />
android:theme="@style/ThemeControlHighlightWorkspaceColor" />
</LinearLayout>
</com.android.quickstep.views.GoOverviewActionsView>

View File

@@ -45,14 +45,24 @@
android:theme="@style/ThemeControlHighlightWorkspaceColor"
android:visibility="gone" />
</LinearLayout>
<LinearLayout
android:id="@+id/group_action_buttons"
android:layout_width="wrap_content"
android:layout_height="@dimen/overview_actions_height"
android:layout_gravity="bottom|center_horizontal"
android:orientation="horizontal"
android:visibility="gone">
<Button
android:id="@+id/action_save_app_pair"
style="@style/OverviewActionButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/action_save_app_pair"
android:theme="@style/ThemeControlHighlightWorkspaceColor"
android:visibility="gone" />
android:theme="@style/ThemeControlHighlightWorkspaceColor" />
</LinearLayout>
</com.android.quickstep.views.OverviewActionsView>

View File

@@ -21,7 +21,6 @@ import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.LauncherState.OVERVIEW_ACTIONS;
import static com.android.launcher3.LauncherState.OVERVIEW_SPLIT_SELECT;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_ACTIONS_FADE;
import static com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VALUE;
import static com.android.quickstep.views.RecentsView.CONTENT_ALPHA;
import static com.android.quickstep.views.RecentsView.FULLSCREEN_PROGRESS;
import static com.android.quickstep.views.RecentsView.TASK_MODALNESS;
@@ -50,6 +49,7 @@ import com.android.quickstep.util.AnimUtils;
import com.android.quickstep.util.SplitAnimationTimings;
import com.android.quickstep.views.ClearAllButton;
import com.android.quickstep.views.LauncherRecentsView;
import com.android.quickstep.views.OverviewActionsView;
import com.android.quickstep.views.RecentsView;
/**
@@ -167,8 +167,8 @@ public final class RecentsViewStateController extends
propertySetter.setFloat(mRecentsView.getClearAllButton(), ClearAllButton.VISIBILITY_ALPHA,
clearAllButtonAlpha, LINEAR);
float overviewButtonAlpha = state.areElementsVisible(mLauncher, OVERVIEW_ACTIONS) ? 1 : 0;
propertySetter.setFloat(mLauncher.getActionsView().getVisibilityAlpha(),
MULTI_PROPERTY_VALUE, overviewButtonAlpha, config.getInterpolator(
propertySetter.setFloat(mLauncher.getActionsView().getVisibilityAlphaSetter(),
OverviewActionsView.FLOAT_SETTER, overviewButtonAlpha, config.getInterpolator(
ANIM_OVERVIEW_ACTIONS_FADE, LINEAR));
}

View File

@@ -248,7 +248,7 @@ public class NoButtonQuickSwitchTouchController implements TouchController,
TASK_THUMBNAIL_SPLASH_ALPHA.set(mRecentsView, fromState.showTaskThumbnailSplash() ? 1f : 0);
mRecentsView.setContentAlpha(1);
mRecentsView.setFullscreenProgress(fromState.getOverviewFullscreenProgress());
mLauncher.getActionsView().getVisibilityAlpha().setValue(
mLauncher.getActionsView().getVisibilityAlphaSetter().accept(
(fromState.getVisibleElements(mLauncher) & OVERVIEW_ACTIONS) != 0 ? 1f : 0f);
mRecentsView.setTaskIconScaledDown(true);

View File

@@ -46,12 +46,10 @@ import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.anim.PropertySetter;
import com.android.launcher3.statemanager.StateManager.StateHandler;
import com.android.launcher3.states.StateAnimationConfig;
import com.android.launcher3.util.MultiPropertyFactory;
import com.android.quickstep.RecentsActivity;
import com.android.quickstep.views.ClearAllButton;
import com.android.quickstep.views.OverviewActionsView;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.RecentsViewContainer;
/**
* State controller for fallback recents activity
@@ -98,8 +96,8 @@ public class FallbackRecentsStateController implements StateHandler<RecentsState
setter.setFloat(mRecentsView.getClearAllButton(), ClearAllButton.VISIBILITY_ALPHA,
clearAllButtonAlpha, LINEAR);
float overviewButtonAlpha = state.hasOverviewActions() ? 1 : 0;
setter.setFloat(mActivity.getActionsView().getVisibilityAlpha(),
MultiPropertyFactory.MULTI_PROPERTY_VALUE, overviewButtonAlpha, LINEAR);
setter.setFloat(mActivity.getActionsView().getVisibilityAlphaSetter(),
OverviewActionsView.FLOAT_SETTER, overviewButtonAlpha, LINEAR);
float[] scaleAndOffset = state.getOverviewScaleAndOffset(mActivity);
setter.setFloat(mRecentsView, RECENTS_SCALE_PROPERTY, scaleAndOffset[0],

View File

@@ -20,6 +20,7 @@ import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.FloatProperty;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
@@ -33,9 +34,7 @@ import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Flags;
import com.android.launcher3.Insettable;
import com.android.launcher3.R;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.MultiPropertyFactory.MultiProperty;
import com.android.launcher3.util.MultiValueAlpha;
import com.android.launcher3.util.NavigationMode;
import com.android.quickstep.TaskOverlayFactory.OverlayUICallbacks;
@@ -43,12 +42,26 @@ import com.android.quickstep.util.LayoutUtils;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
import java.util.function.Consumer;
/**
* View for showing action buttons in Overview
*/
public class OverviewActionsView<T extends OverlayUICallbacks> extends FrameLayout
implements OnClickListener, Insettable {
public static final FloatProperty<Consumer<Float>> FLOAT_SETTER =
new FloatProperty<>("floatSetter") {
@Override
public void setValue(Consumer<Float> consumer, float v) {
consumer.accept(v);
}
@Override
public Float get(Consumer<Float> consumer) {
return -1f;
}
};
private final Rect mInsets = new Rect();
@@ -89,30 +102,24 @@ public class OverviewActionsView<T extends OverlayUICallbacks> extends FrameLayo
private static final int INDEX_HIDDEN_FLAGS_ALPHA = 3;
private static final int INDEX_SHARE_TARGET_ALPHA = 4;
private static final int INDEX_SCROLL_ALPHA = 5;
private static final int NUM_ALPHAS = 6;
public @interface ScreenshotButtonHiddenFlags { }
public static final int FLAG_MULTIPLE_TASKS_HIDE_SCREENSHOT = 1 << 0;
private static final int INDEX_GROUPED_ALPHA = 6;
private static final int INDEX_3P_LAUNCHER = 7;
private static final int NUM_ALPHAS = 8;
public @interface SplitButtonHiddenFlags { }
public static final int FLAG_SMALL_SCREEN_HIDE_SPLIT = 1 << 0;
public static final int FLAG_MULTIPLE_TASKS_HIDE_SPLIT = 1 << 1;
public @interface SplitButtonDisabledFlags { }
public static final int FLAG_SINGLE_TASK_DISABLE_SPLIT = 1 << 0;
public @interface AppPairButtonHiddenFlags { }
public static final int FLAG_SINGLE_TASK_HIDE_APP_PAIR = 1 << 0;
public static final int FLAG_SMALL_SCREEN_HIDE_APP_PAIR = 1 << 1;
public static final int FLAG_3P_LAUNCHER_HIDE_APP_PAIR = 1 << 2;
private MultiValueAlpha mMultiValueAlpha;
/** Holds MultiValueAlpha values for all actions buttons */
private final MultiValueAlpha[] mMultiValueAlphas = new MultiValueAlpha[2];
/** Index used for single-task actions in the mMultiValueAlphas array */
private static final int ACTIONS_ALPHAS = 0;
/** Index used for grouped-task actions in the mMultiValueAlphas array */
private static final int GROUP_ACTIONS_ALPHAS = 1;
/** Container for the action buttons below a focused, non-split Overview tile. */
protected LinearLayout mActionButtons;
// The screenshot button is implemented as a Button in launcher3 and NexusLauncher, but is an
// ImageButton in go launcher (does not share a common class with Button). Take care when
// casting this.
private View mScreenshotButton;
/** Container for the action buttons below a focused, split Overview tile. */
protected LinearLayout mGroupActionButtons;
private Button mSplitButton;
private Button mSaveAppPairButton;
@@ -122,21 +129,16 @@ public class OverviewActionsView<T extends OverlayUICallbacks> extends FrameLayo
@ActionsDisabledFlags
protected int mDisabledFlags;
@ScreenshotButtonHiddenFlags
private int mScreenshotButtonHiddenFlags;
@SplitButtonHiddenFlags
private int mSplitButtonHiddenFlags;
@AppPairButtonHiddenFlags
private int mAppPairButtonHiddenFlags;
@Nullable
protected T mCallbacks;
@Nullable
protected DeviceProfile mDp;
private final Rect mTaskSize = new Rect();
private boolean mIsGroupedTask = false;
public OverviewActionsView(Context context) {
this(context, null);
@@ -153,12 +155,21 @@ public class OverviewActionsView<T extends OverlayUICallbacks> extends FrameLayo
@Override
protected void onFinishInflate() {
super.onFinishInflate();
// Initialize 2 view containers: one for single tasks, one for grouped tasks.
// These will take up the same space on the screen and alternate visibility as needed.
mActionButtons = findViewById(R.id.action_buttons);
mMultiValueAlpha = new MultiValueAlpha(mActionButtons, NUM_ALPHAS);
mMultiValueAlpha.setUpdateVisibility(true);
mGroupActionButtons = findViewById(R.id.group_action_buttons);
// Initialize a list to set alpha on mActionButtons and mGroupActionButtons simultaneously.
mMultiValueAlphas[ACTIONS_ALPHAS] = new MultiValueAlpha(mActionButtons, NUM_ALPHAS);
mMultiValueAlphas[GROUP_ACTIONS_ALPHAS] =
new MultiValueAlpha(mGroupActionButtons, NUM_ALPHAS);
Arrays.stream(mMultiValueAlphas).forEach(a -> a.setUpdateVisibility(true));
mScreenshotButton = findViewById(R.id.action_screenshot);
mScreenshotButton.setOnClickListener(this);
// The screenshot button is implemented as a Button in launcher3 and NexusLauncher, but is
// an ImageButton in go launcher (does not share a common class with Button). Take care when
// casting this.
View screenshotButton = findViewById(R.id.action_screenshot);
screenshotButton.setOnClickListener(this);
mSplitButton = findViewById(R.id.action_split);
mSplitButton.setOnClickListener(this);
mSaveAppPairButton = findViewById(R.id.action_save_app_pair);
@@ -209,7 +220,7 @@ public class OverviewActionsView<T extends OverlayUICallbacks> extends FrameLayo
mHiddenFlags &= ~visibilityFlags;
}
boolean isHidden = mHiddenFlags != 0;
mMultiValueAlpha.get(INDEX_HIDDEN_FLAGS_ALPHA).setValue(isHidden ? 0 : 1);
setActionsAlpha(INDEX_HIDDEN_FLAGS_ALPHA, isHidden ? 0 : 1);
}
/**
@@ -236,12 +247,8 @@ public class OverviewActionsView<T extends OverlayUICallbacks> extends FrameLayo
* @param isGroupedTask True if the focused task is a grouped task.
*/
public void updateForGroupedTask(boolean isGroupedTask) {
// Update flags to see if split button should be hidden.
updateSplitButtonHiddenFlags(FLAG_MULTIPLE_TASKS_HIDE_SPLIT, isGroupedTask);
// Update flags to see if screenshot button should be hidden.
updateScreenshotButtonHiddenFlags(FLAG_MULTIPLE_TASKS_HIDE_SCREENSHOT, isGroupedTask);
// Update flags to see if save app pair button should be hidden.
updateAppPairButtonHiddenFlags(FLAG_SINGLE_TASK_HIDE_APP_PAIR, !isGroupedTask);
mIsGroupedTask = isGroupedTask;
updateActionButtonsVisibility();
}
/**
@@ -251,36 +258,30 @@ public class OverviewActionsView<T extends OverlayUICallbacks> extends FrameLayo
assert mDp != null;
// Update flags to see if split button should be hidden.
updateSplitButtonHiddenFlags(FLAG_SMALL_SCREEN_HIDE_SPLIT, !mDp.isTablet);
// Update flags to see if save app pair button should be hidden.
updateAppPairButtonHiddenFlags(FLAG_SMALL_SCREEN_HIDE_APP_PAIR, !mDp.isTablet);
updateActionButtonsVisibility();
}
private void updateActionButtonsVisibility() {
assert mDp != null;
boolean showSingleTaskActions = !mIsGroupedTask;
boolean showGroupActions = mIsGroupedTask && mDp.isTablet;
getActionsAlphas().get(INDEX_GROUPED_ALPHA).setValue(showSingleTaskActions ? 1 : 0);
getGroupActionsAlphas().get(INDEX_GROUPED_ALPHA).setValue(showGroupActions ? 1 : 0);
}
/**
* Updates flags to hide and show actions buttons for 1p/3p launchers.
*/
public void updateFor3pLauncher(boolean is3pLauncher) {
updateAppPairButtonHiddenFlags(FLAG_3P_LAUNCHER_HIDE_APP_PAIR, is3pLauncher);
getGroupActionsAlphas().get(INDEX_3P_LAUNCHER).setValue(is3pLauncher ? 0 : 1);
}
/**
* Updates the proper flags to indicate whether the "Screenshot" button should be hidden.
*
* @param flag The flag to update.
* @param enable Whether to enable the hidden flag: True will cause view to be hidden.
*/
private void updateScreenshotButtonHiddenFlags(@ScreenshotButtonHiddenFlags int flag,
boolean enable) {
if (mScreenshotButton == null) return;
if (enable) {
mScreenshotButtonHiddenFlags |= flag;
} else {
mScreenshotButtonHiddenFlags &= ~flag;
}
int desiredVisibility = mScreenshotButtonHiddenFlags == 0 ? VISIBLE : GONE;
if (mScreenshotButton.getVisibility() != desiredVisibility) {
mScreenshotButton.setVisibility(desiredVisibility);
mActionButtons.requestLayout();
}
private MultiValueAlpha getActionsAlphas() {
return mMultiValueAlphas[ACTIONS_ALPHAS];
}
private MultiValueAlpha getGroupActionsAlphas() {
return mMultiValueAlphas[GROUP_ACTIONS_ALPHAS];
}
/**
@@ -304,56 +305,36 @@ public class OverviewActionsView<T extends OverlayUICallbacks> extends FrameLayo
}
}
/**
* Updates the proper flags to indicate whether the "Save app pair" button should be disabled.
*
* @param flag The flag to update.
* @param enable Whether to enable the hidden flag: True will cause view to be hidden.
*/
private void updateAppPairButtonHiddenFlags(
@AppPairButtonHiddenFlags int flag, boolean enable) {
if (!FeatureFlags.enableAppPairs()) {
return;
}
if (mSaveAppPairButton == null) return;
if (enable) {
mAppPairButtonHiddenFlags |= flag;
} else {
mAppPairButtonHiddenFlags &= ~flag;
}
int desiredVisibility = mAppPairButtonHiddenFlags == 0 ? VISIBLE : GONE;
if (mSaveAppPairButton.getVisibility() != desiredVisibility) {
mSaveAppPairButton.setVisibility(desiredVisibility);
mActionButtons.requestLayout();
}
private void setActionsAlpha(int index, float value) {
Arrays.stream(mMultiValueAlphas).forEach(a -> a.get(index).setValue(value));
}
public MultiProperty getContentAlpha() {
return mMultiValueAlpha.get(INDEX_CONTENT_ALPHA);
public Consumer<Float> getContentAlphaSetter() {
return v -> setActionsAlpha(INDEX_CONTENT_ALPHA, v);
}
public MultiProperty getVisibilityAlpha() {
return mMultiValueAlpha.get(INDEX_VISIBILITY_ALPHA);
public Consumer<Float> getVisibilityAlphaSetter() {
return v -> setActionsAlpha(INDEX_VISIBILITY_ALPHA, v);
}
public MultiProperty getFullscreenAlpha() {
return mMultiValueAlpha.get(INDEX_FULLSCREEN_ALPHA);
public Consumer<Float> getFullscreenAlphaSetter() {
return v -> setActionsAlpha(INDEX_FULLSCREEN_ALPHA, v);
}
public MultiProperty getShareTargetAlpha() {
return mMultiValueAlpha.get(INDEX_SHARE_TARGET_ALPHA);
public Consumer<Float> getShareTargetAlphaSetter() {
return v -> setActionsAlpha(INDEX_SHARE_TARGET_ALPHA, v);
}
public MultiProperty getIndexScrollAlpha() {
return mMultiValueAlpha.get(INDEX_SCROLL_ALPHA);
public Consumer<Float> getIndexScrollAlphaSetter() {
return v -> setActionsAlpha(INDEX_SCROLL_ALPHA, v);
}
/**
* Returns the visibility of the overview actions buttons.
*/
public @Visibility int getActionsButtonVisibility() {
return mActionButtons.getVisibility();
public boolean areActionsButtonsVisible() {
return mActionButtons.getVisibility() == View.VISIBLE
|| mGroupActionButtons.getVisibility() == View.VISIBLE;
}
/**
@@ -366,10 +347,17 @@ public class OverviewActionsView<T extends OverlayUICallbacks> extends FrameLayo
/** Updates vertical margins for different navigation mode or configuration changes. */
public void updateVerticalMargin(NavigationMode mode) {
updateActionBarPosition(mActionButtons);
updateActionBarPosition(mGroupActionButtons);
}
/** Positions actions buttons according to device settings and insets. */
private void updateActionBarPosition(LinearLayout actionBar) {
if (mDp == null) {
return;
}
LayoutParams actionParams = (LayoutParams) mActionButtons.getLayoutParams();
LayoutParams actionParams = (LayoutParams) actionBar.getLayoutParams();
actionParams.setMargins(
actionParams.leftMargin, mDp.overviewActionsTopMarginPx,
actionParams.rightMargin, getBottomMargin());

View File

@@ -2019,7 +2019,7 @@ public abstract class RecentsView<CONTAINER_TYPE extends Context & RecentsViewCo
mClearAllButton.setFullscreenProgress(fullscreenProgress);
// Fade out the actions view quickly (0.1 range)
mActionsView.getFullscreenAlpha().setValue(
mActionsView.getFullscreenAlphaSetter().accept(
mapToRange(fullscreenProgress, 0, 0.1f, 1f, 0f, LINEAR));
}
@@ -2270,8 +2270,8 @@ public abstract class RecentsView<CONTAINER_TYPE extends Context & RecentsViewCo
}
private void animateActionsViewAlpha(float alphaValue, long duration) {
mActionsViewAlphaAnimator = ObjectAnimator.ofFloat(
mActionsView.getVisibilityAlpha(), MULTI_PROPERTY_VALUE, alphaValue);
mActionsViewAlphaAnimator = ObjectAnimator.ofFloat(mActionsView.getVisibilityAlphaSetter(),
OverviewActionsView.FLOAT_SETTER, alphaValue);
mActionsViewAlphaAnimatorFinalValue = alphaValue;
mActionsViewAlphaAnimator.setDuration(duration);
// Set autocancel to prevent race-conditiony setting of alpha from other animations
@@ -2290,7 +2290,7 @@ public abstract class RecentsView<CONTAINER_TYPE extends Context & RecentsViewCo
mClearAllButton.onRecentsViewScroll(scroll, mOverviewGridEnabled);
// Clear all button alpha was set by the previous line.
mActionsView.getIndexScrollAlpha().setValue(1 - mClearAllButton.getScrollAlpha());
mActionsView.getIndexScrollAlphaSetter().accept(1 - mClearAllButton.getScrollAlpha());
}
@Override
@@ -4295,7 +4295,7 @@ public abstract class RecentsView<CONTAINER_TYPE extends Context & RecentsViewCo
int alphaInt = Math.round(alpha * 255);
mEmptyMessagePaint.setAlpha(alphaInt);
mEmptyIcon.setAlpha(alphaInt);
mActionsView.getContentAlpha().setValue(mContentAlpha);
mActionsView.getContentAlphaSetter().accept(mContentAlpha);
if (alpha > 0) {
setVisibility(VISIBLE);

View File

@@ -130,7 +130,7 @@ public class TaplTestsSplitscreen extends AbstractQuickStepTest {
.tapMenu()
.hasMenuItem("Save app pair"));
} else {
overview.getOverviewActions().assertHasAction("Save app pair");
overview.getOverviewGroupActions().assertHasAction("Save app pair");
}
}

View File

@@ -358,6 +358,21 @@ public class BaseOverview extends LauncherInstrumentation.VisibleContainer {
}
}
/**
* Gets Overview Actions specific to grouped tasks.
*
* @return The Overview group actions bar
*/
@NonNull
public OverviewActions getOverviewGroupActions() {
try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
"want to get overview group actions")) {
verifyActiveContainer();
UiObject2 groupActions = mLauncher.waitForOverviewObject("group_action_buttons");
return new OverviewActions(groupActions, mLauncher);
}
}
/**
* Returns if clear all button is visible.
*/
@@ -449,10 +464,18 @@ public class BaseOverview extends LauncherInstrumentation.VisibleContainer {
private void verifyActionsViewVisibility() {
try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
"want to assert overview actions view visibility")) {
boolean isTablet = mLauncher.isTablet();
OverviewTask task = isTablet ? getFocusedTaskForTablet() : getCurrentTask();
if (isActionsViewVisible()) {
mLauncher.waitForOverviewObject("action_buttons");
if (task.isTaskSplit()) {
mLauncher.waitForOverviewObject("group_action_buttons");
} else {
mLauncher.waitForOverviewObject("action_buttons");
}
} else {
mLauncher.waitUntilOverviewObjectGone("action_buttons");
mLauncher.waitUntilOverviewObjectGone("group_action_buttons");
}
}
}