/* * Copyright (C) 2023 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.taskbar.bubbles; import android.animation.ValueAnimator; import android.annotation.Nullable; import android.content.Context; import android.graphics.Rect; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; import com.android.launcher3.R; import com.android.launcher3.taskbar.TaskbarActivityContext; import com.android.launcher3.views.ActivityContext; import java.util.List; /** * The view that holds all the bubble views. Modifying this view should happen through * {@link BubbleBarViewController}. Updates to the bubbles themselves (adds, removes, updates, * selection) should happen through {@link BubbleBarController} which is the source of truth * for state information about the bubbles. *
* The bubble bar has a couple of visual states: * - stashed as a handle * - unstashed but collapsed, in this state the bar is showing but the bubbles are stacked within it * - unstashed and expanded, in this state the bar is showing and the bubbles are shown in a row * with one of the bubbles being selected. Additionally, WMShell will display the expanded bubble * view above the bar. *
* The bubble bar has some behavior related to taskbar: * - When taskbar is unstashed, bubble bar will also become unstashed (but in its "collapsed" * state) * - When taskbar is stashed, bubble bar will also become stashed (unless bubble bar is in its * "expanded" state) * - When bubble bar is in its "expanded" state, taskbar becomes stashed *
* If there are no bubbles, the bubble bar and bubble stashed handle are not shown. Additionally * the bubble bar and stashed handle are not shown on lockscreen. *
* When taskbar is in persistent or 3 button nav mode, the bubble bar is not available, and instead
* the bubbles are shown fully by WMShell in their floating mode.
*/
public class BubbleBarView extends FrameLayout {
private static final String TAG = BubbleBarView.class.getSimpleName();
// TODO: (b/273594744) calculate the amount of space we have and base the max on that
// if it's smaller than 5.
private static final int MAX_BUBBLES = 5;
private static final int ARROW_POSITION_ANIMATION_DURATION_MS = 200;
private final TaskbarActivityContext mActivityContext;
private final BubbleBarBackground mBubbleBarBackground;
// The current bounds of all the bubble bar.
private final Rect mBubbleBarBounds = new Rect();
// The amount the bubbles overlap when they are stacked in the bubble bar
private final float mIconOverlapAmount;
// The spacing between the bubbles when they are expanded in the bubble bar
private final float mIconSpacing;
// The size of a bubble in the bar
private final float mIconSize;
// The elevation of the bubbles within the bar
private final float mBubbleElevation;
// Whether the bar is expanded (i.e. the bubble activity is being displayed).
private boolean mIsBarExpanded = false;
// The currently selected bubble view.
private BubbleView mSelectedBubbleView;
// The click listener when the bubble bar is collapsed.
private View.OnClickListener mOnClickListener;
private final Rect mTempRect = new Rect();
// We don't reorder the bubbles when they are expanded as it could be jarring for the user
// this runnable will be populated with any reordering of the bubbles that should be applied
// once they are collapsed.
@Nullable
private Runnable mReorderRunnable;
public BubbleBarView(Context context) {
this(context, null);
}
public BubbleBarView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public BubbleBarView(Context context, AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
public BubbleBarView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
mActivityContext = ActivityContext.lookupContext(context);
mIconOverlapAmount = getResources().getDimensionPixelSize(R.dimen.bubblebar_icon_overlap);
mIconSpacing = getResources().getDimensionPixelSize(R.dimen.bubblebar_icon_spacing);
mIconSize = getResources().getDimensionPixelSize(R.dimen.bubblebar_icon_size);
mBubbleElevation = getResources().getDimensionPixelSize(R.dimen.bubblebar_icon_elevation);
setClipToPadding(false);
mBubbleBarBackground = new BubbleBarBackground(mActivityContext,
getResources().getDimensionPixelSize(R.dimen.bubblebar_size));
setBackgroundDrawable(mBubbleBarBackground);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
mBubbleBarBounds.left = left;
mBubbleBarBounds.top = top;
mBubbleBarBounds.right = right;
mBubbleBarBounds.bottom = bottom;
// The bubble bar handle is aligned to the bottom edge of the screen so scale towards that.
setPivotX(getWidth());
setPivotY(getHeight());
// Position the views
updateChildrenRenderNodeProperties();
}
/**
* Returns the bounds of the bubble bar.
*/
public Rect getBubbleBarBounds() {
return mBubbleBarBounds;
}
// TODO: (b/273592694) animate it
@Override
public void addView(View child, int index, ViewGroup.LayoutParams params) {
if (getChildCount() + 1 > MAX_BUBBLES) {
removeViewInLayout(getChildAt(getChildCount() - 1));
}
super.addView(child, index, params);
}
/**
* Updates the z order, positions, and badge visibility of the bubble views in the bar based
* on the expanded state.
*/
// TODO: (b/273592694) animate it
private void updateChildrenRenderNodeProperties() {
int bubbleCount = getChildCount();
final float ty = (mBubbleBarBounds.height() - mIconSize) / 2f;
for (int i = 0; i < bubbleCount; i++) {
BubbleView bv = (BubbleView) getChildAt(i);
bv.setTranslationY(ty);
if (mIsBarExpanded) {
final float tx = i * (mIconSize + mIconSpacing);
bv.setTranslationX(tx);
bv.setZ(0);
bv.showBadge();
} else {
bv.setZ((MAX_BUBBLES * mBubbleElevation) - i);
bv.setTranslationX(i * mIconOverlapAmount);
if (i > 0) {
bv.hideBadge();
} else {
bv.showBadge();
}
}
}
}
/**
* Reorders the views to match the provided list.
*/
public void reorder(List