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); + } +}