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

Commit 6ea766ed authored by Mady Mellor's avatar Mady Mellor
Browse files

Vertical bubbles in landscape

- in landscape the bubbles now show on left/right side & layout vertically
  instead of remaining at the top and being horizontal
- introduces BubblePositioner to track the config changes and available
  screen rect for bubbles based on insets / cutouts
- pointer can now go left or right
- fixes issue where overflow could be past screen height in landscape

Test: manual - bubbles at the top in portrait, bubbles on left / right side
               in landscape, bubbles at the top with notch, bubbles on left
               / right side in landscape with notch, with/without gesture nav
Bug: 167413172
Change-Id: I4528035fd03d17d11865ddcdddc4732f5a8e616a
parent e7bb6167
Loading
Loading
Loading
Loading
+17 −4
Original line number Diff line number Diff line
@@ -177,6 +177,7 @@ public class BubbleController implements Bubbles, ConfigurationController.Config
    private ScrimView mBubbleScrim;
    @Nullable private BubbleStackView mStackView;
    private BubbleIconFactory mBubbleIconFactory;
    private BubblePositioner mBubblePositioner;

    /**
     * The relative position of the stack when we removed it and nulled it out. If the stack is
@@ -387,7 +388,7 @@ public class BubbleController implements Bubbles, ConfigurationController.Config
                dumpManager, floatingContentCoordinator,
                new BubbleDataRepository(context, launcherApps), sysUiState, notificationManager,
                statusBarService, windowManager, windowManagerShellWrapper, launcherApps, logger,
                mainHandler, organizer);
                mainHandler, organizer, new BubblePositioner(context, windowManager));
    }

    /**
@@ -419,7 +420,8 @@ public class BubbleController implements Bubbles, ConfigurationController.Config
            LauncherApps launcherApps,
            BubbleLogger bubbleLogger,
            Handler mainHandler,
            ShellTaskOrganizer organizer) {
            ShellTaskOrganizer organizer,
            BubblePositioner positioner) {
        dumpManager.registerDumpable(TAG, this);
        mContext = context;
        mShadeController = shadeController;
@@ -530,6 +532,7 @@ public class BubbleController implements Bubbles, ConfigurationController.Config

        mBubbleIconFactory = new BubbleIconFactory(context);
        mTaskListener = new MultiWindowTaskListener(mMainHandler, organizer);
        mBubblePositioner = positioner;

        launcherApps.registerCallback(new LauncherApps.Callback() {
            @Override
@@ -809,6 +812,11 @@ public class BubbleController implements Bubbles, ConfigurationController.Config
        return mTaskListener;
    }

    @Override
    public BubblePositioner getPositioner() {
        return mBubblePositioner;
    }

    /**
     * BubbleStackView is lazily created by this method the first time a Bubble is added. This
     * method initializes the stack view and adds it to the StatusBar just above the scrim.
@@ -818,9 +826,10 @@ public class BubbleController implements Bubbles, ConfigurationController.Config
            mStackView = new BubbleStackView(
                    mContext, mBubbleData, mSurfaceSynchronizer, mFloatingContentCoordinator,
                    this::onAllBubblesAnimatedOut, this::onImeVisibilityChanged,
                    this::hideCurrentInputMethod, this::onBubbleExpandChanged);
                    this::hideCurrentInputMethod, this::onBubbleExpandChanged, mBubblePositioner);
            mStackView.setStackStartPosition(mPositionFromRemovedStack);
            mStackView.addView(mBubbleScrim);
            mStackView.onOrientationChanged();
            if (mExpandListener != null) {
                mStackView.setExpandListener(mExpandListener);
            }
@@ -978,10 +987,14 @@ public class BubbleController implements Bubbles, ConfigurationController.Config

    @Override
    public void onConfigChanged(Configuration newConfig) {
        if (mBubblePositioner != null) {
            // This doesn't trigger any changes, always update it
            mBubblePositioner.update(newConfig.orientation);
        }
        if (mStackView != null && newConfig != null) {
            if (newConfig.orientation != mOrientation) {
                mOrientation = newConfig.orientation;
                mStackView.onOrientationChanged(newConfig.orientation);
                mStackView.onOrientationChanged();
            }
            if (newConfig.densityDpi != mDensityDpi) {
                mDensityDpi = newConfig.densityDpi;
+89 −64
Original line number Diff line number Diff line
@@ -38,7 +38,6 @@ import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.Outline;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.ShapeDrawable;
import android.os.Bundle;
@@ -48,7 +47,6 @@ import android.view.SurfaceControl;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewOutlineProvider;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
@@ -83,24 +81,26 @@ public class BubbleExpandedView extends LinearLayout {
    private boolean mImeVisible;
    private boolean mNeedsNewHeight;

    private Point mDisplaySize;
    private int mMinHeight;
    private int mOverflowHeight;
    private int mSettingsIconHeight;
    private int mPointerWidth;
    private int mPointerHeight;
    private ShapeDrawable mPointerDrawable;
    private ShapeDrawable mCurrentPointer;
    private ShapeDrawable mTopPointer;
    private ShapeDrawable mLeftPointer;
    private ShapeDrawable mRightPointer;
    private int mExpandedViewPadding;
    private float mCornerRadius = 0f;

    @Nullable private Bubble mBubble;
    private PendingIntent mPendingIntent;

    // TODO(b/170891664): Don't use a flag, set the BubbleOverflow object instead
    private boolean mIsOverflow;

    private Bubbles mBubbles = Dependency.get(Bubbles.class);
    private WindowManager mWindowManager;
    private BubbleStackView mStackView;
    private BubblePositioner mPositioner;

    /**
     * Container for the ActivityView that has a solid, round-rect background that shows if the
@@ -224,17 +224,6 @@ public class BubbleExpandedView extends LinearLayout {
        updateDimensions();
    }

    void updateDimensions() {
        mDisplaySize = new Point();
        mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
        // Get the real size -- this includes screen decorations (notches, statusbar, navbar).
        mWindowManager.getDefaultDisplay().getRealSize(mDisplaySize);
        Resources res = getResources();
        mMinHeight = res.getDimensionPixelSize(R.dimen.bubble_expanded_default_height);
        mOverflowHeight = res.getDimensionPixelSize(R.dimen.bubble_overflow_height);
        mPointerMargin = res.getDimensionPixelSize(R.dimen.bubble_pointer_margin);
    }

    @SuppressLint("ClickableViewAccessibility")
    @Override
    protected void onFinishInflate() {
@@ -245,14 +234,22 @@ public class BubbleExpandedView extends LinearLayout {
        mPointerWidth = res.getDimensionPixelSize(R.dimen.bubble_pointer_width);
        mPointerHeight = res.getDimensionPixelSize(R.dimen.bubble_pointer_height);

        mPointerDrawable = new ShapeDrawable(TriangleShape.create(
        mTopPointer = new ShapeDrawable(TriangleShape.create(
                mPointerWidth, mPointerHeight, true /* pointUp */));
        mLeftPointer = new ShapeDrawable(TriangleShape.createHorizontal(
                mPointerWidth, mPointerHeight, true /* pointLeft */));
        mRightPointer = new ShapeDrawable(TriangleShape.createHorizontal(
                mPointerWidth, mPointerHeight, false /* pointLeft */));

        mCurrentPointer = mTopPointer;
        mPointerView.setVisibility(INVISIBLE);

        mSettingsIconHeight = getContext().getResources().getDimensionPixelSize(
                R.dimen.bubble_manage_button_height);
        mSettingsIcon = findViewById(R.id.settings_button);

        mPositioner = mBubbles.getPositioner();

        mTaskView = new TaskView(mContext, mBubbles.getTaskManager());
        // Set ActivityView's alpha value as zero, since there is no view content to be shown.
        setContentVisibility(false);
@@ -282,8 +279,7 @@ public class BubbleExpandedView extends LinearLayout {
        applyThemeAttrs();

        mExpandedViewPadding = res.getDimensionPixelSize(R.dimen.bubble_expanded_view_padding);
        setPadding(mExpandedViewPadding, mExpandedViewPadding, mExpandedViewPadding,
                mExpandedViewPadding);
        setClipToPadding(false);
        setOnTouchListener((view, motionEvent) -> {
            if (mTaskView == null) {
                return false;
@@ -311,6 +307,52 @@ public class BubbleExpandedView extends LinearLayout {
        setLayoutDirection(LAYOUT_DIRECTION_LOCALE);
    }

    void updateDimensions() {
        Resources res = getResources();
        mMinHeight = res.getDimensionPixelSize(R.dimen.bubble_expanded_default_height);
        mOverflowHeight = res.getDimensionPixelSize(R.dimen.bubble_overflow_height);
        mPointerMargin = res.getDimensionPixelSize(R.dimen.bubble_pointer_margin);
    }

    void applyThemeAttrs() {
        final TypedArray ta = mContext.obtainStyledAttributes(new int[] {
                android.R.attr.dialogCornerRadius,
                android.R.attr.colorBackgroundFloating});
        mCornerRadius = ta.getDimensionPixelSize(0, 0);
        mExpandedViewContainer.setBackgroundColor(ta.getColor(1, Color.WHITE));
        ta.recycle();

        if (mTaskView != null && ScreenDecorationsUtils.supportsRoundedCornersOnWindows(
                mContext.getResources())) {
            mTaskView.setCornerRadius(mCornerRadius);
        }
        updatePointerView();
    }

    private void updatePointerView() {
        final int mode =
                getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
        switch (mode) {
            case Configuration.UI_MODE_NIGHT_NO:
                mCurrentPointer.setTint(getResources().getColor(R.color.bubbles_light));
                break;
            case Configuration.UI_MODE_NIGHT_YES:
                mCurrentPointer.setTint(getResources().getColor(R.color.bubbles_dark));
                break;
        }
        LayoutParams lp = (LayoutParams) mPointerView.getLayoutParams();
        if (mCurrentPointer == mLeftPointer || mCurrentPointer == mRightPointer) {
            lp.width = mPointerHeight;
            lp.height = mPointerWidth;
        } else {
            lp.width = mPointerWidth;
            lp.height = mPointerHeight;
        }
        mPointerView.setLayoutParams(lp);
        mPointerView.setBackground(mCurrentPointer);
    }


    private String getBubbleKey() {
        return mBubble != null ? mBubble.getKey() : "null";
    }
@@ -371,32 +413,6 @@ public class BubbleExpandedView extends LinearLayout {
        }
    }

    void applyThemeAttrs() {
        final TypedArray ta = mContext.obtainStyledAttributes(new int[] {
                android.R.attr.dialogCornerRadius,
                android.R.attr.colorBackgroundFloating});
        mCornerRadius = ta.getDimensionPixelSize(0, 0);
        mExpandedViewContainer.setBackgroundColor(ta.getColor(1, Color.WHITE));
        ta.recycle();

        if (mTaskView != null && ScreenDecorationsUtils.supportsRoundedCornersOnWindows(
                mContext.getResources())) {
            mTaskView.setCornerRadius(mCornerRadius);
        }

        final int mode =
                getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
        switch (mode) {
            case Configuration.UI_MODE_NIGHT_NO:
                mPointerDrawable.setTint(getResources().getColor(R.color.bubbles_light));
                break;
            case Configuration.UI_MODE_NIGHT_YES:
                mPointerDrawable.setTint(getResources().getColor(R.color.bubbles_dark));
                break;
        }
        mPointerView.setBackground(mPointerDrawable);
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
@@ -522,12 +538,12 @@ public class BubbleExpandedView extends LinearLayout {
        }

        if (mBubble != null || mIsOverflow) {
            float desiredHeight = mOverflowHeight;
            if (!mIsOverflow) {
                desiredHeight = Math.max(mBubble.getDesiredHeight(mContext), mMinHeight);
            }
            float desiredHeight = mIsOverflow
                    ? mOverflowHeight
                    : mBubble.getDesiredHeight(mContext);
            desiredHeight = Math.max(desiredHeight, mMinHeight);
            float height = Math.min(desiredHeight, getMaxExpandedHeight());
            height = Math.max(height, mIsOverflow ? mOverflowHeight : mMinHeight);
            height = Math.max(height, mMinHeight);
            FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mTaskView.getLayoutParams();
            mNeedsNewHeight = lp.height != height;
            if (!mImeVisible) {
@@ -546,21 +562,17 @@ public class BubbleExpandedView extends LinearLayout {
    }

    private int getMaxExpandedHeight() {
        mWindowManager.getDefaultDisplay().getRealSize(mDisplaySize);
        int expandedContainerY = mExpandedViewContainerLocation != null
                ? mExpandedViewContainerLocation[1]
                // Remove top insets back here because availableRect.height would account for that
                ? mExpandedViewContainerLocation[1] - mPositioner.getInsets().top
                : 0;
        int bottomInset = getRootWindowInsets() != null
                ? getRootWindowInsets().getStableInsetBottom()
                : 0;

        return mDisplaySize.y
        return mPositioner.getAvailableRect().height()
                - expandedContainerY
                - getPaddingTop()
                - getPaddingBottom()
                - mSettingsIconHeight
                - mPointerHeight
                - mPointerMargin - bottomInset;
                - mPointerMargin;
    }

    /**
@@ -585,12 +597,25 @@ public class BubbleExpandedView extends LinearLayout {
    }

    /**
     * Set the x position that the tip of the triangle should point to.
     * Set the position that the tip of the triangle should point to.
     */
    public void setPointerPosition(float x) {
        float halfPointerWidth = mPointerWidth / 2f;
        float pointerLeft = x - halfPointerWidth - mExpandedViewPadding;
        mPointerView.setTranslationX(pointerLeft);
    public void setPointerPosition(float x, float y, boolean isLandscape, boolean onLeft) {
        // Pointer gets drawn in the padding
        int paddingLeft = (isLandscape && onLeft) ? mPointerHeight : 0;
        int paddingRight = (isLandscape && !onLeft) ? mPointerHeight : 0;
        int paddingTop = isLandscape ? 0 : mExpandedViewPadding;
        setPadding(paddingLeft, paddingTop, paddingRight, 0);

        if (isLandscape) {
            // TODO: why setY vs setTranslationY ? linearlayout?
            mPointerView.setY(y - (mPointerWidth / 2f));
            mPointerView.setTranslationX(onLeft ? -mPointerHeight : x - mExpandedViewPadding);
        } else {
            mPointerView.setTranslationY(0f);
            mPointerView.setTranslationX(x - mExpandedViewPadding - (mPointerWidth / 2f));
        }
        mCurrentPointer = isLandscape ? onLeft ? mLeftPointer : mRightPointer : mTopPointer;
        updatePointerView();
        mPointerView.setVisibility(VISIBLE);
    }

+89 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2020 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.systemui.bubbles;

import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Insets;
import android.graphics.Rect;
import android.view.WindowInsets;
import android.view.WindowManager;
import android.view.WindowMetrics;

import androidx.annotation.VisibleForTesting;

/**
 * Keeps track of display size, configuration, and specific bubble sizes. One place for all
 * placement and positioning calculations to refer to.
 */
public class BubblePositioner {

    private WindowManager mWindowManager;
    private Rect mPositionRect;
    private int mOrientation;
    private Insets mInsets;

    public BubblePositioner(Context context, WindowManager windowManager) {
        mWindowManager = windowManager;
        update(Configuration.ORIENTATION_UNDEFINED);
    }

    public void update(int orientation) {
        WindowMetrics windowMetrics = mWindowManager.getCurrentWindowMetrics();
        mPositionRect = new Rect(windowMetrics.getBounds());
        WindowInsets metricInsets = windowMetrics.getWindowInsets();

        Insets insets = metricInsets.getInsetsIgnoringVisibility(WindowInsets.Type.navigationBars()
                | WindowInsets.Type.statusBars()
                | WindowInsets.Type.displayCutout());
        update(orientation, insets, windowMetrics.getBounds());
    }

    @VisibleForTesting
    public void update(int orientation, Insets insets, Rect bounds) {
        mOrientation = orientation;
        mInsets = insets;

        mPositionRect = new Rect(bounds);
        mPositionRect.left += mInsets.left;
        mPositionRect.top += mInsets.top;
        mPositionRect.right -= mInsets.right;
        mPositionRect.bottom -= mInsets.bottom;
    }

    /**
     * @return a rect of available screen space for displaying bubbles in the correct orientation,
     * accounting for system bars and cutouts.
     */
    public Rect getAvailableRect() {
        return mPositionRect;
    }

    /**
     * @return the current orientation.
     */
    public int getOrientation() {
        return mOrientation;
    }

    /**
     * @return the relevant insets (status bar, nav bar, cutouts).
     */
    public Insets getInsets() {
        return mInsets;
    }
}
+172 −127

File changed.

Preview size limit exceeded, changes collapsed.

+4 −1
Original line number Diff line number Diff line
@@ -128,6 +128,9 @@ public interface Bubbles {
    /** Set a listener to be notified of when overflow view update. */
    void setOverflowListener(BubbleData.Listener listener);

    /** The task listener for events in bubble tasks. **/
    /** The task listener for events in bubble tasks. */
    MultiWindowTaskListener getTaskManager();

    /** Contains information to help position things on the screen.  */
    BubblePositioner getPositioner();
}
Loading