Loading libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java +48 −13 Original line number Diff line number Diff line Loading @@ -242,6 +242,18 @@ public class BubblePositioner { return mDeviceConfig.isLandscape(); } /** * On large screen (not small tablet), while in portrait, expanded bubbles are aligned to * the bottom of the screen. * * @return whether bubbles are bottom aligned while expanded */ public boolean areBubblesBottomAligned() { return isLargeScreen() && !mDeviceConfig.isSmallTablet() && !isLandscape(); } /** @return whether the screen is considered large. */ public boolean isLargeScreen() { return mDeviceConfig.isLargeScreen(); Loading Loading @@ -417,7 +429,10 @@ public class BubblePositioner { - bottomPadding; } private int getExpandedViewHeightForLargeScreen() { /** * Returns the height to use for the expanded view when showing on a large screen. */ public int getExpandedViewHeightForLargeScreen() { // 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); Loading Loading @@ -460,13 +475,21 @@ public class BubblePositioner { boolean isOverflow = bubble == null || BubbleOverflow.KEY.equals(bubble.getKey()); float expandedViewHeight = getExpandedViewHeight(bubble); float topAlignment = getExpandedViewYTopAligned(); int manageButtonHeight = isOverflow ? mExpandedViewPadding : mManageButtonHeightIncludingMargins; // On largescreen portrait bubbles are bottom aligned. if (areBubblesBottomAligned() && expandedViewHeight == MAX_HEIGHT) { return mPositionRect.bottom - manageButtonHeight - getExpandedViewHeightForLargeScreen() - mPointerWidth; } if (!showBubblesVertically() || expandedViewHeight == MAX_HEIGHT) { // Top-align when bubbles are shown at the top or are max size. return topAlignment; } // If we're here, we're showing vertically & developer has made height less than maximum. int manageButtonHeight = isOverflow ? mExpandedViewPadding : mManageButtonHeightIncludingMargins; float pointerPosition = getPointerPosition(bubblePosition); float bottomIfCentered = pointerPosition + (expandedViewHeight / 2) + manageButtonHeight; float topIfCentered = pointerPosition - (expandedViewHeight / 2); Loading Loading @@ -524,14 +547,8 @@ public class BubblePositioner { // Last bubble has screen index 0 and first bubble has max screen index value. onScreenIndex = state.numberOfBubbles - 1 - index; } final float positionInRow = onScreenIndex * (mBubbleSize + mSpacingBetweenBubbles); final float expandedStackSize = getExpandedStackSize(state.numberOfBubbles); final float centerPosition = showBubblesVertically ? mPositionRect.centerY() : mPositionRect.centerX(); // alignment - centered on the edge final float rowStart = centerPosition - (expandedStackSize / 2f); final float rowStart = getBubbleRowStart(state); float x; float y; if (showBubblesVertically) { Loading @@ -557,6 +574,25 @@ public class BubblePositioner { return new PointF(x, y); } private float getBubbleRowStart(BubbleStackView.StackViewState state) { final float expandedStackSize = getExpandedStackSize(state.numberOfBubbles); final float rowStart; if (areBubblesBottomAligned()) { final float expandedViewHeight = getExpandedViewHeightForLargeScreen(); final float expandedViewBottom = mScreenRect.bottom - Math.max(mInsets.bottom, mInsets.top) - mManageButtonHeight - mPointerWidth; final float expandedViewCenter = expandedViewBottom - (expandedViewHeight / 2f); rowStart = expandedViewCenter - (expandedStackSize / 2f); } else { final float centerPosition = showBubblesVertically() ? mPositionRect.centerY() : mPositionRect.centerX(); rowStart = centerPosition - (expandedStackSize / 2f); } return rowStart; } /** * Returns the position of the bubble on-screen when the stack is expanded and the IME * is showing. Loading @@ -577,9 +613,8 @@ public class BubblePositioner { final float bottomHeight = getImeHeight() + mInsets.bottom + (mSpacingBetweenBubbles * 2); final float bottomInset = mScreenRect.bottom - bottomHeight; final float expandedStackSize = getExpandedStackSize(state.numberOfBubbles); final float centerPosition = mPositionRect.centerY(); final float rowBottom = centerPosition + (expandedStackSize / 2f); final float rowTop = centerPosition - (expandedStackSize / 2f); final float rowTop = getBubbleRowStart(state); final float rowBottom = rowTop + expandedStackSize; float rowTopForIme = rowTop; if (rowBottom > bottomInset) { // We overlap with IME, must shift the bubbles Loading libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblePositionerTest.java +195 −0 Original line number Diff line number Diff line Loading @@ -332,6 +332,201 @@ public class BubblePositionerTest extends ShellTestCase { .isWithin(0.1f).of(expectedHeight); } @Test public void testAreBubblesBottomAligned_largeScreen_true() { Insets insets = Insets.of(10, 20, 5, 15); Rect screenBounds = new Rect(0, 0, 1800, 2600); DeviceConfig deviceConfig = new ConfigBuilder() .setLargeScreen() .setInsets(insets) .setScreenBounds(screenBounds) .build(); mPositioner.update(deviceConfig); assertThat(mPositioner.areBubblesBottomAligned()).isTrue(); } @Test public void testAreBubblesBottomAligned_largeScreen_false() { Insets insets = Insets.of(10, 20, 5, 15); Rect screenBounds = new Rect(0, 0, 1800, 2600); DeviceConfig deviceConfig = new ConfigBuilder() .setLargeScreen() .setLandscape() .setInsets(insets) .setScreenBounds(screenBounds) .build(); mPositioner.update(deviceConfig); assertThat(mPositioner.areBubblesBottomAligned()).isFalse(); } @Test public void testAreBubblesBottomAligned_smallTablet_false() { Insets insets = Insets.of(10, 20, 5, 15); Rect screenBounds = new Rect(0, 0, 1800, 2600); DeviceConfig deviceConfig = new ConfigBuilder() .setLargeScreen() .setSmallTablet() .setInsets(insets) .setScreenBounds(screenBounds) .build(); mPositioner.update(deviceConfig); assertThat(mPositioner.areBubblesBottomAligned()).isFalse(); } @Test public void testAreBubblesBottomAligned_phone_false() { Insets insets = Insets.of(10, 20, 5, 15); Rect screenBounds = new Rect(0, 0, 1800, 2600); DeviceConfig deviceConfig = new ConfigBuilder() .setInsets(insets) .setScreenBounds(screenBounds) .build(); mPositioner.update(deviceConfig); assertThat(mPositioner.areBubblesBottomAligned()).isFalse(); } @Test public void testExpandedViewY_phoneLandscape() { Insets insets = Insets.of(10, 20, 5, 15); Rect screenBounds = new Rect(0, 0, 1800, 2600); DeviceConfig deviceConfig = new ConfigBuilder() .setLandscape() .setInsets(insets) .setScreenBounds(screenBounds) .build(); mPositioner.update(deviceConfig); Intent intent = new Intent(Intent.ACTION_VIEW).setPackage(mContext.getPackageName()); Bubble bubble = Bubble.createAppBubble(intent, new UserHandle(1), null, directExecutor()); // This bubble will have max height so it'll always be top aligned assertThat(mPositioner.getExpandedViewY(bubble, 0f /* bubblePosition */)) .isEqualTo(mPositioner.getExpandedViewYTopAligned()); } @Test public void testExpandedViewY_phonePortrait() { Insets insets = Insets.of(10, 20, 5, 15); Rect screenBounds = new Rect(0, 0, 1800, 2600); DeviceConfig deviceConfig = new ConfigBuilder() .setInsets(insets) .setScreenBounds(screenBounds) .build(); mPositioner.update(deviceConfig); Intent intent = new Intent(Intent.ACTION_VIEW).setPackage(mContext.getPackageName()); Bubble bubble = Bubble.createAppBubble(intent, new UserHandle(1), null, directExecutor()); // Always top aligned in phone portrait assertThat(mPositioner.getExpandedViewY(bubble, 0f /* bubblePosition */)) .isEqualTo(mPositioner.getExpandedViewYTopAligned()); } @Test public void testExpandedViewY_smallTabletLandscape() { Insets insets = Insets.of(10, 20, 5, 15); Rect screenBounds = new Rect(0, 0, 1800, 2600); DeviceConfig deviceConfig = new ConfigBuilder() .setSmallTablet() .setLandscape() .setInsets(insets) .setScreenBounds(screenBounds) .build(); mPositioner.update(deviceConfig); Intent intent = new Intent(Intent.ACTION_VIEW).setPackage(mContext.getPackageName()); Bubble bubble = Bubble.createAppBubble(intent, new UserHandle(1), null, directExecutor()); // This bubble will have max height which is always top aligned on small tablets assertThat(mPositioner.getExpandedViewY(bubble, 0f /* bubblePosition */)) .isEqualTo(mPositioner.getExpandedViewYTopAligned()); } @Test public void testExpandedViewY_smallTabletPortrait() { Insets insets = Insets.of(10, 20, 5, 15); Rect screenBounds = new Rect(0, 0, 1800, 2600); DeviceConfig deviceConfig = new ConfigBuilder() .setSmallTablet() .setInsets(insets) .setScreenBounds(screenBounds) .build(); mPositioner.update(deviceConfig); Intent intent = new Intent(Intent.ACTION_VIEW).setPackage(mContext.getPackageName()); Bubble bubble = Bubble.createAppBubble(intent, new UserHandle(1), null, directExecutor()); // This bubble will have max height which is always top aligned on small tablets assertThat(mPositioner.getExpandedViewY(bubble, 0f /* bubblePosition */)) .isEqualTo(mPositioner.getExpandedViewYTopAligned()); } @Test public void testExpandedViewY_largeScreenLandscape() { Insets insets = Insets.of(10, 20, 5, 15); Rect screenBounds = new Rect(0, 0, 1800, 2600); DeviceConfig deviceConfig = new ConfigBuilder() .setLargeScreen() .setLandscape() .setInsets(insets) .setScreenBounds(screenBounds) .build(); mPositioner.update(deviceConfig); Intent intent = new Intent(Intent.ACTION_VIEW).setPackage(mContext.getPackageName()); Bubble bubble = Bubble.createAppBubble(intent, new UserHandle(1), null, directExecutor()); // This bubble will have max height which is always top aligned on landscape, large tablet assertThat(mPositioner.getExpandedViewY(bubble, 0f /* bubblePosition */)) .isEqualTo(mPositioner.getExpandedViewYTopAligned()); } @Test public void testExpandedViewY_largeScreenPortrait() { Insets insets = Insets.of(10, 20, 5, 15); Rect screenBounds = new Rect(0, 0, 1800, 2600); DeviceConfig deviceConfig = new ConfigBuilder() .setLargeScreen() .setInsets(insets) .setScreenBounds(screenBounds) .build(); mPositioner.update(deviceConfig); Intent intent = new Intent(Intent.ACTION_VIEW).setPackage(mContext.getPackageName()); Bubble bubble = Bubble.createAppBubble(intent, new UserHandle(1), null, directExecutor()); int manageButtonHeight = mContext.getResources().getDimensionPixelSize(R.dimen.bubble_manage_button_height); int manageButtonPlusMargin = manageButtonHeight + 2 * mContext.getResources().getDimensionPixelSize( R.dimen.bubble_manage_button_margin); int pointerWidth = mContext.getResources().getDimensionPixelSize( R.dimen.bubble_pointer_width); final float expectedExpandedViewY = mPositioner.getAvailableRect().bottom - manageButtonPlusMargin - mPositioner.getExpandedViewHeightForLargeScreen() - pointerWidth; // Bubbles are bottom aligned on portrait, large tablet assertThat(mPositioner.getExpandedViewY(bubble, 0f /* bubblePosition */)) .isEqualTo(expectedExpandedViewY); } /** * Calculates the Y position bubbles should be placed based on the config. Based on * the calculations in {@link BubblePositioner#getDefaultStartPosition()} and Loading Loading
libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java +48 −13 Original line number Diff line number Diff line Loading @@ -242,6 +242,18 @@ public class BubblePositioner { return mDeviceConfig.isLandscape(); } /** * On large screen (not small tablet), while in portrait, expanded bubbles are aligned to * the bottom of the screen. * * @return whether bubbles are bottom aligned while expanded */ public boolean areBubblesBottomAligned() { return isLargeScreen() && !mDeviceConfig.isSmallTablet() && !isLandscape(); } /** @return whether the screen is considered large. */ public boolean isLargeScreen() { return mDeviceConfig.isLargeScreen(); Loading Loading @@ -417,7 +429,10 @@ public class BubblePositioner { - bottomPadding; } private int getExpandedViewHeightForLargeScreen() { /** * Returns the height to use for the expanded view when showing on a large screen. */ public int getExpandedViewHeightForLargeScreen() { // 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); Loading Loading @@ -460,13 +475,21 @@ public class BubblePositioner { boolean isOverflow = bubble == null || BubbleOverflow.KEY.equals(bubble.getKey()); float expandedViewHeight = getExpandedViewHeight(bubble); float topAlignment = getExpandedViewYTopAligned(); int manageButtonHeight = isOverflow ? mExpandedViewPadding : mManageButtonHeightIncludingMargins; // On largescreen portrait bubbles are bottom aligned. if (areBubblesBottomAligned() && expandedViewHeight == MAX_HEIGHT) { return mPositionRect.bottom - manageButtonHeight - getExpandedViewHeightForLargeScreen() - mPointerWidth; } if (!showBubblesVertically() || expandedViewHeight == MAX_HEIGHT) { // Top-align when bubbles are shown at the top or are max size. return topAlignment; } // If we're here, we're showing vertically & developer has made height less than maximum. int manageButtonHeight = isOverflow ? mExpandedViewPadding : mManageButtonHeightIncludingMargins; float pointerPosition = getPointerPosition(bubblePosition); float bottomIfCentered = pointerPosition + (expandedViewHeight / 2) + manageButtonHeight; float topIfCentered = pointerPosition - (expandedViewHeight / 2); Loading Loading @@ -524,14 +547,8 @@ public class BubblePositioner { // Last bubble has screen index 0 and first bubble has max screen index value. onScreenIndex = state.numberOfBubbles - 1 - index; } final float positionInRow = onScreenIndex * (mBubbleSize + mSpacingBetweenBubbles); final float expandedStackSize = getExpandedStackSize(state.numberOfBubbles); final float centerPosition = showBubblesVertically ? mPositionRect.centerY() : mPositionRect.centerX(); // alignment - centered on the edge final float rowStart = centerPosition - (expandedStackSize / 2f); final float rowStart = getBubbleRowStart(state); float x; float y; if (showBubblesVertically) { Loading @@ -557,6 +574,25 @@ public class BubblePositioner { return new PointF(x, y); } private float getBubbleRowStart(BubbleStackView.StackViewState state) { final float expandedStackSize = getExpandedStackSize(state.numberOfBubbles); final float rowStart; if (areBubblesBottomAligned()) { final float expandedViewHeight = getExpandedViewHeightForLargeScreen(); final float expandedViewBottom = mScreenRect.bottom - Math.max(mInsets.bottom, mInsets.top) - mManageButtonHeight - mPointerWidth; final float expandedViewCenter = expandedViewBottom - (expandedViewHeight / 2f); rowStart = expandedViewCenter - (expandedStackSize / 2f); } else { final float centerPosition = showBubblesVertically() ? mPositionRect.centerY() : mPositionRect.centerX(); rowStart = centerPosition - (expandedStackSize / 2f); } return rowStart; } /** * Returns the position of the bubble on-screen when the stack is expanded and the IME * is showing. Loading @@ -577,9 +613,8 @@ public class BubblePositioner { final float bottomHeight = getImeHeight() + mInsets.bottom + (mSpacingBetweenBubbles * 2); final float bottomInset = mScreenRect.bottom - bottomHeight; final float expandedStackSize = getExpandedStackSize(state.numberOfBubbles); final float centerPosition = mPositionRect.centerY(); final float rowBottom = centerPosition + (expandedStackSize / 2f); final float rowTop = centerPosition - (expandedStackSize / 2f); final float rowTop = getBubbleRowStart(state); final float rowBottom = rowTop + expandedStackSize; float rowTopForIme = rowTop; if (rowBottom > bottomInset) { // We overlap with IME, must shift the bubbles Loading
libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblePositionerTest.java +195 −0 Original line number Diff line number Diff line Loading @@ -332,6 +332,201 @@ public class BubblePositionerTest extends ShellTestCase { .isWithin(0.1f).of(expectedHeight); } @Test public void testAreBubblesBottomAligned_largeScreen_true() { Insets insets = Insets.of(10, 20, 5, 15); Rect screenBounds = new Rect(0, 0, 1800, 2600); DeviceConfig deviceConfig = new ConfigBuilder() .setLargeScreen() .setInsets(insets) .setScreenBounds(screenBounds) .build(); mPositioner.update(deviceConfig); assertThat(mPositioner.areBubblesBottomAligned()).isTrue(); } @Test public void testAreBubblesBottomAligned_largeScreen_false() { Insets insets = Insets.of(10, 20, 5, 15); Rect screenBounds = new Rect(0, 0, 1800, 2600); DeviceConfig deviceConfig = new ConfigBuilder() .setLargeScreen() .setLandscape() .setInsets(insets) .setScreenBounds(screenBounds) .build(); mPositioner.update(deviceConfig); assertThat(mPositioner.areBubblesBottomAligned()).isFalse(); } @Test public void testAreBubblesBottomAligned_smallTablet_false() { Insets insets = Insets.of(10, 20, 5, 15); Rect screenBounds = new Rect(0, 0, 1800, 2600); DeviceConfig deviceConfig = new ConfigBuilder() .setLargeScreen() .setSmallTablet() .setInsets(insets) .setScreenBounds(screenBounds) .build(); mPositioner.update(deviceConfig); assertThat(mPositioner.areBubblesBottomAligned()).isFalse(); } @Test public void testAreBubblesBottomAligned_phone_false() { Insets insets = Insets.of(10, 20, 5, 15); Rect screenBounds = new Rect(0, 0, 1800, 2600); DeviceConfig deviceConfig = new ConfigBuilder() .setInsets(insets) .setScreenBounds(screenBounds) .build(); mPositioner.update(deviceConfig); assertThat(mPositioner.areBubblesBottomAligned()).isFalse(); } @Test public void testExpandedViewY_phoneLandscape() { Insets insets = Insets.of(10, 20, 5, 15); Rect screenBounds = new Rect(0, 0, 1800, 2600); DeviceConfig deviceConfig = new ConfigBuilder() .setLandscape() .setInsets(insets) .setScreenBounds(screenBounds) .build(); mPositioner.update(deviceConfig); Intent intent = new Intent(Intent.ACTION_VIEW).setPackage(mContext.getPackageName()); Bubble bubble = Bubble.createAppBubble(intent, new UserHandle(1), null, directExecutor()); // This bubble will have max height so it'll always be top aligned assertThat(mPositioner.getExpandedViewY(bubble, 0f /* bubblePosition */)) .isEqualTo(mPositioner.getExpandedViewYTopAligned()); } @Test public void testExpandedViewY_phonePortrait() { Insets insets = Insets.of(10, 20, 5, 15); Rect screenBounds = new Rect(0, 0, 1800, 2600); DeviceConfig deviceConfig = new ConfigBuilder() .setInsets(insets) .setScreenBounds(screenBounds) .build(); mPositioner.update(deviceConfig); Intent intent = new Intent(Intent.ACTION_VIEW).setPackage(mContext.getPackageName()); Bubble bubble = Bubble.createAppBubble(intent, new UserHandle(1), null, directExecutor()); // Always top aligned in phone portrait assertThat(mPositioner.getExpandedViewY(bubble, 0f /* bubblePosition */)) .isEqualTo(mPositioner.getExpandedViewYTopAligned()); } @Test public void testExpandedViewY_smallTabletLandscape() { Insets insets = Insets.of(10, 20, 5, 15); Rect screenBounds = new Rect(0, 0, 1800, 2600); DeviceConfig deviceConfig = new ConfigBuilder() .setSmallTablet() .setLandscape() .setInsets(insets) .setScreenBounds(screenBounds) .build(); mPositioner.update(deviceConfig); Intent intent = new Intent(Intent.ACTION_VIEW).setPackage(mContext.getPackageName()); Bubble bubble = Bubble.createAppBubble(intent, new UserHandle(1), null, directExecutor()); // This bubble will have max height which is always top aligned on small tablets assertThat(mPositioner.getExpandedViewY(bubble, 0f /* bubblePosition */)) .isEqualTo(mPositioner.getExpandedViewYTopAligned()); } @Test public void testExpandedViewY_smallTabletPortrait() { Insets insets = Insets.of(10, 20, 5, 15); Rect screenBounds = new Rect(0, 0, 1800, 2600); DeviceConfig deviceConfig = new ConfigBuilder() .setSmallTablet() .setInsets(insets) .setScreenBounds(screenBounds) .build(); mPositioner.update(deviceConfig); Intent intent = new Intent(Intent.ACTION_VIEW).setPackage(mContext.getPackageName()); Bubble bubble = Bubble.createAppBubble(intent, new UserHandle(1), null, directExecutor()); // This bubble will have max height which is always top aligned on small tablets assertThat(mPositioner.getExpandedViewY(bubble, 0f /* bubblePosition */)) .isEqualTo(mPositioner.getExpandedViewYTopAligned()); } @Test public void testExpandedViewY_largeScreenLandscape() { Insets insets = Insets.of(10, 20, 5, 15); Rect screenBounds = new Rect(0, 0, 1800, 2600); DeviceConfig deviceConfig = new ConfigBuilder() .setLargeScreen() .setLandscape() .setInsets(insets) .setScreenBounds(screenBounds) .build(); mPositioner.update(deviceConfig); Intent intent = new Intent(Intent.ACTION_VIEW).setPackage(mContext.getPackageName()); Bubble bubble = Bubble.createAppBubble(intent, new UserHandle(1), null, directExecutor()); // This bubble will have max height which is always top aligned on landscape, large tablet assertThat(mPositioner.getExpandedViewY(bubble, 0f /* bubblePosition */)) .isEqualTo(mPositioner.getExpandedViewYTopAligned()); } @Test public void testExpandedViewY_largeScreenPortrait() { Insets insets = Insets.of(10, 20, 5, 15); Rect screenBounds = new Rect(0, 0, 1800, 2600); DeviceConfig deviceConfig = new ConfigBuilder() .setLargeScreen() .setInsets(insets) .setScreenBounds(screenBounds) .build(); mPositioner.update(deviceConfig); Intent intent = new Intent(Intent.ACTION_VIEW).setPackage(mContext.getPackageName()); Bubble bubble = Bubble.createAppBubble(intent, new UserHandle(1), null, directExecutor()); int manageButtonHeight = mContext.getResources().getDimensionPixelSize(R.dimen.bubble_manage_button_height); int manageButtonPlusMargin = manageButtonHeight + 2 * mContext.getResources().getDimensionPixelSize( R.dimen.bubble_manage_button_margin); int pointerWidth = mContext.getResources().getDimensionPixelSize( R.dimen.bubble_pointer_width); final float expectedExpandedViewY = mPositioner.getAvailableRect().bottom - manageButtonPlusMargin - mPositioner.getExpandedViewHeightForLargeScreen() - pointerWidth; // Bubbles are bottom aligned on portrait, large tablet assertThat(mPositioner.getExpandedViewY(bubble, 0f /* bubblePosition */)) .isEqualTo(expectedExpandedViewY); } /** * Calculates the Y position bubbles should be placed based on the config. Based on * the calculations in {@link BubblePositioner#getDefaultStartPosition()} and Loading