diff options
Diffstat (limited to 'libs')
30 files changed, 910 insertions, 751 deletions
diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp index 5ad144d50b87..45540e0fbbb8 100644 --- a/libs/WindowManager/Shell/Android.bp +++ b/libs/WindowManager/Shell/Android.bp @@ -175,3 +175,74 @@ android_library { plugins: ["dagger2-compiler"], use_resource_processor: true, } + +android_app { + name: "WindowManagerShellRobolectric", + platform_apis: true, + static_libs: [ + "WindowManager-Shell", + ], + manifest: "multivalentTests/AndroidManifestRobolectric.xml", + use_resource_processor: true, +} + +android_robolectric_test { + name: "WMShellRobolectricTests", + instrumentation_for: "WindowManagerShellRobolectric", + upstream: true, + java_resource_dirs: [ + "multivalentTests/robolectric/config", + ], + srcs: [ + "multivalentTests/src/**/*.kt", + ], + static_libs: [ + "junit", + "androidx.test.runner", + "androidx.test.rules", + "androidx.test.ext.junit", + "mockito-robolectric-prebuilt", + "mockito-kotlin2", + "truth", + ], +} + +android_test { + name: "WMShellMultivalentTestsOnDevice", + srcs: [ + "multivalentTests/src/**/*.kt", + ], + static_libs: [ + "WindowManager-Shell", + "junit", + "androidx.test.runner", + "androidx.test.rules", + "androidx.test.ext.junit", + "frameworks-base-testutils", + "mockito-kotlin2", + "mockito-target-extended-minus-junit4", + "truth", + "platform-test-annotations", + "platform-test-rules", + ], + libs: [ + "android.test.base", + "android.test.runner", + ], + jni_libs: [ + "libdexmakerjvmtiagent", + "libstaticjvmtiagent", + ], + kotlincflags: ["-Xjvm-default=all"], + optimize: { + enabled: false, + }, + test_suites: ["device-tests"], + platform_apis: true, + certificate: "platform", + aaptflags: [ + "--extra-packages", + "com.android.wm.shell", + ], + manifest: "multivalentTests/AndroidManifest.xml", +} diff --git a/libs/WindowManager/Shell/multivalentTests/AndroidManifest.xml b/libs/WindowManager/Shell/multivalentTests/AndroidManifest.xml new file mode 100644 index 000000000000..f8f8338e5f04 --- /dev/null +++ b/libs/WindowManager/Shell/multivalentTests/AndroidManifest.xml @@ -0,0 +1,13 @@ +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.wm.shell.multivalenttests"> + + <application android:debuggable="true" android:supportsRtl="true" > + <uses-library android:name="android.test.runner" /> + </application> + + <instrumentation + android:name="androidx.test.runner.AndroidJUnitRunner" + android:label="Multivalent tests for WindowManager-Shell" + android:targetPackage="com.android.wm.shell.multivalenttests"> + </instrumentation> +</manifest> diff --git a/libs/WindowManager/Shell/multivalentTests/AndroidManifestRobolectric.xml b/libs/WindowManager/Shell/multivalentTests/AndroidManifestRobolectric.xml new file mode 100644 index 000000000000..ffcd7d46fbae --- /dev/null +++ b/libs/WindowManager/Shell/multivalentTests/AndroidManifestRobolectric.xml @@ -0,0 +1,3 @@ +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.wm.shell.multivalenttests"> +</manifest> diff --git a/libs/WindowManager/Shell/multivalentTests/AndroidTest.xml b/libs/WindowManager/Shell/multivalentTests/AndroidTest.xml new file mode 100644 index 000000000000..36fe8ec3370d --- /dev/null +++ b/libs/WindowManager/Shell/multivalentTests/AndroidTest.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> +<configuration description="Runs Tests for WindowManagerShellLib"> + <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> + <option name="cleanup-apks" value="true" /> + <option name="install-arg" value="-t" /> + <option name="test-file-name" value="WMShellMultivalentTestsOnDevice.apk" /> + </target_preparer> + + <option name="test-suite-tag" value="apct" /> + <option name="test-suite-tag" value="framework-base-presubmit" /> + <option name="test-tag" value="WMShellMultivalentTestsOnDevice" /> + <test class="com.android.tradefed.testtype.AndroidJUnitTest" > + <option name="package" value="com.android.wm.shell.multivalenttests" /> + <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" /> + <option name="hidden-api-checks" value="false"/> + </test> +</configuration> diff --git a/libs/WindowManager/Shell/multivalentTests/robolectric/config/robolectric.properties b/libs/WindowManager/Shell/multivalentTests/robolectric/config/robolectric.properties new file mode 100644 index 000000000000..7a0527ccaafb --- /dev/null +++ b/libs/WindowManager/Shell/multivalentTests/robolectric/config/robolectric.properties @@ -0,0 +1,2 @@ +sdk=NEWEST_SDK + diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt new file mode 100644 index 000000000000..ea7c6edd4b56 --- /dev/null +++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt @@ -0,0 +1,481 @@ +/* + * 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.Intent +import android.content.pm.ShortcutInfo +import android.graphics.Insets +import android.graphics.PointF +import android.graphics.Rect +import android.os.UserHandle +import android.view.WindowManager +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.wm.shell.R +import com.android.wm.shell.bubbles.BubblePositioner.MAX_HEIGHT +import com.google.common.truth.Truth.assertThat +import com.google.common.util.concurrent.MoreExecutors.directExecutor +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +/** Tests operations and the resulting state managed by [BubblePositioner]. */ +@SmallTest +@RunWith(AndroidJUnit4::class) +class BubblePositionerTest { + + private lateinit var positioner: BubblePositioner + private val context = ApplicationProvider.getApplicationContext<Context>() + private val defaultDeviceConfig = + DeviceConfig( + windowBounds = Rect(0, 0, 1000, 2000), + isLargeScreen = false, + isSmallTablet = false, + isLandscape = false, + isRtl = false, + insets = Insets.of(0, 0, 0, 0) + ) + + @Before + fun setUp() { + val windowManager = context.getSystemService(WindowManager::class.java) + positioner = BubblePositioner(context, windowManager) + } + + @Test + fun testUpdate() { + val insets = Insets.of(10, 20, 5, 15) + val screenBounds = Rect(0, 0, 1000, 1200) + val availableRect = Rect(screenBounds) + availableRect.inset(insets) + positioner.update(defaultDeviceConfig.copy(insets = insets, windowBounds = screenBounds)) + assertThat(positioner.availableRect).isEqualTo(availableRect) + assertThat(positioner.isLandscape).isFalse() + assertThat(positioner.isLargeScreen).isFalse() + assertThat(positioner.insets).isEqualTo(insets) + } + + @Test + fun testShowBubblesVertically_phonePortrait() { + positioner.update(defaultDeviceConfig) + assertThat(positioner.showBubblesVertically()).isFalse() + } + + @Test + fun testShowBubblesVertically_phoneLandscape() { + positioner.update(defaultDeviceConfig.copy(isLandscape = true)) + assertThat(positioner.isLandscape).isTrue() + assertThat(positioner.showBubblesVertically()).isTrue() + } + + @Test + fun testShowBubblesVertically_tablet() { + positioner.update(defaultDeviceConfig.copy(isLargeScreen = true)) + assertThat(positioner.showBubblesVertically()).isTrue() + } + + /** If a resting position hasn't been set, calling it will return the default position. */ + @Test + fun testGetRestingPosition_returnsDefaultPosition() { + positioner.update(defaultDeviceConfig) + val restingPosition = positioner.getRestingPosition() + val defaultPosition = positioner.defaultStartPosition + assertThat(restingPosition).isEqualTo(defaultPosition) + } + + /** If a resting position has been set, it'll return that instead of the default position. */ + @Test + fun testGetRestingPosition_returnsRestingPosition() { + positioner.update(defaultDeviceConfig) + val restingPosition = PointF(100f, 100f) + positioner.restingPosition = restingPosition + assertThat(positioner.getRestingPosition()).isEqualTo(restingPosition) + } + + /** Test that the default resting position on phone is in upper left. */ + @Test + fun testGetRestingPosition_bubble_onPhone() { + positioner.update(defaultDeviceConfig) + val allowableStackRegion = positioner.getAllowableStackPositionRegion(1 /* bubbleCount */) + val restingPosition = positioner.getRestingPosition() + assertThat(restingPosition.x).isEqualTo(allowableStackRegion.left) + assertThat(restingPosition.y).isEqualTo(defaultYPosition) + } + + @Test + fun testGetRestingPosition_bubble_onPhone_RTL() { + positioner.update(defaultDeviceConfig.copy(isRtl = true)) + val allowableStackRegion = positioner.getAllowableStackPositionRegion(1 /* bubbleCount */) + val restingPosition = positioner.getRestingPosition() + assertThat(restingPosition.x).isEqualTo(allowableStackRegion.right) + assertThat(restingPosition.y).isEqualTo(defaultYPosition) + } + + /** Test that the default resting position on tablet is middle left. */ + @Test + fun testGetRestingPosition_chatBubble_onTablet() { + positioner.update(defaultDeviceConfig.copy(isLargeScreen = true)) + val allowableStackRegion = positioner.getAllowableStackPositionRegion(1 /* bubbleCount */) + val restingPosition = positioner.getRestingPosition() + assertThat(restingPosition.x).isEqualTo(allowableStackRegion.left) + assertThat(restingPosition.y).isEqualTo(defaultYPosition) + } + + @Test + fun testGetRestingPosition_chatBubble_onTablet_RTL() { + positioner.update(defaultDeviceConfig.copy(isLargeScreen = true, isRtl = true)) + val allowableStackRegion = positioner.getAllowableStackPositionRegion(1 /* bubbleCount */) + val restingPosition = positioner.getRestingPosition() + assertThat(restingPosition.x).isEqualTo(allowableStackRegion.right) + assertThat(restingPosition.y).isEqualTo(defaultYPosition) + } + + /** Test that the default resting position on tablet is middle right. */ + @Test + fun testGetDefaultPosition_appBubble_onTablet() { + positioner.update(defaultDeviceConfig.copy(isLargeScreen = true)) + val allowableStackRegion = positioner.getAllowableStackPositionRegion(1 /* bubbleCount */) + val startPosition = positioner.getDefaultStartPosition(true /* isAppBubble */) + assertThat(startPosition.x).isEqualTo(allowableStackRegion.right) + assertThat(startPosition.y).isEqualTo(defaultYPosition) + } + + @Test + fun testGetRestingPosition_appBubble_onTablet_RTL() { + positioner.update(defaultDeviceConfig.copy(isLargeScreen = true, isRtl = true)) + val allowableStackRegion = positioner.getAllowableStackPositionRegion(1 /* bubbleCount */) + val startPosition = positioner.getDefaultStartPosition(true /* isAppBubble */) + assertThat(startPosition.x).isEqualTo(allowableStackRegion.left) + assertThat(startPosition.y).isEqualTo(defaultYPosition) + } + + @Test + fun testHasUserModifiedDefaultPosition_false() { + positioner.update(defaultDeviceConfig.copy(isLargeScreen = true, isRtl = true)) + assertThat(positioner.hasUserModifiedDefaultPosition()).isFalse() + positioner.restingPosition = positioner.defaultStartPosition + assertThat(positioner.hasUserModifiedDefaultPosition()).isFalse() + } + + @Test + fun testHasUserModifiedDefaultPosition_true() { + positioner.update(defaultDeviceConfig.copy(isLargeScreen = true, isRtl = true)) + assertThat(positioner.hasUserModifiedDefaultPosition()).isFalse() + positioner.restingPosition = PointF(0f, 100f) + assertThat(positioner.hasUserModifiedDefaultPosition()).isTrue() + } + + @Test + fun testGetExpandedViewHeight_max() { + val deviceConfig = + defaultDeviceConfig.copy( + isLargeScreen = true, + insets = Insets.of(10, 20, 5, 15), + windowBounds = Rect(0, 0, 1800, 2600) + ) + positioner.update(deviceConfig) + val intent = Intent(Intent.ACTION_VIEW).setPackage(context.packageName) + val bubble = Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor()) + + assertThat(positioner.getExpandedViewHeight(bubble)).isEqualTo(MAX_HEIGHT) + } + + @Test + fun testGetExpandedViewHeight_customHeight_valid() { + val deviceConfig = + defaultDeviceConfig.copy( + isLargeScreen = true, + insets = Insets.of(10, 20, 5, 15), + windowBounds = Rect(0, 0, 1800, 2600) + ) + positioner.update(deviceConfig) + val minHeight = + context.resources.getDimensionPixelSize(R.dimen.bubble_expanded_default_height) + val bubble = + Bubble( + "key", + ShortcutInfo.Builder(context, "id").build(), + minHeight + 100 /* desiredHeight */, + 0 /* desiredHeightResId */, + "title", + 0 /* taskId */, + null /* locus */, + true /* isDismissable */, + directExecutor()) {} + + // Ensure the height is the same as the desired value + assertThat(positioner.getExpandedViewHeight(bubble)) + .isEqualTo(bubble.getDesiredHeight(context)) + } + + @Test + fun testGetExpandedViewHeight_customHeight_tooSmall() { + val deviceConfig = + defaultDeviceConfig.copy( + isLargeScreen = true, + insets = Insets.of(10, 20, 5, 15), + windowBounds = Rect(0, 0, 1800, 2600) + ) + positioner.update(deviceConfig) + + val bubble = + Bubble( + "key", + ShortcutInfo.Builder(context, "id").build(), + 10 /* desiredHeight */, + 0 /* desiredHeightResId */, + "title", + 0 /* taskId */, + null /* locus */, + true /* isDismissable */, + directExecutor()) {} + + // Ensure the height is the same as the desired value + val minHeight = + context.resources.getDimensionPixelSize(R.dimen.bubble_expanded_default_height) + assertThat(positioner.getExpandedViewHeight(bubble)).isEqualTo(minHeight) + } + + @Test + fun testGetMaxExpandedViewHeight_onLargeTablet() { + val deviceConfig = + defaultDeviceConfig.copy( + isLargeScreen = true, + insets = Insets.of(10, 20, 5, 15), + windowBounds = Rect(0, 0, 1800, 2600) + ) + positioner.update(deviceConfig) + + val manageButtonHeight = + context.resources.getDimensionPixelSize(R.dimen.bubble_manage_button_height) + val pointerWidth = context.resources.getDimensionPixelSize(R.dimen.bubble_pointer_width) + val expandedViewPadding = + context.resources.getDimensionPixelSize(R.dimen.bubble_expanded_view_padding) + val expectedHeight = + 1800 - 2 * 20 - manageButtonHeight - pointerWidth - expandedViewPadding * 2 + assertThat(positioner.getMaxExpandedViewHeight(false /* isOverflow */)) + .isEqualTo(expectedHeight) + } + + @Test + fun testAreBubblesBottomAligned_largeScreen_true() { + val deviceConfig = + defaultDeviceConfig.copy( + isLargeScreen = true, + insets = Insets.of(10, 20, 5, 15), + windowBounds = Rect(0, 0, 1800, 2600) + ) + positioner.update(deviceConfig) + + assertThat(positioner.areBubblesBottomAligned()).isTrue() + } + + @Test + fun testAreBubblesBottomAligned_largeScreen_landscape_false() { + val deviceConfig = + defaultDeviceConfig.copy( + isLargeScreen = true, + isLandscape = true, + insets = Insets.of(10, 20, 5, 15), + windowBounds = Rect(0, 0, 1800, 2600) + ) + positioner.update(deviceConfig) + + assertThat(positioner.areBubblesBottomAligned()).isFalse() + } + + @Test + fun testAreBubblesBottomAligned_smallTablet_false() { + val deviceConfig = + defaultDeviceConfig.copy( + isLargeScreen = true, + isSmallTablet = true, + insets = Insets.of(10, 20, 5, 15), + windowBounds = Rect(0, 0, 1800, 2600) + ) + positioner.update(deviceConfig) + + assertThat(positioner.areBubblesBottomAligned()).isFalse() + } + + @Test + fun testAreBubblesBottomAligned_phone_false() { + val deviceConfig = + defaultDeviceConfig.copy( + insets = Insets.of(10, 20, 5, 15), + windowBounds = Rect(0, 0, 1800, 2600) + ) + positioner.update(deviceConfig) + + assertThat(positioner.areBubblesBottomAligned()).isFalse() + } + + @Test + fun testExpandedViewY_phoneLandscape() { + val deviceConfig = + defaultDeviceConfig.copy( + isLandscape = true, + insets = Insets.of(10, 20, 5, 15), + windowBounds = Rect(0, 0, 1800, 2600) + ) + positioner.update(deviceConfig) + + val intent = Intent(Intent.ACTION_VIEW).setPackage(context.packageName) + val bubble = Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor()) + + // This bubble will have max height so it'll always be top aligned + assertThat(positioner.getExpandedViewY(bubble, 0f /* bubblePosition */)) + .isEqualTo(positioner.getExpandedViewYTopAligned()) + } + + @Test + fun testExpandedViewY_phonePortrait() { + val deviceConfig = + defaultDeviceConfig.copy( + insets = Insets.of(10, 20, 5, 15), + windowBounds = Rect(0, 0, 1800, 2600) + ) + positioner.update(deviceConfig) + + val intent = Intent(Intent.ACTION_VIEW).setPackage(context.packageName) + val bubble = Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor()) + + // Always top aligned in phone portrait + assertThat(positioner.getExpandedViewY(bubble, 0f /* bubblePosition */)) + .isEqualTo(positioner.getExpandedViewYTopAligned()) + } + + @Test + fun testExpandedViewY_smallTabletLandscape() { + val deviceConfig = + defaultDeviceConfig.copy( + isSmallTablet = true, + isLandscape = true, + insets = Insets.of(10, 20, 5, 15), + windowBounds = Rect(0, 0, 1800, 2600) + ) + positioner.update(deviceConfig) + + val intent = Intent(Intent.ACTION_VIEW).setPackage(context.packageName) + val bubble = Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor()) + + // This bubble will have max height which is always top aligned on small tablets + assertThat(positioner.getExpandedViewY(bubble, 0f /* bubblePosition */)) + .isEqualTo(positioner.getExpandedViewYTopAligned()) + } + + @Test + fun testExpandedViewY_smallTabletPortrait() { + val deviceConfig = + defaultDeviceConfig.copy( + isSmallTablet = true, + insets = Insets.of(10, 20, 5, 15), + windowBounds = Rect(0, 0, 1800, 2600) + ) + positioner.update(deviceConfig) + + val intent = Intent(Intent.ACTION_VIEW).setPackage(context.packageName) + val bubble = Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor()) + + // This bubble will have max height which is always top aligned on small tablets + assertThat(positioner.getExpandedViewY(bubble, 0f /* bubblePosition */)) + .isEqualTo(positioner.getExpandedViewYTopAligned()) + } + + @Test + fun testExpandedViewY_largeScreenLandscape() { + val deviceConfig = + defaultDeviceConfig.copy( + isLargeScreen = true, + isLandscape = true, + insets = Insets.of(10, 20, 5, 15), + windowBounds = Rect(0, 0, 1800, 2600) + ) + positioner.update(deviceConfig) + + val intent = Intent(Intent.ACTION_VIEW).setPackage(context.packageName) + val bubble = Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor()) + + // This bubble will have max height which is always top aligned on landscape, large tablet + assertThat(positioner.getExpandedViewY(bubble, 0f /* bubblePosition */)) + .isEqualTo(positioner.getExpandedViewYTopAligned()) + } + + @Test + fun testExpandedViewY_largeScreenPortrait() { + val deviceConfig = + defaultDeviceConfig.copy( + isLargeScreen = true, + insets = Insets.of(10, 20, 5, 15), + windowBounds = Rect(0, 0, 1800, 2600) + ) + positioner.update(deviceConfig) + + val intent = Intent(Intent.ACTION_VIEW).setPackage(context.packageName) + val bubble = Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor()) + + val manageButtonHeight = + context.resources.getDimensionPixelSize(R.dimen.bubble_manage_button_height) + val manageButtonPlusMargin = + manageButtonHeight + + 2 * context.resources.getDimensionPixelSize(R.dimen.bubble_manage_button_margin) + val pointerWidth = context.resources.getDimensionPixelSize(R.dimen.bubble_pointer_width) + + val expectedExpandedViewY = + positioner.availableRect.bottom - + manageButtonPlusMargin - + positioner.getExpandedViewHeightForLargeScreen() - + pointerWidth + + // Bubbles are bottom aligned on portrait, large tablet + assertThat(positioner.getExpandedViewY(bubble, 0f /* bubblePosition */)) + .isEqualTo(expectedExpandedViewY) + } + + private val defaultYPosition: Float + /** + * Calculates the Y position bubbles should be placed based on the config. Based on the + * calculations in [BubblePositioner.getDefaultStartPosition] and + * [BubbleStackView.RelativeStackPosition]. + */ + get() { + val isTablet = positioner.isLargeScreen + + // On tablet the position is centered, on phone it is an offset from the top. + val desiredY = + if (isTablet) { + positioner.screenRect.height() / 2f - positioner.bubbleSize / 2f + } else { + context.resources + .getDimensionPixelOffset(R.dimen.bubble_stack_starting_offset_y) + .toFloat() + } + // Since we're visually centering the bubbles on tablet, use total screen height rather + // than the available height. + val height = + if (isTablet) { + positioner.screenRect.height() + } else { + positioner.availableRect.height() + } + val offsetPercent = (desiredY / height).coerceIn(0f, 1f) + val allowableStackRegion = + positioner.getAllowableStackPositionRegion(1 /* bubbleCount */) + return allowableStackRegion.top + allowableStackRegion.height() * offsetPercent + } +} diff --git a/libs/WindowManager/Shell/multivalentTestsForDevice b/libs/WindowManager/Shell/multivalentTestsForDevice new file mode 120000 index 000000000000..20ee34ada103 --- /dev/null +++ b/libs/WindowManager/Shell/multivalentTestsForDevice @@ -0,0 +1 @@ +multivalentTests
\ No newline at end of file diff --git a/libs/WindowManager/Shell/multivalentTestsForDeviceless b/libs/WindowManager/Shell/multivalentTestsForDeviceless new file mode 120000 index 000000000000..20ee34ada103 --- /dev/null +++ b/libs/WindowManager/Shell/multivalentTestsForDeviceless @@ -0,0 +1 @@ +multivalentTests
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java index 81d963877e4c..bb433dbbd2ce 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java @@ -176,6 +176,10 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont private StatusBarCustomizer mCustomizer; private boolean mTrackingLatency; + // Keep previous navigation type before remove mBackNavigationInfo. + @BackNavigationInfo.BackTargetType + private int mPreviousNavigationType; + public BackAnimationController( @NonNull ShellInit shellInit, @NonNull ShellController shellController, @@ -871,6 +875,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont mShellBackAnimationRegistry.resetDefaultCrossActivity(); cancelLatencyTracking(); if (mBackNavigationInfo != null) { + mPreviousNavigationType = mBackNavigationInfo.getType(); mBackNavigationInfo.onBackNavigationFinished(triggerBack); mBackNavigationInfo = null; } @@ -983,7 +988,9 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont mShellExecutor.execute( () -> { if (!mShellBackAnimationRegistry.cancel( - mBackNavigationInfo.getType())) { + mBackNavigationInfo != null + ? mBackNavigationInfo.getType() + : mPreviousNavigationType)) { return; } if (!mBackGestureStarted) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java index 80fc3a867d48..ac2a1c867462 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java @@ -136,6 +136,9 @@ public class CrossTaskBackAnimation extends ShellBackAnimation { mStartTaskRect.set(mClosingTarget.windowConfiguration.getBounds()); mStartTaskRect.offsetTo(0, 0); + // inset bottom in case of pinned taskbar being present + mStartTaskRect.inset(0, 0, 0, mClosingTarget.contentInsets.bottom); + // Draw background. mBackground.ensureBackground(mClosingTarget.windowConfiguration.getBounds(), BACKGROUNDCOLOR, mTransaction); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java index bc1a57572c63..5de8a9be9576 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java @@ -481,18 +481,20 @@ class SplitScreenTransitions { private void startFadeAnimation(@NonNull SurfaceControl leash, boolean show) { final float end = show ? 1.f : 0.f; final float start = 1.f - end; - final SurfaceControl.Transaction transaction = mTransactionPool.acquire(); final ValueAnimator va = ValueAnimator.ofFloat(start, end); va.setDuration(FADE_DURATION); va.setInterpolator(show ? ALPHA_IN : ALPHA_OUT); va.addUpdateListener(animation -> { float fraction = animation.getAnimatedFraction(); + final SurfaceControl.Transaction transaction = mTransactionPool.acquire(); transaction.setAlpha(leash, start * (1.f - fraction) + end * fraction); transaction.apply(); + mTransactionPool.release(transaction); }); va.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { + final SurfaceControl.Transaction transaction = mTransactionPool.acquire(); transaction.setAlpha(leash, end); transaction.apply(); mTransactionPool.release(transaction); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java index 8c861c63a70d..bf783e6af36f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java @@ -197,60 +197,61 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback) { - if (mType == TYPE_ENTER_PIP_FROM_SPLIT) { - return animateEnterPipFromSplit(this, info, startTransaction, finishTransaction, - finishCallback, mPlayer, mMixedHandler, mPipHandler, mSplitHandler); - } else if (mType == TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING) { - return animateEnterPipFromActivityEmbedding( - info, startTransaction, finishTransaction, finishCallback); - } else if (mType == TYPE_DISPLAY_AND_SPLIT_CHANGE) { - return false; - } else if (mType == TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE) { - final boolean handledToPip = animateOpenIntentWithRemoteAndPip( - info, startTransaction, finishTransaction, finishCallback); - // Consume the transition on remote handler if the leftover handler already handle - // this transition. And if it cannot, the transition will be handled by remote - // handler, so don't consume here. - // Need to check leftOverHandler as it may change in - // #animateOpenIntentWithRemoteAndPip - if (handledToPip && mHasRequestToRemote - && mLeftoversHandler != mPlayer.getRemoteTransitionHandler()) { - mPlayer.getRemoteTransitionHandler().onTransitionConsumed( - transition, false, null); - } - return handledToPip; - } else if (mType == TYPE_RECENTS_DURING_SPLIT) { - for (int i = info.getChanges().size() - 1; i >= 0; --i) { - final TransitionInfo.Change change = info.getChanges().get(i); - // Pip auto-entering info might be appended to recent transition like pressing - // home-key in 3-button navigation. This offers split handler the opportunity to - // handle split to pip animation. - if (mPipHandler.isEnteringPip(change, info.getType()) - && mSplitHandler.getSplitItemPosition(change.getLastParent()) - != SPLIT_POSITION_UNDEFINED) { - return animateEnterPipFromSplit( - this, info, startTransaction, finishTransaction, finishCallback, - mPlayer, mMixedHandler, mPipHandler, mSplitHandler); + switch (mType) { + case TYPE_ENTER_PIP_FROM_SPLIT: + return animateEnterPipFromSplit(this, info, startTransaction, finishTransaction, + finishCallback, mPlayer, mMixedHandler, mPipHandler, mSplitHandler); + case TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING: + return animateEnterPipFromActivityEmbedding( + info, startTransaction, finishTransaction, finishCallback); + case TYPE_DISPLAY_AND_SPLIT_CHANGE: + return false; + case TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE: + final boolean handledToPip = animateOpenIntentWithRemoteAndPip( + info, startTransaction, finishTransaction, finishCallback); + // Consume the transition on remote handler if the leftover handler already + // handle this transition. And if it cannot, the transition will be handled by + // remote handler, so don't consume here. + // Need to check leftOverHandler as it may change in + // #animateOpenIntentWithRemoteAndPip + if (handledToPip && mHasRequestToRemote + && mLeftoversHandler != mPlayer.getRemoteTransitionHandler()) { + mPlayer.getRemoteTransitionHandler().onTransitionConsumed( + transition, false, null); + } + return handledToPip; + case TYPE_RECENTS_DURING_SPLIT: + for (int i = info.getChanges().size() - 1; i >= 0; --i) { + final TransitionInfo.Change change = info.getChanges().get(i); + // Pip auto-entering info might be appended to recent transition like + // pressing home-key in 3-button navigation. This offers split handler the + // opportunity to handle split to pip animation. + if (mPipHandler.isEnteringPip(change, info.getType()) + && mSplitHandler.getSplitItemPosition(change.getLastParent()) + != SPLIT_POSITION_UNDEFINED) { + return animateEnterPipFromSplit( + this, info, startTransaction, finishTransaction, finishCallback, + mPlayer, mMixedHandler, mPipHandler, mSplitHandler); + } } - } - return animateRecentsDuringSplit( - info, startTransaction, finishTransaction, finishCallback); - } else if (mType == TYPE_KEYGUARD) { - return animateKeyguard(this, info, startTransaction, finishTransaction, - finishCallback, mKeyguardHandler, mPipHandler); - } else if (mType == TYPE_RECENTS_DURING_KEYGUARD) { - return animateRecentsDuringKeyguard( - info, startTransaction, finishTransaction, finishCallback); - } else if (mType == TYPE_RECENTS_DURING_DESKTOP) { - return animateRecentsDuringDesktop( - info, startTransaction, finishTransaction, finishCallback); - } else if (mType == TYPE_UNFOLD) { - return animateUnfold( - info, startTransaction, finishTransaction, finishCallback); - } else { - throw new IllegalStateException( - "Starting mixed animation without a known mixed type? " + mType); + return animateRecentsDuringSplit( + info, startTransaction, finishTransaction, finishCallback); + case TYPE_KEYGUARD: + return animateKeyguard(this, info, startTransaction, finishTransaction, + finishCallback, mKeyguardHandler, mPipHandler); + case TYPE_RECENTS_DURING_KEYGUARD: + return animateRecentsDuringKeyguard( + info, startTransaction, finishTransaction, finishCallback); + case TYPE_RECENTS_DURING_DESKTOP: + return animateRecentsDuringDesktop( + info, startTransaction, finishTransaction, finishCallback); + case TYPE_UNFOLD: + return animateUnfold( + info, startTransaction, finishTransaction, finishCallback); + default: + throw new IllegalStateException( + "Starting mixed animation without a known mixed type? " + mType); } } @@ -457,79 +458,100 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, @NonNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget, @NonNull Transitions.TransitionFinishCallback finishCallback) { - if (mType == TYPE_DISPLAY_AND_SPLIT_CHANGE) { - // queue since no actual animation. - } else if (mType == TYPE_ENTER_PIP_FROM_SPLIT) { - if (mAnimType == ANIM_TYPE_GOING_HOME) { - boolean ended = mSplitHandler.end(); - // If split couldn't end (because it is remote), then don't end everything else - // since we have to play out the animation anyways. - if (!ended) return; - mPipHandler.end(); - if (mLeftoversHandler != null) { - mLeftoversHandler.mergeAnimation( - transition, info, t, mergeTarget, finishCallback); + switch (mType) { + case TYPE_DISPLAY_AND_SPLIT_CHANGE: + // queue since no actual animation. + break; + case TYPE_ENTER_PIP_FROM_SPLIT: + if (mAnimType == ANIM_TYPE_GOING_HOME) { + boolean ended = mSplitHandler.end(); + // If split couldn't end (because it is remote), then don't end everything + // else since we have to play out the animation anyways. + if (!ended) return; + mPipHandler.end(); + if (mLeftoversHandler != null) { + mLeftoversHandler.mergeAnimation( + transition, info, t, mergeTarget, finishCallback); + } + } else { + mPipHandler.end(); } - } else { + break; + case TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING: mPipHandler.end(); - } - } else if (mType == TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING) { - mPipHandler.end(); - mActivityEmbeddingController.mergeAnimation(transition, info, t, mergeTarget, - finishCallback); - } else if (mType == TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE) { - mPipHandler.end(); - if (mLeftoversHandler != null) { - mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget, + mActivityEmbeddingController.mergeAnimation(transition, info, t, mergeTarget, finishCallback); - } - } else if (mType == TYPE_RECENTS_DURING_SPLIT) { - if (mSplitHandler.isPendingEnter(transition)) { - // Recents -> enter-split means that we are switching from one pair to - // another pair. - mAnimType = ANIM_TYPE_PAIR_TO_PAIR; - } - mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback); - } else if (mType == TYPE_KEYGUARD) { - mKeyguardHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback); - } else if (mType == TYPE_RECENTS_DURING_KEYGUARD) { - if ((info.getFlags() & TRANSIT_FLAG_KEYGUARD_UNOCCLUDING) != 0) { - DefaultMixedHandler.handoverTransitionLeashes(mInfo, info, t, mFinishT); - if (animateKeyguard( - this, info, t, mFinishT, mFinishCB, mKeyguardHandler, mPipHandler)) { - finishCallback.onTransitionFinished(null); + break; + case TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE: + mPipHandler.end(); + if (mLeftoversHandler != null) { + mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget, + finishCallback); } - } - mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback); - } else if (mType == TYPE_RECENTS_DURING_DESKTOP) { - mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback); - } else if (mType == TYPE_UNFOLD) { - mUnfoldHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback); - } else { - throw new IllegalStateException( - "Playing a mixed transition with unknown type? " + mType); + break; + case TYPE_RECENTS_DURING_SPLIT: + if (mSplitHandler.isPendingEnter(transition)) { + // Recents -> enter-split means that we are switching from one pair to + // another pair. + mAnimType = ANIM_TYPE_PAIR_TO_PAIR; + } + mLeftoversHandler.mergeAnimation( + transition, info, t, mergeTarget, finishCallback); + break; + case TYPE_KEYGUARD: + mKeyguardHandler.mergeAnimation( + transition, info, t, mergeTarget, finishCallback); + break; + case TYPE_RECENTS_DURING_KEYGUARD: + if ((info.getFlags() & TRANSIT_FLAG_KEYGUARD_UNOCCLUDING) != 0) { + DefaultMixedHandler.handoverTransitionLeashes(mInfo, info, t, mFinishT); + if (animateKeyguard(this, info, t, mFinishT, mFinishCB, mKeyguardHandler, + mPipHandler)) { + finishCallback.onTransitionFinished(null); + } + } + mLeftoversHandler.mergeAnimation( + transition, info, t, mergeTarget, finishCallback); + break; + case TYPE_RECENTS_DURING_DESKTOP: + mLeftoversHandler.mergeAnimation( + transition, info, t, mergeTarget, finishCallback); + break; + case TYPE_UNFOLD: + mUnfoldHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback); + break; + default: + throw new IllegalStateException( + "Playing a mixed transition with unknown type? " + mType); } } void onTransitionConsumed( @NonNull IBinder transition, boolean aborted, @Nullable SurfaceControl.Transaction finishT) { - if (mType == TYPE_ENTER_PIP_FROM_SPLIT) { - mPipHandler.onTransitionConsumed(transition, aborted, finishT); - } else if (mType == TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING) { - mPipHandler.onTransitionConsumed(transition, aborted, finishT); - mActivityEmbeddingController.onTransitionConsumed(transition, aborted, finishT); - } else if (mType == TYPE_RECENTS_DURING_SPLIT) { - mLeftoversHandler.onTransitionConsumed(transition, aborted, finishT); - } else if (mType == TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE) { - mLeftoversHandler.onTransitionConsumed(transition, aborted, finishT); - } else if (mType == TYPE_KEYGUARD) { - mKeyguardHandler.onTransitionConsumed(transition, aborted, finishT); - } else if (mType == TYPE_RECENTS_DURING_DESKTOP) { - mLeftoversHandler.onTransitionConsumed(transition, aborted, finishT); - } else if (mType == TYPE_UNFOLD) { - mUnfoldHandler.onTransitionConsumed(transition, aborted, finishT); + switch (mType) { + case TYPE_ENTER_PIP_FROM_SPLIT: + mPipHandler.onTransitionConsumed(transition, aborted, finishT); + break; + case TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING: + mPipHandler.onTransitionConsumed(transition, aborted, finishT); + mActivityEmbeddingController.onTransitionConsumed(transition, aborted, finishT); + break; + case TYPE_RECENTS_DURING_SPLIT: + case TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE: + case TYPE_RECENTS_DURING_DESKTOP: + mLeftoversHandler.onTransitionConsumed(transition, aborted, finishT); + break; + case TYPE_KEYGUARD: + mKeyguardHandler.onTransitionConsumed(transition, aborted, finishT); + break; + case TYPE_UNFOLD: + mUnfoldHandler.onTransitionConsumed(transition, aborted, finishT); + break; + default: + break; } + if (mHasRequestToRemote) { mPlayer.getRemoteTransitionHandler().onTransitionConsumed( transition, aborted, finishT); diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/AppsEnterPipTransition.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/AppsEnterPipTransition.kt index 182a9089d040..be771712834f 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/AppsEnterPipTransition.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/AppsEnterPipTransition.kt @@ -101,7 +101,8 @@ abstract class AppsEnterPipTransition(flicker: LegacyFlickerTest) : EnterPipTran override fun pipLayerReduces() { flicker.assertLayers { val pipLayerList = - this.layers { standardAppHelper.layerMatchesAnyOf(it) && it.isVisible } + this.layers { standardAppHelper.packageNameMatcher.layerMatchesAnyOf(it) + && it.isVisible } pipLayerList.zipWithNext { previous, current -> current.visibleRegion.notBiggerThan(previous.visibleRegion.region) } 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 deleted file mode 100644 index 6ebee730756e..000000000000 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblePositionerTest.java +++ /dev/null @@ -1,602 +0,0 @@ -/* - * 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 com.android.wm.shell.bubbles.BubblePositioner.MAX_HEIGHT; - -import static com.google.common.truth.Truth.assertThat; -import static com.google.common.util.concurrent.MoreExecutors.directExecutor; - -import static org.mockito.Mockito.mock; - -import android.content.Intent; -import android.content.pm.ShortcutInfo; -import android.graphics.Insets; -import android.graphics.PointF; -import android.graphics.Rect; -import android.graphics.RectF; -import android.os.UserHandle; -import android.testing.AndroidTestingRunner; -import android.view.WindowManager; - -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; - -/** - * Tests operations and the resulting state managed by {@link BubblePositioner}. - */ -@SmallTest -@RunWith(AndroidTestingRunner.class) -public class BubblePositionerTest extends ShellTestCase { - - private BubblePositioner mPositioner; - - @Before - public void setUp() { - WindowManager windowManager = mContext.getSystemService(WindowManager.class); - mPositioner = new BubblePositioner(mContext, windowManager); - } - - @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); - - DeviceConfig deviceConfig = new ConfigBuilder() - .setInsets(insets) - .setScreenBounds(screenBounds) - .build(); - mPositioner.update(deviceConfig); - - assertThat(mPositioner.getAvailableRect()).isEqualTo(availableRect); - assertThat(mPositioner.isLandscape()).isFalse(); - assertThat(mPositioner.isLargeScreen()).isFalse(); - assertThat(mPositioner.getInsets()).isEqualTo(insets); - } - - @Test - public void testShowBubblesVertically_phonePortrait() { - DeviceConfig deviceConfig = new ConfigBuilder().build(); - mPositioner.update(deviceConfig); - - assertThat(mPositioner.showBubblesVertically()).isFalse(); - } - - @Test - public void testShowBubblesVertically_phoneLandscape() { - DeviceConfig deviceConfig = new ConfigBuilder().setLandscape().build(); - mPositioner.update(deviceConfig); - - assertThat(mPositioner.isLandscape()).isTrue(); - assertThat(mPositioner.showBubblesVertically()).isTrue(); - } - - @Test - public void testShowBubblesVertically_tablet() { - DeviceConfig deviceConfig = new ConfigBuilder().setLargeScreen().build(); - mPositioner.update(deviceConfig); - - assertThat(mPositioner.showBubblesVertically()).isTrue(); - } - - /** If a resting position hasn't been set, calling it will return the default position. */ - @Test - public void testGetRestingPosition_returnsDefaultPosition() { - DeviceConfig deviceConfig = new ConfigBuilder().build(); - mPositioner.update(deviceConfig); - - 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() { - DeviceConfig deviceConfig = new ConfigBuilder().build(); - mPositioner.update(deviceConfig); - - 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() { - DeviceConfig deviceConfig = new ConfigBuilder().build(); - mPositioner.update(deviceConfig); - - 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() { - DeviceConfig deviceConfig = new ConfigBuilder().setRtl().build(); - mPositioner.update(deviceConfig); - - 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() { - DeviceConfig deviceConfig = new ConfigBuilder().setLargeScreen().build(); - mPositioner.update(deviceConfig); - - 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() { - DeviceConfig deviceConfig = new ConfigBuilder().setLargeScreen().setRtl().build(); - mPositioner.update(deviceConfig); - - 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 right. */ - @Test - public void testGetDefaultPosition_appBubble_onTablet() { - DeviceConfig deviceConfig = new ConfigBuilder().setLargeScreen().build(); - mPositioner.update(deviceConfig); - - RectF allowableStackRegion = - mPositioner.getAllowableStackPositionRegion(1 /* bubbleCount */); - PointF startPosition = mPositioner.getDefaultStartPosition(true /* isAppBubble */); - - assertThat(startPosition.x).isEqualTo(allowableStackRegion.right); - assertThat(startPosition.y).isEqualTo(getDefaultYPosition()); - } - - @Test - public void testGetRestingPosition_appBubble_onTablet_RTL() { - DeviceConfig deviceConfig = new ConfigBuilder().setLargeScreen().setRtl().build(); - mPositioner.update(deviceConfig); - - RectF allowableStackRegion = - mPositioner.getAllowableStackPositionRegion(1 /* bubbleCount */); - PointF startPosition = mPositioner.getDefaultStartPosition(true /* isAppBubble */); - - assertThat(startPosition.x).isEqualTo(allowableStackRegion.left); - assertThat(startPosition.y).isEqualTo(getDefaultYPosition()); - } - - @Test - public void testHasUserModifiedDefaultPosition_false() { - DeviceConfig deviceConfig = new ConfigBuilder().setLargeScreen().setRtl().build(); - mPositioner.update(deviceConfig); - - assertThat(mPositioner.hasUserModifiedDefaultPosition()).isFalse(); - - mPositioner.setRestingPosition(mPositioner.getDefaultStartPosition()); - - assertThat(mPositioner.hasUserModifiedDefaultPosition()).isFalse(); - } - - @Test - public void testHasUserModifiedDefaultPosition_true() { - DeviceConfig deviceConfig = new ConfigBuilder().setLargeScreen().setRtl().build(); - mPositioner.update(deviceConfig); - - assertThat(mPositioner.hasUserModifiedDefaultPosition()).isFalse(); - - mPositioner.setRestingPosition(new PointF(0, 100)); - - assertThat(mPositioner.hasUserModifiedDefaultPosition()).isTrue(); - } - - @Test - public void testGetExpandedViewHeight_max() { - 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()); - - assertThat(mPositioner.getExpandedViewHeight(bubble)).isEqualTo(MAX_HEIGHT); - } - - @Test - public void testGetExpandedViewHeight_customHeight_valid() { - 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); - - final int minHeight = mContext.getResources().getDimensionPixelSize( - R.dimen.bubble_expanded_default_height); - Bubble bubble = new Bubble("key", - mock(ShortcutInfo.class), - minHeight + 100 /* desiredHeight */, - 0 /* desiredHeightResId */, - "title", - 0 /* taskId */, - null /* locus */, - true /* isDismissable */, - directExecutor(), - mock(Bubbles.BubbleMetadataFlagListener.class)); - - // Ensure the height is the same as the desired value - assertThat(mPositioner.getExpandedViewHeight(bubble)).isEqualTo( - bubble.getDesiredHeight(mContext)); - } - - - @Test - public void testGetExpandedViewHeight_customHeight_tooSmall() { - 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); - - Bubble bubble = new Bubble("key", - mock(ShortcutInfo.class), - 10 /* desiredHeight */, - 0 /* desiredHeightResId */, - "title", - 0 /* taskId */, - null /* locus */, - true /* isDismissable */, - directExecutor(), - mock(Bubbles.BubbleMetadataFlagListener.class)); - - // Ensure the height is the same as the minimum value - final int minHeight = mContext.getResources().getDimensionPixelSize( - R.dimen.bubble_expanded_default_height); - assertThat(mPositioner.getExpandedViewHeight(bubble)).isEqualTo(minHeight); - } - - @Test - public void testGetMaxExpandedViewHeight_onLargeTablet() { - 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); - - int manageButtonHeight = - mContext.getResources().getDimensionPixelSize(R.dimen.bubble_manage_button_height); - int pointerWidth = mContext.getResources().getDimensionPixelSize( - R.dimen.bubble_pointer_width); - int expandedViewPadding = mContext.getResources().getDimensionPixelSize(R - .dimen.bubble_expanded_view_padding); - float expectedHeight = 1800 - 2 * 20 - manageButtonHeight - pointerWidth - - expandedViewPadding * 2; - assertThat(((float) mPositioner.getMaxExpandedViewHeight(false /* isOverflow */))) - .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 - * {@link BubbleStackView.RelativeStackPosition}. - */ - private float getDefaultYPosition() { - final boolean isTablet = mPositioner.isLargeScreen(); - - // On tablet the position is centered, on phone it is an offset from the top. - final float desiredY = isTablet - ? mPositioner.getScreenRect().height() / 2f - (mPositioner.getBubbleSize() / 2f) - : mContext.getResources().getDimensionPixelOffset( - R.dimen.bubble_stack_starting_offset_y); - // Since we're visually centering the bubbles on tablet, use total screen height rather - // than the available height. - final float height = isTablet - ? mPositioner.getScreenRect().height() - : mPositioner.getAvailableRect().height(); - float offsetPercent = desiredY / 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 static class ConfigBuilder { - private Rect mScreenBounds = new Rect(0, 0, 1000, 2000); - private boolean mIsLargeScreen = false; - private boolean mIsSmallTablet = false; - private boolean mIsLandscape = false; - private boolean mIsRtl = false; - private Insets mInsets = Insets.of(0, 0, 0, 0); - - public ConfigBuilder setScreenBounds(Rect screenBounds) { - mScreenBounds = screenBounds; - return this; - } - - public ConfigBuilder setLargeScreen() { - mIsLargeScreen = true; - return this; - } - - public ConfigBuilder setSmallTablet() { - mIsSmallTablet = true; - return this; - } - - public ConfigBuilder setLandscape() { - mIsLandscape = true; - return this; - } - - public ConfigBuilder setRtl() { - mIsRtl = true; - return this; - } - - public ConfigBuilder setInsets(Insets insets) { - mInsets = insets; - return this; - } - - private DeviceConfig build() { - return new DeviceConfig(mIsLargeScreen, mIsSmallTablet, mIsLandscape, mIsRtl, - mScreenBounds, mInsets); - } - } -} diff --git a/libs/androidfw/Android.bp b/libs/androidfw/Android.bp index 2f28363aedc7..77800a305f02 100644 --- a/libs/androidfw/Android.bp +++ b/libs/androidfw/Android.bp @@ -31,6 +31,12 @@ license { ], } +cc_aconfig_library { + name: "backup_flags_cc_lib", + host_supported: true, + aconfig_declarations: "backup_flags", +} + cc_defaults { name: "libandroidfw_defaults", cpp_std: "gnu++2b", @@ -115,7 +121,10 @@ cc_library { "libutils", "libz", ], - static_libs: ["libziparchive_for_incfs"], + static_libs: [ + "libziparchive_for_incfs", + "backup_flags_cc_lib", + ], static: { enabled: false, }, diff --git a/libs/androidfw/BackupHelpers.cpp b/libs/androidfw/BackupHelpers.cpp index 1a6a952492f6..a1e7c2ffc1b1 100644 --- a/libs/androidfw/BackupHelpers.cpp +++ b/libs/androidfw/BackupHelpers.cpp @@ -36,6 +36,9 @@ #include <utils/KeyedVector.h> #include <utils/String8.h> +#include <com_android_server_backup.h> +namespace backup_flags = com::android::server::backup; + namespace android { #define MAGIC0 0x70616e53 // Snap @@ -214,7 +217,7 @@ write_update_file(BackupDataWriter* dataStream, int fd, int mode, const String8& { LOGP("write_update_file %s (%s) : mode 0%o\n", realFilename, key.c_str(), mode); - const int bufsize = 4*1024; + const int bufsize = backup_flags::enable_max_size_writes_to_pipes() ? (64*1024) : (4*1024); int err; int amt; int fileSize; @@ -550,7 +553,8 @@ int write_tarfile(const String8& packageName, const String8& domain, } // read/write up to this much at a time. - const size_t BUFSIZE = 32 * 1024; + const size_t BUFSIZE = backup_flags::enable_max_size_writes_to_pipes() ? (64*1024) : (32*1024); + char* buf = (char *)calloc(1,BUFSIZE); const size_t PAXHEADER_OFFSET = 512; const size_t PAXHEADER_SIZE = 512; @@ -726,7 +730,7 @@ done: -#define RESTORE_BUF_SIZE (8*1024) +const size_t RESTORE_BUF_SIZE = backup_flags::enable_max_size_writes_to_pipes() ? 64*1024 : 8*1024; RestoreHelperBase::RestoreHelperBase() { diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp index 6c3172a26751..d58c872dbc56 100644 --- a/libs/hwui/Properties.cpp +++ b/libs/hwui/Properties.cpp @@ -56,6 +56,7 @@ std::optional<std::int32_t> render_ahead() { bool Properties::debugLayersUpdates = false; bool Properties::debugOverdraw = false; +bool Properties::debugTraceGpuResourceCategories = false; bool Properties::showDirtyRegions = false; bool Properties::skipEmptyFrames = true; bool Properties::useBufferAge = true; @@ -151,10 +152,12 @@ bool Properties::load() { skpCaptureEnabled = debuggingEnabled && base::GetBoolProperty(PROPERTY_CAPTURE_SKP_ENABLED, false); - SkAndroidFrameworkTraceUtil::setEnableTracing( - base::GetBoolProperty(PROPERTY_SKIA_TRACING_ENABLED, false)); + bool skiaBroadTracing = base::GetBoolProperty(PROPERTY_SKIA_TRACING_ENABLED, false); + SkAndroidFrameworkTraceUtil::setEnableTracing(skiaBroadTracing); SkAndroidFrameworkTraceUtil::setUsePerfettoTrackEvents( base::GetBoolProperty(PROPERTY_SKIA_USE_PERFETTO_TRACK_EVENTS, false)); + debugTraceGpuResourceCategories = + base::GetBoolProperty(PROPERTY_TRACE_GPU_RESOURCES, skiaBroadTracing); runningInEmulator = base::GetBoolProperty(PROPERTY_IS_EMULATOR, false); diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h index bca57e9e4678..b956facf6f90 100644 --- a/libs/hwui/Properties.h +++ b/libs/hwui/Properties.h @@ -143,6 +143,15 @@ enum DebugLevel { #define PROPERTY_CAPTURE_SKP_ENABLED "debug.hwui.capture_skp_enabled" /** + * Might split Skia's GPU resource utilization into separate tracing tracks (slow). + * + * Aggregate total and purgeable numbers will still be reported under a "misc" track when this is + * disabled, they just won't be split into distinct categories. Results may vary depending on GPU + * backend/API, and the category mappings defined in ATraceMemoryDump's hardcoded sResourceMap. + */ +#define PROPERTY_TRACE_GPU_RESOURCES "debug.hwui.trace_gpu_resources" + +/** * Allows broad recording of Skia drawing commands. * * If disabled, a very minimal set of trace events *may* be recorded. @@ -254,6 +263,7 @@ public: static bool debugLayersUpdates; static bool debugOverdraw; + static bool debugTraceGpuResourceCategories; static bool showDirtyRegions; // TODO: Remove after stabilization period static bool skipEmptyFrames; diff --git a/libs/hwui/aconfig/hwui_flags.aconfig b/libs/hwui/aconfig/hwui_flags.aconfig index c156c46a5a9b..72ddeccd26b2 100644 --- a/libs/hwui/aconfig/hwui_flags.aconfig +++ b/libs/hwui/aconfig/hwui_flags.aconfig @@ -22,6 +22,13 @@ flag { } flag { + name: "high_contrast_text_small_text_rect" + namespace: "accessibility" + description: "Draw a solid rectangle background behind text instead of a stroke outline" + bug: "186567103" +} + +flag { name: "hdr_10bit_plus" namespace: "core_graphics" description: "Use 10101010 and FP16 formats for HDR-UI when available" diff --git a/libs/hwui/hwui/Canvas.cpp b/libs/hwui/hwui/Canvas.cpp index 80b6c0385fca..e9f4b81c7624 100644 --- a/libs/hwui/hwui/Canvas.cpp +++ b/libs/hwui/hwui/Canvas.cpp @@ -18,6 +18,7 @@ #include <SkFontMetrics.h> #include <SkRRect.h> +#include <minikin/MinikinRect.h> #include "FeatureFlags.h" #include "MinikinUtils.h" @@ -107,7 +108,13 @@ void Canvas::drawText(const uint16_t* text, int textSize, int start, int count, // care of all alignment. paint.setTextAlign(Paint::kLeft_Align); - DrawTextFunctor f(layout, this, paint, x, y, layout.getAdvance()); + minikin::MinikinRect bounds; + // We only need the bounds to draw a rectangular background in high contrast mode. Let's save + // the cycles otherwise. + if (flags::high_contrast_text_small_text_rect() && isHighContrastText()) { + MinikinUtils::getBounds(&paint, bidiFlags, typeface, text, textSize, &bounds); + } + DrawTextFunctor f(layout, this, paint, x, y, layout.getAdvance(), bounds); MinikinUtils::forFontRun(layout, &paint, f); if (text_feature::fix_double_underline()) { diff --git a/libs/hwui/hwui/DrawTextFunctor.h b/libs/hwui/hwui/DrawTextFunctor.h index 8f999904a8ab..ba6543988a7b 100644 --- a/libs/hwui/hwui/DrawTextFunctor.h +++ b/libs/hwui/hwui/DrawTextFunctor.h @@ -33,6 +33,8 @@ namespace flags = com::android::graphics::hwui::flags; namespace android { +inline constexpr int kHighContrastTextBorderWidth = 4; + static inline void drawStroke(SkScalar left, SkScalar right, SkScalar top, SkScalar thickness, const Paint& paint, Canvas* canvas) { const SkScalar strokeWidth = fmax(thickness, 1.0f); @@ -45,15 +47,26 @@ static void simplifyPaint(int color, Paint* paint) { paint->setShader(nullptr); paint->setColorFilter(nullptr); paint->setLooper(nullptr); - paint->setStrokeWidth(4 + 0.04 * paint->getSkFont().getSize()); + paint->setStrokeWidth(kHighContrastTextBorderWidth + 0.04 * paint->getSkFont().getSize()); paint->setStrokeJoin(SkPaint::kRound_Join); paint->setLooper(nullptr); } class DrawTextFunctor { public: + /** + * Creates a Functor to draw the given text layout. + * + * @param layout + * @param canvas + * @param paint + * @param x + * @param y + * @param totalAdvance + * @param bounds bounds of the text. Only required if high contrast text mode is enabled. + */ DrawTextFunctor(const minikin::Layout& layout, Canvas* canvas, const Paint& paint, float x, - float y, float totalAdvance) + float y, float totalAdvance, const minikin::MinikinRect& bounds) : layout(layout) , canvas(canvas) , paint(paint) @@ -61,7 +74,8 @@ public: , y(y) , totalAdvance(totalAdvance) , underlinePosition(0) - , underlineThickness(0) {} + , underlineThickness(0) + , bounds(bounds) {} void operator()(size_t start, size_t end) { auto glyphFunc = [&](uint16_t* text, float* positions) { @@ -91,7 +105,16 @@ public: Paint outlinePaint(paint); simplifyPaint(darken ? SK_ColorWHITE : SK_ColorBLACK, &outlinePaint); outlinePaint.setStyle(SkPaint::kStrokeAndFill_Style); - canvas->drawGlyphs(glyphFunc, glyphCount, outlinePaint, x, y, totalAdvance); + if (flags::high_contrast_text_small_text_rect()) { + auto bgBounds(bounds); + auto padding = kHighContrastTextBorderWidth + 0.1f * paint.getSkFont().getSize(); + bgBounds.offset(x, y); + canvas->drawRect(bgBounds.mLeft - padding, bgBounds.mTop - padding, + bgBounds.mRight + padding, bgBounds.mBottom + padding, + outlinePaint); + } else { + canvas->drawGlyphs(glyphFunc, glyphCount, outlinePaint, x, y, totalAdvance); + } // inner gDrawTextBlobMode = DrawTextBlobMode::HctInner; @@ -146,6 +169,7 @@ private: float totalAdvance; float underlinePosition; float underlineThickness; + const minikin::MinikinRect& bounds; }; } // namespace android diff --git a/libs/hwui/hwui/MinikinUtils.cpp b/libs/hwui/hwui/MinikinUtils.cpp index 7552b56d2ad6..833069f363c8 100644 --- a/libs/hwui/hwui/MinikinUtils.cpp +++ b/libs/hwui/hwui/MinikinUtils.cpp @@ -96,7 +96,7 @@ void MinikinUtils::getBounds(const Paint* paint, minikin::Bidi bidiFlags, const float MinikinUtils::measureText(const Paint* paint, minikin::Bidi bidiFlags, const Typeface* typeface, const uint16_t* buf, size_t start, size_t count, size_t bufSize, float* advances, - minikin::MinikinRect* bounds) { + minikin::MinikinRect* bounds, uint32_t* clusterCount) { minikin::MinikinPaint minikinPaint = prepareMinikinPaint(paint, typeface); const minikin::U16StringPiece textBuf(buf, bufSize); const minikin::Range range(start, start + count); @@ -104,7 +104,7 @@ float MinikinUtils::measureText(const Paint* paint, minikin::Bidi bidiFlags, const minikin::EndHyphenEdit endHyphen = paint->getEndHyphenEdit(); return minikin::Layout::measureText(textBuf, range, bidiFlags, minikinPaint, startHyphen, - endHyphen, advances, bounds); + endHyphen, advances, bounds, clusterCount); } minikin::MinikinExtent MinikinUtils::getFontExtent(const Paint* paint, minikin::Bidi bidiFlags, diff --git a/libs/hwui/hwui/MinikinUtils.h b/libs/hwui/hwui/MinikinUtils.h index 61bc881faa54..f8574ee50525 100644 --- a/libs/hwui/hwui/MinikinUtils.h +++ b/libs/hwui/hwui/MinikinUtils.h @@ -53,7 +53,7 @@ public: static float measureText(const Paint* paint, minikin::Bidi bidiFlags, const Typeface* typeface, const uint16_t* buf, size_t start, size_t count, size_t bufSize, - float* advances, minikin::MinikinRect* bounds); + float* advances, minikin::MinikinRect* bounds, uint32_t* clusterCount); static minikin::MinikinExtent getFontExtent(const Paint* paint, minikin::Bidi bidiFlags, const Typeface* typeface, const uint16_t* buf, diff --git a/libs/hwui/jni/Graphics.cpp b/libs/hwui/jni/Graphics.cpp index 7cc48661619a..8315c4c0dd4d 100644 --- a/libs/hwui/jni/Graphics.cpp +++ b/libs/hwui/jni/Graphics.cpp @@ -247,6 +247,9 @@ static jfieldID gFontMetricsInt_descent; static jfieldID gFontMetricsInt_bottom; static jfieldID gFontMetricsInt_leading; +static jclass gRunInfo_class; +static jfieldID gRunInfo_clusterCount; + /////////////////////////////////////////////////////////////////////////////// void GraphicsJNI::get_jrect(JNIEnv* env, jobject obj, int* L, int* T, int* R, int* B) @@ -511,6 +514,10 @@ int GraphicsJNI::set_metrics_int(JNIEnv* env, jobject metrics, const SkFontMetri return descent - ascent + leading; } +void GraphicsJNI::set_cluster_count_to_run_info(JNIEnv* env, jobject runInfo, jint clusterCount) { + env->SetIntField(runInfo, gRunInfo_clusterCount, clusterCount); +} + /////////////////////////////////////////////////////////////////////////////////////////// jobject GraphicsJNI::createBitmapRegionDecoder(JNIEnv* env, BitmapRegionDecoderWrapper* bitmap) { @@ -834,5 +841,10 @@ int register_android_graphics_Graphics(JNIEnv* env) gFontMetricsInt_bottom = GetFieldIDOrDie(env, gFontMetricsInt_class, "bottom", "I"); gFontMetricsInt_leading = GetFieldIDOrDie(env, gFontMetricsInt_class, "leading", "I"); + gRunInfo_class = FindClassOrDie(env, "android/graphics/Paint$RunInfo"); + gRunInfo_class = MakeGlobalRefOrDie(env, gRunInfo_class); + + gRunInfo_clusterCount = GetFieldIDOrDie(env, gRunInfo_class, "mClusterCount", "I"); + return 0; } diff --git a/libs/hwui/jni/GraphicsJNI.h b/libs/hwui/jni/GraphicsJNI.h index b9fff36d372e..b0a1074d6693 100644 --- a/libs/hwui/jni/GraphicsJNI.h +++ b/libs/hwui/jni/GraphicsJNI.h @@ -77,6 +77,8 @@ public: static SkRect* jrect_to_rect(JNIEnv*, jobject jrect, SkRect*); static void rect_to_jrectf(const SkRect&, JNIEnv*, jobject jrectf); + static void set_cluster_count_to_run_info(JNIEnv* env, jobject runInfo, jint clusterCount); + static void set_jpoint(JNIEnv*, jobject jrect, int x, int y); static SkIPoint* jpoint_to_ipoint(JNIEnv*, jobject jpoint, SkIPoint* point); diff --git a/libs/hwui/jni/Paint.cpp b/libs/hwui/jni/Paint.cpp index d84b73d1a1ca..58d9d8b9def3 100644 --- a/libs/hwui/jni/Paint.cpp +++ b/libs/hwui/jni/Paint.cpp @@ -114,7 +114,7 @@ namespace PaintGlue { std::unique_ptr<float[]> advancesArray(new float[count]); MinikinUtils::measureText(&paint, static_cast<minikin::Bidi>(bidiFlags), typeface, text, 0, - count, count, advancesArray.get(), nullptr); + count, count, advancesArray.get(), nullptr, nullptr); for (int i = 0; i < count; i++) { // traverse in the given direction @@ -206,7 +206,7 @@ namespace PaintGlue { } const float advance = MinikinUtils::measureText( paint, static_cast<minikin::Bidi>(bidiFlags), typeface, text, start, count, - contextCount, advancesArray.get(), nullptr); + contextCount, advancesArray.get(), nullptr, nullptr); if (advances) { env->SetFloatArrayRegion(advances, advancesIndex, count, advancesArray.get()); } @@ -244,7 +244,7 @@ namespace PaintGlue { minikin::Bidi bidiFlags = dir == 1 ? minikin::Bidi::FORCE_RTL : minikin::Bidi::FORCE_LTR; std::unique_ptr<float[]> advancesArray(new float[count]); MinikinUtils::measureText(paint, bidiFlags, typeface, text, start, count, start + count, - advancesArray.get(), nullptr); + advancesArray.get(), nullptr, nullptr); size_t result = minikin::GraphemeBreak::getTextRunCursor(advancesArray.get(), text, start, count, offset, moveOpt); return static_cast<jint>(result); @@ -508,7 +508,7 @@ namespace PaintGlue { static jfloat doRunAdvance(JNIEnv* env, const Paint* paint, const Typeface* typeface, const jchar buf[], jint start, jint count, jint bufSize, jboolean isRtl, jint offset, jfloatArray advances, - jint advancesIndex, SkRect* drawBounds) { + jint advancesIndex, SkRect* drawBounds, uint32_t* clusterCount) { if (advances) { size_t advancesLength = env->GetArrayLength(advances); if ((size_t)(count + advancesIndex) > advancesLength) { @@ -519,9 +519,9 @@ namespace PaintGlue { minikin::Bidi bidiFlags = isRtl ? minikin::Bidi::FORCE_RTL : minikin::Bidi::FORCE_LTR; minikin::MinikinRect bounds; if (offset == start + count && advances == nullptr) { - float result = - MinikinUtils::measureText(paint, bidiFlags, typeface, buf, start, count, - bufSize, nullptr, drawBounds ? &bounds : nullptr); + float result = MinikinUtils::measureText(paint, bidiFlags, typeface, buf, start, count, + bufSize, nullptr, + drawBounds ? &bounds : nullptr, clusterCount); if (drawBounds) { copyMinikinRectToSkRect(bounds, drawBounds); } @@ -529,7 +529,8 @@ namespace PaintGlue { } std::unique_ptr<float[]> advancesArray(new float[count]); MinikinUtils::measureText(paint, bidiFlags, typeface, buf, start, count, bufSize, - advancesArray.get(), drawBounds ? &bounds : nullptr); + advancesArray.get(), drawBounds ? &bounds : nullptr, + clusterCount); if (drawBounds) { copyMinikinRectToSkRect(bounds, drawBounds); @@ -549,7 +550,7 @@ namespace PaintGlue { ScopedCharArrayRO textArray(env, text); jfloat result = doRunAdvance(env, paint, typeface, textArray.get() + contextStart, start - contextStart, end - start, contextEnd - contextStart, - isRtl, offset - contextStart, nullptr, 0, nullptr); + isRtl, offset - contextStart, nullptr, 0, nullptr, nullptr); return result; } @@ -558,27 +559,41 @@ namespace PaintGlue { jint contextStart, jint contextEnd, jboolean isRtl, jint offset, jfloatArray advances, jint advancesIndex, - jobject drawBounds) { + jobject drawBounds, jobject runInfo) { const Paint* paint = reinterpret_cast<Paint*>(paintHandle); const Typeface* typeface = paint->getAndroidTypeface(); ScopedCharArrayRO textArray(env, text); SkRect skDrawBounds; + uint32_t clusterCount = 0; jfloat result = doRunAdvance(env, paint, typeface, textArray.get() + contextStart, start - contextStart, end - start, contextEnd - contextStart, isRtl, offset - contextStart, advances, advancesIndex, - drawBounds ? &skDrawBounds : nullptr); + drawBounds ? &skDrawBounds : nullptr, &clusterCount); if (drawBounds != nullptr) { GraphicsJNI::rect_to_jrectf(skDrawBounds, env, drawBounds); } + if (runInfo) { + GraphicsJNI::set_cluster_count_to_run_info(env, runInfo, clusterCount); + } return result; } + // This method is kept for old Robolectric JNI signature used by SystemUIGoogleRoboRNGTests. + static jfloat getRunCharacterAdvance___CIIIIZI_FI_F_ForRobolectric( + JNIEnv* env, jclass cls, jlong paintHandle, jcharArray text, jint start, jint end, + jint contextStart, jint contextEnd, jboolean isRtl, jint offset, jfloatArray advances, + jint advancesIndex, jobject drawBounds) { + return getRunCharacterAdvance___CIIIIZI_FI_F(env, cls, paintHandle, text, start, end, + contextStart, contextEnd, isRtl, offset, + advances, advancesIndex, drawBounds, nullptr); + } + static jint doOffsetForAdvance(const Paint* paint, const Typeface* typeface, const jchar buf[], jint start, jint count, jint bufSize, jboolean isRtl, jfloat advance) { minikin::Bidi bidiFlags = isRtl ? minikin::Bidi::FORCE_RTL : minikin::Bidi::FORCE_LTR; std::unique_ptr<float[]> advancesArray(new float[count]); MinikinUtils::measureText(paint, bidiFlags, typeface, buf, start, count, bufSize, - advancesArray.get(), nullptr); + advancesArray.get(), nullptr, nullptr); return minikin::getOffsetForAdvance(advancesArray.get(), buf, start, count, advance); } @@ -1145,8 +1160,11 @@ static const JNINativeMethod methods[] = { (void*)PaintGlue::getCharArrayBounds}, {"nHasGlyph", "(JILjava/lang/String;)Z", (void*)PaintGlue::hasGlyph}, {"nGetRunAdvance", "(J[CIIIIZI)F", (void*)PaintGlue::getRunAdvance___CIIIIZI_F}, - {"nGetRunCharacterAdvance", "(J[CIIIIZI[FILandroid/graphics/RectF;)F", + {"nGetRunCharacterAdvance", + "(J[CIIIIZI[FILandroid/graphics/RectF;Landroid/graphics/Paint$RunInfo;)F", (void*)PaintGlue::getRunCharacterAdvance___CIIIIZI_FI_F}, + {"nGetRunCharacterAdvance", "(J[CIIIIZI[FILandroid/graphics/RectF;)F", + (void*)PaintGlue::getRunCharacterAdvance___CIIIIZI_FI_F_ForRobolectric}, {"nGetOffsetForAdvance", "(J[CIIIIZF)I", (void*)PaintGlue::getOffsetForAdvance___CIIIIZF_I}, {"nGetFontMetricsIntForText", "(J[CIIIIZLandroid/graphics/Paint$FontMetricsInt;)V", (void*)PaintGlue::getFontMetricsIntForText___C}, diff --git a/libs/hwui/pipeline/skia/ATraceMemoryDump.cpp b/libs/hwui/pipeline/skia/ATraceMemoryDump.cpp index 234f42d79cb7..756b937f7de3 100644 --- a/libs/hwui/pipeline/skia/ATraceMemoryDump.cpp +++ b/libs/hwui/pipeline/skia/ATraceMemoryDump.cpp @@ -20,6 +20,8 @@ #include <cstring> +#include "GrDirectContext.h" + namespace android { namespace uirenderer { namespace skiapipeline { @@ -114,8 +116,16 @@ void ATraceMemoryDump::startFrame() { /** * logTraces reads from mCurrentValues and logs the counters with ATRACE. + * + * gpuMemoryIsAlreadyInDump must be true if GrDirectContext::dumpMemoryStatistics(...) was called + * with this tracer, false otherwise. Leaving this false allows this function to quickly query total + * and purgable GPU memory without the caller having to spend time in + * GrDirectContext::dumpMemoryStatistics(...) first, which iterates over every resource in the GPU + * cache. This can save significant time, but buckets all GPU memory into a single "misc" track, + * which may be a loss of granularity depending on the GPU backend and the categories defined in + * sResourceMap. */ -void ATraceMemoryDump::logTraces() { +void ATraceMemoryDump::logTraces(bool gpuMemoryIsAlreadyInDump, GrDirectContext* grContext) { // Accumulate data from last dumpName recordAndResetCountersIfNeeded(""); uint64_t hwui_all_frame_memory = 0; @@ -126,6 +136,20 @@ void ATraceMemoryDump::logTraces() { ATRACE_INT64((std::string("Purgeable ") + it.first).c_str(), it.second.purgeableMemory); } } + + if (!gpuMemoryIsAlreadyInDump && grContext) { + // Total GPU memory + int gpuResourceCount; + size_t gpuResourceBytes; + grContext->getResourceCacheUsage(&gpuResourceCount, &gpuResourceBytes); + hwui_all_frame_memory += (uint64_t)gpuResourceBytes; + ATRACE_INT64("HWUI Misc Memory", gpuResourceBytes); + + // Purgable subset of GPU memory + size_t purgeableGpuResourceBytes = grContext->getResourceCachePurgeableBytes(); + ATRACE_INT64("Purgeable HWUI Misc Memory", purgeableGpuResourceBytes); + } + ATRACE_INT64("HWUI All Memory", hwui_all_frame_memory); } diff --git a/libs/hwui/pipeline/skia/ATraceMemoryDump.h b/libs/hwui/pipeline/skia/ATraceMemoryDump.h index 4592711dd5b5..777d1a2ddb5b 100644 --- a/libs/hwui/pipeline/skia/ATraceMemoryDump.h +++ b/libs/hwui/pipeline/skia/ATraceMemoryDump.h @@ -16,6 +16,7 @@ #pragma once +#include <GrDirectContext.h> #include <SkString.h> #include <SkTraceMemoryDump.h> @@ -50,7 +51,7 @@ public: void startFrame(); - void logTraces(); + void logTraces(bool gpuMemoryIsAlreadyInDump, GrDirectContext* grContext); private: std::string mLastDumpName; diff --git a/libs/hwui/renderthread/CacheManager.cpp b/libs/hwui/renderthread/CacheManager.cpp index 30d461271c89..eb4d4948dc53 100644 --- a/libs/hwui/renderthread/CacheManager.cpp +++ b/libs/hwui/renderthread/CacheManager.cpp @@ -269,13 +269,14 @@ void CacheManager::onFrameCompleted() { cancelDestroyContext(); mFrameCompletions.next() = systemTime(CLOCK_MONOTONIC); if (ATRACE_ENABLED()) { + ATRACE_NAME("dumpingMemoryStatistics"); static skiapipeline::ATraceMemoryDump tracer; tracer.startFrame(); SkGraphics::DumpMemoryStatistics(&tracer); - if (mGrContext) { + if (mGrContext && Properties::debugTraceGpuResourceCategories) { mGrContext->dumpMemoryStatistics(&tracer); } - tracer.logTraces(); + tracer.logTraces(Properties::debugTraceGpuResourceCategories, mGrContext.get()); } } diff --git a/libs/hwui/tests/unit/UnderlineTest.cpp b/libs/hwui/tests/unit/UnderlineTest.cpp index c70a30477ecf..9911bfa70443 100644 --- a/libs/hwui/tests/unit/UnderlineTest.cpp +++ b/libs/hwui/tests/unit/UnderlineTest.cpp @@ -103,8 +103,9 @@ DrawTextFunctor processFunctor(const std::vector<uint16_t>& text, Paint* paint) // Create minikin::Layout std::unique_ptr<Typeface> typeface(makeTypeface()); minikin::Layout layout = doLayout(text, *paint, typeface.get()); + minikin::MinikinRect bounds; - DrawTextFunctor f(layout, &canvas, *paint, 0, 0, layout.getAdvance()); + DrawTextFunctor f(layout, &canvas, *paint, 0, 0, layout.getAdvance(), bounds); MinikinUtils::forFontRun(layout, paint, f); return f; } |