Files
Lawnchair/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
T
mpodolian 77859d0c89 Fixed issue with expanded view incorrect position.
The expanded view was misplaced due to a race condition. This occurred
because the BubbleBarController.onBubbleBarBoundsChanged() method was
invoked while
LauncherTaskbarUIController.onTaskbarInAppDisplayProgressUpdate() was in
progress (e.g. inAppDisplayOverrideProgress value was slightly higher
than 0). The latter method is also used to shift the three navigation
buttons for IME open/collapse events.

This concurrent execution led to an incorrect bubbleBarTranslationY
value being used in the BubbleBarView.getTopToScreenBottom() method,
ultimately causing the misplacement of the expanded view.

Fixes: 416158567
Flag: EXEMPT bugfix
Test: Manual.
- Long Press Chrome icon select "bubble" adds
- Press search bar inside Chrome app bubble to show IME
- Hide IME
- Press Chrome bubble
- Press search bar inside Chrome app bubble to show IME
- Hide IME

Change-Id: I44431f0eb47c3e9e0bea0274624e5b1db0aa15c9
2025-05-16 10:05:01 -07:00

1536 lines
62 KiB
Java

/*
* 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 static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE;
import static com.android.launcher3.Utilities.mapRange;
import static com.android.launcher3.taskbar.TaskbarPinningController.PINNING_PERSISTENT;
import static com.android.launcher3.taskbar.TaskbarPinningController.PINNING_TRANSIENT;
import android.animation.Animator;
import android.animation.AnimatorSet;
import android.content.Intent;
import android.content.pm.ShortcutInfo;
import android.content.res.Resources;
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.View;
import android.widget.FrameLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.app.animation.Interpolators;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.R;
import com.android.launcher3.anim.AnimatedFloat;
import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
import com.android.launcher3.dragndrop.DragController;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.taskbar.TaskbarActivityContext;
import com.android.launcher3.taskbar.TaskbarControllers;
import com.android.launcher3.taskbar.TaskbarInsetsController;
import com.android.launcher3.taskbar.TaskbarSharedState;
import com.android.launcher3.taskbar.TaskbarStashController;
import com.android.launcher3.taskbar.bubbles.BubbleBarLocationDropTarget.BubbleBarDragListener;
import com.android.launcher3.taskbar.bubbles.animation.BubbleBarViewAnimator;
import com.android.launcher3.taskbar.bubbles.flyout.BubbleBarFlyoutController;
import com.android.launcher3.taskbar.bubbles.flyout.BubbleBarFlyoutPositioner;
import com.android.launcher3.taskbar.bubbles.flyout.FlyoutCallbacks;
import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController;
import com.android.launcher3.util.MultiPropertyFactory;
import com.android.launcher3.util.MultiValueAlpha;
import com.android.quickstep.SystemUiProxy;
import com.android.wm.shell.Flags;
import com.android.wm.shell.shared.bubbles.BubbleBarLocation;
import com.android.wm.shell.shared.bubbles.DeviceConfig;
import java.io.PrintWriter;
import java.util.List;
import java.util.Objects;
import java.util.function.Consumer;
/**
* Controller for {@link BubbleBarView}. Manages the visibility of the bubble bar as well as
* responding to changes in bubble state provided by BubbleBarController.
*/
public class BubbleBarViewController {
private static final String TAG = "BubbleBarViewController";
private static final float APP_ICON_SMALL_DP = 44f;
private static final float APP_ICON_MEDIUM_DP = 48f;
private static final float APP_ICON_LARGE_DP = 52f;
/** The dot size is defined as a percentage of the icon size. */
private static final float DOT_TO_BUBBLE_SIZE_RATIO = 0.228f;
public static final int TASKBAR_FADE_IN_DURATION_MS = 150;
public static final int TASKBAR_FADE_IN_DELAY_MS = 50;
public static final int TASKBAR_FADE_OUT_DURATION_MS = 100;
private final SystemUiProxy mSystemUiProxy;
private final TaskbarActivityContext mActivity;
private final BubbleBarView mBarView;
private int mIconSize;
private int mBubbleBarPadding;
private final int mDragElevation;
// Initialized in init.
private BubbleStashController mBubbleStashController;
private BubbleBarController mBubbleBarController;
private BubbleDragController mBubbleDragController;
private TaskbarStashController mTaskbarStashController;
private TaskbarInsetsController mTaskbarInsetsController;
private TaskbarViewPropertiesProvider mTaskbarViewPropertiesProvider;
private View.OnClickListener mBubbleClickListener;
private BubbleView.Controller mBubbleViewController;
private BubbleBarOverflow mOverflowBubble;
// These are exposed to {@link BubbleStashController} to animate for stashing/un-stashing
private final MultiValueAlpha mBubbleBarAlpha;
private final AnimatedFloat mBubbleBarBubbleAlpha = new AnimatedFloat(this::updateBubbleAlpha);
private final AnimatedFloat mBubbleBarBackgroundAlpha = new AnimatedFloat(
this::updateBackgroundAlpha);
private final AnimatedFloat mBubbleBarScaleX = new AnimatedFloat(this::updateScaleX);
private final AnimatedFloat mBubbleBarScaleY = new AnimatedFloat(this::updateScaleY);
private final AnimatedFloat mBubbleBarBackgroundScaleX = new AnimatedFloat(
this::updateBackgroundScaleX);
private final AnimatedFloat mBubbleBarBackgroundScaleY = new AnimatedFloat(
this::updateBackgroundScaleY);
private final AnimatedFloat mBubbleBarTranslationY = new AnimatedFloat(
this::updateTranslationY);
private final AnimatedFloat mBubbleOffsetY = new AnimatedFloat(
this::updateBubbleOffsetY);
private final AnimatedFloat mBubbleBarPinning = new AnimatedFloat(pinningProgress -> {
updateTranslationY();
setBubbleBarScaleAndPadding(pinningProgress);
});
private final BubbleBarDragListener mDragListener = new BubbleBarDragListener() {
@Override
public void getBubbleBarLocationHitRect(@NonNull BubbleBarLocation bubbleBarLocation,
Rect outRect) {
Point screenSize = mActivity.getScreenSize();
outRect.top = screenSize.y - mBubbleBarDropTargetSize;
outRect.bottom = screenSize.y;
if (bubbleBarLocation.isOnLeft(mBarView.isLayoutRtl())) {
outRect.left = 0;
outRect.right = mBubbleBarDropTargetSize;
} else {
outRect.left = screenSize.x - mBubbleBarDropTargetSize;
outRect.right = screenSize.x;
}
}
@Override
public void onLauncherItemDroppedOverBubbleBarDragZone(@NonNull BubbleBarLocation location,
@NonNull ItemInfo itemInfo) {
AbstractFloatingView.closeAllOpenViews(mActivity);
if (itemInfo instanceof WorkspaceItemInfo) {
ShortcutInfo shortcutInfo = ((WorkspaceItemInfo) itemInfo).getDeepShortcutInfo();
if (shortcutInfo != null) {
mSystemUiProxy.showShortcutBubble(shortcutInfo, location);
return;
}
}
Intent itemIntent = itemInfo.getIntent();
if (itemIntent != null && itemIntent.getComponent() != null) {
itemIntent.setPackage(itemIntent.getComponent().getPackageName());
mSystemUiProxy.showAppBubble(itemIntent, itemInfo.user, location);
}
}
@Override
public void onLauncherItemDraggedOutsideBubbleBarDropZone() {
onItemDraggedOutsideBubbleBarDropZone();
mSystemUiProxy.showBubbleDropTarget(/* show = */ false);
}
@Override
public void onLauncherItemDraggedOverBubbleBarDragZone(
@NonNull BubbleBarLocation location) {
onDragItemOverBubbleBarDragZone(location);
mSystemUiProxy.showBubbleDropTarget(/* show = */ true, location);
}
@NonNull
@Override
public View getDropView() {
return mBarView;
}
};
// Modified when swipe up is happening on the bubble bar or task bar.
private float mBubbleBarSwipeUpTranslationY;
// Modified when bubble bar is springing back into the stash handle.
private float mBubbleBarStashTranslationY;
// Minimum distance between the BubbleBar and the taskbar
private final int mBubbleBarTaskbarMinDistance;
// Whether the bar is hidden for a sysui state.
private boolean mHiddenForSysui;
// Whether the bar is hidden because there are no bubbles.
private boolean mHiddenForNoBubbles = true;
// Whether the bar is hidden when stashed
private boolean mHiddenForStashed;
private boolean mShouldShowEducation;
public boolean mOverflowAdded;
private boolean mWasStashedBeforeEnteringBubbleDragZone = false;
/** This field is used solely to track the bubble bar location prior to the start of the drag */
private @Nullable BubbleBarLocation mBubbleBarDragLocation;
private BubbleBarViewAnimator mBubbleBarViewAnimator;
private final FrameLayout mBubbleBarContainer;
private BubbleBarFlyoutController mBubbleBarFlyoutController;
private BubbleBarPinController mBubbleBarPinController;
private TaskbarSharedState mTaskbarSharedState;
private final BubbleBarLocationDropTarget mBubbleBarLeftDropTarget;
private final BubbleBarLocationDropTarget mBubbleBarRightDropTarget;
private final TimeSource mTimeSource = System::currentTimeMillis;
private final int mTaskbarTranslationDelta;
private final int mBubbleBarDropTargetSize;
@Nullable
private BubbleBarBoundsChangeListener mBoundsChangeListener;
public BubbleBarViewController(TaskbarActivityContext activity, BubbleBarView barView,
FrameLayout bubbleBarContainer) {
mActivity = activity;
mBarView = barView;
mBubbleBarContainer = bubbleBarContainer;
mSystemUiProxy = SystemUiProxy.INSTANCE.get(mActivity);
mBubbleBarAlpha = new MultiValueAlpha(mBarView, 1 /* num alpha channels */);
Resources res = activity.getResources();
mIconSize = res.getDimensionPixelSize(R.dimen.bubblebar_icon_size);
mBubbleBarTaskbarMinDistance = res.getDimensionPixelSize(
R.dimen.bubblebar_transient_taskbar_min_distance);
mDragElevation = res.getDimensionPixelSize(R.dimen.dragged_bubble_elevation);
mTaskbarTranslationDelta = getBubbleBarTranslationDeltaForTaskbar(activity);
if (DeviceConfig.isSmallTablet(mActivity)) {
mBubbleBarDropTargetSize = res.getDimensionPixelSize(R.dimen.drag_zone_bubble_fold);
} else {
mBubbleBarDropTargetSize = res.getDimensionPixelSize(R.dimen.drag_zone_bubble_tablet);
}
mBubbleBarLeftDropTarget = new BubbleBarLocationDropTarget(BubbleBarLocation.LEFT,
mDragListener);
mBubbleBarRightDropTarget = new BubbleBarLocationDropTarget(BubbleBarLocation.RIGHT,
mDragListener);
}
/** Initializes controller. */
public void init(TaskbarControllers controllers, BubbleControllers bubbleControllers,
TaskbarViewPropertiesProvider taskbarViewPropertiesProvider) {
mTaskbarSharedState = controllers.getSharedState();
mBubbleStashController = bubbleControllers.bubbleStashController;
mBubbleBarController = bubbleControllers.bubbleBarController;
mBubbleDragController = bubbleControllers.bubbleDragController;
mBubbleBarPinController = bubbleControllers.bubbleBarPinController;
mTaskbarStashController = controllers.taskbarStashController;
mTaskbarInsetsController = controllers.taskbarInsetsController;
mBubbleBarFlyoutController = new BubbleBarFlyoutController(
mBubbleBarContainer, createFlyoutPositioner(), createFlyoutCallbacks());
mBubbleBarViewAnimator = new BubbleBarViewAnimator(
mBarView, mBubbleStashController, mBubbleBarFlyoutController,
createBubbleBarParentViewController(), mBubbleBarController::showExpandedView,
() -> setHiddenForBubbles(false));
mTaskbarViewPropertiesProvider = taskbarViewPropertiesProvider;
onBubbleBarConfigurationChanged(/* animate= */ false);
mActivity.addOnDeviceProfileChangeListener(
dp -> onBubbleBarConfigurationChanged(/* animate= */ true));
mBubbleBarScaleY.updateValue(1f);
mBubbleClickListener = v -> onBubbleClicked((BubbleView) v);
mBubbleDragController.setupBubbleBarView(mBarView);
mOverflowBubble = bubbleControllers.bubbleCreator.createOverflow(mBarView);
if (!Flags.enableOptionalBubbleOverflow()) {
showOverflow(true);
}
if (!mBubbleStashController.isTransientTaskBar()) {
// TODO(b/380274085) for transient taskbar mode, the click is also handled by the input
// consumer. This check can be removed once b/380274085 is fixed.
mBarView.setOnClickListener(v -> animateExpanded(!mBarView.isExpanded()));
}
mBarView.addOnLayoutChangeListener(
(v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
mTaskbarInsetsController.onTaskbarOrBubblebarWindowHeightOrInsetsChanged();
if (mBoundsChangeListener != null) {
mBoundsChangeListener.onBoundsChanged();
}
});
float pinningValue = mActivity.isTransientTaskbar()
? PINNING_TRANSIENT
: PINNING_PERSISTENT;
mBubbleBarPinning.updateValue(pinningValue);
mBarView.setController(new BubbleBarView.Controller() {
@Override
public float getBubbleBarTranslationY() {
return mBubbleStashController.getTargetTranslationYForState();
}
@Override
public void onBubbleBarTouched() {
if (isAnimatingNewBubble()) {
interruptAnimationForTouch();
}
}
@Override
public void expandBubbleBar() {
BubbleBarViewController.this.animateExpanded(
/* isExpanded= */ true, /* maybeShowEdu*/ true);
}
@Override
public void dismissBubbleBar() {
onDismissAllBubbles();
}
@Override
public void updateBubbleBarLocation(BubbleBarLocation location,
@BubbleBarLocation.UpdateSource int source) {
mBubbleBarController.updateBubbleBarLocation(location, source);
}
@Override
public void setIsDragging(boolean dragging) {
mBubbleBarContainer.setElevation(dragging ? mDragElevation : 0);
}
@Override
public void onBubbleBarExpandedStateChanged(boolean expanded) {
if (expanded && !mTaskbarStashController.isStashed()) {
mTaskbarStashController.updateAndAnimateTransientTaskbar(true /* stash */,
false /* shouldBubblesFollow */);
}
}
});
mBubbleViewController = new BubbleView.Controller() {
@Override
public BubbleBarLocation getBubbleBarLocation() {
return BubbleBarViewController.this.getBubbleBarLocation();
}
@Override
public void dismiss(BubbleView bubble) {
if (bubble.getBubble() != null) {
notifySysUiBubbleDismissed(bubble.getBubble());
}
onBubbleDismissed(bubble);
}
@Override
public void collapse() {
collapseBubbleBar();
}
@Override
public void updateBubbleBarLocation(BubbleBarLocation location,
@BubbleBarLocation.UpdateSource int source) {
mBubbleBarController.updateBubbleBarLocation(location, source);
}
};
}
/** Adds bubble bar locations drop zones to the drag controller. */
public void addBubbleBarDropTargets(DragController<?> dragController) {
dragController.addDropTarget(mBubbleBarLeftDropTarget);
dragController.addDropTarget(mBubbleBarRightDropTarget);
}
/** Removes bubble bar locations drop zones to the drag controller. */
public void removeBubbleBarDropTargets(DragController<?> dragController) {
dragController.removeDropTarget(mBubbleBarLeftDropTarget);
dragController.removeDropTarget(mBubbleBarRightDropTarget);
}
/** Returns animated float property responsible for pinning transition animation. */
public AnimatedFloat getBubbleBarPinning() {
return mBubbleBarPinning;
}
private BubbleBarFlyoutPositioner createFlyoutPositioner() {
return new BubbleBarFlyoutPositioner() {
@Override
public boolean isOnLeft() {
boolean shouldRevertLocation =
mBarView.isShowingDropTarget() && isLocationUpdatedForDropTarget();
boolean isOnLeft = mBarView.getBubbleBarLocation().isOnLeft(mBarView.isLayoutRtl());
return shouldRevertLocation != isOnLeft;
}
@Override
public float getTargetTy() {
return mBarView.getTranslationY() - mBarView.getHeight();
}
@Override
@NonNull
public PointF getDistanceToCollapsedPosition() {
// the flyout animates from the selected bubble dot. calculate the distance it needs
// to translate itself to its starting position.
PointF distanceToDotCenter = mBarView.getSelectedBubbleDotDistanceFromTopLeft();
// if we're gravitating left, return the distance between the top left corner of the
// bubble bar and the bottom left corner of the dot.
// if we're gravitating right, return the distance between the top right corner of
// the bubble bar and the bottom right corner of the dot.
float distanceX = isOnLeft()
? distanceToDotCenter.x - getCollapsedSize() / 2
: mBarView.getWidth() - distanceToDotCenter.x - getCollapsedSize() / 2;
float distanceY = distanceToDotCenter.y + getCollapsedSize() / 2;
return new PointF(distanceX, distanceY);
}
@Override
public float getCollapsedSize() {
return mIconSize * DOT_TO_BUBBLE_SIZE_RATIO;
}
@Override
public int getCollapsedColor() {
return mBarView.getSelectedBubbleDotColor();
}
@Override
public float getCollapsedElevation() {
return mBarView.getBubbleElevation();
}
@Override
public float getDistanceToRevealTriangle() {
return getDistanceToCollapsedPosition().y - mBarView.getPointerSize();
}
};
}
private FlyoutCallbacks createFlyoutCallbacks() {
return new FlyoutCallbacks() {
@Override
public void flyoutClicked() {
interruptAnimationForTouch();
animateExpanded(/* isExpanded= */ true, /* maybeShowEdu*/ true);
}
};
}
private BubbleBarParentViewHeightUpdateNotifier createBubbleBarParentViewController() {
return new BubbleBarParentViewHeightUpdateNotifier() {
@Override
public void updateTopBoundary() {
mActivity.setTaskbarWindowForAnimatingBubble();
}
};
}
private void onBubbleClicked(BubbleView bubbleView) {
if (mBubbleBarPinning.isAnimating()) return;
bubbleView.markSeen();
BubbleBarItem bubble = bubbleView.getBubble();
if (bubble == null) {
Log.e(TAG, "bubble click listener, bubble was null");
}
final String currentlySelected = mBubbleBarController.getSelectedBubbleKey();
if (mBarView.isExpanded() && Objects.equals(bubble.getKey(), currentlySelected)) {
// Tapping the currently selected bubble while expanded collapses the view.
collapseBubbleBar();
} else {
mBubbleBarController.showAndSelectBubble(bubble);
}
}
/** Interrupts the running animation for a touch event on the bubble bar or flyout. */
private void interruptAnimationForTouch() {
mBubbleBarViewAnimator.interruptForTouch();
mBubbleStashController.onNewBubbleAnimationInterrupted(false, mBarView.getTranslationY());
}
private void collapseBubbleBar() {
animateExpanded(false);
mBubbleStashController.stashBubbleBar();
}
/** Notifies that the stash state is changing. */
public void onStashStateChanging() {
if (isAnimatingNewBubble()) {
mBubbleBarViewAnimator.onStashStateChangingWhileAnimating();
}
}
/** Shows the education view if it was previously requested. */
private boolean maybeShowEduView() {
if (mShouldShowEducation) {
mShouldShowEducation = false;
// Get the bubble bar bounds on screen
Rect bounds = new Rect();
mBarView.getBoundsOnScreen(bounds);
// Calculate user education reference position in Screen coordinates
Point position = new Point(bounds.centerX(), bounds.top);
// Show user education relative to the reference point
mSystemUiProxy.showUserEducation(position);
return true;
}
return false;
}
/** Notifies that the IME became visible. */
public void onImeVisible() {
if (isAnimatingNewBubble()) {
mBubbleBarViewAnimator.interruptForIme();
}
}
//
// The below animators are exposed to BubbleStashController so it can manage the stashing
// animation.
//
public MultiPropertyFactory<View> getBubbleBarAlpha() {
return mBubbleBarAlpha;
}
public AnimatedFloat getBubbleBarBubbleAlpha() {
return mBubbleBarBubbleAlpha;
}
public AnimatedFloat getBubbleBarBackgroundAlpha() {
return mBubbleBarBackgroundAlpha;
}
public AnimatedFloat getBubbleBarScaleX() {
return mBubbleBarScaleX;
}
public AnimatedFloat getBubbleBarScaleY() {
return mBubbleBarScaleY;
}
public AnimatedFloat getBubbleBarBackgroundScaleX() {
return mBubbleBarBackgroundScaleX;
}
public AnimatedFloat getBubbleBarBackgroundScaleY() {
return mBubbleBarBackgroundScaleY;
}
public AnimatedFloat getBubbleBarTranslationY() {
return mBubbleBarTranslationY;
}
public AnimatedFloat getBubbleOffsetY() {
return mBubbleOffsetY;
}
public float getBubbleBarCollapsedWidth() {
return mBarView.collapsedWidth();
}
public float getBubbleBarCollapsedHeight() {
return mBarView.getBubbleBarCollapsedHeight();
}
/** Returns the bubble bar arrow height.*/
public float getBubbleBarArrowHeight() {
return mBarView.getArrowHeight();
}
/**
* @see BubbleBarView#getRelativePivotX()
*/
public float getBubbleBarRelativePivotX() {
return mBarView.getRelativePivotX();
}
/**
* @see BubbleBarView#getRelativePivotY()
*/
public float getBubbleBarRelativePivotY() {
return mBarView.getRelativePivotY();
}
/**
* @see BubbleBarView#setRelativePivot(float, float)
*/
public void setBubbleBarRelativePivot(float x, float y) {
mBarView.setRelativePivot(x, y);
}
/**
* Whether the bubble bar is visible or not.
*/
public boolean isBubbleBarVisible() {
return mBarView.getVisibility() == VISIBLE;
}
/** Whether the bubble bar has bubbles. */
public boolean hasBubbles() {
return mBarView.getBubbleChildCount() > 0;
}
/**
* @return current {@link BubbleBarLocation}
*/
public BubbleBarLocation getBubbleBarLocation() {
return mBarView.getBubbleBarLocation();
}
/**
* @return the max collapsed width for the bubble bar.
*/
public float getCollapsedWidthWithMaxVisibleBubbles() {
return mBarView.getCollapsedWidthWithMaxVisibleBubbles();
}
/**
* @return {@code true} if bubble bar is on the left edge of the screen, {@code false} if on
* the right
*/
public boolean isBubbleBarOnLeft() {
return mBarView.getBubbleBarLocation().isOnLeft(mBarView.isLayoutRtl());
}
/**
* Update bar {@link BubbleBarLocation}
*/
public void setBubbleBarLocation(BubbleBarLocation bubbleBarLocation) {
mBarView.setBubbleBarLocation(bubbleBarLocation);
}
/**
* Animate bubble bar to the given location. The location change is transient. It does not
* update the state of the bubble bar.
* To update bubble bar pinned location, use {@link #setBubbleBarLocation(BubbleBarLocation)}.
*/
public void animateBubbleBarLocation(BubbleBarLocation bubbleBarLocation) {
mBarView.animateToBubbleBarLocation(bubbleBarLocation);
}
/** Return animator for animating bubble bar in. */
public Animator animateBubbleBarLocationIn(BubbleBarLocation fromLocation,
BubbleBarLocation toLocation) {
return mBarView.animateToBubbleBarLocationIn(fromLocation, toLocation);
}
/** Return animator for animating bubble bar out. */
public Animator animateBubbleBarLocationOut(BubbleBarLocation toLocation) {
return mBarView.animateToBubbleBarLocationOut(toLocation);
}
/** Returns whether the Bubble Bar is currently displaying a drop target. */
public boolean isShowingDropTarget() {
return mBarView.isShowingDropTarget();
}
/**
* Notifies the controller that a drag event is over the Bubble Bar drop zone. The controller
* will display the appropriate drop target and enter drop target mode. The controller will also
* update the return value of {@link #isLocationUpdatedForDropTarget()} to true if location was
* updated.
*/
public void onDragItemOverBubbleBarDragZone(@NonNull BubbleBarLocation bubbleBarLocation) {
mBubbleBarDragLocation = bubbleBarLocation;
mBarView.showDropTarget(/* isDropTarget = */ true);
mWasStashedBeforeEnteringBubbleDragZone = hasBubbles()
&& mBubbleStashController.isStashed();
if (mWasStashedBeforeEnteringBubbleDragZone) {
// bubble bar is stashed - un-stash at drag location
mBubbleStashController.showBubbleBarAtLocation(
/* fromLocation = */ getBubbleBarLocation(),
/* toLocation = */ mBubbleBarDragLocation
);
} else if (hasBubbles()) {
if (isLocationUpdatedForDropTarget()) {
// bubble bar has bubbles and location is changed - animate bar to the opposite side
animateBubbleBarLocation(bubbleBarLocation);
}
} else {
// bubble bar has no bubbles flow just show the empty drop target
mBubbleBarPinController.showDropTarget(bubbleBarLocation);
}
}
/**
* Returns {@code true} if location was updated after most recent
* {@link #onDragItemOverBubbleBarDragZone}}.
*/
public boolean isLocationUpdatedForDropTarget() {
if (mBubbleBarDragLocation == null) {
return false;
}
boolean isRtl = mBarView.isLayoutRtl();
return getBubbleBarLocation().isOnLeft(isRtl)
!= mBubbleBarDragLocation.isOnLeft(isRtl);
}
/**
* Notifies the controller that the drag event is outside the Bubble Bar drop zone.
* This will hide the drop target zone if there are no bubbles or return the
* Bubble Bar to its original location. The controller will also exit drop target
* mode and reset the value returned from {@link #isLocationUpdatedForDropTarget()} to false.
*/
public void onItemDraggedOutsideBubbleBarDropZone() {
if (!isShowingDropTarget()) {
return;
}
if (mWasStashedBeforeEnteringBubbleDragZone && mBubbleBarDragLocation != null) {
// bubble bar was stashed - stash at original location
mBubbleStashController.stashBubbleBarToLocation(
/* fromLocation = */ mBubbleBarDragLocation,
/* toLocation = */ getBubbleBarLocation()
);
} else if (hasBubbles()) {
if (isLocationUpdatedForDropTarget()) {
// bubble bar has bubbles and location was changed - return to the original
// location
animateBubbleBarLocation(getBubbleBarLocation());
}
}
onItemDragCompleted();
}
/**
* Notifies the controller that the drag has completed over the Bubble Bar drop zone.
* The controller will hide the drop target if there are no bubbles and exit drop target mode.
*/
public void onItemDragCompleted() {
mBarView.showDropTarget(/* isDropTarget = */ false);
mBubbleBarPinController.hideDropTarget();
mWasStashedBeforeEnteringBubbleDragZone = false;
mBubbleBarDragLocation = null;
}
/**
* The bounds of the bubble bar.
*/
public Rect getBubbleBarBounds() {
return mBarView.getBubbleBarBounds();
}
/** Returns the bounds of the flyout view if it exists, or {@code null} otherwise. */
@Nullable
public Rect getFlyoutBounds() {
return mBubbleBarFlyoutController.getFlyoutBounds();
}
/** Checks that bubble bar is visible and that the motion event is within bounds. */
public boolean isEventOverBubbleBar(MotionEvent event) {
if (!isBubbleBarVisible()) return false;
final Rect bounds = getBubbleBarBounds();
final int bubbleBarTopOnScreen =
mActivity.getScreenSize().y - mBarView.getTopToScreenBottom();
final float x = event.getX();
return event.getRawY() >= bubbleBarTopOnScreen && x >= bounds.left && x <= bounds.right;
}
/** Whether a new bubble is animating. */
public boolean isAnimatingNewBubble() {
return mBubbleBarViewAnimator != null && mBubbleBarViewAnimator.isAnimating();
}
public boolean isNewBubbleAnimationRunningOrPending() {
return mBubbleBarViewAnimator != null && mBubbleBarViewAnimator.hasAnimation();
}
/** The horizontal margin of the bubble bar from the edge of the screen. */
public int getHorizontalMargin() {
return mBarView.getHorizontalMargin();
}
/**
* When the bubble bar is not stashed, it can be collapsed (the icons are in a stack) or
* expanded (the icons are in a row). This indicates whether the bubble bar is expanded.
*/
public boolean isExpanded() {
return mBarView.isExpanded();
}
/**
* Whether the motion event is within the bounds of the bubble bar.
*/
public boolean isEventOverAnyItem(MotionEvent ev) {
return mBarView.isEventOverAnyItem(ev);
}
//
// Visibility of the bubble bar
//
/**
* Returns whether the bubble bar is hidden because there are no bubbles.
*/
public boolean isHiddenForNoBubbles() {
return mHiddenForNoBubbles;
}
/** Returns maximum height of the bubble bar with the flyout view. */
public int getBubbleBarWithFlyoutMaximumHeight() {
if (!hasBubbles() && !isAnimatingNewBubble()) return 0;
int bubbleBarTopOnHome = (int) (mBubbleStashController.getBubbleBarVerticalCenterForHome()
+ mBarView.getBubbleBarCollapsedHeight() / 2 + mBarView.getArrowHeight());
if (isAnimatingNewBubble()) {
if (mTaskbarStashController.isInApp() && mBubbleStashController.getHasHandleView()) {
// when animating a bubble in an app, the bubble bar will be higher than its
// position on home
float bubbleBarTopDistanceFromBottom =
-mBubbleStashController.getBubbleBarTranslationYForTaskbar()
+ mBarView.getHeight();
return (int) bubbleBarTopDistanceFromBottom
+ mBubbleBarFlyoutController.getMaximumFlyoutHeight();
}
return bubbleBarTopOnHome + mBubbleBarFlyoutController.getMaximumFlyoutHeight();
} else {
return bubbleBarTopOnHome;
}
}
/**
* Sets whether the bubble bar should be hidden because there are no bubbles.
*/
public void setHiddenForBubbles(boolean hidden) {
if (mHiddenForNoBubbles != hidden) {
mHiddenForNoBubbles = hidden;
if (hidden) {
mBarView.dismiss(() -> {
updateVisibilityForStateChange();
mBarView.animateExpanded(false);
adjustTaskbarAndHotseatToBubbleBarState(/* isBubbleBarExpanded= */ false);
mActivity.bubbleBarVisibilityChanged(/* isVisible= */ false);
});
} else {
updateVisibilityForStateChange();
mActivity.bubbleBarVisibilityChanged(/* isVisible= */ true);
}
}
}
/** Sets a callback that updates the selected bubble after the bubble bar collapses. */
public void setUpdateSelectedBubbleAfterCollapse(
Consumer<String> updateSelectedBubbleAfterCollapse) {
mBarView.setUpdateSelectedBubbleAfterCollapse(updateSelectedBubbleAfterCollapse);
}
/** Returns whether the bubble bar should be hidden because of the current sysui state. */
boolean isHiddenForSysui() {
return mHiddenForSysui;
}
/**
* Sets whether the bubble bar should be hidden due to SysUI state (e.g. on lockscreen).
*/
public void setHiddenForSysui(boolean hidden) {
if (mHiddenForSysui != hidden) {
mHiddenForSysui = hidden;
updateVisibilityForStateChange();
}
}
/** Sets whether the bubble bar should be hidden due to stashed state */
public void setHiddenForStashed(boolean hidden) {
if (mHiddenForStashed != hidden) {
mHiddenForStashed = hidden;
updateVisibilityForStateChange();
}
}
private void updateVisibilityForStateChange() {
boolean hiddenForStashedAndNotAnimating =
mHiddenForStashed && !mBubbleBarViewAnimator.isAnimating();
if (mHiddenForSysui || mHiddenForNoBubbles || hiddenForStashedAndNotAnimating) {
//TODO(b/404870188) this visibility change cause search view drag misbehavior
mBarView.setVisibility(INVISIBLE);
} else {
mBarView.setVisibility(VISIBLE);
}
}
/**
* Returns the translation X of the transient taskbar according to the bubble bar location
* regardless of the current taskbar mode.
*/
public int getTransientTaskbarTranslationXForBubbleBar(BubbleBarLocation location) {
int taskbarShift = 0;
if (!isBubbleBarVisible() || mTaskbarViewPropertiesProvider == null) return taskbarShift;
Rect taskbarViewBounds = mTaskbarViewPropertiesProvider.getTaskbarViewBounds();
if (taskbarViewBounds.isEmpty()) return taskbarShift;
int actualDistance =
getDistanceBetweenTransientTaskbarAndBubbleBar(location, taskbarViewBounds);
if (actualDistance < mBubbleBarTaskbarMinDistance) {
taskbarShift = mBubbleBarTaskbarMinDistance - actualDistance;
if (!location.isOnLeft(mBarView.isLayoutRtl())) {
taskbarShift = -taskbarShift;
}
}
return taskbarShift;
}
private int getDistanceBetweenTransientTaskbarAndBubbleBar(BubbleBarLocation location,
Rect taskbarViewBounds) {
Resources res = mActivity.getResources();
DeviceProfile transientDp = mActivity.getTransientTaskbarDeviceProfile();
int transientIconSize = getBubbleBarIconSizeFromDeviceProfile(res, transientDp);
int transientPadding = getBubbleBarPaddingFromDeviceProfile(res, transientDp);
int transientWidthWithMargin = (int) (mBarView.getCollapsedWidthForIconSizeAndPadding(
transientIconSize, transientPadding) + mBarView.getHorizontalMargin());
int distance;
if (location.isOnLeft(mBarView.isLayoutRtl())) {
distance = taskbarViewBounds.left - transientWidthWithMargin;
} else {
int displayWidth = res.getDisplayMetrics().widthPixels;
int bubbleBarLeft = displayWidth - transientWidthWithMargin;
distance = bubbleBarLeft - taskbarViewBounds.right;
}
return distance;
}
//
// Modifying view related properties.
//
/** Notifies controller of configuration change, so bubble bar can be adjusted */
public void onBubbleBarConfigurationChanged(boolean animate) {
int newIconSize;
int newPadding;
Resources res = mActivity.getResources();
if (mBubbleStashController.isBubblesShowingOnHome()
|| mBubbleStashController.isTransientTaskBar()) {
newIconSize = getBubbleBarIconSizeFromDeviceProfile(res);
newPadding = getBubbleBarPaddingFromDeviceProfile(res);
} else {
// the bubble bar is shown inside the persistent task bar, use preset sizes
newIconSize = res.getDimensionPixelSize(R.dimen.bubblebar_icon_size_persistent_taskbar);
newPadding = res.getDimensionPixelSize(
R.dimen.bubblebar_icon_spacing_persistent_taskbar);
}
updateBubbleBarIconSizeAndPadding(newIconSize, newPadding, animate);
}
private int getBubbleBarIconSizeFromDeviceProfile(Resources res) {
return getBubbleBarIconSizeFromDeviceProfile(res, mActivity.getDeviceProfile());
}
private int getBubbleBarIconSizeFromDeviceProfile(Resources res, DeviceProfile deviceProfile) {
DisplayMetrics dm = res.getDisplayMetrics();
float smallIconSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
APP_ICON_SMALL_DP, dm);
float mediumIconSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
APP_ICON_MEDIUM_DP, dm);
float smallMediumThreshold = (smallIconSize + mediumIconSize) / 2f;
int taskbarIconSize = deviceProfile.taskbarIconSize;
return taskbarIconSize <= smallMediumThreshold
? res.getDimensionPixelSize(R.dimen.bubblebar_icon_size_small) :
res.getDimensionPixelSize(R.dimen.bubblebar_icon_size);
}
private int getBubbleBarPaddingFromDeviceProfile(Resources res) {
return getBubbleBarPaddingFromDeviceProfile(res, mActivity.getDeviceProfile());
}
private int getBubbleBarPaddingFromDeviceProfile(Resources res, DeviceProfile deviceProfile) {
DisplayMetrics dm = res.getDisplayMetrics();
float mediumIconSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
APP_ICON_MEDIUM_DP, dm);
float largeIconSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
APP_ICON_LARGE_DP, dm);
float mediumLargeThreshold = (mediumIconSize + largeIconSize) / 2f;
return deviceProfile.taskbarIconSize >= mediumLargeThreshold
? res.getDimensionPixelSize(R.dimen.bubblebar_icon_spacing_large) :
res.getDimensionPixelSize(R.dimen.bubblebar_icon_spacing);
}
private void updateBubbleBarIconSizeAndPadding(int iconSize, int padding, boolean animate) {
if (mIconSize == iconSize && mBubbleBarPadding == padding) return;
mIconSize = iconSize;
mBubbleBarPadding = padding;
if (animate) {
mBarView.animateBubbleBarIconSize(iconSize, padding);
} else {
mBarView.setIconSizeAndPadding(iconSize, padding);
}
}
/**
* Sets the translation of the bubble bar during the swipe up gesture.
*/
public void setTranslationYForSwipe(float transY) {
mBubbleBarSwipeUpTranslationY = transY;
updateTranslationY();
}
/**
* Sets the translation of the bubble bar during the stash animation.
*/
public void setTranslationYForStash(float transY) {
mBubbleBarStashTranslationY = transY;
updateTranslationY();
}
private void updateTranslationY() {
mBarView.setTranslationY(mBubbleBarTranslationY.value + mBubbleBarSwipeUpTranslationY
+ mBubbleBarStashTranslationY + getBubbleBarTranslationYForTaskbarPinning());
}
/** Computes translation y for taskbar pinning. */
private float getBubbleBarTranslationYForTaskbarPinning() {
if (mTaskbarSharedState == null) return 0f;
float pinningProgress = mBubbleBarPinning.value;
if (mTaskbarSharedState.startTaskbarVariantIsTransient) {
return mapRange(pinningProgress, /* min = */ 0f, mTaskbarTranslationDelta);
} else {
return mapRange(pinningProgress, -mTaskbarTranslationDelta, /* max = */ 0f);
}
}
private void setBubbleBarScaleAndPadding(float pinningProgress) {
Resources res = mActivity.getResources();
// determine icon scale for pinning
int persistentIconSize = res.getDimensionPixelSize(
R.dimen.bubblebar_icon_size_persistent_taskbar);
int transientIconSize = getBubbleBarIconSizeFromDeviceProfile(res,
mActivity.getTransientTaskbarDeviceProfile());
float pinningIconSize = mapRange(pinningProgress, transientIconSize, persistentIconSize);
// determine bubble bar padding for pinning
int persistentPadding = res.getDimensionPixelSize(
R.dimen.bubblebar_icon_spacing_persistent_taskbar);
int transientPadding = getBubbleBarPaddingFromDeviceProfile(res,
mActivity.getTransientTaskbarDeviceProfile());
float pinningPadding = mapRange(pinningProgress, transientPadding, persistentPadding);
mBarView.setIconSizeAndPaddingForPinning(pinningIconSize, pinningPadding);
}
/**
* Calculates the vertical difference in the bubble bar positions for pinned and transient
* taskbar modes.
*/
private int getBubbleBarTranslationDeltaForTaskbar(TaskbarActivityContext activity) {
Resources res = activity.getResources();
int persistentBubbleSize = res
.getDimensionPixelSize(R.dimen.bubblebar_icon_size_persistent_taskbar);
int persistentSpacingSize = res
.getDimensionPixelSize(R.dimen.bubblebar_icon_spacing_persistent_taskbar);
int persistentBubbleBarSize = persistentBubbleSize + persistentSpacingSize * 2;
int persistentTaskbarHeight = activity.getPersistentTaskbarDeviceProfile().taskbarHeight;
int persistentBubbleBarY = (persistentTaskbarHeight - persistentBubbleBarSize) / 2;
int transientBubbleBarY = activity.getTransientTaskbarDeviceProfile().taskbarBottomMargin;
return transientBubbleBarY - persistentBubbleBarY;
}
private void updateScaleX(float scale) {
mBarView.setScaleX(scale);
}
private void updateScaleY(float scale) {
mBarView.setScaleY(scale);
}
private void updateBackgroundScaleX(float scale) {
mBarView.setBackgroundScaleX(scale);
}
private void updateBackgroundScaleY(float scale) {
mBarView.setBackgroundScaleY(scale);
}
private void updateBubbleAlpha(float alpha) {
mBarView.setBubbleAlpha(alpha);
}
private void updateBubbleOffsetY(float transY) {
mBarView.setBubbleOffsetY(transY);
}
private void updateBackgroundAlpha(float alpha) {
mBarView.setBackgroundAlpha(alpha);
}
//
// Manipulating the specific bubble views in the bar
//
/**
* Removes the provided bubble from the bubble bar.
*/
public void removeBubble(BubbleBarBubble b) {
if (b != null) {
mBarView.removeBubble(b.getView());
b.getView().setController(null);
} else {
Log.w(TAG, "removeBubble, bubble was null!");
}
}
/** Adds a new bubble and removes an old bubble at the same time. */
public void addBubbleAndRemoveBubble(BubbleBarBubble addedBubble, BubbleBarBubble removedBubble,
@Nullable BubbleBarBubble bubbleToSelect, boolean isExpanding,
boolean suppressAnimation, boolean addOverflowToo) {
BubbleView bubbleToSelectView = bubbleToSelect == null ? null : bubbleToSelect.getView();
mBarView.addBubbleAndRemoveBubble(addedBubble.getView(), removedBubble.getView(),
bubbleToSelectView, addOverflowToo ? () -> showOverflow(true) : null);
addedBubble.getView().setOnClickListener(mBubbleClickListener);
addedBubble.getView().setController(mBubbleViewController);
removedBubble.getView().setController(null);
mBubbleDragController.setupBubbleView(addedBubble.getView());
if (!suppressAnimation) {
animateBubbleNotification(addedBubble, isExpanding, /* isUpdate= */ false);
}
}
/** Whether the overflow view is added to the bubble bar. */
public boolean isOverflowAdded() {
return mOverflowAdded;
}
/** Shows or hides the overflow view. */
public void showOverflow(boolean showOverflow) {
if (mOverflowAdded == showOverflow) return;
mOverflowAdded = showOverflow;
if (mOverflowAdded) {
mBarView.addBubble(mOverflowBubble.getView(), /* suppressAnimation= */ true);
mOverflowBubble.getView().setOnClickListener(mBubbleClickListener);
mOverflowBubble.getView().setController(mBubbleViewController);
} else {
mBarView.removeBubble(mOverflowBubble.getView());
mOverflowBubble.getView().setOnClickListener(null);
mOverflowBubble.getView().setController(null);
}
}
/** Adds the overflow view to the bubble bar while animating a view away. */
public void addOverflowAndRemoveBubble(BubbleBarBubble removedBubble,
@Nullable BubbleBarBubble bubbleToSelect) {
if (mOverflowAdded) return;
mOverflowAdded = true;
BubbleView bubbleToSelectView = bubbleToSelect == null ? null : bubbleToSelect.getView();
mBarView.addBubbleAndRemoveBubble(mOverflowBubble.getView(), removedBubble.getView(),
bubbleToSelectView, null /* onEndRunnable */);
mOverflowBubble.getView().setOnClickListener(mBubbleClickListener);
mOverflowBubble.getView().setController(mBubbleViewController);
removedBubble.getView().setController(null);
}
/** Removes the overflow view to the bubble bar while animating a view in. */
public void removeOverflowAndAddBubble(BubbleBarBubble addedBubble,
@Nullable BubbleBarBubble bubbleToSelect) {
if (!mOverflowAdded) return;
mOverflowAdded = false;
BubbleView bubbleToSelectView = bubbleToSelect == null ? null : bubbleToSelect.getView();
mBarView.addBubbleAndRemoveBubble(addedBubble.getView(), mOverflowBubble.getView(),
bubbleToSelectView, null /* onEndRunnable */);
addedBubble.getView().setOnClickListener(mBubbleClickListener);
addedBubble.getView().setController(mBubbleViewController);
mOverflowBubble.getView().setController(null);
}
/**
* Adds the provided bubble to the bubble bar.
*/
public void addBubble(BubbleBarItem b,
boolean isExpanding,
boolean suppressAnimation,
@Nullable BubbleBarBubble bubbleToSelect
) {
if (b != null) {
BubbleView bubbleToSelectView =
bubbleToSelect == null ? null : bubbleToSelect.getView();
addBubbleView(b.getView(), suppressAnimation, bubbleToSelectView);
if (suppressAnimation || !(b instanceof BubbleBarBubble bubble)) {
// the bubble bar and handle are initialized as part of the first bubble animation.
// if the animation is suppressed, immediately stash or show the bubble bar to
// ensure they've been initialized.
if (mTaskbarStashController.isInApp()
&& mBubbleStashController.isTransientTaskBar()
&& mTaskbarStashController.isStashed()
&& !isExpanded()) {
mBubbleStashController.stashBubbleBarImmediate();
} else {
mBubbleStashController.showBubbleBarImmediate();
}
return;
}
animateBubbleNotification(bubble, isExpanding, /* isUpdate= */ false);
} else {
Log.w(TAG, "addBubble, bubble was null!");
}
}
private void addBubbleView(BubbleView bubbleView, boolean suppressAnimation,
BubbleView selectedBubbleView) {
mBarView.addBubble(bubbleView, selectedBubbleView, suppressAnimation);
bubbleView.setOnClickListener(mBubbleClickListener);
mBubbleDragController.setupBubbleView(bubbleView);
bubbleView.setController(mBubbleViewController);
}
/**
* Restore a previous bubble that is stored in {@link TaskbarSharedState}.
*/
public void restoreBubble(BubbleBarItem b) {
addBubbleView(b.getView(), /* suppressAnimation= */ true, /* bubbleToSelectView= */ null);
}
/** Animates the bubble bar to notify the user about a bubble change. */
public void animateBubbleNotification(BubbleBarBubble bubble, boolean isExpanding,
boolean isUpdate) {
// if we're not already animating another bubble, update the dot visibility. otherwise the
// the dot will be handled as part of the animation.
if (!mBubbleBarViewAnimator.isAnimating()) {
bubble.getView().updateDotVisibility(
/* animate= */ !mBubbleStashController.isStashed());
}
// if we're expanded, don't animate the bubble bar.
if (isExpanded()) {
return;
}
boolean isInApp = mTaskbarStashController.isInApp();
// if this is the first bubble, animate to the initial state.
if (mBarView.getBubbleChildCount() == 1 && !isUpdate) {
// If a drop target is visible and the first bubble is added, hide the empty drop target
if (mBarView.isShowingDropTarget()) {
mBubbleBarPinController.hideDropTarget();
}
mBubbleBarViewAnimator.animateToInitialState(bubble, isInApp, isExpanding,
mBarView.isShowingDropTarget());
return;
}
// if we're not stashed or we're in persistent taskbar, animate for collapsed state.
boolean animateForCollapsed = !mBubbleStashController.isStashed()
|| !mBubbleStashController.isTransientTaskBar();
if (animateForCollapsed) {
mBubbleBarViewAnimator.animateBubbleBarForCollapsed(bubble, isExpanding);
return;
}
if (isInApp && mBubbleStashController.getHasHandleView()) {
mBubbleBarViewAnimator.animateBubbleInForStashed(bubble, isExpanding);
}
}
/**
* Reorders the bubbles based on the provided list.
*/
public void reorderBubbles(List<BubbleBarBubble> newOrder) {
List<BubbleView> viewList = newOrder.stream().filter(Objects::nonNull)
.map(BubbleBarBubble::getView).toList();
mBarView.reorder(viewList);
}
/**
* Updates the selected bubble.
*/
public void updateSelectedBubble(BubbleBarItem newlySelected) {
mBarView.setSelectedBubble(newlySelected.getView());
}
/** @see #animateExpanded(boolean, boolean) */
public void animateExpanded(boolean isExpanded) {
animateExpanded(isExpanded, /* maybeShowEdu= */ false);
}
/**
* Sets whether the bubble bar should be animated to expanded state (not unstashed, but have
* the contents within it expanded). This method notifies SystemUI that the bubble bar is
* expanded and showing a selected bubble. This method should ONLY be called from UI events
* originating from Launcher.
*
* @param isExpanded whether the bar should be expanded
* @param maybeShowEdu whether we should show the edu view before expanding
*/
public void animateExpanded(boolean isExpanded, boolean maybeShowEdu) {
// if we're trying to expand try showing the edu view instead
if (maybeShowEdu && isExpanded && !mBarView.isExpanded() && maybeShowEduView()) {
return;
}
if (!mBubbleBarPinning.isAnimating() && isExpanded != mBarView.isExpanded()) {
mBarView.animateExpanded(isExpanded);
adjustTaskbarAndHotseatToBubbleBarState(isExpanded);
if (!isExpanded) {
mSystemUiProxy.collapseBubbles();
} else {
mBubbleBarController.showSelectedBubble();
}
}
}
/**
* Hides the persistent taskbar if it is going to intersect with the expanded bubble bar if in
* app or overview.
*/
private void adjustTaskbarAndHotseatToBubbleBarState(boolean isBubbleBarExpanded) {
if (!mBubbleStashController.isBubblesShowingOnHome()
&& !mBubbleStashController.isTransientTaskBar()) {
boolean hideTaskbar = isBubbleBarExpanded && isIntersectingTaskbar();
Animator taskbarAlphaAnimator = mTaskbarViewPropertiesProvider.getIconsAlpha()
.animateToValue(hideTaskbar ? 0 : 1);
taskbarAlphaAnimator.setDuration(hideTaskbar
? TASKBAR_FADE_OUT_DURATION_MS : TASKBAR_FADE_IN_DURATION_MS);
if (!hideTaskbar) {
taskbarAlphaAnimator.setStartDelay(TASKBAR_FADE_IN_DELAY_MS);
}
taskbarAlphaAnimator.setInterpolator(Interpolators.LINEAR);
taskbarAlphaAnimator.start();
}
}
/** Return {@code true} if expanded bubble bar would intersect the taskbar. */
public boolean isIntersectingTaskbar() {
if (mBarView.isExpanding() || mBarView.isExpanded()) {
Rect taskbarViewBounds = mTaskbarViewPropertiesProvider.getTaskbarViewBounds();
return mBarView.getBubbleBarExpandedBounds().intersect(taskbarViewBounds);
} else {
return false;
}
}
/**
* Sets whether the bubble bar should be expanded. This method is used in response to UI events
* from SystemUI.
*/
public void setExpandedFromSysui(boolean isExpanded, boolean animate) {
if (isNewBubbleAnimationRunningOrPending() && isExpanded) {
mBubbleBarViewAnimator.expandedWhileAnimating();
return;
}
if (animate) {
if (!isExpanded) {
mBubbleStashController.stashBubbleBar();
} else {
mBubbleStashController.showBubbleBar(true /* expand the bubbles */);
}
} else {
if (!isExpanded) {
mBubbleStashController.stashBubbleBarImmediate();
} else {
mBubbleStashController.showBubbleBarImmediate();
mBarView.setExpanded(true);
adjustTaskbarAndHotseatToBubbleBarState(true);
}
}
}
/**
* Stores a request to show the education view for later processing when appropriate.
*
* @see #maybeShowEduView()
*/
public void prepareToShowEducation() {
mShouldShowEducation = true;
}
/**
* Updates the dragged bubble view in the bubble bar view, and notifies SystemUI
* that a bubble is being dragged to dismiss.
*
* @param bubbleView dragged bubble view
*/
public void onBubbleDragStart(@NonNull BubbleView bubbleView) {
if (bubbleView.getBubble() == null) return;
mSystemUiProxy.startBubbleDrag(bubbleView.getBubble().getKey());
mBarView.setDraggedBubble(bubbleView);
}
/**
* Notifies SystemUI to expand the selected bubble when the bubble is released.
*/
public void onBubbleDragRelease(BubbleBarLocation location) {
mSystemUiProxy.stopBubbleDrag(location, mBarView.getTopToScreenBottom());
}
/** Handle given bubble being dismissed */
public void onBubbleDismissed(BubbleView bubble) {
mBubbleBarController.onBubbleDismissed(bubble);
mBarView.removeBubble(bubble);
}
/**
* Notifies {@link BubbleBarView} that drag and all animations are finished.
*/
public void onBubbleDragEnd() {
mBarView.setDraggedBubble(null);
}
/** Notifies that dragging the bubble bar ended. */
public void onBubbleBarDragEnd() {
// we may have changed the bubble bar translation Y value from the value it had at the
// beginning of the drag, so update the translation Y animator state
mBubbleBarTranslationY.updateValue(mBarView.getTranslationY());
}
/**
* Get translation for bubble bar when drag is released.
*
* @see BubbleBarView#getBubbleBarDragReleaseTranslation(PointF, BubbleBarLocation)
*/
public PointF getBubbleBarDragReleaseTranslation(PointF initialTranslation,
BubbleBarLocation location) {
return mBarView.getBubbleBarDragReleaseTranslation(initialTranslation, location);
}
/**
* Get translation for bubble view when drag is released.
*
* @see BubbleBarView#getDraggedBubbleReleaseTranslation(PointF, BubbleBarLocation)
*/
public PointF getDraggedBubbleReleaseTranslation(PointF initialTranslation,
BubbleBarLocation location) {
if (location == mBarView.getBubbleBarLocation()) {
return initialTranslation;
}
return mBarView.getDraggedBubbleReleaseTranslation(initialTranslation, location);
}
/**
* Notify SystemUI that the given bubble has been dismissed.
*/
public void notifySysUiBubbleDismissed(@NonNull BubbleBarItem bubble) {
mSystemUiProxy.dragBubbleToDismiss(bubble.getKey(), mTimeSource.currentTimeMillis());
}
/**
* Called when bubble stack was dismissed
*/
public void onDismissAllBubbles() {
mSystemUiProxy.removeAllBubbles();
}
/** Removes all existing bubble views */
public void removeAllBubbles() {
mOverflowAdded = false;
mBarView.removeAllViews();
}
/** Returns the view index of the existing bubble */
public int bubbleViewIndex(View bubbleView) {
return mBarView.indexOfChild(bubbleView);
}
/**
* Set listener to be notified when bubble bar bounds have changed
*/
public void setBoundsChangeListener(@Nullable BubbleBarBoundsChangeListener listener) {
mBoundsChangeListener = listener;
}
/** Called when the controller is destroyed. */
public void onDestroy() {
adjustTaskbarAndHotseatToBubbleBarState(/*isBubbleBarExpanded = */false);
}
/**
* Removes the bubble from the bubble bar and notifies sysui that the bubble should move to
* full screen.
*/
public void moveDraggedBubbleToFullscreen(@NonNull BubbleView bubbleView, Point dropLocation) {
if (bubbleView.getBubble() == null) {
return;
}
String key = bubbleView.getBubble().getKey();
mSystemUiProxy.moveDraggedBubbleToFullscreen(key, dropLocation);
onBubbleDismissed(bubbleView);
}
/**
* Create an animator for showing or hiding bubbles when stashed state changes
*
* @param isStashed {@code true} when bubble bar should be stashed to the handle
*/
public Animator createRevealAnimatorForStashChange(boolean isStashed) {
Rect stashedHandleBounds = new Rect();
mBubbleStashController.getHandleBounds(stashedHandleBounds);
int childCount = mBarView.getChildCount();
float newChildWidth = (float) stashedHandleBounds.width() / childCount;
AnimatorSet animatorSet = new AnimatorSet();
for (int i = 0; i < childCount; i++) {
BubbleView child = (BubbleView) mBarView.getChildAt(i);
animatorSet.play(
createRevealAnimForBubble(child, isStashed, stashedHandleBounds,
newChildWidth));
}
return animatorSet;
}
private Animator createRevealAnimForBubble(BubbleView bubbleView, boolean isStashed,
Rect stashedHandleBounds, float newWidth) {
Rect viewBounds = new Rect(0, 0, bubbleView.getWidth(), bubbleView.getHeight());
int viewCenterY = viewBounds.centerY();
int halfHandleHeight = stashedHandleBounds.height() / 2;
int widthDelta = Math.max(0, (int) (viewBounds.width() - newWidth) / 2);
Rect stashedViewBounds = new Rect(
viewBounds.left + widthDelta,
viewCenterY - halfHandleHeight,
viewBounds.right - widthDelta,
viewCenterY + halfHandleHeight
);
float viewRadius = 0f; // Use 0 to not clip the new message dot or the app icon
float stashedRadius = stashedViewBounds.height() / 2f;
return new RoundedRectRevealOutlineProvider(viewRadius, stashedRadius, viewBounds,
stashedViewBounds).createRevealAnimator(bubbleView, !isStashed, 0);
}
/**
* Listener to receive updates about bubble bar bounds changing
*/
public interface BubbleBarBoundsChangeListener {
/** Called when bounds have changed */
void onBoundsChanged();
}
/** Interface for getting the current timestamp. */
interface TimeSource {
long currentTimeMillis();
}
/** Dumps the state of BubbleBarViewController. */
public void dump(PrintWriter pw) {
pw.println("Bubble bar view controller state:");
pw.println(" mHiddenForSysui: " + mHiddenForSysui);
pw.println(" mHiddenForNoBubbles: " + mHiddenForNoBubbles);
pw.println(" mHiddenForStashed: " + mHiddenForStashed);
pw.println(" mShouldShowEducation: " + mShouldShowEducation);
pw.println(" mBubbleBarTranslationY.value: " + mBubbleBarTranslationY.value);
pw.println(" mBubbleBarSwipeUpTranslationY: " + mBubbleBarSwipeUpTranslationY);
pw.println(" mOverflowAdded: " + mOverflowAdded);
if (mBarView != null) {
mBarView.dump(pw);
} else {
pw.println(" Bubble bar view is null!");
}
}
/** Interface for BubbleBarViewController to get the taskbar view properties. */
public interface TaskbarViewPropertiesProvider {
/** Returns the bounds of the taskbar. */
Rect getTaskbarViewBounds();
/** Returns taskbar icons alpha */
MultiPropertyFactory<View>.MultiProperty getIconsAlpha();
}
}