06fb33ceb3
Added logic to adjust the drop target to account for changes in the hotseat position when accommodating the bubble bar. Fixes: 382348730 Test: all tests in package src.com.android.launcher3.celllayout Test: Manual. Ensure bubbles are present, and the hotseat width is adjusted. Long-press any icon in the hotseat, begin dragging it over other hotseat icons, and observe that the drop target is drawn appropriately. Flag: com.android.wm.shell.enable_bubble_bar Change-Id: Ice961f40ace03077ab8c9312a93eda1a16d7da34
387 lines
15 KiB
Java
387 lines
15 KiB
Java
/*
|
|
* Copyright (C) 2011 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
package com.android.launcher3;
|
|
|
|
import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
|
|
import static com.android.launcher3.util.MultiTranslateDelegate.INDEX_BUBBLE_ADJUSTMENT_ANIM;
|
|
|
|
import android.animation.AnimatorSet;
|
|
import android.animation.ObjectAnimator;
|
|
import android.animation.ValueAnimator;
|
|
import android.content.Context;
|
|
import android.graphics.Rect;
|
|
import android.util.AttributeSet;
|
|
import android.view.Gravity;
|
|
import android.view.LayoutInflater;
|
|
import android.view.MotionEvent;
|
|
import android.view.View;
|
|
import android.view.ViewDebug;
|
|
import android.view.ViewGroup;
|
|
import android.widget.FrameLayout;
|
|
|
|
import androidx.annotation.IntDef;
|
|
import androidx.annotation.Nullable;
|
|
|
|
import com.android.launcher3.ShortcutAndWidgetContainer.TranslationProvider;
|
|
import com.android.launcher3.celllayout.CellLayoutLayoutParams;
|
|
import com.android.launcher3.util.HorizontalInsettableView;
|
|
import com.android.launcher3.util.MultiPropertyFactory;
|
|
import com.android.launcher3.util.MultiPropertyFactory.MultiProperty;
|
|
import com.android.launcher3.util.MultiTranslateDelegate;
|
|
import com.android.launcher3.util.MultiValueAlpha;
|
|
import com.android.launcher3.views.ActivityContext;
|
|
|
|
import java.io.PrintWriter;
|
|
import java.lang.annotation.Retention;
|
|
import java.lang.annotation.RetentionPolicy;
|
|
|
|
/**
|
|
* View class that represents the bottom row of the home screen.
|
|
*/
|
|
public class Hotseat extends CellLayout implements Insettable {
|
|
|
|
public static final int ALPHA_CHANNEL_TASKBAR_ALIGNMENT = 0;
|
|
public static final int ALPHA_CHANNEL_PREVIEW_RENDERER = 1;
|
|
public static final int ALPHA_CHANNEL_TASKBAR_STASH = 2;
|
|
public static final int ALPHA_CHANNEL_CHANNELS_COUNT = 3;
|
|
|
|
@Retention(RetentionPolicy.RUNTIME)
|
|
@IntDef({ALPHA_CHANNEL_TASKBAR_ALIGNMENT, ALPHA_CHANNEL_PREVIEW_RENDERER,
|
|
ALPHA_CHANNEL_TASKBAR_STASH})
|
|
public @interface HotseatQsbAlphaId {
|
|
}
|
|
|
|
public static final int ICONS_TRANSLATION_X_NAV_BAR_ALIGNMENT = 0;
|
|
public static final int ICONS_TRANSLATION_X_CHANNELS_COUNT = 1;
|
|
|
|
@Retention(RetentionPolicy.RUNTIME)
|
|
@IntDef({ICONS_TRANSLATION_X_NAV_BAR_ALIGNMENT})
|
|
public @interface IconsTranslationX {
|
|
}
|
|
|
|
// Ratio of empty space, qsb should take up to appear visually centered.
|
|
public static final float QSB_CENTER_FACTOR = .325f;
|
|
private static final int BUBBLE_BAR_ADJUSTMENT_ANIMATION_DURATION_MS = 250;
|
|
|
|
@ViewDebug.ExportedProperty(category = "launcher")
|
|
private boolean mHasVerticalHotseat;
|
|
private Workspace<?> mWorkspace;
|
|
private boolean mSendTouchToWorkspace;
|
|
private final MultiValueAlpha mIconsAlphaChannels;
|
|
private final MultiValueAlpha mQsbAlphaChannels;
|
|
|
|
private @Nullable MultiProperty mQsbTranslationX;
|
|
|
|
private final MultiPropertyFactory mIconsTranslationXFactory;
|
|
|
|
private final View mQsb;
|
|
|
|
public Hotseat(Context context) {
|
|
this(context, null);
|
|
}
|
|
|
|
public Hotseat(Context context, AttributeSet attrs) {
|
|
this(context, attrs, 0);
|
|
}
|
|
|
|
public Hotseat(Context context, AttributeSet attrs, int defStyle) {
|
|
super(context, attrs, defStyle);
|
|
mQsb = LayoutInflater.from(context).inflate(R.layout.search_container_hotseat, this, false);
|
|
addView(mQsb);
|
|
mIconsAlphaChannels = new MultiValueAlpha(getShortcutsAndWidgets(),
|
|
ALPHA_CHANNEL_CHANNELS_COUNT);
|
|
if (mQsb instanceof Reorderable qsbReorderable) {
|
|
mQsbTranslationX = qsbReorderable.getTranslateDelegate()
|
|
.getTranslationX(MultiTranslateDelegate.INDEX_NAV_BAR_ANIM);
|
|
}
|
|
mIconsTranslationXFactory = new MultiPropertyFactory<>(getShortcutsAndWidgets(),
|
|
VIEW_TRANSLATE_X, ICONS_TRANSLATION_X_CHANNELS_COUNT, Float::sum);
|
|
mQsbAlphaChannels = new MultiValueAlpha(mQsb, ALPHA_CHANNEL_CHANNELS_COUNT);
|
|
}
|
|
|
|
/** Provides translation X for hotseat icons for the channel. */
|
|
public MultiProperty getIconsTranslationX(@IconsTranslationX int channelId) {
|
|
return mIconsTranslationXFactory.get(channelId);
|
|
}
|
|
|
|
/** Provides translation X for hotseat Qsb. */
|
|
@Nullable
|
|
public MultiProperty getQsbTranslationX() {
|
|
return mQsbTranslationX;
|
|
}
|
|
|
|
/**
|
|
* Returns orientation specific cell X given invariant order in the hotseat
|
|
*/
|
|
public int getCellXFromOrder(int rank) {
|
|
return mHasVerticalHotseat ? 0 : rank;
|
|
}
|
|
|
|
/**
|
|
* Returns orientation specific cell Y given invariant order in the hotseat
|
|
*/
|
|
public int getCellYFromOrder(int rank) {
|
|
return mHasVerticalHotseat ? (getCountY() - (rank + 1)) : 0;
|
|
}
|
|
|
|
boolean isHasVerticalHotseat() {
|
|
return mHasVerticalHotseat;
|
|
}
|
|
|
|
public void resetLayout(boolean hasVerticalHotseat) {
|
|
ActivityContext activityContext = ActivityContext.lookupContext(getContext());
|
|
boolean bubbleBarEnabled = activityContext.isBubbleBarEnabled();
|
|
boolean hasBubbles = activityContext.hasBubbles();
|
|
removeAllViewsInLayout();
|
|
mHasVerticalHotseat = hasVerticalHotseat;
|
|
DeviceProfile dp = mActivity.getDeviceProfile();
|
|
|
|
if (bubbleBarEnabled) {
|
|
if (dp.shouldAdjustHotseatForBubbleBar(getContext(), hasBubbles)) {
|
|
getShortcutsAndWidgets().setTranslationProvider(
|
|
cellX -> dp.getHotseatAdjustedTranslation(getContext(), cellX));
|
|
if (mQsb instanceof HorizontalInsettableView) {
|
|
HorizontalInsettableView insettableQsb = (HorizontalInsettableView) mQsb;
|
|
final float insetFraction = (float) dp.iconSizePx / dp.hotseatQsbWidth;
|
|
// post this to the looper so that QSB has a chance to redraw itself, e.g.
|
|
// after device rotation
|
|
mQsb.post(() -> insettableQsb.setHorizontalInsets(insetFraction));
|
|
}
|
|
} else {
|
|
getShortcutsAndWidgets().setTranslationProvider(null);
|
|
if (mQsb instanceof HorizontalInsettableView) {
|
|
((HorizontalInsettableView) mQsb).setHorizontalInsets(0);
|
|
}
|
|
}
|
|
}
|
|
|
|
resetCellSize(dp);
|
|
if (hasVerticalHotseat) {
|
|
setGridSize(1, dp.numShownHotseatIcons);
|
|
} else {
|
|
setGridSize(dp.numShownHotseatIcons, 1);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Adjust the hotseat icons for the bubble bar.
|
|
*
|
|
* <p>When the bubble bar becomes visible, if needed, this method animates the hotseat icons
|
|
* to reduce the spacing between them and make room for the bubble bar. The QSB width is
|
|
* animated as well to align with the hotseat icons.
|
|
*
|
|
* <p>When the bubble bar goes away, any adjustments that were previously made are reversed.
|
|
*/
|
|
public void adjustForBubbleBar(boolean isBubbleBarVisible) {
|
|
DeviceProfile dp = mActivity.getDeviceProfile();
|
|
boolean shouldAdjust = isBubbleBarVisible
|
|
&& dp.shouldAdjustHotseatOrQsbForBubbleBar(getContext());
|
|
boolean shouldAdjustHotseat = shouldAdjust
|
|
&& dp.shouldAlignBubbleBarWithHotseat();
|
|
ShortcutAndWidgetContainer icons = getShortcutsAndWidgets();
|
|
// update the translation provider for future layout passes of hotseat icons.
|
|
if (shouldAdjustHotseat) {
|
|
icons.setTranslationProvider(
|
|
cellX -> dp.getHotseatAdjustedTranslation(getContext(), cellX));
|
|
} else {
|
|
icons.setTranslationProvider(null);
|
|
}
|
|
AnimatorSet animatorSet = new AnimatorSet();
|
|
for (int i = 0; i < icons.getChildCount(); i++) {
|
|
View child = icons.getChildAt(i);
|
|
if (child.getLayoutParams() instanceof CellLayoutLayoutParams lp) {
|
|
float tx = shouldAdjustHotseat
|
|
? dp.getHotseatAdjustedTranslation(getContext(), lp.getCellX()) : 0;
|
|
if (child instanceof Reorderable) {
|
|
MultiTranslateDelegate mtd = ((Reorderable) child).getTranslateDelegate();
|
|
animatorSet.play(
|
|
mtd.getTranslationX(INDEX_BUBBLE_ADJUSTMENT_ANIM).animateToValue(tx));
|
|
} else {
|
|
animatorSet.play(ObjectAnimator.ofFloat(child, VIEW_TRANSLATE_X, tx));
|
|
}
|
|
}
|
|
}
|
|
//TODO(b/381109832) refactor & simplify adjustment logic
|
|
boolean shouldAdjustQsb =
|
|
shouldAdjustHotseat || (shouldAdjust && dp.shouldAlignBubbleBarWithQSB());
|
|
if (mQsb instanceof HorizontalInsettableView horizontalInsettableQsb) {
|
|
final float currentInsetFraction = horizontalInsettableQsb.getHorizontalInsets();
|
|
final float targetInsetFraction = shouldAdjustQsb
|
|
? (float) dp.iconSizePx / dp.hotseatQsbWidth : 0;
|
|
ValueAnimator qsbAnimator =
|
|
ValueAnimator.ofFloat(currentInsetFraction, targetInsetFraction);
|
|
qsbAnimator.addUpdateListener(animation -> {
|
|
float insetFraction = (float) animation.getAnimatedValue();
|
|
horizontalInsettableQsb.setHorizontalInsets(insetFraction);
|
|
});
|
|
animatorSet.play(qsbAnimator);
|
|
}
|
|
animatorSet.setDuration(BUBBLE_BAR_ADJUSTMENT_ANIMATION_DURATION_MS).start();
|
|
}
|
|
|
|
@Override
|
|
protected int getTranslationXForCell(int cellX, int cellY) {
|
|
TranslationProvider translationProvider = getShortcutsAndWidgets().getTranslationProvider();
|
|
if (translationProvider == null) return 0;
|
|
return (int) translationProvider.getTranslationX(cellX);
|
|
}
|
|
|
|
@Override
|
|
public void setInsets(Rect insets) {
|
|
FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams();
|
|
DeviceProfile grid = mActivity.getDeviceProfile();
|
|
|
|
if (grid.isVerticalBarLayout()) {
|
|
mQsb.setVisibility(View.GONE);
|
|
lp.height = ViewGroup.LayoutParams.MATCH_PARENT;
|
|
if (grid.isSeascape()) {
|
|
lp.gravity = Gravity.LEFT;
|
|
lp.width = grid.hotseatBarSizePx + insets.left;
|
|
} else {
|
|
lp.gravity = Gravity.RIGHT;
|
|
lp.width = grid.hotseatBarSizePx + insets.right;
|
|
}
|
|
} else {
|
|
mQsb.setVisibility(View.VISIBLE);
|
|
lp.gravity = Gravity.BOTTOM;
|
|
lp.width = ViewGroup.LayoutParams.MATCH_PARENT;
|
|
lp.height = grid.hotseatBarSizePx;
|
|
}
|
|
|
|
Rect padding = grid.getHotseatLayoutPadding(getContext());
|
|
setPadding(padding.left, padding.top, padding.right, padding.bottom);
|
|
setLayoutParams(lp);
|
|
InsettableFrameLayout.dispatchInsets(this, insets);
|
|
}
|
|
|
|
public void setWorkspace(Workspace<?> w) {
|
|
mWorkspace = w;
|
|
setCellLayoutContainer(w);
|
|
}
|
|
|
|
@Override
|
|
public boolean onInterceptTouchEvent(MotionEvent ev) {
|
|
// We allow horizontal workspace scrolling from within the Hotseat. We do this by delegating
|
|
// touch intercept the Workspace, and if it intercepts, delegating touch to the Workspace
|
|
// for the remainder of the this input stream.
|
|
int yThreshold = getMeasuredHeight() - getPaddingBottom();
|
|
if (mWorkspace != null && ev.getY() <= yThreshold) {
|
|
mSendTouchToWorkspace = mWorkspace.onInterceptTouchEvent(ev);
|
|
return mSendTouchToWorkspace;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
@Override
|
|
public boolean onTouchEvent(MotionEvent event) {
|
|
// See comment in #onInterceptTouchEvent
|
|
if (mSendTouchToWorkspace) {
|
|
final int action = event.getAction();
|
|
switch (action & MotionEvent.ACTION_MASK) {
|
|
case MotionEvent.ACTION_UP:
|
|
case MotionEvent.ACTION_CANCEL:
|
|
mSendTouchToWorkspace = false;
|
|
}
|
|
return mWorkspace.onTouchEvent(event);
|
|
}
|
|
// Always let touch follow through to Workspace.
|
|
return false;
|
|
}
|
|
|
|
@Override
|
|
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
|
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
|
|
|
DeviceProfile dp = mActivity.getDeviceProfile();
|
|
mQsb.measure(MeasureSpec.makeMeasureSpec(dp.hotseatQsbWidth, MeasureSpec.EXACTLY),
|
|
MeasureSpec.makeMeasureSpec(dp.hotseatQsbHeight, MeasureSpec.EXACTLY));
|
|
}
|
|
|
|
@Override
|
|
protected void onLayout(boolean changed, int l, int t, int r, int b) {
|
|
super.onLayout(changed, l, t, r, b);
|
|
|
|
int qsbMeasuredWidth = mQsb.getMeasuredWidth();
|
|
int left;
|
|
DeviceProfile dp = mActivity.getDeviceProfile();
|
|
if (dp.isQsbInline) {
|
|
int qsbSpace = dp.hotseatBorderSpace;
|
|
left = Utilities.isRtl(getResources()) ? r - getPaddingRight() + qsbSpace
|
|
: l + getPaddingLeft() - qsbMeasuredWidth - qsbSpace;
|
|
} else {
|
|
left = (r - l - qsbMeasuredWidth) / 2;
|
|
}
|
|
int right = left + qsbMeasuredWidth;
|
|
|
|
int bottom = b - t - dp.getQsbOffsetY();
|
|
int top = bottom - dp.hotseatQsbHeight;
|
|
mQsb.layout(left, top, right, bottom);
|
|
}
|
|
|
|
/**
|
|
* Sets the alpha value of the specified alpha channel of just our ShortcutAndWidgetContainer.
|
|
*/
|
|
public void setIconsAlpha(float alpha, @HotseatQsbAlphaId int channelId) {
|
|
getIconsAlpha(channelId).setValue(alpha);
|
|
}
|
|
|
|
/**
|
|
* Sets the alpha value of just our QSB.
|
|
*/
|
|
public void setQsbAlpha(float alpha, @HotseatQsbAlphaId int channelId) {
|
|
getQsbAlpha(channelId).setValue(alpha);
|
|
}
|
|
|
|
/** Returns the alpha channel for ShortcutAndWidgetContainer */
|
|
public MultiProperty getIconsAlpha(@HotseatQsbAlphaId int channelId) {
|
|
return mIconsAlphaChannels.get(channelId);
|
|
}
|
|
|
|
/** Returns the alpha channel for Qsb */
|
|
public MultiProperty getQsbAlpha(@HotseatQsbAlphaId int channelId) {
|
|
return mQsbAlphaChannels.get(channelId);
|
|
}
|
|
|
|
/**
|
|
* Returns the QSB inside hotseat
|
|
*/
|
|
public View getQsb() {
|
|
return mQsb;
|
|
}
|
|
|
|
/** Dumps the Hotseat internal state */
|
|
public void dump(String prefix, PrintWriter writer) {
|
|
writer.println(prefix + "Hotseat:");
|
|
mIconsAlphaChannels.dump(
|
|
prefix + "\t",
|
|
writer,
|
|
"mIconsAlphaChannels",
|
|
"ALPHA_CHANNEL_TASKBAR_ALIGNMENT",
|
|
"ALPHA_CHANNEL_PREVIEW_RENDERER",
|
|
"ALPHA_CHANNEL_TASKBAR_STASH");
|
|
mQsbAlphaChannels.dump(
|
|
prefix + "\t",
|
|
writer,
|
|
"mQsbAlphaChannels",
|
|
"ALPHA_CHANNEL_TASKBAR_ALIGNMENT",
|
|
"ALPHA_CHANNEL_PREVIEW_RENDERER",
|
|
"ALPHA_CHANNEL_TASKBAR_STASH"
|
|
);
|
|
}
|
|
|
|
}
|