From b738e93597b01c946c7cc1e3a778f0bdec8ab95d Mon Sep 17 00:00:00 2001 From: Mady Mellor Date: Wed, 2 Aug 2023 15:07:56 -0700 Subject: [PATCH] Add some basic tests for BubblePositioner Test: atest BubblePositioner Bug: 283910436 Change-Id: I932a5ebb359354a09c9bbeb73b0aeeae2c856b6c --- .../shell/bubbles/BubblePositionerTest.java | 271 ++++++++++++++++++ 1 file changed, 271 insertions(+) create mode 100644 libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblePositionerTest.java diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblePositionerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblePositionerTest.java new file mode 100644 index 0000000000000..5f7239702d31a --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblePositionerTest.java @@ -0,0 +1,271 @@ +/* + * 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 static android.content.res.Configuration.ORIENTATION_LANDSCAPE; +import static android.content.res.Configuration.ORIENTATION_PORTRAIT; +import static android.view.View.LAYOUT_DIRECTION_LTR; +import static android.view.View.LAYOUT_DIRECTION_RTL; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +import android.content.res.Configuration; +import android.graphics.Insets; +import android.graphics.PointF; +import android.graphics.Rect; +import android.graphics.RectF; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; +import android.testing.TestableResources; +import android.view.WindowInsets; +import android.view.WindowManager; +import android.view.WindowMetrics; + +import androidx.test.filters.SmallTest; + +import com.android.wm.shell.R; +import com.android.wm.shell.ShellTestCase; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Tests operations and the resulting state managed by {@link BubblePositioner}. + */ +@SmallTest +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper(setAsMainLooper = true) +public class BubblePositionerTest extends ShellTestCase { + + private static final int MIN_WIDTH_FOR_TABLET = 600; + + private BubblePositioner mPositioner; + private Configuration mConfiguration; + + @Mock + private WindowManager mWindowManager; + @Mock + private WindowMetrics mWindowMetrics; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + mConfiguration = spy(new Configuration()); + TestableResources testableResources = mContext.getOrCreateTestableResources(); + testableResources.overrideConfiguration(mConfiguration); + + mPositioner = new BubblePositioner(mContext, mWindowManager); + } + + @Test + public void testUpdate() { + Insets insets = Insets.of(10, 20, 5, 15); + Rect screenBounds = new Rect(0, 0, 1000, 1200); + Rect availableRect = new Rect(screenBounds); + availableRect.inset(insets); + + new WindowManagerConfig() + .setInsets(insets) + .setScreenBounds(screenBounds) + .setUpConfig(); + mPositioner.update(); + + assertThat(mPositioner.getAvailableRect()).isEqualTo(availableRect); + assertThat(mPositioner.isLandscape()).isFalse(); + assertThat(mPositioner.isLargeScreen()).isFalse(); + assertThat(mPositioner.getInsets()).isEqualTo(insets); + } + + @Test + public void testShowBubblesVertically_phonePortrait() { + new WindowManagerConfig().setOrientation(ORIENTATION_PORTRAIT).setUpConfig(); + mPositioner.update(); + + assertThat(mPositioner.showBubblesVertically()).isFalse(); + } + + @Test + public void testShowBubblesVertically_phoneLandscape() { + new WindowManagerConfig().setOrientation(ORIENTATION_LANDSCAPE).setUpConfig(); + mPositioner.update(); + + assertThat(mPositioner.isLandscape()).isTrue(); + assertThat(mPositioner.showBubblesVertically()).isTrue(); + } + + @Test + public void testShowBubblesVertically_tablet() { + new WindowManagerConfig().setLargeScreen().setUpConfig(); + mPositioner.update(); + + assertThat(mPositioner.showBubblesVertically()).isTrue(); + } + + /** If a resting position hasn't been set, calling it will return the default position. */ + @Test + public void testGetRestingPosition_returnsDefaultPosition() { + new WindowManagerConfig().setUpConfig(); + mPositioner.update(); + + PointF restingPosition = mPositioner.getRestingPosition(); + PointF defaultPosition = mPositioner.getDefaultStartPosition(); + + assertThat(restingPosition).isEqualTo(defaultPosition); + } + + /** If a resting position has been set, it'll return that instead of the default position. */ + @Test + public void testGetRestingPosition_returnsRestingPosition() { + new WindowManagerConfig().setUpConfig(); + mPositioner.update(); + + PointF restingPosition = new PointF(100, 100); + mPositioner.setRestingPosition(restingPosition); + + assertThat(mPositioner.getRestingPosition()).isEqualTo(restingPosition); + } + + /** Test that the default resting position on phone is in upper left. */ + @Test + public void testGetRestingPosition_bubble_onPhone() { + new WindowManagerConfig().setUpConfig(); + mPositioner.update(); + + RectF allowableStackRegion = + mPositioner.getAllowableStackPositionRegion(1 /* bubbleCount */); + PointF restingPosition = mPositioner.getRestingPosition(); + + assertThat(restingPosition.x).isEqualTo(allowableStackRegion.left); + assertThat(restingPosition.y).isEqualTo(getDefaultYPosition()); + } + + @Test + public void testGetRestingPosition_bubble_onPhone_RTL() { + new WindowManagerConfig().setLayoutDirection(LAYOUT_DIRECTION_RTL).setUpConfig(); + mPositioner.update(); + + RectF allowableStackRegion = + mPositioner.getAllowableStackPositionRegion(1 /* bubbleCount */); + PointF restingPosition = mPositioner.getRestingPosition(); + + assertThat(restingPosition.x).isEqualTo(allowableStackRegion.right); + assertThat(restingPosition.y).isEqualTo(getDefaultYPosition()); + } + + /** Test that the default resting position on tablet is middle left. */ + @Test + public void testGetRestingPosition_chatBubble_onTablet() { + new WindowManagerConfig().setLargeScreen().setUpConfig(); + mPositioner.update(); + + RectF allowableStackRegion = + mPositioner.getAllowableStackPositionRegion(1 /* bubbleCount */); + PointF restingPosition = mPositioner.getRestingPosition(); + + assertThat(restingPosition.x).isEqualTo(allowableStackRegion.left); + assertThat(restingPosition.y).isEqualTo(getDefaultYPosition()); + } + + @Test + public void testGetRestingPosition_chatBubble_onTablet_RTL() { + new WindowManagerConfig().setLargeScreen().setLayoutDirection( + LAYOUT_DIRECTION_RTL).setUpConfig(); + mPositioner.update(); + + RectF allowableStackRegion = + mPositioner.getAllowableStackPositionRegion(1 /* bubbleCount */); + PointF restingPosition = mPositioner.getRestingPosition(); + + assertThat(restingPosition.x).isEqualTo(allowableStackRegion.right); + assertThat(restingPosition.y).isEqualTo(getDefaultYPosition()); + } + + /** + * Calculates the Y position bubbles should be placed based on the config. Based on + * the calculations in {@link BubblePositioner#getDefaultStartPosition()} and + * {@link BubbleStackView.RelativeStackPosition}. + */ + private float getDefaultYPosition() { + final float desiredY = mContext.getResources().getDimensionPixelOffset( + R.dimen.bubble_stack_starting_offset_y); + float offsetPercent = desiredY / mPositioner.getAvailableRect().height(); + offsetPercent = Math.max(0f, Math.min(1f, offsetPercent)); + final RectF allowableStackRegion = + mPositioner.getAllowableStackPositionRegion(1 /* bubbleCount */); + return allowableStackRegion.top + allowableStackRegion.height() * offsetPercent; + } + + /** + * Sets up window manager to return config values based on what you need for the test. + * By default it sets up a portrait phone without any insets. + */ + private class WindowManagerConfig { + private Rect mScreenBounds = new Rect(0, 0, 1000, 2000); + private boolean mIsLargeScreen = false; + private int mOrientation = ORIENTATION_PORTRAIT; + private int mLayoutDirection = LAYOUT_DIRECTION_LTR; + private Insets mInsets = Insets.of(0, 0, 0, 0); + + public WindowManagerConfig setScreenBounds(Rect screenBounds) { + mScreenBounds = screenBounds; + return this; + } + + public WindowManagerConfig setLargeScreen() { + mIsLargeScreen = true; + return this; + } + + public WindowManagerConfig setOrientation(int orientation) { + mOrientation = orientation; + return this; + } + + public WindowManagerConfig setLayoutDirection(int layoutDirection) { + mLayoutDirection = layoutDirection; + return this; + } + + public WindowManagerConfig setInsets(Insets insets) { + mInsets = insets; + return this; + } + + public void setUpConfig() { + mConfiguration.smallestScreenWidthDp = mIsLargeScreen + ? MIN_WIDTH_FOR_TABLET + : MIN_WIDTH_FOR_TABLET - 1; + mConfiguration.orientation = mOrientation; + + when(mConfiguration.getLayoutDirection()).thenReturn(mLayoutDirection); + WindowInsets windowInsets = mock(WindowInsets.class); + when(windowInsets.getInsetsIgnoringVisibility(anyInt())).thenReturn(mInsets); + when(mWindowMetrics.getWindowInsets()).thenReturn(windowInsets); + when(mWindowMetrics.getBounds()).thenReturn(mScreenBounds); + when(mWindowManager.getCurrentWindowMetrics()).thenReturn(mWindowMetrics); + } + } +}