Merge changes Ie68f7538,I9e8410c6,I79ab3cbe,Iee05e39f into udc-dev

* changes:
  Create BubbleViewCallback for the bubble bar and use it
  Use BubbleBarLayerView & BubbleBarExpandedView
  Introduce BubbleBarLayerView & animation for BubbleBarExpandedView
  Introduce BubbleBarExpandedView
This commit is contained in:
Mady Mellor
2023-04-03 22:24:27 +00:00
committed by Android (Google) Code Review
11 changed files with 1102 additions and 71 deletions

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ 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.
-->
<com.android.wm.shell.bubbles.bar.BubbleBarExpandedView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:orientation="vertical"
android:id="@+id/bubble_bar_expanded_view">
</com.android.wm.shell.bubbles.bar.BubbleBarExpandedView>

View File

@@ -228,6 +228,8 @@
<dimen name="bubble_user_education_stack_padding">16dp</dimen>
<!-- Size of the bubble bar (height), should match transient_taskbar_size in Launcher. -->
<dimen name="bubblebar_size">72dp</dimen>
<!-- The size of the drag handle / menu shown along with a bubble bar expanded view. -->
<dimen name="bubblebar_expanded_view_menu_size">16dp</dimen>
<!-- Bottom and end margin for compat buttons. -->
<dimen name="compat_button_margin">24dp</dimen>

View File

@@ -47,6 +47,8 @@ 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;
@@ -87,8 +89,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 +339,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 +382,9 @@ public class Bubble implements BubbleViewProvider {
mExpandedView.cleanUpExpandedState();
mExpandedView = null;
}
if (mBubbleBarExpandedView != null) {
mBubbleBarExpandedView.cleanUpExpandedState();
}
if (mIntent != null) {
mIntent.unregisterCancelListener(mIntentCancelListener);
}
@@ -410,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) {
@@ -428,6 +451,7 @@ public class Bubble implements BubbleViewProvider {
context,
controller,
stackView,
layerView,
iconFactory,
badgeIconFactory,
skipInflation,
@@ -445,7 +469,7 @@ public class Bubble implements BubbleViewProvider {
}
boolean isInflated() {
return mIconView != null && mExpandedView != null;
return (mIconView != null && mExpandedView != null) || mBubbleBarExpandedView != null;
}
void stopInflation() {
@@ -459,6 +483,7 @@ public class Bubble implements BubbleViewProvider {
if (!isInflated()) {
mIconView = info.imageView;
mExpandedView = info.expandedView;
mBubbleBarExpandedView = info.bubbleBarExpandedView;
}
mShortcutInfo = info.shortcutInfo;
@@ -469,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;
@@ -478,6 +503,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 +635,9 @@ public class Bubble implements BubbleViewProvider {
*/
@Override
public int getTaskId() {
if (mBubbleBarExpandedView != null) {
return mBubbleBarExpandedView.getTaskId();
}
return mExpandedView != null ? mExpandedView.getTaskId() : mTaskId;
}

View File

@@ -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;
@@ -260,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,
@@ -341,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);
@@ -546,7 +554,7 @@ public class BubbleController implements ConfigurationChangeListener,
}
private void openBubbleOverflow() {
ensureStackViewCreated();
ensureBubbleViewsAndWindowCreated();
mBubbleData.setShowingOverflow(true);
mBubbleData.setSelectedBubble(mBubbleData.getOverflow());
mBubbleData.setExpanded(true);
@@ -586,7 +594,7 @@ public class BubbleController implements ConfigurationChangeListener,
expandStackAndSelectBubble(mNotifEntryToExpandOnShadeUnlock);
}
updateStack();
updateBubbleViews();
}
@VisibleForTesting
@@ -682,39 +690,59 @@ public class BubbleController implements ConfigurationChangeListener,
return mBubblePositioner;
}
Bubbles.SysuiProxy getSysuiProxy() {
public Bubbles.SysuiProxy getSysuiProxy() {
return mSysuiProxy;
}
/**
* 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 +765,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 +811,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 +832,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 +930,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 +1012,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 +1219,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 +1296,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 */);
}
@@ -1406,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) {
@@ -1458,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<Bubble> 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() {
@@ -1475,7 +1591,7 @@ public class BubbleController implements ConfigurationChangeListener,
+ " unsuppressed=" + (update.unsuppressedBubble != null));
}
ensureStackViewCreated();
ensureBubbleViewsAndWindowCreated();
// Lazy load overflow bubbles from disk
loadOverflowBubblesFromDisk();
@@ -1570,7 +1686,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 +1766,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

View File

@@ -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
}

View File

@@ -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<Void, Void, BubbleViewInfoTask
private WeakReference<Context> mContext;
private WeakReference<BubbleController> mController;
private WeakReference<BubbleStackView> mStackView;
private WeakReference<BubbleBarLayerView> mLayerView;
private BubbleIconFactory mIconFactory;
private BubbleBadgeIconFactory mBadgeIconFactory;
private boolean mSkipInflation;
@@ -83,7 +85,8 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask
BubbleViewInfoTask(Bubble b,
Context context,
BubbleController controller,
BubbleStackView stackView,
@Nullable BubbleStackView stackView,
@Nullable BubbleBarLayerView layerView,
BubbleIconFactory factory,
BubbleBadgeIconFactory badgeFactory,
boolean skipInflation,
@@ -93,6 +96,7 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask
mContext = new WeakReference<>(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<Void, Void, BubbleViewInfoTask
@Override
protected BubbleViewInfo doInBackground(Void... voids) {
return BubbleViewInfo.populate(mContext.get(), mController.get(), mStackView.get(),
mIconFactory, mBadgeIconFactory, mBubble, mSkipInflation);
if (mController.get().isShowingAsBubbleBar()) {
return BubbleViewInfo.populateForBubbleBar(mContext.get(), mController.get(),
mLayerView.get(), mBadgeIconFactory, mBubble, mSkipInflation);
} else {
return BubbleViewInfo.populate(mContext.get(), mController.get(), mStackView.get(),
mIconFactory, mBadgeIconFactory, mBubble, mSkipInflation);
}
}
@Override
@@ -124,16 +133,70 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask
*/
@VisibleForTesting
public static class BubbleViewInfo {
BadgedImageView imageView;
BubbleExpandedView expandedView;
// TODO(b/273312602): for foldables it might make sense to populate all of the views
// Always populated
ShortcutInfo shortcutInfo;
String appName;
Bitmap bubbleBitmap;
Bitmap badgeBitmap;
Bitmap mRawBadgeBitmap;
Bitmap rawBadgeBitmap;
// Only populated when showing in taskbar
BubbleBarExpandedView bubbleBarExpandedView;
// These are only populated when not showing in taskbar
BadgedImageView imageView;
BubbleExpandedView expandedView;
int dotColor;
Path dotPath;
Bubble.FlyoutMessage flyoutMessage;
Bitmap bubbleBitmap;
Bitmap badgeBitmap;
@Nullable
public static BubbleViewInfo populateForBubbleBar(Context c, BubbleController controller,
BubbleBarLayerView layerView, BubbleBadgeIconFactory badgeIconFactory, Bubble b,
boolean skipInflation) {
BubbleViewInfo info = new BubbleViewInfo();
if (!skipInflation && !b.isInflated()) {
LayoutInflater inflater = LayoutInflater.from(c);
info.bubbleBarExpandedView = (BubbleBarExpandedView) inflater.inflate(
R.layout.bubble_bar_expanded_view, layerView, false /* attachToRoot */);
info.bubbleBarExpandedView.initialize(controller);
}
if (b.getShortcutInfo() != null) {
info.shortcutInfo = b.getShortcutInfo();
}
// App name & app icon
PackageManager pm = BubbleController.getPackageManagerForUser(c,
b.getUser().getIdentifier());
ApplicationInfo appInfo;
Drawable badgedIcon;
Drawable appIcon;
try {
appInfo = pm.getApplicationInfo(
b.getPackageName(),
PackageManager.MATCH_UNINSTALLED_PACKAGES
| PackageManager.MATCH_DISABLED_COMPONENTS
| PackageManager.MATCH_DIRECT_BOOT_UNAWARE
| PackageManager.MATCH_DIRECT_BOOT_AWARE);
if (appInfo != null) {
info.appName = String.valueOf(pm.getApplicationLabel(appInfo));
}
appIcon = pm.getApplicationIcon(b.getPackageName());
badgedIcon = pm.getUserBadgedIcon(appIcon, b.getUser());
} catch (PackageManager.NameNotFoundException exception) {
// If we can't find package... don't think we should show the bubble.
Log.w(TAG, "Unable to find package: " + b.getPackageName());
return null;
}
info.rawBadgeBitmap = badgeIconFactory.getBadgeBitmap(badgedIcon, false).icon;
return info;
}
@VisibleForTesting
@Nullable
@@ -195,7 +258,7 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask
b.isImportantConversation());
info.badgeBitmap = badgeBitmapInfo.icon;
// Raw badge bitmap never includes the important conversation ring
info.mRawBadgeBitmap = b.isImportantConversation()
info.rawBadgeBitmap = b.isImportantConversation()
? badgeIconFactory.getBadgeBitmap(badgedIcon, false).icon
: badgeBitmapInfo.icon;

View File

@@ -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();

View File

@@ -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();
}
}

View File

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

View File

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

View File

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