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

Commit 70444875 authored by Liran Binyamin's avatar Liran Binyamin
Browse files

Create DeviceConfig class for positioning bubbles

This change introduces a new `DeviceConfig` class that stores device configuration properties used by BubblePositioner.
This allows testing different configurations more easily without mocking, and will be followed up with a CL that converts BubblePositionerTest to bivalent.

Bug: 308004028
Test: atest BubblePositionerTest
Change-Id: I328cd4f0c946ce062609951f59a0862a7e43078f
parent 70831a58
Loading
Loading
Loading
Loading
+3 −3
Original line number Diff line number Diff line
@@ -787,7 +787,7 @@ public class BubbleController implements ConfigurationChangeListener,
                mLayerView.setOnApplyWindowInsetsListener((view, windowInsets) -> {
                    if (!windowInsets.equals(mWindowInsets) && mLayerView != null) {
                        mWindowInsets = windowInsets;
                        mBubblePositioner.update();
                        mBubblePositioner.update(DeviceConfig.create(mContext, mWindowManager));
                        mLayerView.onDisplaySizeChanged();
                    }
                    return windowInsets;
@@ -797,7 +797,7 @@ public class BubbleController implements ConfigurationChangeListener,
                mStackView.setOnApplyWindowInsetsListener((view, windowInsets) -> {
                    if (!windowInsets.equals(mWindowInsets) && mStackView != null) {
                        mWindowInsets = windowInsets;
                        mBubblePositioner.update();
                        mBubblePositioner.update(DeviceConfig.create(mContext, mWindowManager));
                        mStackView.onDisplaySizeChanged();
                    }
                    return windowInsets;
@@ -979,7 +979,7 @@ public class BubbleController implements ConfigurationChangeListener,
    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        if (mBubblePositioner != null) {
            mBubblePositioner.update();
            mBubblePositioner.update(DeviceConfig.create(mContext, mWindowManager));
        }
        if (mStackView != null && newConfig != null) {
            if (newConfig.densityDpi != mDensityDpi
+24 −55
Original line number Diff line number Diff line
@@ -16,10 +16,7 @@

package com.android.wm.shell.bubbles;

import static android.view.View.LAYOUT_DIRECTION_RTL;

import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Insets;
import android.graphics.Point;
@@ -28,9 +25,7 @@ import android.graphics.Rect;
import android.graphics.RectF;
import android.util.Log;
import android.view.Surface;
import android.view.WindowInsets;
import android.view.WindowManager;
import android.view.WindowMetrics;

import androidx.annotation.VisibleForTesting;

@@ -68,15 +63,12 @@ public class BubblePositioner {
    private static final float EXPANDED_VIEW_BUBBLE_BAR_LANDSCAPE_WIDTH_PERCENT = 0.4f;

    private Context mContext;
    private WindowManager mWindowManager;
    private DeviceConfig mDeviceConfig;
    private Rect mScreenRect;
    private @Surface.Rotation int mRotation = Surface.ROTATION_0;
    private Insets mInsets;
    private boolean mImeVisible;
    private int mImeHeight;
    private boolean mIsLargeScreen;
    private boolean mIsSmallTablet;

    private Rect mPositionRect;
    private int mDefaultMaxBubbles;
    private int mMaxBubbles;
@@ -110,44 +102,27 @@ public class BubblePositioner {

    public BubblePositioner(Context context, WindowManager windowManager) {
        mContext = context;
        mWindowManager = windowManager;
        update();
        mDeviceConfig = DeviceConfig.create(context, windowManager);
        update(mDeviceConfig);
    }

    /**
     * Available space and inset information. Call this when config changes
     * occur or when added to a window.
     */
    public void update() {
        WindowMetrics windowMetrics = mWindowManager.getCurrentWindowMetrics();
        if (windowMetrics == null) {
            return;
        }
        WindowInsets metricInsets = windowMetrics.getWindowInsets();
        Insets insets = metricInsets.getInsetsIgnoringVisibility(WindowInsets.Type.navigationBars()
                | WindowInsets.Type.statusBars()
                | WindowInsets.Type.displayCutout());

        final Rect bounds = windowMetrics.getBounds();
        Configuration config = mContext.getResources().getConfiguration();
        mIsLargeScreen = config.smallestScreenWidthDp >= 600;
        if (mIsLargeScreen) {
            float largestEdgeDp = Math.max(config.screenWidthDp, config.screenHeightDp);
            mIsSmallTablet = largestEdgeDp < 960;
        } else {
            mIsSmallTablet = false;
        }
    public void update(DeviceConfig deviceConfig) {
        mDeviceConfig = deviceConfig;

        if (BubbleDebugConfig.DEBUG_POSITIONER) {
            Log.w(TAG, "update positioner:"
                    + " rotation: " + mRotation
                    + " insets: " + insets
                    + " isLargeScreen: " + mIsLargeScreen
                    + " isSmallTablet: " + mIsSmallTablet
                    + " insets: " + deviceConfig.getInsets()
                    + " isLargeScreen: " + deviceConfig.isLargeScreen()
                    + " isSmallTablet: " + deviceConfig.isSmallTablet()
                    + " showingInBubbleBar: " + mShowingInBubbleBar
                    + " bounds: " + bounds);
                    + " bounds: " + deviceConfig.getWindowBounds());
        }
        updateInternal(mRotation, insets, bounds);
        updateInternal(mRotation, deviceConfig.getInsets(), deviceConfig.getWindowBounds());
    }

    @VisibleForTesting
@@ -175,15 +150,15 @@ public class BubblePositioner {
            mExpandedViewLargeScreenWidth = isLandscape()
                    ? (int) (bounds.width() * EXPANDED_VIEW_BUBBLE_BAR_LANDSCAPE_WIDTH_PERCENT)
                    : (int) (bounds.width() * EXPANDED_VIEW_BUBBLE_BAR_PORTRAIT_WIDTH_PERCENT);
        } else if (mIsSmallTablet) {
        } else if (mDeviceConfig.isSmallTablet()) {
            mExpandedViewLargeScreenWidth = (int) (bounds.width()
                    * EXPANDED_VIEW_SMALL_TABLET_WIDTH_PERCENT);
        } else {
            mExpandedViewLargeScreenWidth =
                    res.getDimensionPixelSize(R.dimen.bubble_expanded_view_largescreen_width);
        }
        if (mIsLargeScreen) {
            if (mIsSmallTablet) {
        if (mDeviceConfig.isLargeScreen()) {
            if (mDeviceConfig.isSmallTablet()) {
                final int centeredInset = (bounds.width() - mExpandedViewLargeScreenWidth) / 2;
                mExpandedViewLargeScreenInsetClosestEdge = centeredInset;
                mExpandedViewLargeScreenInsetFurthestEdge = centeredInset;
@@ -264,13 +239,12 @@ public class BubblePositioner {

    /** @return whether the device is in landscape orientation. */
    public boolean isLandscape() {
        return mContext.getResources().getConfiguration().orientation
                == Configuration.ORIENTATION_LANDSCAPE;
        return mDeviceConfig.isLandscape();
    }

    /** @return whether the screen is considered large. */
    public boolean isLargeScreen() {
        return mIsLargeScreen;
        return mDeviceConfig.isLargeScreen();
    }

    /**
@@ -281,7 +255,7 @@ public class BubblePositioner {
     * to the left or right side.
     */
    public boolean showBubblesVertically() {
        return isLandscape() || mIsLargeScreen;
        return isLandscape() || mDeviceConfig.isLargeScreen();
    }

    /** Size of the bubble. */
@@ -334,7 +308,7 @@ public class BubblePositioner {
    }

    private int getExpandedViewLargeScreenInsetFurthestEdge(boolean isOverflow) {
        if (isOverflow && mIsLargeScreen) {
        if (isOverflow && mDeviceConfig.isLargeScreen()) {
            return mScreenRect.width()
                    - mExpandedViewLargeScreenInsetClosestEdge
                    - mOverflowWidth;
@@ -358,7 +332,7 @@ public class BubblePositioner {
        final int pointerTotalHeight = getPointerSize();
        final int expandedViewLargeScreenInsetFurthestEdge =
                getExpandedViewLargeScreenInsetFurthestEdge(isOverflow);
        if (mIsLargeScreen) {
        if (mDeviceConfig.isLargeScreen()) {
            // Note:
            // If we're in portrait OR if we're a small tablet, then the two insets values will
            // be equal. If we're landscape and a large tablet, the two values will be different.
@@ -439,12 +413,12 @@ public class BubblePositioner {
     */
    public float getExpandedViewHeight(BubbleViewProvider bubble) {
        boolean isOverflow = bubble == null || BubbleOverflow.KEY.equals(bubble.getKey());
        if (isOverflow && showBubblesVertically() && !mIsLargeScreen) {
        if (isOverflow && showBubblesVertically() && !mDeviceConfig.isLargeScreen()) {
            // overflow in landscape on phone is max
            return MAX_HEIGHT;
        }

        if (mIsLargeScreen && !mIsSmallTablet && !isOverflow) {
        if (mDeviceConfig.isLargeScreen() && !mDeviceConfig.isSmallTablet() && !isOverflow) {
            // the expanded view height on large tablets is calculated based on the shortest screen
            // size and is the same in both portrait and landscape
            int maxVerticalInset = Math.max(mInsets.top, mInsets.bottom);
@@ -529,11 +503,9 @@ public class BubblePositioner {
     */
    public PointF getExpandedBubbleXY(int index, BubbleStackView.StackViewState state) {
        boolean showBubblesVertically = showBubblesVertically();
        boolean isRtl = mContext.getResources().getConfiguration().getLayoutDirection()
                == LAYOUT_DIRECTION_RTL;

        int onScreenIndex;
        if (showBubblesVertically || !isRtl) {
        if (showBubblesVertically || !mDeviceConfig.isRtl()) {
            onScreenIndex = index;
        } else {
            // If bubbles are shown horizontally, check if RTL language is used.
@@ -554,10 +526,10 @@ public class BubblePositioner {
        if (showBubblesVertically) {
            int inset = mExpandedViewLargeScreenInsetClosestEdge;
            y = rowStart + positionInRow;
            int left = mIsLargeScreen
            int left = mDeviceConfig.isLargeScreen()
                    ? inset - mExpandedViewPadding - mBubbleSize
                    : mPositionRect.left;
            int right = mIsLargeScreen
            int right = mDeviceConfig.isLargeScreen()
                    ? mPositionRect.right - inset + mExpandedViewPadding
                    : mPositionRect.right - mBubbleSize;
            x = state.onLeft
@@ -693,13 +665,10 @@ public class BubblePositioner {
     * @param isAppBubble whether this start position is for an app bubble or not.
     */
    public PointF getDefaultStartPosition(boolean isAppBubble) {
        final int layoutDirection = mContext.getResources().getConfiguration().getLayoutDirection();
        // Normal bubbles start on the left if we're in LTR, right otherwise.
        // TODO (b/294284894): update language around "app bubble" here
        // App bubbles start on the right in RTL, left otherwise.
        final boolean startOnLeft = isAppBubble
                ? layoutDirection == LAYOUT_DIRECTION_RTL
                : layoutDirection != LAYOUT_DIRECTION_RTL;
        final boolean startOnLeft = isAppBubble ? mDeviceConfig.isRtl() : !mDeviceConfig.isRtl();
        return getStartPosition(startOnLeft ? StackPinnedEdge.LEFT : StackPinnedEdge.RIGHT);
    }

+5 −2
Original line number Diff line number Diff line
@@ -61,6 +61,7 @@ import android.view.ViewGroup;
import android.view.ViewOutlineProvider;
import android.view.ViewPropertyAnimator;
import android.view.ViewTreeObserver;
import android.view.WindowManager;
import android.view.WindowManagerPolicyConstants;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
@@ -1001,7 +1002,8 @@ public class BubbleStackView extends FrameLayout

        mOrientationChangedListener =
                (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
                    mPositioner.update();
                    mPositioner.update(DeviceConfig.create(mContext, mContext.getSystemService(
                            WindowManager.class)));
                    onDisplaySizeChanged();
                    mExpandedAnimationController.updateResources();
                    mStackAnimationController.updateResources();
@@ -1522,7 +1524,8 @@ public class BubbleStackView extends FrameLayout
    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        mPositioner.update();
        WindowManager windowManager = mContext.getSystemService(WindowManager.class);
        mPositioner.update(DeviceConfig.create(mContext, Objects.requireNonNull(windowManager)));
        getViewTreeObserver().addOnComputeInternalInsetsListener(this);
        getViewTreeObserver().addOnDrawListener(mSystemGestureExcludeUpdater);
    }
+67 −0
Original line number Diff line number Diff line
/*
 * 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

import android.content.Context
import android.content.res.Configuration
import android.content.res.Configuration.ORIENTATION_LANDSCAPE
import android.graphics.Insets
import android.graphics.Rect
import android.view.View.LAYOUT_DIRECTION_RTL
import android.view.WindowInsets
import android.view.WindowManager
import kotlin.math.max

/** Contains device configuration used for positioning bubbles on the screen. */
data class DeviceConfig(
        val isLargeScreen: Boolean,
        val isSmallTablet: Boolean,
        val isLandscape: Boolean,
        val isRtl: Boolean,
        val windowBounds: Rect,
        val insets: Insets
) {
    companion object {

        private const val LARGE_SCREEN_MIN_EDGE_DP = 600
        private const val SMALL_TABLET_MAX_EDGE_DP = 960

        @JvmStatic
        fun create(context: Context, windowManager: WindowManager): DeviceConfig {
            val windowMetrics = windowManager.currentWindowMetrics
            val metricInsets = windowMetrics.windowInsets
            val insets = metricInsets.getInsetsIgnoringVisibility(WindowInsets.Type.navigationBars()
                    or WindowInsets.Type.statusBars()
                    or WindowInsets.Type.displayCutout())
            val windowBounds = windowMetrics.bounds
            val config: Configuration = context.resources.configuration
            val isLargeScreen = config.smallestScreenWidthDp >= LARGE_SCREEN_MIN_EDGE_DP
            val largestEdgeDp = max(config.screenWidthDp, config.screenHeightDp)
            val isSmallTablet = isLargeScreen && largestEdgeDp < SMALL_TABLET_MAX_EDGE_DP
            val isLandscape = context.resources.configuration.orientation == ORIENTATION_LANDSCAPE
            val isRtl = context.resources.configuration.layoutDirection == LAYOUT_DIRECTION_RTL
            return DeviceConfig(
                    isLargeScreen = isLargeScreen,
                    isSmallTablet = isSmallTablet,
                    isLandscape = isLandscape,
                    isRtl = isRtl,
                    windowBounds = windowBounds,
                    insets = insets
            )
        }
    }
}
+5 −1
Original line number Diff line number Diff line
@@ -28,13 +28,16 @@ import android.graphics.drawable.ColorDrawable;
import android.view.TouchDelegate;
import android.view.View;
import android.view.ViewTreeObserver;
import android.view.WindowManager;
import android.widget.FrameLayout;

import com.android.wm.shell.bubbles.BubbleController;
import com.android.wm.shell.bubbles.BubbleOverflow;
import com.android.wm.shell.bubbles.BubblePositioner;
import com.android.wm.shell.bubbles.BubbleViewProvider;
import com.android.wm.shell.bubbles.DeviceConfig;

import java.util.Objects;
import java.util.function.Consumer;

import kotlin.Unit;
@@ -104,7 +107,8 @@ public class BubbleBarLayerView extends FrameLayout
    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        mPositioner.update();
        WindowManager windowManager = mContext.getSystemService(WindowManager.class);
        mPositioner.update(DeviceConfig.create(mContext, Objects.requireNonNull(windowManager)));
        getViewTreeObserver().addOnComputeInternalInsetsListener(this);
    }

Loading