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

Commit 84113768 authored by Mady Mellor's avatar Mady Mellor
Browse files

Use BubbleBarLayerView & BubbleBarExpandedView

* Adds BubbleBarLayerView to BubbleController, if the bubble
  bar is active, BBLV will be used instead of BubbleStackView. Some
  methods in BubbleController are updated to account for BBLV.

* Updates the BubbleViewInfoTask (the inflation code for bubbles) to
  expect either BubbleBarExpandedView OR the BadgedImageView and
  BubbleExpandedView used when bubbles are NOT in the bubble bar.
  When showing in the bubble bar, BubbleBarExpandedView will be the
  only view inflated.

Test: treehugger / manual with other CLs
Bug: 253318833
Change-Id: I9e8410c62260304e286eddd9bf3c32fecc7ee317
parent 596688d1
Loading
Loading
Loading
Loading
+8 −3
Original line number Diff line number Diff line
@@ -48,6 +48,7 @@ import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.InstanceId;
import com.android.wm.shell.bubbles.bar.BubbleBarExpandedView;
import com.android.wm.shell.bubbles.bar.BubbleBarLayerView;
import com.android.wm.shell.common.bubbles.BubbleInfo;

import java.io.PrintWriter;
@@ -430,14 +431,16 @@ public class Bubble implements BubbleViewProvider {
     * @param callback the callback to notify one the bubble is ready to be displayed.
     * @param context the context for the bubble.
     * @param controller the bubble controller.
     * @param stackView the stackView the bubble is eventually added to.
     * @param stackView the view the bubble is added to, iff showing as floating.
     * @param layerView the layer the bubble is added to, iff showing in the bubble bar.
     * @param iconFactory the icon factory use to create images for the bubble.
     * @param badgeIconFactory the icon factory to create app badges for the bubble.
     */
    void inflate(BubbleViewInfoTask.Callback callback,
            Context context,
            BubbleController controller,
            BubbleStackView stackView,
            @Nullable BubbleStackView stackView,
            @Nullable BubbleBarLayerView layerView,
            BubbleIconFactory iconFactory,
            BubbleBadgeIconFactory badgeIconFactory,
            boolean skipInflation) {
@@ -448,6 +451,7 @@ public class Bubble implements BubbleViewProvider {
                context,
                controller,
                stackView,
                layerView,
                iconFactory,
                badgeIconFactory,
                skipInflation,
@@ -479,6 +483,7 @@ public class Bubble implements BubbleViewProvider {
        if (!isInflated()) {
            mIconView = info.imageView;
            mExpandedView = info.expandedView;
            mBubbleBarExpandedView = info.bubbleBarExpandedView;
        }

        mShortcutInfo = info.shortcutInfo;
@@ -489,7 +494,7 @@ public class Bubble implements BubbleViewProvider {
        mFlyoutMessage = info.flyoutMessage;

        mBadgeBitmap = info.badgeBitmap;
        mRawBadgeBitmap = info.mRawBadgeBitmap;
        mRawBadgeBitmap = info.rawBadgeBitmap;
        mBubbleBitmap = info.bubbleBitmap;

        mDotColor = info.dotColor;
+117 −50
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;
@@ -546,7 +548,7 @@ public class BubbleController implements ConfigurationChangeListener,
    }

    private void openBubbleOverflow() {
        ensureStackViewCreated();
        ensureBubbleViewsAndWindowCreated();
        mBubbleData.setShowingOverflow(true);
        mBubbleData.setSelectedBubble(mBubbleData.getOverflow());
        mBubbleData.setExpanded(true);
@@ -586,7 +588,7 @@ public class BubbleController implements ConfigurationChangeListener,
            expandStackAndSelectBubble(mNotifEntryToExpandOnShadeUnlock);
        }

        updateStack();
        updateBubbleViews();
    }

    @VisibleForTesting
@@ -687,13 +689,27 @@ public class BubbleController implements ConfigurationChangeListener,
    }

    /**
     * BubbleStackView is lazily created by this method the first time a Bubble is added. This
     * method initializes the stack view and adds it to window manager.
     * The view and window for bubbles is lazily created by this method the first time a Bubble
     * is added. Depending on the device state, this method will:
     * - initialize a {@link BubbleStackView} and add it to window manager OR
     * - initialize a {@link com.android.wm.shell.bubbles.bar.BubbleBarLayerView} and adds
     *   it to window manager.
     */
    private void ensureStackViewCreated() {
    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 +717,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 +759,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 +780,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 +805,7 @@ public class BubbleController implements ConfigurationChangeListener,
        }
    }

    /** Removes the BubbleStackView from the WindowManager if it's there. */
    /** Removes any bubble views from the WindowManager that exist. */
    private void removeFromWindowManagerMaybe() {
        if (!mAddedToWindowManager) {
            return;
@@ -791,8 +826,10 @@ public class BubbleController implements ConfigurationChangeListener,
            if (mStackView != null) {
                mWindowManager.removeView(mStackView);
                mBubbleData.getOverflow().cleanUpExpandedState();
            } else {
                Log.w(TAG, "StackView added to WindowManager, but was null when removing!");
            }
            if (mLayerView != null) {
                mWindowManager.removeView(mLayerView);
                mBubbleData.getOverflow().cleanUpExpandedState();
            }
        } catch (IllegalArgumentException e) {
            // This means the stack has already been removed - it shouldn't happen, but ignore if it
@@ -887,12 +924,22 @@ public class BubbleController implements ConfigurationChangeListener,

        // Reload each bubble
        for (Bubble b : mBubbleData.getBubbles()) {
            b.inflate(null /* callback */, mContext, this, mStackView, mBubbleIconFactory,
            b.inflate(null /* callback */,
                    mContext,
                    this,
                    mStackView,
                    mLayerView,
                    mBubbleIconFactory,
                    mBubbleBadgeIconFactory,
                    false /* skipInflation */);
        }
        for (Bubble b : mBubbleData.getOverflowBubbles()) {
            b.inflate(null /* callback */, mContext, this, mStackView, mBubbleIconFactory,
            b.inflate(null /* callback */,
                    mContext,
                    this,
                    mStackView,
                    mLayerView,
                    mBubbleIconFactory,
                    mBubbleBadgeIconFactory,
                    false /* skipInflation */);
        }
@@ -959,7 +1006,7 @@ public class BubbleController implements ConfigurationChangeListener,
     */
    @VisibleForTesting
    public boolean hasBubbles() {
        if (mStackView == null) {
        if (mStackView == null && mLayerView == null) {
            return false;
        }
        return mBubbleData.hasBubbles() || mBubbleData.isShowingOverflow();
@@ -1166,7 +1213,12 @@ public class BubbleController implements ConfigurationChangeListener,
                }
                bubble.inflate(
                        (b) -> mBubbleData.overflowBubble(Bubbles.DISMISS_RELOAD_FROM_DISK, bubble),
                        mContext, this, mStackView, mBubbleIconFactory, mBubbleBadgeIconFactory,
                        mContext,
                        this,
                        mStackView,
                        mLayerView,
                        mBubbleIconFactory,
                        mBubbleBadgeIconFactory,
                        true /* skipInflation */);
            });
            return null;
@@ -1238,10 +1290,11 @@ public class BubbleController implements ConfigurationChangeListener,
    @VisibleForTesting
    public void inflateAndAdd(Bubble bubble, boolean suppressFlyout, boolean showInShade) {
        // Lazy init stack view when a bubble is created
        ensureStackViewCreated();
        ensureBubbleViewsAndWindowCreated();
        bubble.setInflateSynchronously(mInflateSynchronously);
        bubble.inflate(b -> mBubbleData.notificationEntryUpdated(b, suppressFlyout, showInShade),
                mContext, this, mStackView, mBubbleIconFactory, mBubbleBadgeIconFactory,
                mContext, this, mStackView,  mLayerView,
                mBubbleIconFactory, mBubbleBadgeIconFactory,
                false /* skipInflation */);
    }

@@ -1475,7 +1528,7 @@ public class BubbleController implements ConfigurationChangeListener,
                        + " unsuppressed=" + (update.unsuppressedBubble != null));
            }

            ensureStackViewCreated();
            ensureBubbleViewsAndWindowCreated();

            // Lazy load overflow bubbles from disk
            loadOverflowBubblesFromDisk();
@@ -1570,7 +1623,7 @@ public class BubbleController implements ConfigurationChangeListener,
            }

            mSysuiProxy.notifyInvalidateNotifications("BubbleData.Listener.applyUpdate");
            updateStack();
            updateBubbleViews();

            // Update the cached state for queries from SysUI
            mImpl.mCachedState.update(update);
@@ -1650,28 +1703,42 @@ public class BubbleController implements ConfigurationChangeListener,

    /**
     * Updates the visibility of the bubbles based on current state.
     * Does not un-bubble, just hides or un-hides.
     * Updates stack description for TalkBack focus.
     * Updates bubbles' icon views clickable states
     * Does not un-bubble, just hides or un-hides the views themselves.
     *
     * Updates view description for TalkBack focus.
     * Updates bubbles' icon views clickable states (when floating).
     */
    public void updateStack() {
        if (mStackView == null) {
    public void updateBubbleViews() {
        if (mStackView == null && mLayerView == null) {
            return;
        }

        if (!mIsStatusBarShade) {
            // Bubbles don't appear over the locked shade.
            // 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
+75 −12
Original line number Diff line number Diff line
@@ -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,9 +106,14 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask

    @Override
    protected BubbleViewInfo doInBackground(Void... voids) {
        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
    protected void onPostExecute(BubbleViewInfo viewInfo) {
@@ -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;