From fa7f85b53104f8f31bf01ceab0c1b48096d0c199 Mon Sep 17 00:00:00 2001 From: Mady Mellor Date: Wed, 15 Feb 2023 16:26:50 -0800 Subject: [PATCH 1/4] Introduce BubbleBarExpandedView * Adds the new BubbleBarExpandedView & basic HandleView classes for rendering a bubble's expanded view when bubbles are in the bubble bar. * The HandleView class will eventually render the manage menu but this is not implemented yet. * Adds BubbleBarExpandedView to BubbleViewProvider & Bubble, this will be a nullable view that is only inflated when the bubble bar is active. The inflation / creation of this view is done in a separate CL. Test: treehugger / manual with other CLs Bug: 253318833 Change-Id: Iee05e39fc7c06f5210c0adccbe69ba6991e722df --- .../res/layout/bubble_bar_expanded_view.xml | 24 ++ libs/WindowManager/Shell/res/values/dimen.xml | 2 + .../com/android/wm/shell/bubbles/Bubble.java | 30 +- .../wm/shell/bubbles/BubbleOverflow.kt | 5 + .../wm/shell/bubbles/BubbleViewProvider.java | 26 +- .../bubbles/bar/BubbleBarExpandedView.java | 267 ++++++++++++++++++ .../wm/shell/bubbles/bar/HandleView.java | 42 +++ 7 files changed, 392 insertions(+), 4 deletions(-) create mode 100644 libs/WindowManager/Shell/res/layout/bubble_bar_expanded_view.xml create mode 100644 libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java create mode 100644 libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/HandleView.java diff --git a/libs/WindowManager/Shell/res/layout/bubble_bar_expanded_view.xml b/libs/WindowManager/Shell/res/layout/bubble_bar_expanded_view.xml new file mode 100644 index 0000000000000..681a52bea2b2c --- /dev/null +++ b/libs/WindowManager/Shell/res/layout/bubble_bar_expanded_view.xml @@ -0,0 +1,24 @@ + + + + + diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml index 3a8614aa65137..df8292ede3e28 100644 --- a/libs/WindowManager/Shell/res/values/dimen.xml +++ b/libs/WindowManager/Shell/res/values/dimen.xml @@ -228,6 +228,8 @@ 16dp 72dp + + 16dp 24dp diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java index 5f2b63089009c..bd3aa16e726aa 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java @@ -47,6 +47,7 @@ import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.InstanceId; +import com.android.wm.shell.bubbles.bar.BubbleBarExpandedView; import com.android.wm.shell.common.bubbles.BubbleInfo; import java.io.PrintWriter; @@ -87,8 +88,18 @@ public class Bubble implements BubbleViewProvider { private String mAppName; private ShortcutInfo mShortcutInfo; private String mMetadataShortcutId; + + /** + * If {@link BubbleController#isShowingAsBubbleBar()} is true, the only view that will be + * populated will be {@link #mBubbleBarExpandedView}. If it is false, {@link #mIconView} + * and {@link #mExpandedView} will be populated. + */ + @Nullable private BadgedImageView mIconView; + @Nullable private BubbleExpandedView mExpandedView; + @Nullable + private BubbleBarExpandedView mBubbleBarExpandedView; private BubbleViewInfoTask mInflationTask; private boolean mInflateSynchronously; @@ -327,12 +338,18 @@ public class Bubble implements BubbleViewProvider { return mIconView; } - @Override @Nullable + @Override public BubbleExpandedView getExpandedView() { return mExpandedView; } + @Nullable + @Override + public BubbleBarExpandedView getBubbleBarExpandedView() { + return mBubbleBarExpandedView; + } + @Nullable public String getTitle() { return mTitle; @@ -364,6 +381,9 @@ public class Bubble implements BubbleViewProvider { mExpandedView.cleanUpExpandedState(); mExpandedView = null; } + if (mBubbleBarExpandedView != null) { + mBubbleBarExpandedView.cleanUpExpandedState(); + } if (mIntent != null) { mIntent.unregisterCancelListener(mIntentCancelListener); } @@ -445,7 +465,7 @@ public class Bubble implements BubbleViewProvider { } boolean isInflated() { - return mIconView != null && mExpandedView != null; + return (mIconView != null && mExpandedView != null) || mBubbleBarExpandedView != null; } void stopInflation() { @@ -478,6 +498,9 @@ public class Bubble implements BubbleViewProvider { if (mExpandedView != null) { mExpandedView.update(this /* bubble */); } + if (mBubbleBarExpandedView != null) { + mBubbleBarExpandedView.update(this /* bubble */); + } if (mIconView != null) { mIconView.setRenderedBubble(this /* bubble */); } @@ -607,6 +630,9 @@ public class Bubble implements BubbleViewProvider { */ @Override public int getTaskId() { + if (mBubbleBarExpandedView != null) { + return mBubbleBarExpandedView.getTaskId(); + } return mExpandedView != null ? mExpandedView.getTaskId() : mTaskId; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt index eb7929b8ca54e..6cdb80b2bb93d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt @@ -29,6 +29,7 @@ import android.util.TypedValue import android.view.LayoutInflater import android.widget.FrameLayout import com.android.wm.shell.R +import com.android.wm.shell.bubbles.bar.BubbleBarExpandedView class BubbleOverflow( private val context: Context, @@ -136,6 +137,10 @@ class BubbleOverflow( return expandedView } + override fun getBubbleBarExpandedView(): BubbleBarExpandedView? { + return null + } + override fun getDotColor(): Int { return dotColor } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewProvider.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewProvider.java index 3f6d41bb2b684..6bdc3b9f5aece 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewProvider.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewProvider.java @@ -22,18 +22,40 @@ import android.view.View; import androidx.annotation.Nullable; +import com.android.wm.shell.bubbles.bar.BubbleBarExpandedView; + /** * Interface to represent actual Bubbles and UI elements that act like bubbles, like BubbleOverflow. */ public interface BubbleViewProvider { - @Nullable BubbleExpandedView getExpandedView(); + + /** + * Returns the icon view used for a bubble (the click target when collapsed). This is populated + * when bubbles are floating, i.e. when {@link BubbleController#isShowingAsBubbleBar()} is + * false. + */ + @Nullable + View getIconView(); + + /** + * Returns the expanded view used for a bubble. This is populated when bubbles are floating, + * i.e. when {@link BubbleController#isShowingAsBubbleBar()} is false. + */ + @Nullable + BubbleExpandedView getExpandedView(); + + /** + * Returns the expanded view used for a bubble being show in the bubble bar. This is populated + * when {@link BubbleController#isShowingAsBubbleBar()} is true. + */ + @Nullable + BubbleBarExpandedView getBubbleBarExpandedView(); /** * Sets whether the contents of the bubble's TaskView should be visible. */ void setTaskViewVisibility(boolean visible); - @Nullable View getIconView(); String getKey(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java new file mode 100644 index 0000000000000..b8f049becb6f7 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java @@ -0,0 +1,267 @@ +/* + * 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.wm.shell.bubbles.bar; + +import android.app.ActivityManager; +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Color; +import android.graphics.Outline; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewOutlineProvider; +import android.widget.FrameLayout; + +import com.android.internal.policy.ScreenDecorationsUtils; +import com.android.wm.shell.R; +import com.android.wm.shell.bubbles.Bubble; +import com.android.wm.shell.bubbles.BubbleController; +import com.android.wm.shell.bubbles.BubbleTaskViewHelper; +import com.android.wm.shell.taskview.TaskView; + +/** + * Expanded view of a bubble when it's part of the bubble bar. + * + * {@link BubbleController#isShowingAsBubbleBar()} + */ +public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskViewHelper.Listener { + + private static final String TAG = BubbleBarExpandedView.class.getSimpleName(); + private static final int INVALID_TASK_ID = -1; + + private BubbleController mController; + private BubbleTaskViewHelper mBubbleTaskViewHelper; + + private HandleView mMenuView; + private TaskView mTaskView; + + private int mMenuHeight; + private int mBackgroundColor; + private float mCornerRadius = 0f; + + /** + * Whether we want the {@code TaskView}'s content to be visible (alpha = 1f). If + * {@link #mIsAnimating} is true, this may not reflect the {@code TaskView}'s actual alpha + * value until the animation ends. + */ + private boolean mIsContentVisible = false; + private boolean mIsAnimating; + + public BubbleBarExpandedView(Context context) { + this(context, null); + } + + public BubbleBarExpandedView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public BubbleBarExpandedView(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public BubbleBarExpandedView(Context context, AttributeSet attrs, int defStyleAttr, + int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + Context context = getContext(); + setElevation(getResources().getDimensionPixelSize(R.dimen.bubble_elevation)); + mMenuHeight = context.getResources().getDimensionPixelSize( + R.dimen.bubblebar_expanded_view_menu_size); + mMenuView = new HandleView(context); + addView(mMenuView); + + applyThemeAttrs(); + setClipToOutline(true); + setOutlineProvider(new ViewOutlineProvider() { + @Override + public void getOutline(View view, Outline outline) { + outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), mCornerRadius); + } + }); + } + + /** Set the BubbleController on the view, must be called before doing anything else. */ + public void initialize(BubbleController controller) { + mController = controller; + mBubbleTaskViewHelper = new BubbleTaskViewHelper(mContext, mController, + /* listener= */ this, + /* viewParent= */ this); + mTaskView = mBubbleTaskViewHelper.getTaskView(); + addView(mTaskView); + mTaskView.setEnableSurfaceClipping(true); + mTaskView.setCornerRadius(mCornerRadius); + } + + // TODO (b/275087636): call this when theme/config changes + void applyThemeAttrs() { + boolean supportsRoundedCorners = ScreenDecorationsUtils.supportsRoundedCornersOnWindows( + mContext.getResources()); + final TypedArray ta = mContext.obtainStyledAttributes(new int[] { + android.R.attr.dialogCornerRadius, + android.R.attr.colorBackgroundFloating}); + mCornerRadius = supportsRoundedCorners ? ta.getDimensionPixelSize(0, 0) : 0; + mCornerRadius = mCornerRadius / 2f; + mBackgroundColor = ta.getColor(1, Color.WHITE); + + ta.recycle(); + + mMenuView.setCornerRadius(mCornerRadius); + mMenuHeight = getResources().getDimensionPixelSize( + R.dimen.bubblebar_expanded_view_menu_size); + + if (mTaskView != null) { + mTaskView.setCornerRadius(mCornerRadius); + mTaskView.setElevation(150); + updateMenuColor(); + } + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + int height = MeasureSpec.getSize(heightMeasureSpec); + + // Add corner radius here so that the menu extends behind the rounded corners of TaskView. + int menuViewHeight = Math.min((int) (mMenuHeight + mCornerRadius), height); + measureChild(mMenuView, widthMeasureSpec, MeasureSpec.makeMeasureSpec(menuViewHeight, + MeasureSpec.getMode(heightMeasureSpec))); + + if (mTaskView != null) { + int taskViewHeight = height - menuViewHeight; + measureChild(mTaskView, widthMeasureSpec, MeasureSpec.makeMeasureSpec(taskViewHeight, + MeasureSpec.getMode(heightMeasureSpec))); + } + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + // Drag handle above + final int dragHandleBottom = t + mMenuView.getMeasuredHeight(); + mMenuView.layout(l, t, r, dragHandleBottom); + if (mTaskView != null) { + // Subtract radius so that the menu extends behind the rounded corners of TaskView. + mTaskView.layout(l, (int) (dragHandleBottom - mCornerRadius), r, + dragHandleBottom + mTaskView.getMeasuredHeight()); + } + } + + @Override + public void onTaskCreated() { + setContentVisibility(true); + updateMenuColor(); + } + + @Override + public void onContentVisibilityChanged(boolean visible) { + setContentVisibility(visible); + } + + @Override + public void onBackPressed() { + mController.collapseStack(); + } + + /** Cleans up task view, should be called when the bubble is no longer active. */ + public void cleanUpExpandedState() { + if (mBubbleTaskViewHelper != null) { + if (mTaskView != null) { + removeView(mTaskView); + } + mBubbleTaskViewHelper.cleanUpTaskView(); + } + } + + /** Updates the bubble shown in this task view. */ + public void update(Bubble bubble) { + mBubbleTaskViewHelper.update(bubble); + } + + /** The task id of the activity shown in the task view, if it exists. */ + public int getTaskId() { + return mBubbleTaskViewHelper != null ? mBubbleTaskViewHelper.getTaskId() : INVALID_TASK_ID; + } + + /** + * Call when the location or size of the view has changed to update TaskView. + */ + public void updateLocation() { + if (mTaskView == null) return; + mTaskView.onLocationChanged(); + } + + /** Sets the alpha of the task view. */ + public void setContentVisibility(boolean visible) { + mIsContentVisible = visible; + + if (mTaskView == null) return; + + if (!mIsAnimating) { + mTaskView.setAlpha(visible ? 1f : 0f); + } + } + + /** Updates the menu bar to be the status bar color specified by the app. */ + private void updateMenuColor() { + if (mTaskView == null) return; + ActivityManager.RunningTaskInfo info = mTaskView.getTaskInfo(); + final int taskBgColor = info.taskDescription.getStatusBarColor(); + final int color = Color.valueOf(taskBgColor == -1 ? Color.WHITE : taskBgColor).toArgb(); + if (color != -1) { + mMenuView.setBackgroundColor(color); + } else { + mMenuView.setBackgroundColor(mBackgroundColor); + } + } + + /** + * Sets the alpha of both this view and the task view. + */ + public void setTaskViewAlpha(float alpha) { + if (mTaskView != null) { + mTaskView.setAlpha(alpha); + } + setAlpha(alpha); + } + + /** + * Sets whether the surface displaying app content should sit on top. This is useful for + * ordering surfaces during animations. When content is drawn on top of the app (e.g. bubble + * being dragged out, the manage menu) this is set to false, otherwise it should be true. + */ + public void setSurfaceZOrderedOnTop(boolean onTop) { + if (mTaskView == null) { + return; + } + mTaskView.setZOrderedOnTop(onTop, true /* allowDynamicChange */); + } + + /** + * Sets whether the view is animating, in this case we won't change the content visibility + * until the animation is done. + */ + public void setAnimating(boolean animating) { + mIsAnimating = animating; + // If we're done animating, apply the correct visibility. + if (!animating) { + setContentVisibility(mIsContentVisible); + } + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/HandleView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/HandleView.java new file mode 100644 index 0000000000000..9ee8a9d98aa1c --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/HandleView.java @@ -0,0 +1,42 @@ +/* + * 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.wm.shell.bubbles.bar; + +import android.content.Context; +import android.view.Gravity; +import android.widget.LinearLayout; + +/** + * Handle / menu view to show at the top of a bubble bar expanded view. + */ +public class HandleView extends LinearLayout { + + // TODO(b/273307221): implement the manage menu in this view. + public HandleView(Context context) { + super(context); + setOrientation(LinearLayout.HORIZONTAL); + setGravity(Gravity.CENTER); + } + + /** + * The menu extends past the top of the TaskView because of the rounded corners. This means + * to center content in the menu we must subtract the radius (i.e. the amount of space covered + * by TaskView). + */ + public void setCornerRadius(float radius) { + setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(), (int) radius); + } +} From 596688d10f48602410ecf5f9076f51bd409b38ea Mon Sep 17 00:00:00 2001 From: Mady Mellor Date: Wed, 15 Feb 2023 17:17:29 -0800 Subject: [PATCH 2/4] Introduce BubbleBarLayerView & animation for BubbleBarExpandedView * BubbleBarLayerView will be used in place of BubbleStackView when the bubbles are shown in the bubble bar in launcher. It'll be added to the window manager instead and it'll host and animate the expanded view for bubbles from the bubble bar. * BubbleBarExpandedViewAnimationHelper contains code to animate the expanded view shown in BubbleBarLayerView. * This CL just adds the classes, usuage of them happens in a future CL. Test: treehugger / manual with other CLs Bug: 253318833 Change-Id: I79ab3cbe357439f1ec51d48c5f06504b899509f0 --- .../wm/shell/bubbles/BubbleController.java | 2 +- .../bubbles/bar/BubbleBarAnimationHelper.java | 233 ++++++++++++++++++ .../shell/bubbles/bar/BubbleBarLayerView.java | 212 ++++++++++++++++ 3 files changed, 446 insertions(+), 1 deletion(-) create mode 100644 libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java create mode 100644 libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java index 3dbb745f0c6c7..8040bd72e2a3b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java @@ -682,7 +682,7 @@ public class BubbleController implements ConfigurationChangeListener, return mBubblePositioner; } - Bubbles.SysuiProxy getSysuiProxy() { + public Bubbles.SysuiProxy getSysuiProxy() { return mSysuiProxy; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java new file mode 100644 index 0000000000000..23f65f943aa46 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java @@ -0,0 +1,233 @@ +/* + * 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.wm.shell.bubbles.bar; + +import static android.view.View.VISIBLE; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ValueAnimator; +import android.content.Context; +import android.graphics.PointF; +import android.util.Log; +import android.widget.FrameLayout; + +import com.android.wm.shell.animation.Interpolators; +import com.android.wm.shell.animation.PhysicsAnimator; +import com.android.wm.shell.bubbles.BubblePositioner; +import com.android.wm.shell.bubbles.BubbleViewProvider; +import com.android.wm.shell.bubbles.animation.AnimatableScaleMatrix; + +/** + * Helper class to animate a {@link BubbleBarExpandedView} on a bubble. + */ +public class BubbleBarAnimationHelper { + + private static final String TAG = BubbleBarAnimationHelper.class.getSimpleName(); + + private static final float EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT = 0.1f; + private static final float EXPANDED_VIEW_ANIMATE_OUT_SCALE_AMOUNT = .75f; + private static final int EXPANDED_VIEW_ALPHA_ANIMATION_DURATION = 150; + + /** Spring config for the expanded view scale-in animation. */ + private final PhysicsAnimator.SpringConfig mScaleInSpringConfig = + new PhysicsAnimator.SpringConfig(300f, 0.9f); + + /** Spring config for the expanded view scale-out animation. */ + private final PhysicsAnimator.SpringConfig mScaleOutSpringConfig = + new PhysicsAnimator.SpringConfig(900f, 1f); + + /** Matrix used to scale the expanded view container with a given pivot point. */ + private final AnimatableScaleMatrix mExpandedViewContainerMatrix = new AnimatableScaleMatrix(); + + /** Animator for animating the expanded view's alpha (including the TaskView inside it). */ + private final ValueAnimator mExpandedViewAlphaAnimator = ValueAnimator.ofFloat(0f, 1f); + + private final Context mContext; + private final BubbleBarLayerView mLayerView; + private final BubblePositioner mPositioner; + + private BubbleViewProvider mExpandedBubble; + private boolean mIsExpanded = false; + + public BubbleBarAnimationHelper(Context context, + BubbleBarLayerView bubbleBarLayerView, + BubblePositioner positioner) { + mContext = context; + mLayerView = bubbleBarLayerView; + mPositioner = positioner; + + mExpandedViewAlphaAnimator.setDuration(EXPANDED_VIEW_ALPHA_ANIMATION_DURATION); + mExpandedViewAlphaAnimator.setInterpolator(Interpolators.PANEL_CLOSE_ACCELERATED); + mExpandedViewAlphaAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + if (mExpandedBubble != null && mExpandedBubble.getBubbleBarExpandedView() != null) { + // We need to be Z ordered on top in order for alpha animations to work. + mExpandedBubble.getBubbleBarExpandedView().setSurfaceZOrderedOnTop(true); + mExpandedBubble.getBubbleBarExpandedView().setAnimating(true); + } + } + + @Override + public void onAnimationEnd(Animator animation) { + if (mExpandedBubble != null && mExpandedBubble.getBubbleBarExpandedView() != null) { + // The surface needs to be Z ordered on top for alpha values to work on the + // TaskView, and if we're temporarily hidden, we are still on the screen + // with alpha = 0f until we animate back. Stay Z ordered on top so the alpha + // = 0f remains in effect. + if (mIsExpanded) { + mExpandedBubble.getBubbleBarExpandedView().setSurfaceZOrderedOnTop(false); + } + + mExpandedBubble.getBubbleBarExpandedView().setContentVisibility(mIsExpanded); + mExpandedBubble.getBubbleBarExpandedView().setAnimating(false); + } + } + }); + mExpandedViewAlphaAnimator.addUpdateListener(valueAnimator -> { + if (mExpandedBubble != null && mExpandedBubble.getBubbleBarExpandedView() != null) { + float alpha = (float) valueAnimator.getAnimatedValue(); + mExpandedBubble.getBubbleBarExpandedView().setTaskViewAlpha(alpha); + mExpandedBubble.getBubbleBarExpandedView().setAlpha(alpha); + } + }); + } + + /** + * Animates the provided bubble's expanded view to the expanded state. + */ + public void animateExpansion(BubbleViewProvider expandedBubble) { + mExpandedBubble = expandedBubble; + if (mExpandedBubble == null) { + return; + } + BubbleBarExpandedView bev = mExpandedBubble.getBubbleBarExpandedView(); + if (bev == null) { + return; + } + mIsExpanded = true; + + mExpandedViewContainerMatrix.setScaleX(0f); + mExpandedViewContainerMatrix.setScaleY(0f); + + updateExpandedView(); + bev.setAnimating(true); + bev.setContentVisibility(false); + bev.setAlpha(0f); + bev.setTaskViewAlpha(0f); + bev.setVisibility(VISIBLE); + + // Set the pivot point for the scale, so the view animates out from the bubble bar. + PointF bubbleBarPosition = mPositioner.getBubbleBarPosition(); + mExpandedViewContainerMatrix.setScale( + 1f - EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT, + 1f - EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT, + bubbleBarPosition.x, + bubbleBarPosition.y); + + bev.setAnimationMatrix(mExpandedViewContainerMatrix); + + mExpandedViewAlphaAnimator.start(); + + PhysicsAnimator.getInstance(mExpandedViewContainerMatrix).cancel(); + PhysicsAnimator.getInstance(mExpandedViewContainerMatrix) + .spring(AnimatableScaleMatrix.SCALE_X, + AnimatableScaleMatrix.getAnimatableValueForScaleFactor(1f), + mScaleInSpringConfig) + .spring(AnimatableScaleMatrix.SCALE_Y, + AnimatableScaleMatrix.getAnimatableValueForScaleFactor(1f), + mScaleInSpringConfig) + .addUpdateListener((target, values) -> { + mExpandedBubble.getBubbleBarExpandedView().setAnimationMatrix( + mExpandedViewContainerMatrix); + }) + .withEndActions(() -> { + bev.setAnimationMatrix(null); + updateExpandedView(); + bev.setSurfaceZOrderedOnTop(false); + }) + .start(); + } + + /** + * Collapses the currently expanded bubble. + * + * @param endRunnable a runnable to run at the end of the animation. + */ + public void animateCollapse(Runnable endRunnable) { + mIsExpanded = false; + if (mExpandedBubble == null || mExpandedBubble.getBubbleBarExpandedView() == null) { + Log.w(TAG, "Trying to animate collapse without a bubble"); + return; + } + + mExpandedViewContainerMatrix.setScaleX(1f); + mExpandedViewContainerMatrix.setScaleY(1f); + + PhysicsAnimator.getInstance(mExpandedViewContainerMatrix).cancel(); + PhysicsAnimator.getInstance(mExpandedViewContainerMatrix) + .spring(AnimatableScaleMatrix.SCALE_X, + AnimatableScaleMatrix.getAnimatableValueForScaleFactor( + EXPANDED_VIEW_ANIMATE_OUT_SCALE_AMOUNT), + mScaleOutSpringConfig) + .spring(AnimatableScaleMatrix.SCALE_Y, + AnimatableScaleMatrix.getAnimatableValueForScaleFactor( + EXPANDED_VIEW_ANIMATE_OUT_SCALE_AMOUNT), + mScaleOutSpringConfig) + .addUpdateListener((target, values) -> { + if (mExpandedBubble != null + && mExpandedBubble.getBubbleBarExpandedView() != null) { + mExpandedBubble.getBubbleBarExpandedView().setAnimationMatrix( + mExpandedViewContainerMatrix); + } + }) + .withEndActions(() -> { + if (mExpandedBubble != null + && mExpandedBubble.getBubbleBarExpandedView() != null) { + mExpandedBubble.getBubbleBarExpandedView().setAnimationMatrix(null); + } + if (endRunnable != null) { + endRunnable.run(); + } + }) + .start(); + mExpandedViewAlphaAnimator.reverse(); + } + + private void updateExpandedView() { + if (mExpandedBubble == null || mExpandedBubble.getBubbleBarExpandedView() == null) { + Log.w(TAG, "Trying to update the expanded view without a bubble"); + return; + } + BubbleBarExpandedView bbev = mExpandedBubble.getBubbleBarExpandedView(); + + final int padding = mPositioner.getBubbleBarExpandedViewPadding(); + final int width = mPositioner.getExpandedViewWidthForBubbleBar(); + final int height = mPositioner.getExpandedViewHeightForBubbleBar(); + FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) bbev.getLayoutParams(); + lp.width = width; + lp.height = height; + bbev.setLayoutParams(lp); + if (mLayerView.isOnLeft()) { + bbev.setX(mPositioner.getInsets().left + padding); + } else { + bbev.setX(mPositioner.getAvailableRect().width() - width - padding); + } + bbev.setY(mPositioner.getInsets().top + padding); + bbev.updateLocation(); + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java new file mode 100644 index 0000000000000..b1a725b6e5c4b --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java @@ -0,0 +1,212 @@ +/* + * 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.wm.shell.bubbles.bar; + +import static com.android.wm.shell.animation.Interpolators.ALPHA_IN; +import static com.android.wm.shell.animation.Interpolators.ALPHA_OUT; + +import android.annotation.Nullable; +import android.content.Context; +import android.graphics.Rect; +import android.graphics.Region; +import android.graphics.drawable.ColorDrawable; +import android.view.View; +import android.view.ViewTreeObserver; +import android.widget.FrameLayout; + +import com.android.wm.shell.bubbles.BubbleController; +import com.android.wm.shell.bubbles.BubblePositioner; +import com.android.wm.shell.bubbles.BubbleViewProvider; + +/** + * Similar to {@link com.android.wm.shell.bubbles.BubbleStackView}, this view is added to window + * manager to display bubbles. However, it is only used when bubbles are being displayed in + * launcher in the bubble bar. This view does not show a stack of bubbles that can be moved around + * on screen and instead shows & animates the expanded bubble for the bubble bar. + */ +public class BubbleBarLayerView extends FrameLayout + implements ViewTreeObserver.OnComputeInternalInsetsListener { + + private static final String TAG = BubbleBarLayerView.class.getSimpleName(); + + private static final float SCRIM_ALPHA = 0.2f; + + private final BubbleController mBubbleController; + private final BubblePositioner mPositioner; + private final BubbleBarAnimationHelper mAnimationHelper; + private final View mScrimView; + + @Nullable + private BubbleViewProvider mExpandedBubble; + private BubbleBarExpandedView mExpandedView; + + // TODO(b/273310265) - currently the view is always on the right, need to update for RTL. + /** Whether the expanded view is displaying on the left of the screen or not. */ + private boolean mOnLeft = false; + + /** Whether a bubble is expanded. */ + private boolean mIsExpanded = false; + + private final Region mTouchableRegion = new Region(); + private final Rect mTempRect = new Rect(); + + public BubbleBarLayerView(Context context, BubbleController controller) { + super(context); + mBubbleController = controller; + mPositioner = mBubbleController.getPositioner(); + + mAnimationHelper = new BubbleBarAnimationHelper(context, + this, mPositioner); + + mScrimView = new View(getContext()); + mScrimView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO); + mScrimView.setBackgroundDrawable(new ColorDrawable( + getResources().getColor(android.R.color.system_neutral1_1000))); + addView(mScrimView); + mScrimView.setAlpha(0f); + mScrimView.setBackgroundDrawable(new ColorDrawable( + getResources().getColor(android.R.color.system_neutral1_1000))); + + setOnClickListener(view -> { + mBubbleController.collapseStack(); + }); + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + mPositioner.update(); + getViewTreeObserver().addOnComputeInternalInsetsListener(this); + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + getViewTreeObserver().removeOnComputeInternalInsetsListener(this); + + if (mExpandedView != null) { + removeView(mExpandedView); + mExpandedView = null; + } + } + + @Override + public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo inoutInfo) { + inoutInfo.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION); + mTouchableRegion.setEmpty(); + getTouchableRegion(mTouchableRegion); + inoutInfo.touchableRegion.set(mTouchableRegion); + } + + /** Updates the sizes of any displaying expanded view. */ + public void onDisplaySizeChanged() { + if (mIsExpanded && mExpandedView != null) { + updateExpandedView(); + } + } + + /** Whether the stack of bubbles is expanded or not. */ + public boolean isExpanded() { + return mIsExpanded; + } + + // (TODO: b/273310265): BubblePositioner should be source of truth when this work is done. + /** Whether the expanded view is positioned on the left or right side of the screen. */ + public boolean isOnLeft() { + return mOnLeft; + } + + /** Shows the expanded view of the provided bubble. */ + public void showExpandedView(BubbleViewProvider b) { + BubbleBarExpandedView expandedView = b.getBubbleBarExpandedView(); + if (expandedView == null) { + return; + } + if (mExpandedBubble != null && !b.getKey().equals(mExpandedBubble.getKey())) { + removeView(mExpandedView); + mExpandedView = null; + } + if (mExpandedView == null) { + mExpandedBubble = b; + mExpandedView = expandedView; + final int width = mPositioner.getExpandedViewWidthForBubbleBar(); + final int height = mPositioner.getExpandedViewHeightForBubbleBar(); + mExpandedView.setVisibility(GONE); + addView(mExpandedView, new FrameLayout.LayoutParams(width, height)); + } + + mIsExpanded = true; + mBubbleController.getSysuiProxy().onStackExpandChanged(true); + mAnimationHelper.animateExpansion(mExpandedBubble); + showScrim(true); + } + + /** Collapses any showing expanded view */ + public void collapse() { + mIsExpanded = false; + final BubbleBarExpandedView viewToRemove = mExpandedView; + mAnimationHelper.animateCollapse(() -> removeView(viewToRemove)); + mBubbleController.getSysuiProxy().onStackExpandChanged(false); + mExpandedView = null; + showScrim(false); + } + + /** Updates the expanded view size and position. */ + private void updateExpandedView() { + if (mExpandedView == null) return; + final int padding = mPositioner.getBubbleBarExpandedViewPadding(); + final int width = mPositioner.getExpandedViewWidthForBubbleBar(); + final int height = mPositioner.getExpandedViewHeightForBubbleBar(); + FrameLayout.LayoutParams lp = (LayoutParams) mExpandedView.getLayoutParams(); + lp.width = width; + lp.height = height; + mExpandedView.setLayoutParams(lp); + if (mOnLeft) { + mExpandedView.setX(mPositioner.getInsets().left + padding); + } else { + mExpandedView.setX(mPositioner.getAvailableRect().width() - width - padding); + } + mExpandedView.setY(mPositioner.getInsets().top + padding); + mExpandedView.updateLocation(); + } + + private void showScrim(boolean show) { + if (show) { + mScrimView.animate() + .setInterpolator(ALPHA_IN) + .alpha(SCRIM_ALPHA) + .start(); + } else { + mScrimView.animate() + .alpha(0f) + .setInterpolator(ALPHA_OUT) + .start(); + } + } + + /** + * Fills in the touchable region for expanded view. This is used by window manager to + * decide which touch events go to the expanded view. + */ + private void getTouchableRegion(Region outRegion) { + mTempRect.setEmpty(); + if (mIsExpanded) { + getBoundsOnScreen(mTempRect); + outRegion.op(mTempRect, Region.Op.UNION); + } + } +} From 8411376884622d75ddbd26d3c17dfdc0e4448523 Mon Sep 17 00:00:00 2001 From: Mady Mellor Date: Wed, 8 Mar 2023 15:23:18 -0800 Subject: [PATCH 3/4] Use BubbleBarLayerView & BubbleBarExpandedView * Adds BubbleBarLayerView to BubbleController, if the bubble bar is active, BBLV will be used instead of BubbleStackView. Some methods in BubbleController are updated to account for BBLV. * Updates the BubbleViewInfoTask (the inflation code for bubbles) to expect either BubbleBarExpandedView OR the BadgedImageView and BubbleExpandedView used when bubbles are NOT in the bubble bar. When showing in the bubble bar, BubbleBarExpandedView will be the only view inflated. Test: treehugger / manual with other CLs Bug: 253318833 Change-Id: I9e8410c62260304e286eddd9bf3c32fecc7ee317 --- .../com/android/wm/shell/bubbles/Bubble.java | 11 +- .../wm/shell/bubbles/BubbleController.java | 167 ++++++++++++------ .../wm/shell/bubbles/BubbleViewInfoTask.java | 87 +++++++-- 3 files changed, 200 insertions(+), 65 deletions(-) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java index bd3aa16e726aa..8f364b448bf2d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java @@ -48,6 +48,7 @@ import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.InstanceId; import com.android.wm.shell.bubbles.bar.BubbleBarExpandedView; +import com.android.wm.shell.bubbles.bar.BubbleBarLayerView; import com.android.wm.shell.common.bubbles.BubbleInfo; import java.io.PrintWriter; @@ -430,14 +431,16 @@ public class Bubble implements BubbleViewProvider { * @param callback the callback to notify one the bubble is ready to be displayed. * @param context the context for the bubble. * @param controller the bubble controller. - * @param stackView the stackView the bubble is eventually added to. + * @param stackView the view the bubble is added to, iff showing as floating. + * @param layerView the layer the bubble is added to, iff showing in the bubble bar. * @param iconFactory the icon factory use to create images for the bubble. * @param badgeIconFactory the icon factory to create app badges for the bubble. */ void inflate(BubbleViewInfoTask.Callback callback, Context context, BubbleController controller, - BubbleStackView stackView, + @Nullable BubbleStackView stackView, + @Nullable BubbleBarLayerView layerView, BubbleIconFactory iconFactory, BubbleBadgeIconFactory badgeIconFactory, boolean skipInflation) { @@ -448,6 +451,7 @@ public class Bubble implements BubbleViewProvider { context, controller, stackView, + layerView, iconFactory, badgeIconFactory, skipInflation, @@ -479,6 +483,7 @@ public class Bubble implements BubbleViewProvider { if (!isInflated()) { mIconView = info.imageView; mExpandedView = info.expandedView; + mBubbleBarExpandedView = info.bubbleBarExpandedView; } mShortcutInfo = info.shortcutInfo; @@ -489,7 +494,7 @@ public class Bubble implements BubbleViewProvider { mFlyoutMessage = info.flyoutMessage; mBadgeBitmap = info.badgeBitmap; - mRawBadgeBitmap = info.mRawBadgeBitmap; + mRawBadgeBitmap = info.rawBadgeBitmap; mBubbleBitmap = info.bubbleBitmap; mDotColor = info.dotColor; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java index 8040bd72e2a3b..0bb1cabb9b5ef 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java @@ -89,6 +89,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.statusbar.IStatusBarService; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.WindowManagerShellWrapper; +import com.android.wm.shell.bubbles.bar.BubbleBarLayerView; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.ExternalInterfaceBinder; import com.android.wm.shell.common.FloatingContentCoordinator; @@ -201,6 +202,7 @@ public class BubbleController implements ConfigurationChangeListener, private BubbleLogger mLogger; private BubbleData mBubbleData; @Nullable private BubbleStackView mStackView; + @Nullable private BubbleBarLayerView mLayerView; private BubbleIconFactory mBubbleIconFactory; private BubbleBadgeIconFactory mBubbleBadgeIconFactory; private BubblePositioner mBubblePositioner; @@ -546,7 +548,7 @@ public class BubbleController implements ConfigurationChangeListener, } private void openBubbleOverflow() { - ensureStackViewCreated(); + ensureBubbleViewsAndWindowCreated(); mBubbleData.setShowingOverflow(true); mBubbleData.setSelectedBubble(mBubbleData.getOverflow()); mBubbleData.setExpanded(true); @@ -586,7 +588,7 @@ public class BubbleController implements ConfigurationChangeListener, expandStackAndSelectBubble(mNotifEntryToExpandOnShadeUnlock); } - updateStack(); + updateBubbleViews(); } @VisibleForTesting @@ -687,34 +689,54 @@ public class BubbleController implements ConfigurationChangeListener, } /** - * BubbleStackView is lazily created by this method the first time a Bubble is added. This - * method initializes the stack view and adds it to window manager. + * The view and window for bubbles is lazily created by this method the first time a Bubble + * is added. Depending on the device state, this method will: + * - initialize a {@link BubbleStackView} and add it to window manager OR + * - initialize a {@link com.android.wm.shell.bubbles.bar.BubbleBarLayerView} and adds + * it to window manager. */ - private void ensureStackViewCreated() { - if (mStackView == null) { - mStackView = new BubbleStackView( - mContext, this, mBubbleData, mSurfaceSynchronizer, mFloatingContentCoordinator, - mMainExecutor); - mStackView.onOrientationChanged(); - if (mExpandListener != null) { - mStackView.setExpandListener(mExpandListener); + private void ensureBubbleViewsAndWindowCreated() { + mBubblePositioner.setShowingInBubbleBar(isShowingAsBubbleBar()); + if (isShowingAsBubbleBar()) { + // When we're showing in launcher / bubble bar is enabled, we don't have bubble stack + // view, instead we just show the expanded bubble view as necessary. We still need a + // window to show this in, but we use a separate code path. + // TODO(b/273312602): consider foldables where we do need a stack view when folded + if (mLayerView == null) { + mLayerView = new BubbleBarLayerView(mContext, this); + } + } else { + if (mStackView == null) { + mStackView = new BubbleStackView( + mContext, this, mBubbleData, mSurfaceSynchronizer, + mFloatingContentCoordinator, + mMainExecutor); + mStackView.onOrientationChanged(); + if (mExpandListener != null) { + mStackView.setExpandListener(mExpandListener); + } + mStackView.setUnbubbleConversationCallback(mSysuiProxy::onUnbubbleConversation); } - mStackView.setUnbubbleConversationCallback(mSysuiProxy::onUnbubbleConversation); } - addToWindowManagerMaybe(); } - /** Adds the BubbleStackView to the WindowManager if it's not already there. */ + /** Adds the appropriate view to WindowManager if it's not already there. */ private void addToWindowManagerMaybe() { - // If the stack is null, or already added, don't add it. - if (mStackView == null || mAddedToWindowManager) { + // If already added, don't add it. + if (mAddedToWindowManager) { + return; + } + // If the appropriate view is null, don't add it. + if (isShowingAsBubbleBar() && mLayerView == null) { + return; + } else if (!isShowingAsBubbleBar() && mStackView == null) { return; } mWmLayoutParams = new WindowManager.LayoutParams( // Fill the screen so we can use translation animations to position the bubble - // stack. We'll use touchable regions to ignore touches that are not on the bubbles + // views. We'll use touchable regions to ignore touches that are not on the bubbles // themselves. ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, @@ -737,17 +759,30 @@ public class BubbleController implements ConfigurationChangeListener, mAddedToWindowManager = true; registerBroadcastReceiver(); mBubbleData.getOverflow().initialize(this); - mWindowManager.addView(mStackView, mWmLayoutParams); - mStackView.setOnApplyWindowInsetsListener((view, windowInsets) -> { - if (!windowInsets.equals(mWindowInsets)) { - mWindowInsets = windowInsets; - mBubblePositioner.update(); - mStackView.onDisplaySizeChanged(); - } - return windowInsets; - }); + // (TODO: b/273314541) some duplication in the inset listener + if (isShowingAsBubbleBar()) { + mWindowManager.addView(mLayerView, mWmLayoutParams); + mLayerView.setOnApplyWindowInsetsListener((view, windowInsets) -> { + if (!windowInsets.equals(mWindowInsets)) { + mWindowInsets = windowInsets; + mBubblePositioner.update(); + mLayerView.onDisplaySizeChanged(); + } + return windowInsets; + }); + } else { + mWindowManager.addView(mStackView, mWmLayoutParams); + mStackView.setOnApplyWindowInsetsListener((view, windowInsets) -> { + if (!windowInsets.equals(mWindowInsets)) { + mWindowInsets = windowInsets; + mBubblePositioner.update(); + mStackView.onDisplaySizeChanged(); + } + return windowInsets; + }); + } } catch (IllegalStateException e) { - // This means the stack has already been added. This shouldn't happen... + // This means the view has already been added. This shouldn't happen... e.printStackTrace(); } } @@ -770,7 +805,7 @@ public class BubbleController implements ConfigurationChangeListener, } } - /** Removes the BubbleStackView from the WindowManager if it's there. */ + /** Removes any bubble views from the WindowManager that exist. */ private void removeFromWindowManagerMaybe() { if (!mAddedToWindowManager) { return; @@ -791,8 +826,10 @@ public class BubbleController implements ConfigurationChangeListener, if (mStackView != null) { mWindowManager.removeView(mStackView); mBubbleData.getOverflow().cleanUpExpandedState(); - } else { - Log.w(TAG, "StackView added to WindowManager, but was null when removing!"); + } + if (mLayerView != null) { + mWindowManager.removeView(mLayerView); + mBubbleData.getOverflow().cleanUpExpandedState(); } } catch (IllegalArgumentException e) { // This means the stack has already been removed - it shouldn't happen, but ignore if it @@ -887,12 +924,22 @@ public class BubbleController implements ConfigurationChangeListener, // Reload each bubble for (Bubble b : mBubbleData.getBubbles()) { - b.inflate(null /* callback */, mContext, this, mStackView, mBubbleIconFactory, + b.inflate(null /* callback */, + mContext, + this, + mStackView, + mLayerView, + mBubbleIconFactory, mBubbleBadgeIconFactory, false /* skipInflation */); } for (Bubble b : mBubbleData.getOverflowBubbles()) { - b.inflate(null /* callback */, mContext, this, mStackView, mBubbleIconFactory, + b.inflate(null /* callback */, + mContext, + this, + mStackView, + mLayerView, + mBubbleIconFactory, mBubbleBadgeIconFactory, false /* skipInflation */); } @@ -959,7 +1006,7 @@ public class BubbleController implements ConfigurationChangeListener, */ @VisibleForTesting public boolean hasBubbles() { - if (mStackView == null) { + if (mStackView == null && mLayerView == null) { return false; } return mBubbleData.hasBubbles() || mBubbleData.isShowingOverflow(); @@ -1166,7 +1213,12 @@ public class BubbleController implements ConfigurationChangeListener, } bubble.inflate( (b) -> mBubbleData.overflowBubble(Bubbles.DISMISS_RELOAD_FROM_DISK, bubble), - mContext, this, mStackView, mBubbleIconFactory, mBubbleBadgeIconFactory, + mContext, + this, + mStackView, + mLayerView, + mBubbleIconFactory, + mBubbleBadgeIconFactory, true /* skipInflation */); }); return null; @@ -1238,10 +1290,11 @@ public class BubbleController implements ConfigurationChangeListener, @VisibleForTesting public void inflateAndAdd(Bubble bubble, boolean suppressFlyout, boolean showInShade) { // Lazy init stack view when a bubble is created - ensureStackViewCreated(); + ensureBubbleViewsAndWindowCreated(); bubble.setInflateSynchronously(mInflateSynchronously); bubble.inflate(b -> mBubbleData.notificationEntryUpdated(b, suppressFlyout, showInShade), - mContext, this, mStackView, mBubbleIconFactory, mBubbleBadgeIconFactory, + mContext, this, mStackView, mLayerView, + mBubbleIconFactory, mBubbleBadgeIconFactory, false /* skipInflation */); } @@ -1475,7 +1528,7 @@ public class BubbleController implements ConfigurationChangeListener, + " unsuppressed=" + (update.unsuppressedBubble != null)); } - ensureStackViewCreated(); + ensureBubbleViewsAndWindowCreated(); // Lazy load overflow bubbles from disk loadOverflowBubblesFromDisk(); @@ -1570,7 +1623,7 @@ public class BubbleController implements ConfigurationChangeListener, } mSysuiProxy.notifyInvalidateNotifications("BubbleData.Listener.applyUpdate"); - updateStack(); + updateBubbleViews(); // Update the cached state for queries from SysUI mImpl.mCachedState.update(update); @@ -1650,28 +1703,42 @@ public class BubbleController implements ConfigurationChangeListener, /** * Updates the visibility of the bubbles based on current state. - * Does not un-bubble, just hides or un-hides. - * Updates stack description for TalkBack focus. - * Updates bubbles' icon views clickable states + * Does not un-bubble, just hides or un-hides the views themselves. + * + * Updates view description for TalkBack focus. + * Updates bubbles' icon views clickable states (when floating). */ - public void updateStack() { - if (mStackView == null) { + public void updateBubbleViews() { + if (mStackView == null && mLayerView == null) { return; } if (!mIsStatusBarShade) { - // Bubbles don't appear over the locked shade. - mStackView.setVisibility(INVISIBLE); + // Bubbles don't appear when the device is locked. + if (mStackView != null) { + mStackView.setVisibility(INVISIBLE); + } + if (mLayerView != null) { + mLayerView.setVisibility(INVISIBLE); + } } else if (hasBubbles()) { // If we're unlocked, show the stack if we have bubbles. If we don't have bubbles, the // stack will be set to INVISIBLE in onAllBubblesAnimatedOut after the bubbles animate // out. - mStackView.setVisibility(VISIBLE); + if (mStackView != null) { + mStackView.setVisibility(VISIBLE); + } + if (mLayerView != null && isStackExpanded()) { + mLayerView.setVisibility(VISIBLE); + } } - mStackView.updateContentDescription(); - - mStackView.updateBubblesAcessibillityStates(); + if (mStackView != null) { + mStackView.updateContentDescription(); + mStackView.updateBubblesAcessibillityStates(); + } else if (mLayerView != null) { + // TODO(b/273313561): handle a11y for BubbleBarLayerView + } } @VisibleForTesting diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java index f437553337efe..1a97c0504b379 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java @@ -22,6 +22,7 @@ import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES; import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME; import android.annotation.NonNull; +import android.annotation.Nullable; import android.content.Context; import android.content.Intent; import android.content.pm.ApplicationInfo; @@ -38,12 +39,12 @@ import android.util.Log; import android.util.PathParser; import android.view.LayoutInflater; -import androidx.annotation.Nullable; -import androidx.annotation.VisibleForTesting; - +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.graphics.ColorUtils; import com.android.launcher3.icons.BitmapInfo; import com.android.wm.shell.R; +import com.android.wm.shell.bubbles.bar.BubbleBarExpandedView; +import com.android.wm.shell.bubbles.bar.BubbleBarLayerView; import java.lang.ref.WeakReference; import java.util.Objects; @@ -70,6 +71,7 @@ public class BubbleViewInfoTask extends AsyncTask mContext; private WeakReference mController; private WeakReference mStackView; + private WeakReference mLayerView; private BubbleIconFactory mIconFactory; private BubbleBadgeIconFactory mBadgeIconFactory; private boolean mSkipInflation; @@ -83,7 +85,8 @@ public class BubbleViewInfoTask extends AsyncTask(context); mController = new WeakReference<>(controller); mStackView = new WeakReference<>(stackView); + mLayerView = new WeakReference<>(layerView); mIconFactory = factory; mBadgeIconFactory = badgeFactory; mSkipInflation = skipInflation; @@ -102,8 +106,13 @@ public class BubbleViewInfoTask extends AsyncTask Date: Thu, 16 Feb 2023 16:05:29 -0800 Subject: [PATCH 4/4] Create BubbleViewCallback for the bubble bar and use it * Creates a separate BubbleViewCallback for use when bubbles are showing in the bubble bar. When bubbles are in the bubble bar we use a different set of views, so this callback sends the updates to those views (BubbleBarLayerView). * BubbleBarLayerView only deals with the expanded state for bubbles so only updates relevant to the expanded state get sent to it. Test: treehugger / manual with other CLs Bug: 253318833 Change-Id: Ie68f7538820993680a1cce6a6f782b622fcebbd0 --- .../wm/shell/bubbles/BubbleController.java | 65 ++++++++++++++++++- 1 file changed, 64 insertions(+), 1 deletion(-) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java index 0bb1cabb9b5ef..cf0dbe340f2b6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java @@ -262,6 +262,9 @@ public class BubbleController implements ConfigurationChangeListener, /** Used to send bubble events to launcher. */ private Bubbles.BubbleStateListener mBubbleStateListener; + /** Used to send updates to the views from {@link #mBubbleDataListener}. */ + private BubbleViewCallback mBubbleViewCallback; + public BubbleController(Context context, ShellInit shellInit, ShellCommandHandler shellCommandHandler, @@ -343,6 +346,9 @@ public class BubbleController implements ConfigurationChangeListener, } protected void onInit() { + mBubbleViewCallback = isShowingAsBubbleBar() + ? mBubbleBarViewCallback + : mBubbleStackViewCallback; mBubbleData.setListener(mBubbleDataListener); mBubbleData.setSuppressionChangedListener(this::onBubbleMetadataFlagChanged); mDataRepository.setSuppressionChangedListener(this::onBubbleMetadataFlagChanged); @@ -1459,7 +1465,8 @@ public class BubbleController implements ConfigurationChangeListener, }); } - private final BubbleViewCallback mBubbleViewCallback = new BubbleViewCallback() { + /** When bubbles are floating, this will be used to notify the floating views. */ + private final BubbleViewCallback mBubbleStackViewCallback = new BubbleViewCallback() { @Override public void removeBubble(Bubble removedBubble) { if (mStackView != null) { @@ -1511,6 +1518,62 @@ public class BubbleController implements ConfigurationChangeListener, } }; + /** When bubbles are in the bubble bar, this will be used to notify bubble bar views. */ + private final BubbleViewCallback mBubbleBarViewCallback = new BubbleViewCallback() { + @Override + public void removeBubble(Bubble removedBubble) { + if (mLayerView != null) { + // TODO: need to check if there's something that needs to happen here, e.g. if + // the currently selected & expanded bubble is removed? + } + } + + @Override + public void addBubble(Bubble addedBubble) { + // Nothing to do for adds, these are handled by launcher / in the bubble bar. + } + + @Override + public void updateBubble(Bubble updatedBubble) { + // Nothing to do for updates, these are handled by launcher / in the bubble bar. + } + + @Override + public void bubbleOrderChanged(List bubbleOrder, boolean updatePointer) { + // Nothing to do for order changes, these are handled by launcher / in the bubble bar. + } + + @Override + public void suppressionChanged(Bubble bubble, boolean isSuppressed) { + if (mLayerView != null) { + // TODO (b/273316505) handle suppression changes, although might not need to + // to do anything on the layerview side for this... + } + } + + @Override + public void expansionChanged(boolean isExpanded) { + if (mLayerView != null) { + if (!isExpanded) { + mLayerView.collapse(); + } else { + BubbleViewProvider selectedBubble = mBubbleData.getSelectedBubble(); + if (selectedBubble != null) { + mLayerView.showExpandedView(selectedBubble); + } + } + } + } + + @Override + public void selectionChanged(BubbleViewProvider selectedBubble) { + // Only need to update the layer view if we're currently expanded for selection changes. + if (mLayerView != null && isStackExpanded()) { + mLayerView.showExpandedView(selectedBubble); + } + } + }; + @SuppressWarnings("FieldCanBeLocal") private final BubbleData.Listener mBubbleDataListener = new BubbleData.Listener() {