Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 2c63ff06 authored by Mady Mellor's avatar Mady Mellor Committed by Android (Google) Code Review
Browse files

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
parents 4b34b707 305b3ba4
Loading
Loading
Loading
Loading
+24 −0
Original line number Diff line number Diff line
<?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>
+2 −0
Original line number Diff line number Diff line
@@ -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>
+36 −5
Original line number Diff line number Diff line
@@ -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;
    }

+182 −52
Original line number Diff line number Diff line
@@ -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,18 +690,32 @@ 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() {
    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,
                        mContext, this, mBubbleData, mSurfaceSynchronizer,
                        mFloatingContentCoordinator,
                        mMainExecutor);
                mStackView.onOrientationChanged();
                if (mExpandListener != null) {
@@ -701,20 +723,26 @@ public class BubbleController implements ConfigurationChangeListener,
                }
                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,6 +765,18 @@ public class BubbleController implements ConfigurationChangeListener,
            mAddedToWindowManager = true;
            registerBroadcastReceiver();
            mBubbleData.getOverflow().initialize(this);
            // (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)) {
@@ -746,8 +786,9 @@ public class BubbleController implements ConfigurationChangeListener,
                    }
                    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.
            // 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.
            if (mStackView != null) {
                mStackView.setVisibility(VISIBLE);
            }
            if (mLayerView != null && isStackExpanded()) {
                mLayerView.setVisibility(VISIBLE);
            }
        }

        if (mStackView != null) {
            mStackView.updateContentDescription();

            mStackView.updateBubblesAcessibillityStates();
        } else if (mLayerView != null) {
            // TODO(b/273313561): handle a11y for BubbleBarLayerView
        }
    }

    @VisibleForTesting
+5 −0
Original line number Diff line number Diff line
@@ -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
    }
Loading