diff options
Diffstat (limited to 'libs')
236 files changed, 5887 insertions, 1756 deletions
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java index 438532725686..76eb207a31c9 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java @@ -16,8 +16,10 @@ package androidx.window.extensions.area; +import static android.hardware.devicestate.DeviceState.PROPERTY_EMULATED_ONLY; import static android.hardware.devicestate.DeviceState.PROPERTY_FEATURE_DUAL_DISPLAY_INTERNAL_DEFAULT; import static android.hardware.devicestate.DeviceState.PROPERTY_FEATURE_REAR_DISPLAY; +import static android.hardware.devicestate.DeviceState.PROPERTY_FEATURE_REAR_DISPLAY_OUTER_DEFAULT; import static android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY; import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE; import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE_IDENTIFIER; @@ -103,6 +105,30 @@ public class WindowAreaComponentImpl implements WindowAreaComponent, @GuardedBy("mLock") private int mLastReportedRearDisplayPresentationStatus; + @VisibleForTesting + static int getRdmV1Identifier(List<DeviceState> currentSupportedDeviceStates) { + for (int i = 0; i < currentSupportedDeviceStates.size(); i++) { + DeviceState state = currentSupportedDeviceStates.get(i); + if (state.hasProperty(PROPERTY_FEATURE_REAR_DISPLAY) + && !state.hasProperty(PROPERTY_FEATURE_REAR_DISPLAY_OUTER_DEFAULT)) { + return state.getIdentifier(); + } + } + return INVALID_DEVICE_STATE_IDENTIFIER; + } + + @VisibleForTesting + static int getRdmV2Identifier(List<DeviceState> currentSupportedDeviceStates) { + for (int i = 0; i < currentSupportedDeviceStates.size(); i++) { + DeviceState state = currentSupportedDeviceStates.get(i); + if (state.hasProperties(PROPERTY_FEATURE_REAR_DISPLAY, + PROPERTY_FEATURE_REAR_DISPLAY_OUTER_DEFAULT)) { + return state.getIdentifier(); + } + } + return INVALID_DEVICE_STATE_IDENTIFIER; + } + public WindowAreaComponentImpl(@NonNull Context context) { mDeviceStateManager = context.getSystemService(DeviceStateManager.class); mDisplayManager = context.getSystemService(DisplayManager.class); @@ -111,12 +137,10 @@ public class WindowAreaComponentImpl implements WindowAreaComponent, mCurrentSupportedDeviceStates = mDeviceStateManager.getSupportedDeviceStates(); if (Flags.deviceStatePropertyMigration()) { - for (int i = 0; i < mCurrentSupportedDeviceStates.size(); i++) { - DeviceState state = mCurrentSupportedDeviceStates.get(i); - if (state.hasProperty(PROPERTY_FEATURE_REAR_DISPLAY)) { - mRearDisplayState = state.getIdentifier(); - break; - } + if (Flags.deviceStateRdmV2()) { + mRearDisplayState = getRdmV2Identifier(mCurrentSupportedDeviceStates); + } else { + mRearDisplayState = getRdmV1Identifier(mCurrentSupportedDeviceStates); } } else { mFoldedDeviceStates = context.getResources().getIntArray( @@ -569,7 +593,8 @@ public class WindowAreaComponentImpl implements WindowAreaComponent, private boolean isDeviceFolded() { if (Flags.deviceStatePropertyApi()) { return mCurrentDeviceState.hasProperty( - PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY); + PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY) + && !mCurrentDeviceState.hasProperty(PROPERTY_EMULATED_ONLY); } else { return ArrayUtils.contains(mFoldedDeviceStates, mCurrentDeviceState.getIdentifier()); } diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/area/WindowAreaComponentImplTests.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/area/WindowAreaComponentImplTests.java index ccb4ebe9199e..d677fef5c22c 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/area/WindowAreaComponentImplTests.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/area/WindowAreaComponentImplTests.java @@ -16,8 +16,13 @@ package androidx.window.extensions.area; +import static android.hardware.devicestate.DeviceState.PROPERTY_FEATURE_REAR_DISPLAY; +import static android.hardware.devicestate.DeviceState.PROPERTY_FEATURE_REAR_DISPLAY_OUTER_DEFAULT; +import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE_IDENTIFIER; + import static org.junit.Assert.assertEquals; +import android.hardware.devicestate.DeviceState; import android.platform.test.annotations.Presubmit; import android.util.DisplayMetrics; import android.view.Surface; @@ -29,11 +34,34 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + @Presubmit @SmallTest @RunWith(AndroidJUnit4.class) public class WindowAreaComponentImplTests { + private static final DeviceState REAR_DISPLAY_STATE_V1 = new DeviceState( + new DeviceState.Configuration.Builder(1, "STATE_0") + .setSystemProperties( + Set.of(PROPERTY_FEATURE_REAR_DISPLAY)) + .build()); + private static final DeviceState REAR_DISPLAY_STATE_V2 = new DeviceState( + new DeviceState.Configuration.Builder(2, "STATE_0") + .setSystemProperties( + Set.of(PROPERTY_FEATURE_REAR_DISPLAY, + PROPERTY_FEATURE_REAR_DISPLAY_OUTER_DEFAULT)) + .build()); + // The PROPERTY_FEATURE_REAR_DISPLAY_OUTER_DEFAULT state must be present together with the + // PROPERTY_FEATURE_REAR_DISPLAY state in order to be a valid state. + private static final DeviceState INVALID_REAR_DISPLAY_STATE = new DeviceState( + new DeviceState.Configuration.Builder(2, "STATE_0") + .setSystemProperties( + Set.of(PROPERTY_FEATURE_REAR_DISPLAY_OUTER_DEFAULT)) + .build()); + private final DisplayMetrics mTestDisplayMetrics = new DisplayMetrics(); @Before @@ -93,4 +121,37 @@ public class WindowAreaComponentImplTests { Surface.ROTATION_270, Surface.ROTATION_0, mTestDisplayMetrics); assertEquals(expectedMetrics, mTestDisplayMetrics); } + + @Test + public void testRdmV1Identifier() { + final List<DeviceState> supportedStates = new ArrayList<>(); + supportedStates.add(REAR_DISPLAY_STATE_V2); + assertEquals(INVALID_DEVICE_STATE_IDENTIFIER, + WindowAreaComponentImpl.getRdmV1Identifier(supportedStates)); + + supportedStates.add(REAR_DISPLAY_STATE_V1); + assertEquals(REAR_DISPLAY_STATE_V1.getIdentifier(), + WindowAreaComponentImpl.getRdmV1Identifier(supportedStates)); + } + + @Test + public void testRdmV2Identifier_whenStateIsImproperlyConfigured() { + final List<DeviceState> supportedStates = new ArrayList<>(); + supportedStates.add(INVALID_REAR_DISPLAY_STATE); + assertEquals(INVALID_DEVICE_STATE_IDENTIFIER, + WindowAreaComponentImpl.getRdmV2Identifier(supportedStates)); + } + + @Test + public void testRdmV2Identifier_whenStateIsProperlyConfigured() { + final List<DeviceState> supportedStates = new ArrayList<>(); + + supportedStates.add(REAR_DISPLAY_STATE_V1); + assertEquals(INVALID_DEVICE_STATE_IDENTIFIER, + WindowAreaComponentImpl.getRdmV2Identifier(supportedStates)); + + supportedStates.add(REAR_DISPLAY_STATE_V2); + assertEquals(REAR_DISPLAY_STATE_V2.getIdentifier(), + WindowAreaComponentImpl.getRdmV2Identifier(supportedStates)); + } } diff --git a/libs/WindowManager/Shell/AndroidManifest.xml b/libs/WindowManager/Shell/AndroidManifest.xml index b2ac640a468d..636e3cfd571d 100644 --- a/libs/WindowManager/Shell/AndroidManifest.xml +++ b/libs/WindowManager/Shell/AndroidManifest.xml @@ -32,7 +32,6 @@ android:name=".desktopmode.DesktopWallpaperActivity" android:excludeFromRecents="true" android:launchMode="singleInstance" - android:showForAllUsers="true" android:theme="@style/DesktopWallpaperTheme" /> <activity diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt index 0b515f590f98..5f42bb161204 100644 --- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt +++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt @@ -475,6 +475,6 @@ class BubbleStackViewTest { override fun hideCurrentInputMethod() {} - override fun updateBubbleBarLocation(location: BubbleBarLocation) {} + override fun updateBubbleBarLocation(location: BubbleBarLocation, source: Int) {} } } diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt index 08d647de4a51..6ac36a3319c9 100644 --- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt +++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt @@ -47,6 +47,7 @@ import com.android.wm.shell.shared.handles.RegionSamplingHelper import com.android.wm.shell.taskview.TaskView import com.android.wm.shell.taskview.TaskViewTaskController import com.google.common.truth.Truth.assertThat +import com.google.common.truth.Truth.assertWithMessage import com.google.common.util.concurrent.MoreExecutors.directExecutor import java.util.Collections import java.util.concurrent.Executor @@ -147,6 +148,7 @@ class BubbleBarExpandedViewTest { @After fun tearDown() { testableRegionSamplingHelper?.stopAndDestroy() + getInstrumentation().waitForIdleSync() } @Test @@ -218,8 +220,8 @@ class BubbleBarExpandedViewTest { @Test fun testEventLogging_dismissBubbleViaAppMenu() { getInstrumentation().runOnMainSync { bubbleExpandedView.handleView.performClick() } - val dismissMenuItem = - bubbleExpandedView.findViewWithTag<View>(BubbleBarMenuView.DISMISS_ACTION_TAG) + val dismissMenuItem = bubbleExpandedView.menuView() + .actionViewWithText(context.getString(R.string.bubble_dismiss_text)) assertThat(dismissMenuItem).isNotNull() getInstrumentation().runOnMainSync { dismissMenuItem.performClick() } assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1) @@ -228,6 +230,42 @@ class BubbleBarExpandedViewTest { assertThat(uiEventLoggerFake.logs[0]).hasBubbleInfo(bubble) } + @Test + fun testEventLogging_openAppSettings() { + getInstrumentation().runOnMainSync { bubbleExpandedView.handleView.performClick() } + val appMenuItem = bubbleExpandedView.menuView() + .actionViewWithText(context.getString(R.string.bubbles_app_settings, bubble.appName)) + getInstrumentation().runOnMainSync { appMenuItem.performClick() } + assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1) + assertThat(uiEventLoggerFake.logs[0].eventId) + .isEqualTo(BubbleLogger.Event.BUBBLE_BAR_APP_MENU_GO_TO_SETTINGS.id) + assertThat(uiEventLoggerFake.logs[0]).hasBubbleInfo(bubble) + } + + @Test + fun testEventLogging_unBubbleConversation() { + getInstrumentation().runOnMainSync { bubbleExpandedView.handleView.performClick() } + val menuItem = bubbleExpandedView.menuView() + .actionViewWithText(context.getString(R.string.bubbles_dont_bubble_conversation)) + getInstrumentation().runOnMainSync { menuItem.performClick() } + assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1) + assertThat(uiEventLoggerFake.logs[0].eventId) + .isEqualTo(BubbleLogger.Event.BUBBLE_BAR_APP_MENU_OPT_OUT.id) + assertThat(uiEventLoggerFake.logs[0]).hasBubbleInfo(bubble) + } + + private fun BubbleBarExpandedView.menuView(): BubbleBarMenuView { + return findViewByPredicate { it is BubbleBarMenuView } + } + + private fun BubbleBarMenuView.actionViewWithText(text: CharSequence): View { + val views = ArrayList<View>() + findViewsWithText(views, text, View.FIND_VIEWS_WITH_TEXT) + assertWithMessage("Expecting a single action with text '$text'").that(views).hasSize(1) + // findViewsWithText returns the TextView, but the click listener is on the parent container + return views.first().parent as View + } + private inner class FakeBubbleTaskViewFactory : BubbleTaskViewFactory { override fun create(): BubbleTaskView { val taskViewTaskController = mock<TaskViewTaskController>() @@ -337,7 +375,7 @@ class BubbleBarExpandedViewTest { override fun hideCurrentInputMethod() { } - override fun updateBubbleBarLocation(location: BubbleBarLocation) { + override fun updateBubbleBarLocation(location: BubbleBarLocation, source: Int) { } } } diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt index 2fbf089d99d6..0044593ad228 100644 --- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt +++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt @@ -19,12 +19,15 @@ package com.android.wm.shell.bubbles.bar import android.app.ActivityManager import android.content.Context import android.content.pm.LauncherApps +import android.graphics.PointF import android.os.Handler import android.os.UserManager import android.view.IWindowManager import android.view.LayoutInflater +import android.view.MotionEvent import android.view.View import android.view.WindowManager +import androidx.core.animation.AnimatorTestRule import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest @@ -48,6 +51,7 @@ import com.android.wm.shell.bubbles.BubbleTaskViewFactory import com.android.wm.shell.bubbles.Bubbles.SysuiProxy import com.android.wm.shell.bubbles.FakeBubbleFactory import com.android.wm.shell.bubbles.UiEventSubject.Companion.assertThat +import com.android.wm.shell.bubbles.animation.AnimatableScaleMatrix import com.android.wm.shell.bubbles.properties.BubbleProperties import com.android.wm.shell.bubbles.storage.BubblePersistentRepository import com.android.wm.shell.common.DisplayController @@ -57,6 +61,7 @@ import com.android.wm.shell.common.ShellExecutor import com.android.wm.shell.common.SyncTransactionQueue import com.android.wm.shell.common.TaskStackListenerImpl import com.android.wm.shell.shared.TransactionPool +import com.android.wm.shell.shared.animation.PhysicsAnimatorTestUtils import com.android.wm.shell.shared.bubbles.BubbleBarLocation import com.android.wm.shell.sysui.ShellCommandHandler import com.android.wm.shell.sysui.ShellController @@ -66,8 +71,10 @@ import com.android.wm.shell.taskview.TaskViewTaskController import com.android.wm.shell.taskview.TaskViewTransitions import com.android.wm.shell.transition.Transitions import com.google.common.truth.Truth.assertThat +import org.junit.After import java.util.Collections import org.junit.Before +import org.junit.ClassRule import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mockito.mock @@ -79,18 +86,28 @@ import org.mockito.kotlin.whenever @RunWith(AndroidJUnit4::class) class BubbleBarLayerViewTest { + companion object { + @JvmField @ClassRule + val animatorTestRule: AnimatorTestRule = AnimatorTestRule() + } + private val context = ApplicationProvider.getApplicationContext<Context>() private lateinit var bubbleBarLayerView: BubbleBarLayerView private lateinit var uiEventLoggerFake: UiEventLoggerFake + private lateinit var bubbleController: BubbleController + + private lateinit var bubblePositioner: BubblePositioner + private lateinit var bubble: Bubble @Before fun setUp() { ProtoLog.REQUIRE_PROTOLOGTOOL = false ProtoLog.init() + PhysicsAnimatorTestUtils.prepareForTest() uiEventLoggerFake = UiEventLoggerFake() val bubbleLogger = BubbleLogger(uiEventLoggerFake) @@ -100,7 +117,7 @@ class BubbleBarLayerViewTest { val windowManager = context.getSystemService(WindowManager::class.java) - val bubblePositioner = BubblePositioner(context, windowManager) + bubblePositioner = BubblePositioner(context, windowManager) bubblePositioner.setShowingInBubbleBar(true) val bubbleData = @@ -113,7 +130,7 @@ class BubbleBarLayerViewTest { bgExecutor, ) - val bubbleController = + bubbleController = createBubbleController( bubbleData, windowManager, @@ -151,6 +168,12 @@ class BubbleBarLayerViewTest { bubble = FakeBubbleFactory.createChatBubbleWithViewInfo(context, viewInfo = viewInfo) } + @After + fun tearDown() { + PhysicsAnimatorTestUtils.tearDown() + getInstrumentation().waitForIdleSync() + } + private fun createBubbleController( bubbleData: BubbleData, windowManager: WindowManager?, @@ -224,6 +247,70 @@ class BubbleBarLayerViewTest { assertThat(uiEventLoggerFake.logs[0]).hasBubbleInfo(bubble) } + @Test + fun testEventLogging_dragExpandedViewLeft() { + bubblePositioner.bubbleBarLocation = BubbleBarLocation.RIGHT + + getInstrumentation().runOnMainSync { + bubbleBarLayerView.showExpandedView(bubble) + } + waitForExpandedViewAnimation() + + val handleView = bubbleBarLayerView.findViewById<View>(R.id.bubble_bar_handle_view) + assertThat(handleView).isNotNull() + + // Drag from right to left + handleView.dispatchTouchEvent(0L, MotionEvent.ACTION_DOWN, rightEdge()) + handleView.dispatchTouchEvent(10L, MotionEvent.ACTION_MOVE, leftEdge()) + handleView.dispatchTouchEvent(20L, MotionEvent.ACTION_UP, leftEdge()) + + assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1) + assertThat(uiEventLoggerFake.logs[0].eventId) + .isEqualTo(BubbleLogger.Event.BUBBLE_BAR_MOVED_LEFT_DRAG_EXP_VIEW.id) + assertThat(uiEventLoggerFake.logs[0]).hasBubbleInfo(bubble) + } + + @Test + fun testEventLogging_dragExpandedViewRight() { + bubblePositioner.bubbleBarLocation = BubbleBarLocation.LEFT + + getInstrumentation().runOnMainSync { + bubbleBarLayerView.showExpandedView(bubble) + } + waitForExpandedViewAnimation() + + val handleView = bubbleBarLayerView.findViewById<View>(R.id.bubble_bar_handle_view) + assertThat(handleView).isNotNull() + + // Drag from left to right + handleView.dispatchTouchEvent(0L, MotionEvent.ACTION_DOWN, leftEdge()) + handleView.dispatchTouchEvent(10L, MotionEvent.ACTION_MOVE, rightEdge()) + handleView.dispatchTouchEvent(20L, MotionEvent.ACTION_UP, rightEdge()) + + assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1) + assertThat(uiEventLoggerFake.logs[0].eventId) + .isEqualTo(BubbleLogger.Event.BUBBLE_BAR_MOVED_RIGHT_DRAG_EXP_VIEW.id) + assertThat(uiEventLoggerFake.logs[0]).hasBubbleInfo(bubble) + } + + private fun leftEdge(): PointF { + val screenSize = bubblePositioner.availableRect + return PointF(screenSize.left.toFloat(), screenSize.height() / 2f) + } + + private fun rightEdge(): PointF { + val screenSize = bubblePositioner.availableRect + return PointF(screenSize.right.toFloat(), screenSize.height() / 2f) + } + + private fun waitForExpandedViewAnimation() { + // wait for idle to allow the animation to start + getInstrumentation().waitForIdleSync() + getInstrumentation().runOnMainSync { animatorTestRule.advanceTimeBy(200) } + PhysicsAnimatorTestUtils.blockUntilAnimationsEnd( + AnimatableScaleMatrix.SCALE_X, AnimatableScaleMatrix.SCALE_Y) + } + private inner class FakeBubbleTaskViewFactory(private val mainExecutor: ShellExecutor) : BubbleTaskViewFactory { override fun create(): BubbleTaskView { @@ -264,7 +351,7 @@ class BubbleBarLayerViewTest { override fun hideCurrentInputMethod() {} - override fun updateBubbleBarLocation(location: BubbleBarLocation) {} + override fun updateBubbleBarLocation(location: BubbleBarLocation, source: Int) {} } } @@ -290,4 +377,9 @@ class BubbleBarLayerViewTest { } } } + + private fun View.dispatchTouchEvent(eventTime: Long, action: Int, point: PointF) { + val event = MotionEvent.obtain(0L, eventTime, action, point.x, point.y, 0) + getInstrumentation().runOnMainSync { dispatchTouchEvent(event) } + } } diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt index ecb2b25a02f1..d4cbe6e10971 100644 --- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt +++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt @@ -74,6 +74,7 @@ class BubbleExpandedViewPinControllerTest { @Before fun setUp() { ProtoLog.REQUIRE_PROTOLOGTOOL = false + ProtoLog.init() container = FrameLayout(context) val windowManager = context.getSystemService(WindowManager::class.java) positioner = BubblePositioner(context, windowManager) @@ -85,7 +86,7 @@ class BubbleExpandedViewPinControllerTest { isSmallTablet = false, isLandscape = true, isRtl = false, - insets = Insets.of(10, 20, 30, 40) + insets = Insets.of(10, 20, 30, 40), ) positioner.update(deviceConfig) positioner.bubbleBarTopOnScreen = @@ -407,12 +408,26 @@ class BubbleExpandedViewPinControllerTest { assertThat(testListener.locationReleases).containsExactly(RIGHT) } + /** Send drag start event when on left */ + @Test + fun start_onLeft_sendStartEventOnLeft() { + getInstrumentation().runOnMainSync { controller.onDragStart(initialLocationOnLeft = true) } + assertThat(testListener.locationStart).containsExactly(LEFT) + } + + /** Send drag start event when on right */ + @Test + fun start_onRight_sendStartEventOnRight() { + getInstrumentation().runOnMainSync { controller.onDragStart(initialLocationOnLeft = false) } + assertThat(testListener.locationStart).containsExactly(RIGHT) + } + private fun getExpectedDropTargetBoundsOnLeft(): Rect = Rect().also { positioner.getBubbleBarExpandedViewBounds( true /* onLeft */, false /* isOverflowExpanded */, - it + it, ) } @@ -421,7 +436,7 @@ class BubbleExpandedViewPinControllerTest { positioner.getBubbleBarExpandedViewBounds( false /* onLeft */, false /* isOverflowExpanded */, - it + it, ) } @@ -446,8 +461,14 @@ class BubbleExpandedViewPinControllerTest { } internal class TestLocationChangeListener : BaseBubblePinController.LocationChangeListener { + val locationStart = mutableListOf<BubbleBarLocation>() val locationChanges = mutableListOf<BubbleBarLocation>() val locationReleases = mutableListOf<BubbleBarLocation>() + + override fun onStart(location: BubbleBarLocation) { + locationStart.add(location) + } + override fun onChange(location: BubbleBarLocation) { locationChanges.add(location) } diff --git a/libs/WindowManager/Shell/res/layout/bubble_bar_expanded_view.xml b/libs/WindowManager/Shell/res/layout/bubble_bar_expanded_view.xml index 501bedd50f55..c2755ef6ccb6 100644 --- a/libs/WindowManager/Shell/res/layout/bubble_bar_expanded_view.xml +++ b/libs/WindowManager/Shell/res/layout/bubble_bar_expanded_view.xml @@ -19,6 +19,7 @@ android:layout_height="wrap_content" android:layout_width="wrap_content" android:orientation="vertical" + android:clipChildren="false" android:id="@+id/bubble_expanded_view"> <com.android.wm.shell.bubbles.bar.BubbleBarHandleView diff --git a/libs/WindowManager/Shell/res/layout/bubble_bar_menu_view.xml b/libs/WindowManager/Shell/res/layout/bubble_bar_menu_view.xml index f1ecde49ce78..7aca921dccc7 100644 --- a/libs/WindowManager/Shell/res/layout/bubble_bar_menu_view.xml +++ b/libs/WindowManager/Shell/res/layout/bubble_bar_menu_view.xml @@ -14,20 +14,18 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License --> -<com.android.wm.shell.bubbles.bar.BubbleBarMenuView - xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:app="http://schemas.android.com/apk/res-auto" +<com.android.wm.shell.bubbles.bar.BubbleBarMenuView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" + android:clipToPadding="false" android:minWidth="@dimen/bubble_bar_manage_menu_min_width" android:orientation="vertical" - android:elevation="@dimen/bubble_manage_menu_elevation" - android:paddingTop="@dimen/bubble_bar_manage_menu_padding_top" - android:paddingHorizontal="@dimen/bubble_bar_manage_menu_padding" - android:paddingBottom="@dimen/bubble_bar_manage_menu_padding" - android:clipToPadding="false"> + android:visibility="invisible" + tools:visibility="visible"> <LinearLayout android:id="@+id/bubble_bar_manage_menu_bubble_section" diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml index f90e165ffc74..a18a2510f0f7 100644 --- a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml +++ b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml @@ -168,7 +168,7 @@ </LinearLayout> <LinearLayout - android:id="@+id/open_in_browser_pill" + android:id="@+id/open_in_app_or_browser_pill" android:layout_width="match_parent" android:layout_height="@dimen/desktop_mode_handle_menu_open_in_browser_pill_height" android:layout_marginTop="@dimen/desktop_mode_handle_menu_pill_spacing_margin" @@ -178,7 +178,7 @@ android:background="@drawable/desktop_mode_decor_handle_menu_background"> <Button - android:id="@+id/open_in_browser_button" + android:id="@+id/open_in_app_or_browser_button" android:layout_weight="1" android:contentDescription="@string/open_in_browser_text" android:text="@string/open_in_browser_text" diff --git a/libs/WindowManager/Shell/res/values-af/strings.xml b/libs/WindowManager/Shell/res/values-af/strings.xml index 95c2bb59d202..a4aa3480fb46 100644 --- a/libs/WindowManager/Shell/res/values-af/strings.xml +++ b/libs/WindowManager/Shell/res/values-af/strings.xml @@ -127,16 +127,15 @@ <string name="open_in_browser_text" msgid="9181692926376072904">"Maak in blaaier oop"</string> <string name="new_window_text" msgid="6318648868380652280">"Nuwe venster"</string> <string name="manage_windows_text" msgid="5567366688493093920">"Bestuur vensters"</string> + <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Verander aspekverhouding"</string> <string name="close_text" msgid="4986518933445178928">"Maak toe"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Maak kieslys toe"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Maak kieslys oop"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maksimeer skerm"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Gryp skerm vas"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"App kan nie hierheen geskuif word nie"</string> - <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) --> - <skip /> - <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) --> - <skip /> + <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Meesleurend"</string> + <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Stel terug"</string> <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Maksimeer"</string> <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Stel terug"</string> <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Spring na links"</string> @@ -146,4 +145,5 @@ <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"In die app"</string> <string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"In jou blaaier"</string> <string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"OK"</string> + <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Maak hier apps vinnig in jou blaaier oop"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-am/strings.xml b/libs/WindowManager/Shell/res/values-am/strings.xml index ba74e342f353..1cd980460cee 100644 --- a/libs/WindowManager/Shell/res/values-am/strings.xml +++ b/libs/WindowManager/Shell/res/values-am/strings.xml @@ -127,16 +127,15 @@ <string name="open_in_browser_text" msgid="9181692926376072904">"በአሳሽ ውስጥ ክፈት"</string> <string name="new_window_text" msgid="6318648868380652280">"አዲስ መስኮት"</string> <string name="manage_windows_text" msgid="5567366688493093920">"መስኮቶችን አስተዳድር"</string> + <string name="change_aspect_ratio_text" msgid="9104456064548212806">"ምጥጥነ ገፅታ ለውጥ"</string> <string name="close_text" msgid="4986518933445178928">"ዝጋ"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"ምናሌ ዝጋ"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"ምናሌን ክፈት"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"የማያ ገጹ መጠን አሳድግ"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"ማያ ገጹን አሳድግ"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"መተግበሪያ ወደዚህ መንቀሳቀስ አይችልም"</string> - <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) --> - <skip /> - <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) --> - <skip /> + <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"አስማጭ"</string> + <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"ወደነበረበት መልስ"</string> <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"አሳድግ"</string> <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"ወደነበረበት መልስ"</string> <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"ወደ ግራ አሳድግ"</string> @@ -146,4 +145,5 @@ <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"በመተግበሪያው ውስጥ"</string> <string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"በአሳሽዎ ውስጥ"</string> <string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"እሺ"</string> + <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"እዚህ አሳሽዎ ውስጥ መተግበሪያዎችን በፍጥነት ይክፈቱ"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-ar/strings.xml b/libs/WindowManager/Shell/res/values-ar/strings.xml index a8febc80ffc1..41ebfcd0ee85 100644 --- a/libs/WindowManager/Shell/res/values-ar/strings.xml +++ b/libs/WindowManager/Shell/res/values-ar/strings.xml @@ -127,6 +127,7 @@ <string name="open_in_browser_text" msgid="9181692926376072904">"فتح في المتصفِّح"</string> <string name="new_window_text" msgid="6318648868380652280">"نافذة جديدة"</string> <string name="manage_windows_text" msgid="5567366688493093920">"إدارة النوافذ"</string> + <string name="change_aspect_ratio_text" msgid="9104456064548212806">"تغيير نسبة العرض إلى الارتفاع"</string> <string name="close_text" msgid="4986518933445178928">"إغلاق"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"إغلاق القائمة"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"فتح القائمة"</string> @@ -144,4 +145,5 @@ <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"في التطبيق"</string> <string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"في المتصفِّح"</string> <string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"حسنًا"</string> + <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"فتح التطبيقات بسرعة في المتصفِّح هنا"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-as/strings.xml b/libs/WindowManager/Shell/res/values-as/strings.xml index 8c924e342875..203fed0aecef 100644 --- a/libs/WindowManager/Shell/res/values-as/strings.xml +++ b/libs/WindowManager/Shell/res/values-as/strings.xml @@ -127,6 +127,7 @@ <string name="open_in_browser_text" msgid="9181692926376072904">"ব্ৰাউজাৰত খোলক"</string> <string name="new_window_text" msgid="6318648868380652280">"নতুন ৱিণ্ড’"</string> <string name="manage_windows_text" msgid="5567366688493093920">"ৱিণ্ড’ পৰিচালনা কৰক"</string> + <string name="change_aspect_ratio_text" msgid="9104456064548212806">"আকাৰৰ অনুপাত সলনি কৰক"</string> <string name="close_text" msgid="4986518933445178928">"বন্ধ কৰক"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"মেনু বন্ধ কৰক"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"মেনু খোলক"</string> @@ -144,4 +145,5 @@ <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"এপ্টোত"</string> <string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"আপোনাৰ ব্ৰাউজাৰত"</string> <string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"ঠিক আছে"</string> + <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"ইয়াত আপোনাৰ ব্ৰাউজাৰত ক্ষিপ্ৰভাৱে এপ্ খোলক"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-az/strings.xml b/libs/WindowManager/Shell/res/values-az/strings.xml index aa232e330604..31ddc9b78b68 100644 --- a/libs/WindowManager/Shell/res/values-az/strings.xml +++ b/libs/WindowManager/Shell/res/values-az/strings.xml @@ -127,16 +127,15 @@ <string name="open_in_browser_text" msgid="9181692926376072904">"Brauzerdə açın"</string> <string name="new_window_text" msgid="6318648868380652280">"Yeni pəncərə"</string> <string name="manage_windows_text" msgid="5567366688493093920">"Pəncərələri idarə edin"</string> + <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Tərəflər nisbətini dəyişin"</string> <string name="close_text" msgid="4986518933445178928">"Bağlayın"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Menyunu bağlayın"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Menyunu açın"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Ekranı maksimum böyüdün"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Ekranı çəkin"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Tətbiqi bura köçürmək mümkün deyil"</string> - <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) --> - <skip /> - <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) --> - <skip /> + <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"İmmersiv"</string> + <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Bərpa edin"</string> <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Böyüdün"</string> <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Bərpa edin"</string> <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Sola tərəf çəkin"</string> @@ -146,4 +145,5 @@ <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Tətbiqdə"</string> <string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"Brauzerinizdə"</string> <string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"OK"</string> + <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Brauzerinizdəki tətbiqləri burada sürətlə açın"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml b/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml index 256344a4cb31..486b3cfbfee4 100644 --- a/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml +++ b/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml @@ -127,16 +127,15 @@ <string name="open_in_browser_text" msgid="9181692926376072904">"Otvorite u pregledaču"</string> <string name="new_window_text" msgid="6318648868380652280">"Novi prozor"</string> <string name="manage_windows_text" msgid="5567366688493093920">"Upravljajte prozorima"</string> + <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Promenite razmeru"</string> <string name="close_text" msgid="4986518933445178928">"Zatvorite"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Zatvorite meni"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Otvorite meni"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Povećaj ekran"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Uklopi ekran"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Aplikacija ne može da se premesti ovde"</string> - <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) --> - <skip /> - <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) --> - <skip /> + <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Imerzivne"</string> + <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Vrati"</string> <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Uvećajte"</string> <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Vratite"</string> <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Prikačite levo"</string> @@ -146,4 +145,5 @@ <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"U aplikaciji"</string> <string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"U pregledaču"</string> <string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"Potvrdi"</string> + <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Ovde možete brzo da otvarate aplikacije u pregledaču"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-be/strings.xml b/libs/WindowManager/Shell/res/values-be/strings.xml index 701c51091aa4..cc42da947c36 100644 --- a/libs/WindowManager/Shell/res/values-be/strings.xml +++ b/libs/WindowManager/Shell/res/values-be/strings.xml @@ -127,16 +127,15 @@ <string name="open_in_browser_text" msgid="9181692926376072904">"Адкрыць у браўзеры"</string> <string name="new_window_text" msgid="6318648868380652280">"Новае акно"</string> <string name="manage_windows_text" msgid="5567366688493093920">"Кіраваць вокнамі"</string> + <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Змяніць суадносіны бакоў"</string> <string name="close_text" msgid="4986518933445178928">"Закрыць"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Закрыць меню"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Адкрыць меню"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Разгарнуць на ўвесь экран"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Размясціць на палавіне экрана"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Нельга перамясціць сюды праграму"</string> - <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) --> - <skip /> - <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) --> - <skip /> + <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"З эфектам прысутнасці"</string> + <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Аднавіць"</string> <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Разгарнуць"</string> <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Аднавіць"</string> <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Размясціць злева"</string> @@ -146,4 +145,5 @@ <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"У праграме"</string> <string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"У браўзеры"</string> <string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"ОК"</string> + <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Тут можна хутка адкрываць праграмы ў браўзеры"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-bg/strings.xml b/libs/WindowManager/Shell/res/values-bg/strings.xml index 9ab86f4cbc56..c12b37b34d5a 100644 --- a/libs/WindowManager/Shell/res/values-bg/strings.xml +++ b/libs/WindowManager/Shell/res/values-bg/strings.xml @@ -127,16 +127,15 @@ <string name="open_in_browser_text" msgid="9181692926376072904">"Отваряне в браузър"</string> <string name="new_window_text" msgid="6318648868380652280">"Нов прозорец"</string> <string name="manage_windows_text" msgid="5567366688493093920">"Управление на прозорците"</string> + <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Промяна на съотношението"</string> <string name="close_text" msgid="4986518933445178928">"Затваряне"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Затваряне на менюто"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Отваряне на менюто"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Увеличаване на екрана"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Прилепване на екрана"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Приложението не може да бъде преместено тук"</string> - <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) --> - <skip /> - <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) --> - <skip /> + <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Реалистично"</string> + <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Възстановяване"</string> <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Увеличаване"</string> <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Възстановяване"</string> <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Прилепване наляво"</string> @@ -146,4 +145,5 @@ <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"В приложението"</string> <string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"В браузъра ви"</string> <string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"OK"</string> + <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Бързо отваряйте приложения в браузъра си оттук"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-bn/strings.xml b/libs/WindowManager/Shell/res/values-bn/strings.xml index 22a445f1754c..aca5b34ae4c0 100644 --- a/libs/WindowManager/Shell/res/values-bn/strings.xml +++ b/libs/WindowManager/Shell/res/values-bn/strings.xml @@ -127,6 +127,7 @@ <string name="open_in_browser_text" msgid="9181692926376072904">"ব্রাউজারে খুলুন"</string> <string name="new_window_text" msgid="6318648868380652280">"নতুন উইন্ডো"</string> <string name="manage_windows_text" msgid="5567366688493093920">"উইন্ডো ম্যানেজ করুন"</string> + <string name="change_aspect_ratio_text" msgid="9104456064548212806">"অ্যাস্পেক্ট রেশিও পরিবর্তন করুন"</string> <string name="close_text" msgid="4986518933445178928">"বন্ধ করুন"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"\'মেনু\' বন্ধ করুন"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"মেনু খুলুন"</string> @@ -144,4 +145,5 @@ <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"অ্যাপের মধ্যে"</string> <string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"আপনার ব্রাউজারে"</string> <string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"ঠিক আছে"</string> + <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"এখানে আপনার ব্রাউজারে দ্রুত অ্যাপ খুলুন"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-bs/strings.xml b/libs/WindowManager/Shell/res/values-bs/strings.xml index 73f30d797883..6bd6473a5f13 100644 --- a/libs/WindowManager/Shell/res/values-bs/strings.xml +++ b/libs/WindowManager/Shell/res/values-bs/strings.xml @@ -127,14 +127,15 @@ <string name="open_in_browser_text" msgid="9181692926376072904">"Otvaranje u pregledniku"</string> <string name="new_window_text" msgid="6318648868380652280">"Novi prozor"</string> <string name="manage_windows_text" msgid="5567366688493093920">"Upravljanje prozorima"</string> + <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Promjena formata slike"</string> <string name="close_text" msgid="4986518933445178928">"Zatvaranje"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Zatvaranje menija"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Otvaranje menija"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maksimiziraj ekran"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Snimi ekran"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Ne možete premjestiti aplikaciju ovdje"</string> - <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Interaktivno"</string> - <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Vrati"</string> + <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Uvjerljivo"</string> + <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Vraćanje"</string> <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Maksimiziranje"</string> <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Vraćanje"</string> <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Pomicanje ulijevo"</string> @@ -144,4 +145,5 @@ <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"U aplikaciji"</string> <string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"U pregledniku"</string> <string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"Uredu"</string> + <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Ovdje možete brzo otvarati aplikacije u pregledniku"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-ca/strings.xml b/libs/WindowManager/Shell/res/values-ca/strings.xml index 499ed329e511..d9ad5a68d163 100644 --- a/libs/WindowManager/Shell/res/values-ca/strings.xml +++ b/libs/WindowManager/Shell/res/values-ca/strings.xml @@ -127,16 +127,15 @@ <string name="open_in_browser_text" msgid="9181692926376072904">"Obre al navegador"</string> <string name="new_window_text" msgid="6318648868380652280">"Finestra nova"</string> <string name="manage_windows_text" msgid="5567366688493093920">"Gestiona les finestres"</string> + <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Canvia la relació d\'aspecte"</string> <string name="close_text" msgid="4986518933445178928">"Tanca"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Tanca el menú"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Obre el menú"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximitza la pantalla"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Ajusta la pantalla"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"L\'aplicació no es pot moure aquí"</string> - <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) --> - <skip /> - <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) --> - <skip /> + <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Immersiu"</string> + <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Restaura"</string> <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Maximitza"</string> <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Restaura"</string> <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Ajusta a l\'esquerra"</string> @@ -146,4 +145,5 @@ <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"A l\'aplicació"</string> <string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"Al navegador"</string> <string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"D\'acord"</string> + <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Obre ràpidament aplicacions al navegador aquí"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-cs/strings.xml b/libs/WindowManager/Shell/res/values-cs/strings.xml index 6a5780e01822..ab51b666cdda 100644 --- a/libs/WindowManager/Shell/res/values-cs/strings.xml +++ b/libs/WindowManager/Shell/res/values-cs/strings.xml @@ -127,6 +127,7 @@ <string name="open_in_browser_text" msgid="9181692926376072904">"Otevřít v prohlížeči"</string> <string name="new_window_text" msgid="6318648868380652280">"Nové okno"</string> <string name="manage_windows_text" msgid="5567366688493093920">"Spravovat okna"</string> + <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Změnit poměr stran"</string> <string name="close_text" msgid="4986518933445178928">"Zavřít"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Zavřít nabídku"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Otevřít nabídku"</string> @@ -144,4 +145,5 @@ <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"V aplikaci"</string> <string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"V prohlížeči"</string> <string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"OK"</string> + <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Odtud můžete v prohlížeči rychle otevírat aplikace"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-da/strings.xml b/libs/WindowManager/Shell/res/values-da/strings.xml index 430cf96cd72f..443620804e10 100644 --- a/libs/WindowManager/Shell/res/values-da/strings.xml +++ b/libs/WindowManager/Shell/res/values-da/strings.xml @@ -127,16 +127,15 @@ <string name="open_in_browser_text" msgid="9181692926376072904">"Åbn i browser"</string> <string name="new_window_text" msgid="6318648868380652280">"Nyt vindue"</string> <string name="manage_windows_text" msgid="5567366688493093920">"Administrer vinduer"</string> + <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Skift billedformat"</string> <string name="close_text" msgid="4986518933445178928">"Luk"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Luk menu"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Åbn menu"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maksimér skærm"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Tilpas skærm"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Apps kan ikke flyttes hertil"</string> - <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) --> - <skip /> - <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) --> - <skip /> + <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Opslugende"</string> + <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Gendan"</string> <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Maksimér"</string> <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Gendan"</string> <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Fastgør til venstre"</string> @@ -146,4 +145,5 @@ <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"I appen"</string> <string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"I din browser"</string> <string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"OK"</string> + <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Åbn hurtigt apps i din browser her"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-de/strings.xml b/libs/WindowManager/Shell/res/values-de/strings.xml index cafaa89f57e3..b6e89c0eeb8e 100644 --- a/libs/WindowManager/Shell/res/values-de/strings.xml +++ b/libs/WindowManager/Shell/res/values-de/strings.xml @@ -127,16 +127,15 @@ <string name="open_in_browser_text" msgid="9181692926376072904">"Im Browser öffnen"</string> <string name="new_window_text" msgid="6318648868380652280">"Neues Fenster"</string> <string name="manage_windows_text" msgid="5567366688493093920">"Fenster verwalten"</string> + <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Seitenverhältnis ändern"</string> <string name="close_text" msgid="4986518933445178928">"Schließen"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Menü schließen"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Menü öffnen"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Bildschirm maximieren"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Bildschirm teilen"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Die App kann nicht hierher verschoben werden"</string> - <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) --> - <skip /> - <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) --> - <skip /> + <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Immersiv"</string> + <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Wiederherstellen"</string> <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Maximieren"</string> <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Wiederherstellen"</string> <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Links andocken"</string> @@ -146,4 +145,5 @@ <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"In der App"</string> <string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"In deinem Browser"</string> <string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"Ok"</string> + <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Hier kannst du Apps schnell in deinem Browser öffnen"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-el/strings.xml b/libs/WindowManager/Shell/res/values-el/strings.xml index d02fae2a986d..601c0ceee27f 100644 --- a/libs/WindowManager/Shell/res/values-el/strings.xml +++ b/libs/WindowManager/Shell/res/values-el/strings.xml @@ -127,6 +127,7 @@ <string name="open_in_browser_text" msgid="9181692926376072904">"Άνοιγμα σε πρόγραμμα περιήγησης"</string> <string name="new_window_text" msgid="6318648868380652280">"Νέο παράθυρο"</string> <string name="manage_windows_text" msgid="5567366688493093920">"Διαχείριση παραθύρων"</string> + <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Αλλαγή λόγου διαστάσεων"</string> <string name="close_text" msgid="4986518933445178928">"Κλείσιμο"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Κλείσιμο μενού"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Άνοιγμα μενού"</string> @@ -144,4 +145,5 @@ <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Στην εφαρμογή"</string> <string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"Στο πρόγραμμα περιήγησής σας"</string> <string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"ΟΚ"</string> + <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Ανοίξτε γρήγορα εφαρμογές στο πρόγραμμα περιήγησής σας εδώ"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-en-rAU/strings.xml b/libs/WindowManager/Shell/res/values-en-rAU/strings.xml index f9911451f4b5..fd6317530109 100644 --- a/libs/WindowManager/Shell/res/values-en-rAU/strings.xml +++ b/libs/WindowManager/Shell/res/values-en-rAU/strings.xml @@ -127,6 +127,7 @@ <string name="open_in_browser_text" msgid="9181692926376072904">"Open in browser"</string> <string name="new_window_text" msgid="6318648868380652280">"New window"</string> <string name="manage_windows_text" msgid="5567366688493093920">"Manage windows"</string> + <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Change aspect ratio"</string> <string name="close_text" msgid="4986518933445178928">"Close"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Close menu"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Open menu"</string> @@ -144,4 +145,5 @@ <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"In the app"</string> <string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"In your browser"</string> <string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"OK"</string> + <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Quickly open apps in your browser here"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-en-rCA/strings.xml b/libs/WindowManager/Shell/res/values-en-rCA/strings.xml index 2d123ec3a3d4..dac1b9a1460d 100644 --- a/libs/WindowManager/Shell/res/values-en-rCA/strings.xml +++ b/libs/WindowManager/Shell/res/values-en-rCA/strings.xml @@ -127,6 +127,7 @@ <string name="open_in_browser_text" msgid="9181692926376072904">"Open in browser"</string> <string name="new_window_text" msgid="6318648868380652280">"New Window"</string> <string name="manage_windows_text" msgid="5567366688493093920">"Manage Windows"</string> + <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Change aspect ratio"</string> <string name="close_text" msgid="4986518933445178928">"Close"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Close Menu"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Open Menu"</string> @@ -144,4 +145,5 @@ <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"In the app"</string> <string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"In your browser"</string> <string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"OK"</string> + <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Quickly open apps in your browser here"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-en-rGB/strings.xml b/libs/WindowManager/Shell/res/values-en-rGB/strings.xml index f9911451f4b5..fd6317530109 100644 --- a/libs/WindowManager/Shell/res/values-en-rGB/strings.xml +++ b/libs/WindowManager/Shell/res/values-en-rGB/strings.xml @@ -127,6 +127,7 @@ <string name="open_in_browser_text" msgid="9181692926376072904">"Open in browser"</string> <string name="new_window_text" msgid="6318648868380652280">"New window"</string> <string name="manage_windows_text" msgid="5567366688493093920">"Manage windows"</string> + <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Change aspect ratio"</string> <string name="close_text" msgid="4986518933445178928">"Close"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Close menu"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Open menu"</string> @@ -144,4 +145,5 @@ <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"In the app"</string> <string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"In your browser"</string> <string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"OK"</string> + <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Quickly open apps in your browser here"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-en-rIN/strings.xml b/libs/WindowManager/Shell/res/values-en-rIN/strings.xml index f9911451f4b5..fd6317530109 100644 --- a/libs/WindowManager/Shell/res/values-en-rIN/strings.xml +++ b/libs/WindowManager/Shell/res/values-en-rIN/strings.xml @@ -127,6 +127,7 @@ <string name="open_in_browser_text" msgid="9181692926376072904">"Open in browser"</string> <string name="new_window_text" msgid="6318648868380652280">"New window"</string> <string name="manage_windows_text" msgid="5567366688493093920">"Manage windows"</string> + <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Change aspect ratio"</string> <string name="close_text" msgid="4986518933445178928">"Close"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Close menu"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Open menu"</string> @@ -144,4 +145,5 @@ <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"In the app"</string> <string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"In your browser"</string> <string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"OK"</string> + <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Quickly open apps in your browser here"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-es-rUS/strings.xml b/libs/WindowManager/Shell/res/values-es-rUS/strings.xml index 210b708b49af..e67fc8e2c63c 100644 --- a/libs/WindowManager/Shell/res/values-es-rUS/strings.xml +++ b/libs/WindowManager/Shell/res/values-es-rUS/strings.xml @@ -127,16 +127,15 @@ <string name="open_in_browser_text" msgid="9181692926376072904">"Abrir en el navegador"</string> <string name="new_window_text" msgid="6318648868380652280">"Nueva ventana"</string> <string name="manage_windows_text" msgid="5567366688493093920">"Administrar ventanas"</string> + <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Cambiar relación de aspecto"</string> <string name="close_text" msgid="4986518933445178928">"Cerrar"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Cerrar menú"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Abrir el menú"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximizar pantalla"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Ajustar pantalla"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"No se puede mover la app aquí"</string> - <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) --> - <skip /> - <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) --> - <skip /> + <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Inmersivo"</string> + <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Restablecer"</string> <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Maximizar"</string> <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Restablecer"</string> <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Ajustar a la izquierda"</string> @@ -146,4 +145,5 @@ <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"En la app"</string> <string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"En un navegador"</string> <string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"Aceptar"</string> + <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Abre rápidamente apps en tu navegador aquí"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-es/strings.xml b/libs/WindowManager/Shell/res/values-es/strings.xml index 3c7bfe5a3cb4..2f5ec64be629 100644 --- a/libs/WindowManager/Shell/res/values-es/strings.xml +++ b/libs/WindowManager/Shell/res/values-es/strings.xml @@ -127,16 +127,15 @@ <string name="open_in_browser_text" msgid="9181692926376072904">"Abrir en el navegador"</string> <string name="new_window_text" msgid="6318648868380652280">"Ventana nueva"</string> <string name="manage_windows_text" msgid="5567366688493093920">"Gestionar ventanas"</string> + <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Cambiar relación de aspecto"</string> <string name="close_text" msgid="4986518933445178928">"Cerrar"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Cerrar menú"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Abrir menú"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximizar pantalla"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Ajustar pantalla"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"La aplicación no se puede mover aquí"</string> - <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) --> - <skip /> - <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) --> - <skip /> + <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Inmersivo"</string> + <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Restaurar"</string> <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Maximizar"</string> <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Restaurar"</string> <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Acoplar a la izquierda"</string> @@ -146,4 +145,5 @@ <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"En la aplicación"</string> <string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"En el navegador"</string> <string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"Aceptar"</string> + <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Abre rápidamente aplicaciones en tu navegador aquí"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-et/strings.xml b/libs/WindowManager/Shell/res/values-et/strings.xml index d17ee02a3a7f..dd78628093d4 100644 --- a/libs/WindowManager/Shell/res/values-et/strings.xml +++ b/libs/WindowManager/Shell/res/values-et/strings.xml @@ -127,16 +127,15 @@ <string name="open_in_browser_text" msgid="9181692926376072904">"Avamine brauseris"</string> <string name="new_window_text" msgid="6318648868380652280">"Uus aken"</string> <string name="manage_windows_text" msgid="5567366688493093920">"Akende haldamine"</string> + <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Kuvasuhte muutmine"</string> <string name="close_text" msgid="4986518933445178928">"Sule"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Sule menüü"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Ava menüü"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Kuva täisekraanil"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Kuva poolel ekraanil"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Rakendust ei saa siia teisaldada"</string> - <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) --> - <skip /> - <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) --> - <skip /> + <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Kaasahaarav"</string> + <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Taasta"</string> <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Maksimeeri"</string> <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Taasta"</string> <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Tõmmake vasakule"</string> @@ -146,4 +145,5 @@ <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Rakenduses"</string> <string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"Brauseris"</string> <string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"OK"</string> + <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Avage rakendusi kiiresti oma brauseris siin"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-eu/strings.xml b/libs/WindowManager/Shell/res/values-eu/strings.xml index f9419bc4614b..1cfc69457ce9 100644 --- a/libs/WindowManager/Shell/res/values-eu/strings.xml +++ b/libs/WindowManager/Shell/res/values-eu/strings.xml @@ -127,16 +127,15 @@ <string name="open_in_browser_text" msgid="9181692926376072904">"Ireki arakatzailean"</string> <string name="new_window_text" msgid="6318648868380652280">"Leiho berria"</string> <string name="manage_windows_text" msgid="5567366688493093920">"Kudeatu leihoak"</string> + <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Aldatu aspektu-erlazioa"</string> <string name="close_text" msgid="4986518933445178928">"Itxi"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Itxi menua"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Ireki menua"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Handitu pantaila"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Zatitu pantaila"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Aplikazioa ezin da hona ekarri"</string> - <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) --> - <skip /> - <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) --> - <skip /> + <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Murgiltzailea"</string> + <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Leheneratu"</string> <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Maximizatu"</string> <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Leheneratu"</string> <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Ezarri ezkerrean"</string> @@ -146,4 +145,5 @@ <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Aplikazioan"</string> <string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"Arakatzailean"</string> <string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"Ados"</string> + <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Ireki aplikazioak arakatzailean bizkor"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-fa/strings.xml b/libs/WindowManager/Shell/res/values-fa/strings.xml index a3d3cbc872fd..f76f67d2e796 100644 --- a/libs/WindowManager/Shell/res/values-fa/strings.xml +++ b/libs/WindowManager/Shell/res/values-fa/strings.xml @@ -127,16 +127,15 @@ <string name="open_in_browser_text" msgid="9181692926376072904">"باز کردن در مرورگر"</string> <string name="new_window_text" msgid="6318648868380652280">"پنجره جدید"</string> <string name="manage_windows_text" msgid="5567366688493093920">"مدیریت کردن پنجرهها"</string> + <string name="change_aspect_ratio_text" msgid="9104456064548212806">"تغییر نسبت ابعادی"</string> <string name="close_text" msgid="4986518933445178928">"بستن"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"بستن منو"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"باز کردن منو"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"بزرگ کردن صفحه"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"بزرگ کردن صفحه"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"برنامه را نمیتوان به اینجا منتقل کرد"</string> - <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) --> - <skip /> - <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) --> - <skip /> + <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"فراگیر"</string> + <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"بازیابی"</string> <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"بزرگ کردن"</string> <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"بازیابی کردن"</string> <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"کشیدن بهچپ"</string> @@ -146,4 +145,5 @@ <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"در برنامه"</string> <string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"در مرورگر"</string> <string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"تأیید"</string> + <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"در اینجا سریع برنامهها را در مرورگرتان باز کنید"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-fi/strings.xml b/libs/WindowManager/Shell/res/values-fi/strings.xml index ee5dd6516098..a1ec0150ffae 100644 --- a/libs/WindowManager/Shell/res/values-fi/strings.xml +++ b/libs/WindowManager/Shell/res/values-fi/strings.xml @@ -127,16 +127,15 @@ <string name="open_in_browser_text" msgid="9181692926376072904">"Avaa selaimessa"</string> <string name="new_window_text" msgid="6318648868380652280">"Uusi ikkuna"</string> <string name="manage_windows_text" msgid="5567366688493093920">"Hallinnoi ikkunoita"</string> + <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Vaihda kuvasuhdetta"</string> <string name="close_text" msgid="4986518933445178928">"Sulje"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Sulje valikko"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Avaa valikko"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Suurenna näyttö"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Jaa näyttö"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Sovellusta ei voi siirtää tänne"</string> - <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) --> - <skip /> - <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) --> - <skip /> + <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Immersiivinen"</string> + <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Palauta"</string> <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Suurenna"</string> <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Palauta"</string> <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Siirrä vasemmalle"</string> @@ -146,4 +145,5 @@ <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Sovelluksessa"</string> <string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"Selaimella"</string> <string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"OK"</string> + <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Avaa sovellukset nopeasti selaimessa täältä"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml b/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml index dc4789169146..1b9b74a45671 100644 --- a/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml +++ b/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml @@ -127,16 +127,15 @@ <string name="open_in_browser_text" msgid="9181692926376072904">"Ouvrir dans le navigateur"</string> <string name="new_window_text" msgid="6318648868380652280">"Nouvelle fenêtre"</string> <string name="manage_windows_text" msgid="5567366688493093920">"Gérer les fenêtres"</string> + <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Modifier les proportions"</string> <string name="close_text" msgid="4986518933445178928">"Fermer"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Fermer le menu"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Ouvrir le menu"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Agrandir l\'écran"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Aligner l\'écran"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Impossible de déplacer l\'appli ici"</string> - <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) --> - <skip /> - <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) --> - <skip /> + <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Immersif"</string> + <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Restaurer"</string> <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Agrandir"</string> <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Restaurer"</string> <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Épingler à gauche"</string> @@ -146,4 +145,5 @@ <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Dans l\'appli"</string> <string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"Dans votre navigateur"</string> <string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"OK"</string> + <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Ouvrez rapidement des applis dans votre navigateur ici"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-fr/strings.xml b/libs/WindowManager/Shell/res/values-fr/strings.xml index a52ab49da3ab..7e0a0b1acb7b 100644 --- a/libs/WindowManager/Shell/res/values-fr/strings.xml +++ b/libs/WindowManager/Shell/res/values-fr/strings.xml @@ -127,16 +127,15 @@ <string name="open_in_browser_text" msgid="9181692926376072904">"Ouvrir dans un navigateur"</string> <string name="new_window_text" msgid="6318648868380652280">"Nouvelle fenêtre"</string> <string name="manage_windows_text" msgid="5567366688493093920">"Gérer les fenêtres"</string> + <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Modifier le format"</string> <string name="close_text" msgid="4986518933445178928">"Fermer"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Fermer le menu"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Ouvrir le menu"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Mettre en plein écran"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Fractionner l\'écran"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Impossible de déplacer l\'appli ici"</string> - <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) --> - <skip /> - <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) --> - <skip /> + <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Immersif"</string> + <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Restaurer"</string> <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Agrandir"</string> <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Restaurer"</string> <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Ancrer à gauche"</string> @@ -146,4 +145,5 @@ <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Dans l\'application"</string> <string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"Dans votre navigateur"</string> <string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"OK"</string> + <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Ouvrez rapidement des applications dans votre navigateur ici"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-gl/strings.xml b/libs/WindowManager/Shell/res/values-gl/strings.xml index 97d5e51e5b98..bdd07476efcf 100644 --- a/libs/WindowManager/Shell/res/values-gl/strings.xml +++ b/libs/WindowManager/Shell/res/values-gl/strings.xml @@ -127,16 +127,15 @@ <string name="open_in_browser_text" msgid="9181692926376072904">"Abrir no navegador"</string> <string name="new_window_text" msgid="6318648868380652280">"Ventá nova"</string> <string name="manage_windows_text" msgid="5567366688493093920">"Xestionar as ventás"</string> + <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Cambiar a proporción"</string> <string name="close_text" msgid="4986518933445178928">"Pechar"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Pechar o menú"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Abrir o menú"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximizar pantalla"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Encaixar pantalla"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Non se pode mover aquí a aplicación"</string> - <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) --> - <skip /> - <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) --> - <skip /> + <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Envolvente"</string> + <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Restaurar"</string> <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Maximizar"</string> <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Restaurar"</string> <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Axustar á esquerda"</string> @@ -146,4 +145,5 @@ <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Na aplicación"</string> <string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"No navegador"</string> <string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"Aceptar"</string> + <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Abre rapidamente aplicacións no navegador aquí"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-gu/strings.xml b/libs/WindowManager/Shell/res/values-gu/strings.xml index 362ff8d874bb..d23c4fd8f0e0 100644 --- a/libs/WindowManager/Shell/res/values-gu/strings.xml +++ b/libs/WindowManager/Shell/res/values-gu/strings.xml @@ -127,16 +127,15 @@ <string name="open_in_browser_text" msgid="9181692926376072904">"બ્રાઉઝરમાં ખોલો"</string> <string name="new_window_text" msgid="6318648868380652280">"નવી વિન્ડો"</string> <string name="manage_windows_text" msgid="5567366688493093920">"વિન્ડો મેનેજ કરો"</string> + <string name="change_aspect_ratio_text" msgid="9104456064548212806">"સાપેક્ષ ગુણોત્તર બદલો"</string> <string name="close_text" msgid="4986518933445178928">"બંધ કરો"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"મેનૂ બંધ કરો"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"મેનૂ ખોલો"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"સ્ક્રીન કરો મોટી કરો"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"સ્ક્રીન સ્નૅપ કરો"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"ઍપ અહીં ખસેડી શકાતી નથી"</string> - <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) --> - <skip /> - <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) --> - <skip /> + <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"ઇમર્સિવ"</string> + <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"રિસ્ટોર કરો"</string> <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"મોટું કરો"</string> <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"રિસ્ટોર કરો"</string> <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"ડાબે સ્નૅપ કરો"</string> @@ -146,4 +145,5 @@ <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"ઍપમાં"</string> <string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"તમારા બ્રાઉઝરમાં"</string> <string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"ઓકે"</string> + <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"તમારા બ્રાઉઝરમાં અહીં ઝડપથી ઍપ ખોલો"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-hi/strings.xml b/libs/WindowManager/Shell/res/values-hi/strings.xml index 527793eac9c3..4eec6f877fab 100644 --- a/libs/WindowManager/Shell/res/values-hi/strings.xml +++ b/libs/WindowManager/Shell/res/values-hi/strings.xml @@ -127,6 +127,7 @@ <string name="open_in_browser_text" msgid="9181692926376072904">"ब्राउज़र में खोलें"</string> <string name="new_window_text" msgid="6318648868380652280">"नई विंडो"</string> <string name="manage_windows_text" msgid="5567366688493093920">"विंडो मैनेज करें"</string> + <string name="change_aspect_ratio_text" msgid="9104456064548212806">"आसपेक्ट रेशियो (लंबाई-चौड़ाई का अनुपात) बदलें"</string> <string name="close_text" msgid="4986518933445178928">"बंद करें"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"मेन्यू बंद करें"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"मेन्यू खोलें"</string> @@ -144,4 +145,5 @@ <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"ऐप्लिकेशन में"</string> <string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"आपके ब्राउज़र में"</string> <string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"ठीक है"</string> + <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"अपने ब्राउज़र में तुरंत ऐप्लिकेशन खोलें"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-hr/strings.xml b/libs/WindowManager/Shell/res/values-hr/strings.xml index 659d1ec39b73..a119d9e7f782 100644 --- a/libs/WindowManager/Shell/res/values-hr/strings.xml +++ b/libs/WindowManager/Shell/res/values-hr/strings.xml @@ -127,6 +127,7 @@ <string name="open_in_browser_text" msgid="9181692926376072904">"Otvori u pregledniku"</string> <string name="new_window_text" msgid="6318648868380652280">"Novi prozor"</string> <string name="manage_windows_text" msgid="5567366688493093920">"Upravljanje prozorima"</string> + <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Promijeni omjer slike"</string> <string name="close_text" msgid="4986518933445178928">"Zatvorite"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Zatvorite izbornik"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Otvaranje izbornika"</string> @@ -144,4 +145,5 @@ <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"U aplikaciji"</string> <string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"U pregledniku"</string> <string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"U redu"</string> + <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Ovdje brzo otvorite aplikacije u pregledniku"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-hu/strings.xml b/libs/WindowManager/Shell/res/values-hu/strings.xml index 943b5eb30768..c07b6c3b9f1d 100644 --- a/libs/WindowManager/Shell/res/values-hu/strings.xml +++ b/libs/WindowManager/Shell/res/values-hu/strings.xml @@ -127,16 +127,15 @@ <string name="open_in_browser_text" msgid="9181692926376072904">"Megnyitás böngészőben"</string> <string name="new_window_text" msgid="6318648868380652280">"Új ablak"</string> <string name="manage_windows_text" msgid="5567366688493093920">"Ablakok kezelése"</string> + <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Méretarány módosítása"</string> <string name="close_text" msgid="4986518933445178928">"Bezárás"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Menü bezárása"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Menü megnyitása"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Képernyő méretének maximalizálása"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Igazodás a képernyő adott részéhez"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Az alkalmazás nem helyezhető át ide"</string> - <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) --> - <skip /> - <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) --> - <skip /> + <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Magával ragadó"</string> + <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Visszaállítás"</string> <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Teljes méret"</string> <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Visszaállítás"</string> <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Balra igazítás"</string> @@ -146,4 +145,5 @@ <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Az alkalmazásban"</string> <string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"A böngészőben"</string> <string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"OK"</string> + <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Itt gyorsan megnyithatja az alkalmazásokat a böngészőben"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-hy/strings.xml b/libs/WindowManager/Shell/res/values-hy/strings.xml index 6bcfc9a22d6e..52eb18580de1 100644 --- a/libs/WindowManager/Shell/res/values-hy/strings.xml +++ b/libs/WindowManager/Shell/res/values-hy/strings.xml @@ -92,8 +92,8 @@ <string name="bubble_shortcut_label" msgid="666269077944378311">"Ամպիկներ"</string> <string name="bubble_shortcut_long_label" msgid="6088437544312894043">"Ցույց տալ ամպիկներ"</string> <string name="restart_button_description" msgid="4564728020654658478">"Հպեք՝ հավելվածը վերագործարկելու և ավելի հարմար տեսք ընտրելու համար"</string> - <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Փոխել հավելվածի կողմերի հարաբերակցությունը Կարգավորումներում"</string> - <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Փոխել չափերի հարաբերակցությունը"</string> + <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Փոխել հավելվածի կողմերի հարաբերությունը Կարգավորումներում"</string> + <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Փոխել չափերի հարաբերությունը"</string> <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Տեսախցիկի հետ կապված խնդիրնե՞ր կան։\nՀպեք՝ վերակարգավորելու համար։"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Չհաջողվե՞ց շտկել։\nՀպեք՝ փոփոխությունները չեղարկելու համար։"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Տեսախցիկի հետ կապված խնդիրներ չկա՞ն։ Փակելու համար հպեք։"</string> @@ -127,16 +127,15 @@ <string name="open_in_browser_text" msgid="9181692926376072904">"Բացել դիտարկիչում"</string> <string name="new_window_text" msgid="6318648868380652280">"Նոր պատուհան"</string> <string name="manage_windows_text" msgid="5567366688493093920">"Կառավարել պատուհանները"</string> + <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Փոխել կողմերի հարաբերակցությունը"</string> <string name="close_text" msgid="4986518933445178928">"Փակել"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Փակել ընտրացանկը"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Բացել ընտրացանկը"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Ծավալել էկրանը"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Ծալել էկրանը"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Հավելվածը հնարավոր չէ տեղափոխել այստեղ"</string> - <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) --> - <skip /> - <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) --> - <skip /> + <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Ներկայության էֆեկտով"</string> + <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Վերականգնել"</string> <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Ծավալել"</string> <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Վերականգնել"</string> <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Ամրացնել ձախ կողմում"</string> @@ -146,4 +145,5 @@ <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Հավելվածում"</string> <string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"Ձեր դիտարկիչում"</string> <string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"Եղավ"</string> + <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Արագ բացեք հավելվածները ձեր դիտարկիչում"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-in/strings.xml b/libs/WindowManager/Shell/res/values-in/strings.xml index 96a3ebce9a45..f8f9d5e16439 100644 --- a/libs/WindowManager/Shell/res/values-in/strings.xml +++ b/libs/WindowManager/Shell/res/values-in/strings.xml @@ -127,16 +127,15 @@ <string name="open_in_browser_text" msgid="9181692926376072904">"Buka di browser"</string> <string name="new_window_text" msgid="6318648868380652280">"Jendela Baru"</string> <string name="manage_windows_text" msgid="5567366688493093920">"Kelola Jendela"</string> + <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Ubah rasio aspek"</string> <string name="close_text" msgid="4986518933445178928">"Tutup"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Tutup Menu"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Buka Menu"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Perbesar Layar"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Gabungkan Layar"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Aplikasi tidak dapat dipindahkan ke sini"</string> - <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) --> - <skip /> - <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) --> - <skip /> + <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Imersif"</string> + <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Pulihkan"</string> <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Maksimalkan"</string> <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Pulihkan"</string> <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Maksimalkan ke kiri"</string> @@ -146,4 +145,5 @@ <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Di aplikasi"</string> <string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"Di browser Anda"</string> <string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"Oke"</string> + <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Buka aplikasi di browser Anda dengan cepat di sini"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-is/strings.xml b/libs/WindowManager/Shell/res/values-is/strings.xml index ca1bc15edf33..8a9e3c0ca0a4 100644 --- a/libs/WindowManager/Shell/res/values-is/strings.xml +++ b/libs/WindowManager/Shell/res/values-is/strings.xml @@ -127,6 +127,7 @@ <string name="open_in_browser_text" msgid="9181692926376072904">"Opna í vafra"</string> <string name="new_window_text" msgid="6318648868380652280">"Nýr gluggi"</string> <string name="manage_windows_text" msgid="5567366688493093920">"Stjórna gluggum"</string> + <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Breyta myndhlutfalli"</string> <string name="close_text" msgid="4986518933445178928">"Loka"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Loka valmynd"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Opna valmynd"</string> @@ -144,4 +145,5 @@ <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Í forritinu"</string> <string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"Í vafranum"</string> <string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"Í lagi"</string> + <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Opna forrit fljótt í vafranum þínum hér"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-it/strings.xml b/libs/WindowManager/Shell/res/values-it/strings.xml index 87919b5dc1ff..138adefed160 100644 --- a/libs/WindowManager/Shell/res/values-it/strings.xml +++ b/libs/WindowManager/Shell/res/values-it/strings.xml @@ -127,6 +127,7 @@ <string name="open_in_browser_text" msgid="9181692926376072904">"Apri nel browser"</string> <string name="new_window_text" msgid="6318648868380652280">"Nuova finestra"</string> <string name="manage_windows_text" msgid="5567366688493093920">"Gestisci finestre"</string> + <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Cambia proporzioni"</string> <string name="close_text" msgid="4986518933445178928">"Chiudi"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Chiudi il menu"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Apri il menu"</string> @@ -144,4 +145,5 @@ <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"All\'interno dell\'app"</string> <string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"Nel browser"</string> <string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"Ok"</string> + <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Apri rapidamente le app nel browser qui"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-iw/strings.xml b/libs/WindowManager/Shell/res/values-iw/strings.xml index 17ffe8ee7600..917738dc1575 100644 --- a/libs/WindowManager/Shell/res/values-iw/strings.xml +++ b/libs/WindowManager/Shell/res/values-iw/strings.xml @@ -109,7 +109,7 @@ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"אפשר להפעיל מחדש את האפליקציה כדי שהיא תוצג באופן טוב יותר במסך, אבל ייתכן שההתקדמות שלך או כל שינוי שלא נשמר יאבדו"</string> <string name="letterbox_restart_cancel" msgid="1342209132692537805">"ביטול"</string> <string name="letterbox_restart_restart" msgid="8529976234412442973">"הפעלה מחדש"</string> - <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"אין להציג שוב"</string> + <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"לא להציג שוב"</string> <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"אפשר להקיש הקשה כפולה כדי\nלהעביר את האפליקציה למקום אחר"</string> <string name="maximize_button_text" msgid="1650859196290301963">"הגדלה"</string> <string name="minimize_button_text" msgid="271592547935841753">"מזעור"</string> @@ -127,16 +127,15 @@ <string name="open_in_browser_text" msgid="9181692926376072904">"פתיחה בדפדפן"</string> <string name="new_window_text" msgid="6318648868380652280">"חלון חדש"</string> <string name="manage_windows_text" msgid="5567366688493093920">"ניהול החלונות"</string> + <string name="change_aspect_ratio_text" msgid="9104456064548212806">"שינוי של יחס גובה-רוחב"</string> <string name="close_text" msgid="4986518933445178928">"סגירה"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"סגירת התפריט"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"פתיחת התפריט"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"הגדלת המסך"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"כיווץ המסך"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"לא ניתן להעביר את האפליקציה לכאן"</string> - <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) --> - <skip /> - <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) --> - <skip /> + <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"סוחף"</string> + <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"שחזור"</string> <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"הגדלה"</string> <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"שחזור"</string> <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"הצמדה לשמאל"</string> @@ -146,4 +145,5 @@ <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"באפליקציה"</string> <string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"בדפדפן"</string> <string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"אישור"</string> + <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"כאן אפשר לפתוח אפליקציות בדפדפן במהירות"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-ja/strings.xml b/libs/WindowManager/Shell/res/values-ja/strings.xml index c7a77d9b9214..35c48212cf39 100644 --- a/libs/WindowManager/Shell/res/values-ja/strings.xml +++ b/libs/WindowManager/Shell/res/values-ja/strings.xml @@ -127,6 +127,7 @@ <string name="open_in_browser_text" msgid="9181692926376072904">"ブラウザで開く"</string> <string name="new_window_text" msgid="6318648868380652280">"新しいウィンドウ"</string> <string name="manage_windows_text" msgid="5567366688493093920">"ウィンドウを管理する"</string> + <string name="change_aspect_ratio_text" msgid="9104456064548212806">"アスペクト比を変更"</string> <string name="close_text" msgid="4986518933445178928">"閉じる"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"メニューを閉じる"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"メニューを開く"</string> @@ -144,4 +145,5 @@ <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"アプリ内"</string> <string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"ブラウザ内"</string> <string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"OK"</string> + <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"ブラウザでアプリをすばやく開けます"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-ka/strings.xml b/libs/WindowManager/Shell/res/values-ka/strings.xml index 39362ef4ca57..9b9966f152ee 100644 --- a/libs/WindowManager/Shell/res/values-ka/strings.xml +++ b/libs/WindowManager/Shell/res/values-ka/strings.xml @@ -127,6 +127,7 @@ <string name="open_in_browser_text" msgid="9181692926376072904">"ბრაუზერში გახსნა"</string> <string name="new_window_text" msgid="6318648868380652280">"ახალი ფანჯარა"</string> <string name="manage_windows_text" msgid="5567366688493093920">"ფანჯრების მართვა"</string> + <string name="change_aspect_ratio_text" msgid="9104456064548212806">"თანაფარდობის შეცვლა"</string> <string name="close_text" msgid="4986518933445178928">"დახურვა"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"მენიუს დახურვა"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"მენიუს გახსნა"</string> @@ -144,4 +145,5 @@ <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"აპში"</string> <string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"თქვენს ბრაუზერში"</string> <string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"კარგი"</string> + <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"სწრაფად გახსენით აპები თქვენს ბრაუზერში აქ"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-kk/strings.xml b/libs/WindowManager/Shell/res/values-kk/strings.xml index 45f85b9e9a70..8618ba9b2b0f 100644 --- a/libs/WindowManager/Shell/res/values-kk/strings.xml +++ b/libs/WindowManager/Shell/res/values-kk/strings.xml @@ -127,16 +127,15 @@ <string name="open_in_browser_text" msgid="9181692926376072904">"Браузерден ашу"</string> <string name="new_window_text" msgid="6318648868380652280">"Жаңа терезе"</string> <string name="manage_windows_text" msgid="5567366688493093920">"Терезелерді басқару"</string> + <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Арақатынасты өзгерту"</string> <string name="close_text" msgid="4986518933445178928">"Жабу"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Мәзірді жабу"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Мәзірді ашу"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Экранды ұлғайту"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Экранды бөлу"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Қолданба бұл жерге қойылмайды."</string> - <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) --> - <skip /> - <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) --> - <skip /> + <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Әсерлі"</string> + <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Қалпына келтіру"</string> <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Жаю"</string> <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Қалпына келтіру"</string> <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Солға тіркеу"</string> @@ -146,4 +145,5 @@ <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Қолданбада"</string> <string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"Браузерде"</string> <string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"Жарайды"</string> + <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Осындағы браузерде қолданбаларды жылдам ашуға болады."</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-km/strings.xml b/libs/WindowManager/Shell/res/values-km/strings.xml index 9c4ae05f3a36..7f853f3e1e2f 100644 --- a/libs/WindowManager/Shell/res/values-km/strings.xml +++ b/libs/WindowManager/Shell/res/values-km/strings.xml @@ -127,6 +127,7 @@ <string name="open_in_browser_text" msgid="9181692926376072904">"បើកក្នុងកម្មវិធីរុករកតាមអ៊ីនធឺណិត"</string> <string name="new_window_text" msgid="6318648868380652280">"វិនដូថ្មី"</string> <string name="manage_windows_text" msgid="5567366688493093920">"គ្រប់គ្រងវិនដូ"</string> + <string name="change_aspect_ratio_text" msgid="9104456064548212806">"ប្ដូរសមាមាត្រ"</string> <string name="close_text" msgid="4986518933445178928">"បិទ"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"បិទម៉ឺនុយ"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"បើកម៉ឺនុយ"</string> @@ -144,4 +145,5 @@ <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"នៅក្នុងកម្មវិធី"</string> <string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"នៅក្នុងកម្មវិធីរុករកតាមអ៊ីនធឺណិតរបស់អ្នក"</string> <string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"យល់ព្រម"</string> + <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"បើកកម្មវិធីយ៉ាងរហ័សនៅក្នុងកម្មវិធីរុករកតាមអ៊ីនធឺណិតរបស់អ្នកនៅទីនេះ"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-kn/strings.xml b/libs/WindowManager/Shell/res/values-kn/strings.xml index f365cfb34412..456dea2fdb0f 100644 --- a/libs/WindowManager/Shell/res/values-kn/strings.xml +++ b/libs/WindowManager/Shell/res/values-kn/strings.xml @@ -127,6 +127,7 @@ <string name="open_in_browser_text" msgid="9181692926376072904">"ಬ್ರೌಸರ್ನಲ್ಲಿ ತೆರೆಯಿರಿ"</string> <string name="new_window_text" msgid="6318648868380652280">"ಹೊಸ ವಿಂಡೋ"</string> <string name="manage_windows_text" msgid="5567366688493093920">"ವಿಂಡೋಗಳನ್ನು ನಿರ್ವಹಿಸಿ"</string> + <string name="change_aspect_ratio_text" msgid="9104456064548212806">"ದೃಶ್ಯಾನುಪಾತವನ್ನು ಬದಲಾಯಿಸಿ"</string> <string name="close_text" msgid="4986518933445178928">"ಮುಚ್ಚಿ"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"ಮೆನು ಮುಚ್ಚಿ"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"ಮೆನು ತೆರೆಯಿರಿ"</string> @@ -144,4 +145,5 @@ <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"ಆ್ಯಪ್ನಲ್ಲಿ"</string> <string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"ನಿಮ್ಮ ಬ್ರೌಸರ್ನಲ್ಲಿ"</string> <string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"ಸರಿ"</string> + <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"ಇಲ್ಲಿಂದ ನಿಮ್ಮ ಬ್ರೌಸರ್ನಲ್ಲಿ ಆ್ಯಪ್ಗಳನ್ನು ತ್ವರಿತವಾಗಿ ತೆರೆಯಿರಿ"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-ko/strings.xml b/libs/WindowManager/Shell/res/values-ko/strings.xml index 2bf1b05a919b..763cda738541 100644 --- a/libs/WindowManager/Shell/res/values-ko/strings.xml +++ b/libs/WindowManager/Shell/res/values-ko/strings.xml @@ -127,16 +127,15 @@ <string name="open_in_browser_text" msgid="9181692926376072904">"브라우저에서 열기"</string> <string name="new_window_text" msgid="6318648868380652280">"새 창"</string> <string name="manage_windows_text" msgid="5567366688493093920">"창 관리"</string> + <string name="change_aspect_ratio_text" msgid="9104456064548212806">"가로세로 비율 변경"</string> <string name="close_text" msgid="4986518933445178928">"닫기"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"메뉴 닫기"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"메뉴 열기"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"화면 최대화"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"화면 분할"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"앱을 여기로 이동할 수 없음"</string> - <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) --> - <skip /> - <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) --> - <skip /> + <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"몰입형"</string> + <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"복원"</string> <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"최대화하기"</string> <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"복원"</string> <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"왼쪽으로 맞추기"</string> @@ -146,4 +145,5 @@ <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"앱에서"</string> <string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"브라우저에서"</string> <string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"확인"</string> + <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"이 브라우저에서 앱을 빠르게 여세요."</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-ky/strings.xml b/libs/WindowManager/Shell/res/values-ky/strings.xml index 392ae4cab107..bffc3b1d11a8 100644 --- a/libs/WindowManager/Shell/res/values-ky/strings.xml +++ b/libs/WindowManager/Shell/res/values-ky/strings.xml @@ -127,16 +127,15 @@ <string name="open_in_browser_text" msgid="9181692926376072904">"Серепчиден ачуу"</string> <string name="new_window_text" msgid="6318648868380652280">"Жаңы терезе"</string> <string name="manage_windows_text" msgid="5567366688493093920">"Терезелерди тескөө"</string> + <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Тараптардын катнашын өзгөртүү"</string> <string name="close_text" msgid="4986518933445178928">"Жабуу"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Менюну жабуу"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Менюну ачуу"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Экранды чоңойтуу"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Экранды сүрөткө тартып алуу"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Колдонмону бул жерге жылдырууга болбойт"</string> - <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) --> - <skip /> - <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) --> - <skip /> + <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Сүңгүтүүчү"</string> + <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Калыбына келтирүү"</string> <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Чоңойтуу"</string> <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Калыбына келтирүү"</string> <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Солго жылдыруу"</string> @@ -146,4 +145,5 @@ <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Колдонмодо"</string> <string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"Серепчиңизде"</string> <string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"Жарайт"</string> + <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Бул жерде серепчиңизден колдонмолорду тез ачасыз"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-lo/strings.xml b/libs/WindowManager/Shell/res/values-lo/strings.xml index 4e4b678755b2..b48b07067521 100644 --- a/libs/WindowManager/Shell/res/values-lo/strings.xml +++ b/libs/WindowManager/Shell/res/values-lo/strings.xml @@ -127,16 +127,15 @@ <string name="open_in_browser_text" msgid="9181692926376072904">"ເປີດໃນໂປຣແກຣມທ່ອງເວັບ"</string> <string name="new_window_text" msgid="6318648868380652280">"ໜ້າຈໍໃໝ່"</string> <string name="manage_windows_text" msgid="5567366688493093920">"ຈັດການໜ້າຈໍ"</string> + <string name="change_aspect_ratio_text" msgid="9104456064548212806">"ປ່ຽນອັດຕາສ່ວນຮູບ"</string> <string name="close_text" msgid="4986518933445178928">"ປິດ"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"ປິດເມນູ"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"ເປີດເມນູ"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"ປັບຈໍໃຫຍ່ສຸດ"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"ສະແນັບໜ້າຈໍ"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"ບໍ່ສາມາດຍ້າຍແອັບມາບ່ອນນີ້ໄດ້"</string> - <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) --> - <skip /> - <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) --> - <skip /> + <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"ສົມຈິງ"</string> + <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"ກູ້ຄືນ"</string> <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"ຂະຫຍາຍໃຫຍ່ສຸດ"</string> <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"ກູ້ຄືນ"</string> <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"ແນບຊ້າຍ"</string> @@ -146,4 +145,5 @@ <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"ໃນແອັບ"</string> <string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"ໃນໂປຣແກຣມທ່ອງເວັບຂອງທ່ານ"</string> <string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"ຕົກລົງ"</string> + <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"ເປີດແອັບຢ່າງວ່ອງໄວໃນໂປຣແກຣມທ່ອງເວັບຂອງທ່ານບ່ອນນີ້"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-lt/strings.xml b/libs/WindowManager/Shell/res/values-lt/strings.xml index 5a7f58e5781a..d7a907cdc105 100644 --- a/libs/WindowManager/Shell/res/values-lt/strings.xml +++ b/libs/WindowManager/Shell/res/values-lt/strings.xml @@ -127,6 +127,7 @@ <string name="open_in_browser_text" msgid="9181692926376072904">"Atidaryti naršyklėje"</string> <string name="new_window_text" msgid="6318648868380652280">"Naujas langas"</string> <string name="manage_windows_text" msgid="5567366688493093920">"Tvarkyti langus"</string> + <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Keisti kraštinių santykį"</string> <string name="close_text" msgid="4986518933445178928">"Uždaryti"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Uždaryti meniu"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Atidaryti meniu"</string> @@ -144,4 +145,5 @@ <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Programoje"</string> <string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"Naršyklėje"</string> <string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"Gerai"</string> + <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Čia greitai atidarykite programas naršyklėje"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-lv/strings.xml b/libs/WindowManager/Shell/res/values-lv/strings.xml index 60912f627841..4ba7c2346a33 100644 --- a/libs/WindowManager/Shell/res/values-lv/strings.xml +++ b/libs/WindowManager/Shell/res/values-lv/strings.xml @@ -127,16 +127,15 @@ <string name="open_in_browser_text" msgid="9181692926376072904">"Atvērt pārlūkā"</string> <string name="new_window_text" msgid="6318648868380652280">"Jauns logs"</string> <string name="manage_windows_text" msgid="5567366688493093920">"Pārvaldīt logus"</string> + <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Mainīt malu attiecību"</string> <string name="close_text" msgid="4986518933445178928">"Aizvērt"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Aizvērt izvēlni"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Atvērt izvēlni"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maksimizēt ekrānu"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Fiksēt ekrānu"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Lietotni nevar pārvietot šeit."</string> - <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) --> - <skip /> - <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) --> - <skip /> + <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Iekļaujoši"</string> + <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Atjaunot"</string> <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Maksimizēt"</string> <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Atjaunot"</string> <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Piestiprināt pa kreisi"</string> @@ -146,4 +145,5 @@ <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Lietotnē"</string> <string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"Pārlūkprogrammā"</string> <string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"Labi"</string> + <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"No šejienes varat ātri atvērt lietotnes savā pārlūkprogrammā"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-mk/strings.xml b/libs/WindowManager/Shell/res/values-mk/strings.xml index 7c0c856ed1a7..d20eba55eac9 100644 --- a/libs/WindowManager/Shell/res/values-mk/strings.xml +++ b/libs/WindowManager/Shell/res/values-mk/strings.xml @@ -127,16 +127,15 @@ <string name="open_in_browser_text" msgid="9181692926376072904">"Отвори во прелистувач"</string> <string name="new_window_text" msgid="6318648868380652280">"Нов прозорец"</string> <string name="manage_windows_text" msgid="5567366688493093920">"Управувајте со прозорци"</string> + <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Промени го соодносот"</string> <string name="close_text" msgid="4986518933445178928">"Затворете"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Затворете го менито"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Отвори го менито"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Максимизирај го екранот"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Подели го екранот на половина"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Апликацијата не може да се премести овде"</string> - <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) --> - <skip /> - <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) --> - <skip /> + <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Реалистично"</string> + <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Врати"</string> <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Максимизирај"</string> <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Врати"</string> <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Фотографирај лево"</string> @@ -146,4 +145,5 @@ <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Во апликацијата"</string> <string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"Во прелистувачот"</string> <string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"Во ред"</string> + <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Брзо отворајте ги апликациите во вашиот прелистувач овде"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-ml/strings.xml b/libs/WindowManager/Shell/res/values-ml/strings.xml index e14ab8b0161c..81c5094526c1 100644 --- a/libs/WindowManager/Shell/res/values-ml/strings.xml +++ b/libs/WindowManager/Shell/res/values-ml/strings.xml @@ -127,16 +127,15 @@ <string name="open_in_browser_text" msgid="9181692926376072904">"ബ്രൗസറിൽ തുറക്കുക"</string> <string name="new_window_text" msgid="6318648868380652280">"പുതിയ വിന്ഡോ"</string> <string name="manage_windows_text" msgid="5567366688493093920">"വിൻഡോകൾ മാനേജ് ചെയ്യുക"</string> + <string name="change_aspect_ratio_text" msgid="9104456064548212806">"വീക്ഷണ അനുപാതം മാറ്റുക"</string> <string name="close_text" msgid="4986518933445178928">"അടയ്ക്കുക"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"മെനു അടയ്ക്കുക"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"മെനു തുറക്കുക"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"സ്ക്രീൻ വലുതാക്കുക"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"സ്ക്രീൻ സ്നാപ്പ് ചെയ്യുക"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"ആപ്പ് ഇവിടേക്ക് നീക്കാനാകില്ല"</string> - <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) --> - <skip /> - <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) --> - <skip /> + <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"ഇമേഴ്സീവ്"</string> + <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"പുനഃസ്ഥാപിക്കുക"</string> <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"വലുതാക്കുക"</string> <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"പുനഃസ്ഥാപിക്കുക"</string> <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"ഇടതുവശത്തേക്ക് സ്നാപ്പ് ചെയ്യുക"</string> @@ -146,4 +145,5 @@ <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"ആപ്പിൽ"</string> <string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"നിങ്ങളുടെ ബ്രൗസറിൽ"</string> <string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"ശരി"</string> + <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"നിങ്ങളുടെ ബ്രൗസറിലെ ആപ്പുകൾ ഇവിടെ അതിവേഗം തുറക്കുക"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-mn/strings.xml b/libs/WindowManager/Shell/res/values-mn/strings.xml index d406b99e80b3..35da93e774b5 100644 --- a/libs/WindowManager/Shell/res/values-mn/strings.xml +++ b/libs/WindowManager/Shell/res/values-mn/strings.xml @@ -127,16 +127,15 @@ <string name="open_in_browser_text" msgid="9181692926376072904">"Хөтчид нээх"</string> <string name="new_window_text" msgid="6318648868380652280">"Шинэ цонх"</string> <string name="manage_windows_text" msgid="5567366688493093920">"Windows-г удирдах"</string> + <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Харьцааг өөрчлөх"</string> <string name="close_text" msgid="4986518933445178928">"Хаах"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Цэсийг хаах"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Цэсийг нээх"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Дэлгэцийг томруулах"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Дэлгэцийг таллах"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Аппыг ийш зөөх боломжгүй"</string> - <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) --> - <skip /> - <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) --> - <skip /> + <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Бодит мэт"</string> + <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Сэргээх"</string> <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Томруулах"</string> <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Сэргээх"</string> <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Зүүн тийш зэрэгцүүлэх"</string> @@ -146,4 +145,5 @@ <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Аппад"</string> <string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"Хөтчидөө"</string> <string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"OK"</string> + <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Эндээс хөтчидөө аппуудыг шуурхай нээгээрэй"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-mr/strings.xml b/libs/WindowManager/Shell/res/values-mr/strings.xml index 871bc3fcc8e7..c6b874a6780b 100644 --- a/libs/WindowManager/Shell/res/values-mr/strings.xml +++ b/libs/WindowManager/Shell/res/values-mr/strings.xml @@ -127,6 +127,7 @@ <string name="open_in_browser_text" msgid="9181692926376072904">"ब्राउझरमध्ये उघडा"</string> <string name="new_window_text" msgid="6318648868380652280">"नवीन विंडो"</string> <string name="manage_windows_text" msgid="5567366688493093920">"विंडो व्यवस्थापित करा"</string> + <string name="change_aspect_ratio_text" msgid="9104456064548212806">"आस्पेक्ट रेशो बदला"</string> <string name="close_text" msgid="4986518933445178928">"बंद करा"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"मेनू बंद करा"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"मेनू उघडा"</string> @@ -144,4 +145,5 @@ <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"ॲपमध्ये"</string> <string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"तुमच्या ब्राउझरमध्ये"</string> <string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"ओके"</string> + <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"इथे तुमच्या ब्राउझरमध्ये अॅप्स झटपट उघडा"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-ms/strings.xml b/libs/WindowManager/Shell/res/values-ms/strings.xml index 71666cae93c8..0fce0e9da45f 100644 --- a/libs/WindowManager/Shell/res/values-ms/strings.xml +++ b/libs/WindowManager/Shell/res/values-ms/strings.xml @@ -127,6 +127,7 @@ <string name="open_in_browser_text" msgid="9181692926376072904">"Buka dalam penyemak imbas"</string> <string name="new_window_text" msgid="6318648868380652280">"Tetingkap Baharu"</string> <string name="manage_windows_text" msgid="5567366688493093920">"Urus Tetingkap"</string> + <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Tukar nisbah bidang"</string> <string name="close_text" msgid="4986518933445178928">"Tutup"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Tutup Menu"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Buka Menu"</string> @@ -144,4 +145,5 @@ <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Pada apl"</string> <string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"Pada penyemak imbas"</string> <string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"OK"</string> + <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Buka apl dengan pantas dalam penyemak imbas anda di sini"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-my/strings.xml b/libs/WindowManager/Shell/res/values-my/strings.xml index ae34624c98a0..abc2a19ba989 100644 --- a/libs/WindowManager/Shell/res/values-my/strings.xml +++ b/libs/WindowManager/Shell/res/values-my/strings.xml @@ -127,16 +127,15 @@ <string name="open_in_browser_text" msgid="9181692926376072904">"ဘရောင်ဇာတွင် ဖွင့်ရန်"</string> <string name="new_window_text" msgid="6318648868380652280">"ဝင်းဒိုးအသစ်"</string> <string name="manage_windows_text" msgid="5567366688493093920">"ဝင်းဒိုးများ စီမံရန်"</string> + <string name="change_aspect_ratio_text" msgid="9104456064548212806">"အချိုးအစား ပြောင်းရန်"</string> <string name="close_text" msgid="4986518933445178928">"ပိတ်ရန်"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"မီနူး ပိတ်ရန်"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"မီနူး ဖွင့်ရန်"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"စခရင်ကို ချဲ့မည်"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"စခရင်ကို ချုံ့မည်"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"အက်ပ်ကို ဤနေရာသို့ ရွှေ့၍မရပါ"</string> - <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) --> - <skip /> - <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) --> - <skip /> + <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"သုံးဘက်မြင်"</string> + <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"ပြန်ပြောင်းရန်"</string> <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"ချဲ့ရန်"</string> <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"ပြန်ပြောင်းရန်"</string> <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"ဘယ်တွင် ချဲ့ရန်"</string> @@ -146,4 +145,5 @@ <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"အက်ပ်တွင်"</string> <string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"သင်၏ဘရောင်ဇာတွင်"</string> <string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"OK"</string> + <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"အက်ပ်များကို သင့်ဘရောင်ဇာတွင် ဤနေရာ၌ အမြန်ဖွင့်နိုင်သည်"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-nb/strings.xml b/libs/WindowManager/Shell/res/values-nb/strings.xml index 9270dc859728..ed6fb900564f 100644 --- a/libs/WindowManager/Shell/res/values-nb/strings.xml +++ b/libs/WindowManager/Shell/res/values-nb/strings.xml @@ -127,16 +127,15 @@ <string name="open_in_browser_text" msgid="9181692926376072904">"Åpne i nettleseren"</string> <string name="new_window_text" msgid="6318648868380652280">"Nytt vindu"</string> <string name="manage_windows_text" msgid="5567366688493093920">"Administrer vinduene"</string> + <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Endre høyde/bredde-forholdet"</string> <string name="close_text" msgid="4986518933445178928">"Lukk"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Lukk menyen"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Åpne menyen"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maksimer skjermen"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Fest skjermen"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Appen kan ikke flyttes hit"</string> - <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) --> - <skip /> - <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) --> - <skip /> + <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Oppslukende"</string> + <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Gjenopprett"</string> <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Maksimer"</string> <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Gjenopprett"</string> <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Fest til venstre"</string> @@ -146,4 +145,5 @@ <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"I appen"</string> <string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"I nettleseren"</string> <string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"OK"</string> + <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Her kan du raskt åpne apper i nettleseren"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-ne/strings.xml b/libs/WindowManager/Shell/res/values-ne/strings.xml index 7015b2c11b32..aff712901ff6 100644 --- a/libs/WindowManager/Shell/res/values-ne/strings.xml +++ b/libs/WindowManager/Shell/res/values-ne/strings.xml @@ -127,6 +127,7 @@ <string name="open_in_browser_text" msgid="9181692926376072904">"ब्राउजरमा खोल्नुहोस्"</string> <string name="new_window_text" msgid="6318648868380652280">"नयाँ विन्डो"</string> <string name="manage_windows_text" msgid="5567366688493093920">"विन्डोहरू व्यवस्थापन गर्नुहोस्"</string> + <string name="change_aspect_ratio_text" msgid="9104456064548212806">"एस्पेक्ट रेसियो परिवर्तन गर्नुहोस्"</string> <string name="close_text" msgid="4986518933445178928">"बन्द गर्नुहोस्"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"मेनु बन्द गर्नुहोस्"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"मेनु खोल्नुहोस्"</string> @@ -144,4 +145,5 @@ <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"एपमा"</string> <string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"तपाईंको ब्राउजरमा"</string> <string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"ठिक छ"</string> + <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"आफ्नो ब्राउजरबाट यहाँ तुरुन्तै एपहरू खोल्नुहोस्"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-nl/strings.xml b/libs/WindowManager/Shell/res/values-nl/strings.xml index 45305d62a69b..8db3a0ecfc2f 100644 --- a/libs/WindowManager/Shell/res/values-nl/strings.xml +++ b/libs/WindowManager/Shell/res/values-nl/strings.xml @@ -127,16 +127,15 @@ <string name="open_in_browser_text" msgid="9181692926376072904">"Openen in browser"</string> <string name="new_window_text" msgid="6318648868380652280">"Nieuw venster"</string> <string name="manage_windows_text" msgid="5567366688493093920">"Vensters beheren"</string> + <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Beeldverhouding wijzigen"</string> <string name="close_text" msgid="4986518933445178928">"Sluiten"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Menu sluiten"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Menu openen"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Scherm maximaliseren"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Scherm halveren"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Kan de app niet hierheen verplaatsen"</string> - <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) --> - <skip /> - <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) --> - <skip /> + <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Immersief"</string> + <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Herstellen"</string> <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Maximaliseren"</string> <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Herstellen"</string> <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Links uitlijnen"</string> @@ -146,4 +145,5 @@ <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"In de app"</string> <string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"In je browser"</string> <string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"OK"</string> + <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Open hier snel apps in je browser"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-or/strings.xml b/libs/WindowManager/Shell/res/values-or/strings.xml index 2d30441ab6f4..69540898161c 100644 --- a/libs/WindowManager/Shell/res/values-or/strings.xml +++ b/libs/WindowManager/Shell/res/values-or/strings.xml @@ -127,16 +127,15 @@ <string name="open_in_browser_text" msgid="9181692926376072904">"ବ୍ରାଉଜରରେ ଖୋଲନ୍ତୁ"</string> <string name="new_window_text" msgid="6318648868380652280">"ନୂଆ ୱିଣ୍ଡୋ"</string> <string name="manage_windows_text" msgid="5567366688493093920">"ୱିଣ୍ଡୋଗୁଡ଼ିକୁ ପରିଚାଳନା କରନ୍ତୁ"</string> + <string name="change_aspect_ratio_text" msgid="9104456064548212806">"ଚଉଡ଼ା ଓ ଉଚ୍ଚତାର ଅନୁପାତ ପରିବର୍ତ୍ତନ କରନ୍ତୁ"</string> <string name="close_text" msgid="4986518933445178928">"ବନ୍ଦ କରନ୍ତୁ"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"ମେନୁ ବନ୍ଦ କରନ୍ତୁ"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"ମେନୁ ଖୋଲନ୍ତୁ"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"ସ୍କ୍ରିନକୁ ବଡ଼ କରନ୍ତୁ"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"ସ୍କ୍ରିନକୁ ସ୍ନାପ କରନ୍ତୁ"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"ଆପକୁ ଏଠାକୁ ମୁଭ କରାଯାଇପାରିବ ନାହିଁ"</string> - <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) --> - <skip /> - <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) --> - <skip /> + <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"ଇମର୍ସିଭ"</string> + <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"ରିଷ୍ଟୋର କରନ୍ତୁ"</string> <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"ବଡ଼ କରନ୍ତୁ"</string> <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"ରିଷ୍ଟୋର କରନ୍ତୁ"</string> <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"ବାମରେ ସ୍ନାପ କରନ୍ତୁ"</string> @@ -146,4 +145,5 @@ <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"ଆପରେ"</string> <string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"ଆପଣଙ୍କ ବ୍ରାଉଜରରେ"</string> <string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"ଠିକ ଅଛି"</string> + <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"ଏଠାରେ ଆପଣଙ୍କ ବ୍ରାଉଜରରେ ଥିବା ଆପ୍ସକୁ ଶୀଘ୍ର ଖୋଲନ୍ତୁ"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-pa/strings.xml b/libs/WindowManager/Shell/res/values-pa/strings.xml index 26ba461cba5d..c627d7fcc2c5 100644 --- a/libs/WindowManager/Shell/res/values-pa/strings.xml +++ b/libs/WindowManager/Shell/res/values-pa/strings.xml @@ -127,16 +127,15 @@ <string name="open_in_browser_text" msgid="9181692926376072904">"ਬ੍ਰਾਊਜ਼ਰ ਵਿੱਚ ਖੋਲ੍ਹੋ"</string> <string name="new_window_text" msgid="6318648868380652280">"ਨਵੀਂ ਵਿੰਡੋ"</string> <string name="manage_windows_text" msgid="5567366688493093920">"ਵਿੰਡੋਆਂ ਦਾ ਪ੍ਰਬੰਧਨ ਕਰੋ"</string> + <string name="change_aspect_ratio_text" msgid="9104456064548212806">"ਆਕਾਰ ਅਨੁਪਾਤ ਬਦਲੋ"</string> <string name="close_text" msgid="4986518933445178928">"ਬੰਦ ਕਰੋ"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"ਮੀਨੂ ਬੰਦ ਕਰੋ"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"ਮੀਨੂ ਖੋਲ੍ਹੋ"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"ਸਕ੍ਰੀਨ ਦਾ ਆਕਾਰ ਵਧਾਓ"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"ਸਕ੍ਰੀਨ ਨੂੰ ਸਨੈਪ ਕਰੋ"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"ਐਪ ਨੂੰ ਇੱਥੇ ਨਹੀਂ ਲਿਜਾਇਆ ਜਾ ਸਕਦਾ"</string> - <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) --> - <skip /> - <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) --> - <skip /> + <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"ਇਮਰਸਿਵ"</string> + <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"ਮੁੜ-ਬਹਾਲ ਕਰੋ"</string> <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"ਵੱਡਾ ਕਰੋ"</string> <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"ਮੁੜ-ਬਹਾਲ ਕਰੋ"</string> <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"ਖੱਬੇ ਪਾਸੇ ਸਨੈਪ ਕਰੋ"</string> @@ -146,4 +145,5 @@ <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"ਐਪ ਵਿੱਚ"</string> <string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"ਤੁਹਾਡੇ ਬ੍ਰਾਊਜ਼ਰ ਵਿੱਚ"</string> <string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"ਠੀਕ ਹੈ"</string> + <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"ਇੱਥੇ ਆਪਣੇ ਬ੍ਰਾਊਜ਼ਰ ਵਿੱਚ ਤੇਜ਼ੀ ਨਾਲ ਐਪਾਂ ਖੋਲ੍ਹੋ"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-pl/strings.xml b/libs/WindowManager/Shell/res/values-pl/strings.xml index 5f78b134ad22..a138c08d319a 100644 --- a/libs/WindowManager/Shell/res/values-pl/strings.xml +++ b/libs/WindowManager/Shell/res/values-pl/strings.xml @@ -127,6 +127,7 @@ <string name="open_in_browser_text" msgid="9181692926376072904">"Otwórz w przeglądarce"</string> <string name="new_window_text" msgid="6318648868380652280">"Nowe okno"</string> <string name="manage_windows_text" msgid="5567366688493093920">"Zarządzaj oknami"</string> + <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Zmień format obrazu"</string> <string name="close_text" msgid="4986518933445178928">"Zamknij"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Zamknij menu"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Otwórz menu"</string> @@ -144,4 +145,5 @@ <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"W aplikacji"</string> <string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"W przeglądarce"</string> <string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"OK"</string> + <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Tutaj możesz szybko otwierać aplikacje w przeglądarce"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml b/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml index 8c7f9e73296d..9942f6980306 100644 --- a/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml +++ b/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml @@ -52,10 +52,10 @@ <string name="accessibility_split_right" msgid="8441001008181296837">"Dividir para a direita"</string> <string name="accessibility_split_top" msgid="2789329702027147146">"Dividir para cima"</string> <string name="accessibility_split_bottom" msgid="8694551025220868191">"Dividir para baixo"</string> - <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Como usar o modo uma mão"</string> + <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Como usar o modo para uma mão"</string> <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Para sair, deslize de baixo para cima na tela ou toque em qualquer lugar acima do app"</string> - <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Iniciar o modo uma mão"</string> - <string name="accessibility_action_stop_one_handed" msgid="1369940261782179442">"Sair do modo uma mão"</string> + <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Iniciar o modo para uma mão"</string> + <string name="accessibility_action_stop_one_handed" msgid="1369940261782179442">"Sair do modo para uma mão"</string> <string name="bubbles_settings_button_description" msgid="1301286017420516912">"Configurações de balões do <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> <string name="bubble_overflow_button_content_description" msgid="8160974472718594382">"Menu flutuante"</string> <string name="bubble_accessibility_action_add_back" msgid="1830101076853540953">"Devolver à pilha"</string> @@ -127,16 +127,15 @@ <string name="open_in_browser_text" msgid="9181692926376072904">"Abrir no navegador"</string> <string name="new_window_text" msgid="6318648868380652280">"Nova janela"</string> <string name="manage_windows_text" msgid="5567366688493093920">"Gerenciar janelas"</string> + <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Mudar a proporção"</string> <string name="close_text" msgid="4986518933445178928">"Fechar"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Fechar menu"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Abrir o menu"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Ampliar tela"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Ajustar tela"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Não é possível mover o app para cá"</string> - <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) --> - <skip /> - <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) --> - <skip /> + <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Imersivo"</string> + <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Restaurar"</string> <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Maximizar"</string> <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Restaurar"</string> <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Ajustar à esquerda"</string> @@ -146,4 +145,5 @@ <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"No app"</string> <string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"No navegador"</string> <string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"OK"</string> + <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Abra apps no navegador rapidamente aqui"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml b/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml index cd78ef95d88e..559eea2d3cab 100644 --- a/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml +++ b/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml @@ -127,6 +127,7 @@ <string name="open_in_browser_text" msgid="9181692926376072904">"Abrir no navegador"</string> <string name="new_window_text" msgid="6318648868380652280">"Nova janela"</string> <string name="manage_windows_text" msgid="5567366688493093920">"Faça a gestão das janelas"</string> + <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Alterar formato"</string> <string name="close_text" msgid="4986518933445178928">"Fechar"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Fechar menu"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Abrir menu"</string> @@ -144,4 +145,5 @@ <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Na app"</string> <string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"No navegador"</string> <string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"OK"</string> + <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Abra rapidamente apps no navegador aqui"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-pt/strings.xml b/libs/WindowManager/Shell/res/values-pt/strings.xml index 8c7f9e73296d..9942f6980306 100644 --- a/libs/WindowManager/Shell/res/values-pt/strings.xml +++ b/libs/WindowManager/Shell/res/values-pt/strings.xml @@ -52,10 +52,10 @@ <string name="accessibility_split_right" msgid="8441001008181296837">"Dividir para a direita"</string> <string name="accessibility_split_top" msgid="2789329702027147146">"Dividir para cima"</string> <string name="accessibility_split_bottom" msgid="8694551025220868191">"Dividir para baixo"</string> - <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Como usar o modo uma mão"</string> + <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Como usar o modo para uma mão"</string> <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Para sair, deslize de baixo para cima na tela ou toque em qualquer lugar acima do app"</string> - <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Iniciar o modo uma mão"</string> - <string name="accessibility_action_stop_one_handed" msgid="1369940261782179442">"Sair do modo uma mão"</string> + <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Iniciar o modo para uma mão"</string> + <string name="accessibility_action_stop_one_handed" msgid="1369940261782179442">"Sair do modo para uma mão"</string> <string name="bubbles_settings_button_description" msgid="1301286017420516912">"Configurações de balões do <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> <string name="bubble_overflow_button_content_description" msgid="8160974472718594382">"Menu flutuante"</string> <string name="bubble_accessibility_action_add_back" msgid="1830101076853540953">"Devolver à pilha"</string> @@ -127,16 +127,15 @@ <string name="open_in_browser_text" msgid="9181692926376072904">"Abrir no navegador"</string> <string name="new_window_text" msgid="6318648868380652280">"Nova janela"</string> <string name="manage_windows_text" msgid="5567366688493093920">"Gerenciar janelas"</string> + <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Mudar a proporção"</string> <string name="close_text" msgid="4986518933445178928">"Fechar"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Fechar menu"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Abrir o menu"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Ampliar tela"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Ajustar tela"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Não é possível mover o app para cá"</string> - <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) --> - <skip /> - <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) --> - <skip /> + <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Imersivo"</string> + <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Restaurar"</string> <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Maximizar"</string> <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Restaurar"</string> <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Ajustar à esquerda"</string> @@ -146,4 +145,5 @@ <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"No app"</string> <string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"No navegador"</string> <string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"OK"</string> + <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Abra apps no navegador rapidamente aqui"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-ro/strings.xml b/libs/WindowManager/Shell/res/values-ro/strings.xml index e3fe2804bdcd..df0ee45695b2 100644 --- a/libs/WindowManager/Shell/res/values-ro/strings.xml +++ b/libs/WindowManager/Shell/res/values-ro/strings.xml @@ -127,16 +127,15 @@ <string name="open_in_browser_text" msgid="9181692926376072904">"Deschide în browser"</string> <string name="new_window_text" msgid="6318648868380652280">"Fereastră nouă"</string> <string name="manage_windows_text" msgid="5567366688493093920">"Gestionează ferestrele"</string> + <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Schimbă raportul de dimensiuni"</string> <string name="close_text" msgid="4986518933445178928">"Închide"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Închide meniul"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Deschide meniul"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximizează fereastra"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Micșorează fereastra și fixeaz-o"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Aplicația nu poate fi mutată aici"</string> - <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) --> - <skip /> - <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) --> - <skip /> + <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Captivant"</string> + <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Restabilește"</string> <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Maximizează"</string> <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Restabilește"</string> <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Trage la stânga"</string> @@ -146,4 +145,5 @@ <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"În aplicație"</string> <string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"În browser"</string> <string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"OK"</string> + <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Deschide rapid aplicații în browser aici"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-ru/strings.xml b/libs/WindowManager/Shell/res/values-ru/strings.xml index 442fca3ef0a7..430f1b1448da 100644 --- a/libs/WindowManager/Shell/res/values-ru/strings.xml +++ b/libs/WindowManager/Shell/res/values-ru/strings.xml @@ -127,16 +127,15 @@ <string name="open_in_browser_text" msgid="9181692926376072904">"Открыть в браузере"</string> <string name="new_window_text" msgid="6318648868380652280">"Новое окно"</string> <string name="manage_windows_text" msgid="5567366688493093920">"Управление окнами"</string> + <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Изменить соотношение сторон"</string> <string name="close_text" msgid="4986518933445178928">"Закрыть"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Закрыть меню"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Открыть меню"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Развернуть на весь экран"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Свернуть"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Приложение нельзя сюда переместить"</string> - <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) --> - <skip /> - <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) --> - <skip /> + <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Погружение"</string> + <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Восстановить"</string> <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Развернуть"</string> <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Восстановить"</string> <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Привязать слева"</string> @@ -146,4 +145,5 @@ <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"В приложении"</string> <string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"В браузере"</string> <string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"ОК"</string> + <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Здесь можно быстро открывать приложения в браузере"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-si/strings.xml b/libs/WindowManager/Shell/res/values-si/strings.xml index 8a7ad3b9f80c..3e3766768a7f 100644 --- a/libs/WindowManager/Shell/res/values-si/strings.xml +++ b/libs/WindowManager/Shell/res/values-si/strings.xml @@ -127,16 +127,15 @@ <string name="open_in_browser_text" msgid="9181692926376072904">"බ්රව්සරයේ විවෘත කරන්න"</string> <string name="new_window_text" msgid="6318648868380652280">"නව කවුළුව"</string> <string name="manage_windows_text" msgid="5567366688493093920">"කවුළු කළමනාකරණය කරන්න"</string> + <string name="change_aspect_ratio_text" msgid="9104456064548212806">"දර්ශන අනුපාතය වෙනස් කරන්න"</string> <string name="close_text" msgid="4986518933445178928">"වසන්න"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"මෙනුව වසන්න"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"මෙනුව විවෘත කරන්න"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"තිරය උපරිම කරන්න"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"ස්නැප් තිරය"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"යෙදුම මෙතැනට ගෙන යා නොහැක"</string> - <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) --> - <skip /> - <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) --> - <skip /> + <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"ගිලෙන සුළු"</string> + <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"ප්රතිසාධනය කරන්න"</string> <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"විහිදන්න"</string> <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"ප්රතිසාධනය කරන්න"</string> <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"වමට ස්නැප් කරන්න"</string> @@ -146,4 +145,5 @@ <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"යෙදුම තුළ"</string> <string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"ඔබේ බ්රව්සරය තුළ"</string> <string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"හරි"</string> + <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"ඔබේ බ්රව්සරයේ යෙදුම් ඉක්මනින් විවෘත කරන්න"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-sk/strings.xml b/libs/WindowManager/Shell/res/values-sk/strings.xml index 4234e8073bc8..56a7edd08b23 100644 --- a/libs/WindowManager/Shell/res/values-sk/strings.xml +++ b/libs/WindowManager/Shell/res/values-sk/strings.xml @@ -127,16 +127,15 @@ <string name="open_in_browser_text" msgid="9181692926376072904">"Otvoriť v prehliadači"</string> <string name="new_window_text" msgid="6318648868380652280">"Nové okno"</string> <string name="manage_windows_text" msgid="5567366688493093920">"Správa okien"</string> + <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Zmeniť pomer strán"</string> <string name="close_text" msgid="4986518933445178928">"Zavrieť"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Zavrieť ponuku"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Otvoriť ponuku"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximalizovať obrazovku"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Zobraziť polovicu obrazovky"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Aplikácia sa sem nedá presunúť"</string> - <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) --> - <skip /> - <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) --> - <skip /> + <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Pútavé"</string> + <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Obnoviť"</string> <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Maximalizovať"</string> <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Obnoviť"</string> <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Prichytiť vľavo"</string> @@ -146,4 +145,5 @@ <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"V aplikácii"</string> <string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"V prehliadači"</string> <string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"OK"</string> + <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Tu môžete rýchlo otvárať aplikácie v prehliadači"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-sl/strings.xml b/libs/WindowManager/Shell/res/values-sl/strings.xml index ae7e524da6cc..b6344c981fc9 100644 --- a/libs/WindowManager/Shell/res/values-sl/strings.xml +++ b/libs/WindowManager/Shell/res/values-sl/strings.xml @@ -127,6 +127,7 @@ <string name="open_in_browser_text" msgid="9181692926376072904">"Odpri v brskalniku"</string> <string name="new_window_text" msgid="6318648868380652280">"Novo okno"</string> <string name="manage_windows_text" msgid="5567366688493093920">"Upravljanje oken"</string> + <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Sprememba razmerja stranic"</string> <string name="close_text" msgid="4986518933445178928">"Zapri"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Zapri meni"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Odpri meni"</string> @@ -144,4 +145,5 @@ <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"V aplikaciji"</string> <string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"V brskalniku"</string> <string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"V redu"</string> + <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Tukaj hitro odprete aplikacije v brskalniku"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-sq/strings.xml b/libs/WindowManager/Shell/res/values-sq/strings.xml index de6f681cfe74..ab7499d505f8 100644 --- a/libs/WindowManager/Shell/res/values-sq/strings.xml +++ b/libs/WindowManager/Shell/res/values-sq/strings.xml @@ -127,16 +127,15 @@ <string name="open_in_browser_text" msgid="9181692926376072904">"Hape në shfletues"</string> <string name="new_window_text" msgid="6318648868380652280">"Dritare e re"</string> <string name="manage_windows_text" msgid="5567366688493093920">"Menaxho dritaret"</string> + <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Ndrysho raportin e pamjes"</string> <string name="close_text" msgid="4986518933445178928">"Mbyll"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Mbyll menynë"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Hap menynë"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maksimizo ekranin"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Regjistro ekranin"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Aplikacioni nuk mund të zhvendoset këtu"</string> - <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) --> - <skip /> - <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) --> - <skip /> + <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Përfshirës"</string> + <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Restauro"</string> <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Maksimizo"</string> <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Restauro"</string> <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Zhvendos majtas"</string> @@ -146,4 +145,5 @@ <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Në aplikacion"</string> <string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"Në shfletuesin tënd"</string> <string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"Në rregull"</string> + <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Hapi me shpejtësi aplikacionet në shfletues këtu"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-sr/strings.xml b/libs/WindowManager/Shell/res/values-sr/strings.xml index 901d6d967a7d..773ed16dc4b9 100644 --- a/libs/WindowManager/Shell/res/values-sr/strings.xml +++ b/libs/WindowManager/Shell/res/values-sr/strings.xml @@ -127,16 +127,15 @@ <string name="open_in_browser_text" msgid="9181692926376072904">"Отворите у прегледачу"</string> <string name="new_window_text" msgid="6318648868380652280">"Нови прозор"</string> <string name="manage_windows_text" msgid="5567366688493093920">"Управљајте прозорима"</string> + <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Промените размеру"</string> <string name="close_text" msgid="4986518933445178928">"Затворите"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Затворите мени"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Отворите мени"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Повећај екран"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Уклопи екран"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Апликација не може да се премести овде"</string> - <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) --> - <skip /> - <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) --> - <skip /> + <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Имерзивне"</string> + <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Врати"</string> <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Увећајте"</string> <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Вратите"</string> <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Прикачите лево"</string> @@ -146,4 +145,5 @@ <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"У апликацији"</string> <string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"У прегледачу"</string> <string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"Потврди"</string> + <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Овде можете брзо да отварате апликације у прегледачу"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-sv/strings.xml b/libs/WindowManager/Shell/res/values-sv/strings.xml index 6566801b7c63..6f6a97b4495f 100644 --- a/libs/WindowManager/Shell/res/values-sv/strings.xml +++ b/libs/WindowManager/Shell/res/values-sv/strings.xml @@ -127,16 +127,15 @@ <string name="open_in_browser_text" msgid="9181692926376072904">"Öppna i webbläsaren"</string> <string name="new_window_text" msgid="6318648868380652280">"Nytt fönster"</string> <string name="manage_windows_text" msgid="5567366688493093920">"Hantera fönster"</string> + <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Ändra bildformat"</string> <string name="close_text" msgid="4986518933445178928">"Stäng"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Stäng menyn"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Öppna menyn"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximera skärmen"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Fäst skärmen"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Det går inte att flytta appen hit"</string> - <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) --> - <skip /> - <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) --> - <skip /> + <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Uppslukande"</string> + <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Återställ"</string> <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Utöka"</string> <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Återställ"</string> <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Fäst till vänster"</string> @@ -146,4 +145,5 @@ <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"I appen"</string> <string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"I webbläsaren"</string> <string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"OK"</string> + <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Öppna snabbt appar i webbläsaren här"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-sw/strings.xml b/libs/WindowManager/Shell/res/values-sw/strings.xml index a952011385de..72b7384fa83a 100644 --- a/libs/WindowManager/Shell/res/values-sw/strings.xml +++ b/libs/WindowManager/Shell/res/values-sw/strings.xml @@ -127,16 +127,15 @@ <string name="open_in_browser_text" msgid="9181692926376072904">"Fungua katika kivinjari"</string> <string name="new_window_text" msgid="6318648868380652280">"Dirisha Jipya"</string> <string name="manage_windows_text" msgid="5567366688493093920">"Dhibiti Windows"</string> + <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Badilisha uwiano"</string> <string name="close_text" msgid="4986518933445178928">"Funga"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Funga Menyu"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Fungua Menyu"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Panua Dirisha kwenye Skrini"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Panga Madirisha kwenye Skrini"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Imeshindwa kuhamishia programu hapa"</string> - <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) --> - <skip /> - <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) --> - <skip /> + <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Shirikishi"</string> + <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Rejesha"</string> <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Panua"</string> <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Rejesha"</string> <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Telezesha kushoto"</string> @@ -146,4 +145,5 @@ <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Kwenye programu"</string> <string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"Kwenye kivinjari chako"</string> <string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"Sawa"</string> + <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Fungua programu kwa haraka katika kivinjari chako hapa"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-ta/strings.xml b/libs/WindowManager/Shell/res/values-ta/strings.xml index 2c73d3a14620..9d902912b377 100644 --- a/libs/WindowManager/Shell/res/values-ta/strings.xml +++ b/libs/WindowManager/Shell/res/values-ta/strings.xml @@ -127,6 +127,7 @@ <string name="open_in_browser_text" msgid="9181692926376072904">"உலாவியில் திறக்கும்"</string> <string name="new_window_text" msgid="6318648868380652280">"புதிய சாளரம்"</string> <string name="manage_windows_text" msgid="5567366688493093920">"சாளரங்களை நிர்வகிக்கலாம்"</string> + <string name="change_aspect_ratio_text" msgid="9104456064548212806">"தோற்ற விகிதத்தை மாற்று"</string> <string name="close_text" msgid="4986518933445178928">"மூடும்"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"மெனுவை மூடும்"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"மெனுவைத் திறக்கும்"</string> @@ -144,4 +145,5 @@ <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"ஆப்ஸில்"</string> <string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"உங்கள் பிரவுசரில்"</string> <string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"சரி"</string> + <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"உங்கள் உலாவியில் ஆப்ஸை இங்கே விரைவாகத் திறக்கலாம்"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-te/strings.xml b/libs/WindowManager/Shell/res/values-te/strings.xml index b17d4d1afaf7..3c7c06a4fc1a 100644 --- a/libs/WindowManager/Shell/res/values-te/strings.xml +++ b/libs/WindowManager/Shell/res/values-te/strings.xml @@ -127,6 +127,7 @@ <string name="open_in_browser_text" msgid="9181692926376072904">"బ్రౌజర్లో తెరవండి"</string> <string name="new_window_text" msgid="6318648868380652280">"కొత్త విండో"</string> <string name="manage_windows_text" msgid="5567366688493093920">"విండోలను మేనేజ్ చేయండి"</string> + <string name="change_aspect_ratio_text" msgid="9104456064548212806">"ఆకార నిష్పత్తిని మార్చండి"</string> <string name="close_text" msgid="4986518933445178928">"మూసివేయండి"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"మెనూను మూసివేయండి"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"మెనూను తెరవండి"</string> @@ -144,4 +145,5 @@ <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"యాప్లో"</string> <string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"మీ బ్రౌజర్లో"</string> <string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"సరే"</string> + <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"మీ బ్రౌజర్లో ఇక్కడ యాప్లను వేగంగా తెరవండి"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-th/strings.xml b/libs/WindowManager/Shell/res/values-th/strings.xml index 43cee41f5a15..9071bfb66e92 100644 --- a/libs/WindowManager/Shell/res/values-th/strings.xml +++ b/libs/WindowManager/Shell/res/values-th/strings.xml @@ -127,6 +127,7 @@ <string name="open_in_browser_text" msgid="9181692926376072904">"เปิดในเบราว์เซอร์"</string> <string name="new_window_text" msgid="6318648868380652280">"หน้าต่างใหม่"</string> <string name="manage_windows_text" msgid="5567366688493093920">"จัดการหน้าต่าง"</string> + <string name="change_aspect_ratio_text" msgid="9104456064548212806">"เปลี่ยนสัดส่วนการแสดงผล"</string> <string name="close_text" msgid="4986518933445178928">"ปิด"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"ปิดเมนู"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"เปิดเมนู"</string> @@ -144,4 +145,5 @@ <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"ในแอป"</string> <string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"ในเบราว์เซอร์"</string> <string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"ตกลง"</string> + <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"เปิดแอปในเบราว์เซอร์ได้อย่างรวดเร็วที่นี่"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-tl/strings.xml b/libs/WindowManager/Shell/res/values-tl/strings.xml index 428499532005..a00f7839186b 100644 --- a/libs/WindowManager/Shell/res/values-tl/strings.xml +++ b/libs/WindowManager/Shell/res/values-tl/strings.xml @@ -127,6 +127,7 @@ <string name="open_in_browser_text" msgid="9181692926376072904">"Buksan sa browser"</string> <string name="new_window_text" msgid="6318648868380652280">"Bagong Window"</string> <string name="manage_windows_text" msgid="5567366688493093920">"Pamahalaan ang Mga Window"</string> + <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Baguhin ang aspect ratio"</string> <string name="close_text" msgid="4986518933445178928">"Isara"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Isara ang Menu"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Buksan ang Menu"</string> @@ -144,4 +145,5 @@ <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Sa app"</string> <string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"Sa iyong browser"</string> <string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"OK"</string> + <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Mabilis na buksan ang mga app sa iyong browser dito"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-tr/strings.xml b/libs/WindowManager/Shell/res/values-tr/strings.xml index 7eac4a8e4ffb..8310a66e9d33 100644 --- a/libs/WindowManager/Shell/res/values-tr/strings.xml +++ b/libs/WindowManager/Shell/res/values-tr/strings.xml @@ -127,16 +127,15 @@ <string name="open_in_browser_text" msgid="9181692926376072904">"Tarayıcıda aç"</string> <string name="new_window_text" msgid="6318648868380652280">"Yeni Pencere"</string> <string name="manage_windows_text" msgid="5567366688493093920">"Pencereleri yönet"</string> + <string name="change_aspect_ratio_text" msgid="9104456064548212806">"En boy oranını değiştir"</string> <string name="close_text" msgid="4986518933445178928">"Kapat"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Menüyü kapat"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Menüyü aç"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Ekranı Büyüt"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Ekranın Yarısına Tuttur"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Uygulama buraya taşınamıyor"</string> - <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) --> - <skip /> - <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) --> - <skip /> + <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Etkileyici"</string> + <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Geri yükle"</string> <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Ekranı kapla"</string> <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Geri yükle"</string> <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Sola tuttur"</string> @@ -146,4 +145,5 @@ <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Uygulamada"</string> <string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"Tarayıcınızda"</string> <string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"Tamam"</string> + <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Buradan tarayıcınızda uygulamaları hızlıca açın"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-uk/strings.xml b/libs/WindowManager/Shell/res/values-uk/strings.xml index 5fb14bf50e10..624a19e67724 100644 --- a/libs/WindowManager/Shell/res/values-uk/strings.xml +++ b/libs/WindowManager/Shell/res/values-uk/strings.xml @@ -127,16 +127,15 @@ <string name="open_in_browser_text" msgid="9181692926376072904">"Відкрити у вебпереглядачі"</string> <string name="new_window_text" msgid="6318648868380652280">"Нове вікно"</string> <string name="manage_windows_text" msgid="5567366688493093920">"Керувати вікнами"</string> + <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Змінити формат"</string> <string name="close_text" msgid="4986518933445178928">"Закрити"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Закрити меню"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Відкрити меню"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Розгорнути екран"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Зафіксувати екран"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Сюди не можна перемістити додаток"</string> - <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) --> - <skip /> - <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) --> - <skip /> + <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Реалістичність"</string> + <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Відновити"</string> <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Розгорнути"</string> <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Відновити"</string> <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Закріпити ліворуч"</string> @@ -146,4 +145,5 @@ <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"У додатку"</string> <string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"У вебпереглядачі"</string> <string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"OK"</string> + <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Швидко відкривайте додатки у вебпереглядачі"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-ur/strings.xml b/libs/WindowManager/Shell/res/values-ur/strings.xml index bb0358f12b7a..2ccaf50fee21 100644 --- a/libs/WindowManager/Shell/res/values-ur/strings.xml +++ b/libs/WindowManager/Shell/res/values-ur/strings.xml @@ -127,16 +127,15 @@ <string name="open_in_browser_text" msgid="9181692926376072904">"براؤزر میں کھولیں"</string> <string name="new_window_text" msgid="6318648868380652280">"نئی ونڈو"</string> <string name="manage_windows_text" msgid="5567366688493093920">"Windows کا نظم کریں"</string> + <string name="change_aspect_ratio_text" msgid="9104456064548212806">"تناسبی شرح کو تبدیل کریں"</string> <string name="close_text" msgid="4986518933445178928">"بند کریں"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"مینیو بند کریں"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"مینو کھولیں"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"اسکرین کو بڑا کریں"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"اسکرین کا اسناپ شاٹ لیں"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"ایپ کو یہاں منتقل نہیں کیا جا سکتا"</string> - <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) --> - <skip /> - <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) --> - <skip /> + <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"عمیق"</string> + <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"بحال کریں"</string> <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"بڑا کریں"</string> <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"بحال کریں"</string> <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"دائیں منتقل کریں"</string> @@ -146,4 +145,5 @@ <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"ایپ میں"</string> <string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"آپ کے براؤزر میں"</string> <string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"ٹھیک ہے"</string> + <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"اپنے براؤزر میں ایپس کو فوری طور پر یہاں کھولیں"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-uz/strings.xml b/libs/WindowManager/Shell/res/values-uz/strings.xml index 0648dd1c1bb8..88edc929528b 100644 --- a/libs/WindowManager/Shell/res/values-uz/strings.xml +++ b/libs/WindowManager/Shell/res/values-uz/strings.xml @@ -127,16 +127,15 @@ <string name="open_in_browser_text" msgid="9181692926376072904">"Brauzerda ochish"</string> <string name="new_window_text" msgid="6318648868380652280">"Yangi oyna"</string> <string name="manage_windows_text" msgid="5567366688493093920">"Oynalarni boshqarish"</string> + <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Tomonlar nisbatini oʻzgartirish"</string> <string name="close_text" msgid="4986518933445178928">"Yopish"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Menyuni yopish"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Menyuni ochish"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Ekranni yoyish"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Ekranni biriktirish"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Ilova bu yerga surilmaydi"</string> - <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) --> - <skip /> - <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) --> - <skip /> + <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Immersiv"</string> + <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Tiklash"</string> <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Yoyish"</string> <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Tiklash"</string> <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Chapga tortish"</string> @@ -146,4 +145,5 @@ <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Ilovada"</string> <string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"Brauzerda"</string> <string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"OK"</string> + <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Brauzerda ilovalarni shu yerda tezkor oching"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-vi/strings.xml b/libs/WindowManager/Shell/res/values-vi/strings.xml index dda2225b5f3e..c1c7653ce90c 100644 --- a/libs/WindowManager/Shell/res/values-vi/strings.xml +++ b/libs/WindowManager/Shell/res/values-vi/strings.xml @@ -127,16 +127,15 @@ <string name="open_in_browser_text" msgid="9181692926376072904">"Mở trong trình duyệt"</string> <string name="new_window_text" msgid="6318648868380652280">"Cửa sổ mới"</string> <string name="manage_windows_text" msgid="5567366688493093920">"Quản lý cửa sổ"</string> + <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Thay đổi tỷ lệ khung hình"</string> <string name="close_text" msgid="4986518933445178928">"Đóng"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Đóng trình đơn"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Mở Trình đơn"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Mở rộng màn hình"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Điều chỉnh kích thước màn hình"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Không di chuyển được ứng dụng đến đây"</string> - <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) --> - <skip /> - <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) --> - <skip /> + <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Hiển thị tối đa"</string> + <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Khôi phục"</string> <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Phóng to tối đa"</string> <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Khôi phục"</string> <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Di chuyển nhanh sang trái"</string> @@ -146,4 +145,5 @@ <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Trong ứng dụng"</string> <string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"Trên trình duyệt"</string> <string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"OK"</string> + <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Mở nhanh các ứng dụng trong trình duyệt tại đây"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml b/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml index 2fb3f5ab5ea4..83e15d8cbdf6 100644 --- a/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml +++ b/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml @@ -127,6 +127,7 @@ <string name="open_in_browser_text" msgid="9181692926376072904">"在浏览器中打开"</string> <string name="new_window_text" msgid="6318648868380652280">"新窗口"</string> <string name="manage_windows_text" msgid="5567366688493093920">"管理窗口"</string> + <string name="change_aspect_ratio_text" msgid="9104456064548212806">"更改宽高比"</string> <string name="close_text" msgid="4986518933445178928">"关闭"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"关闭菜单"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"打开菜单"</string> @@ -144,4 +145,5 @@ <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"在此应用内"</string> <string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"在浏览器中"</string> <string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"确定"</string> + <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"在此处快速在浏览器中打开应用"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml b/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml index 1d7fb4c4c6e2..f60b3efc6f38 100644 --- a/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml +++ b/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml @@ -127,16 +127,15 @@ <string name="open_in_browser_text" msgid="9181692926376072904">"在瀏覽器中開啟"</string> <string name="new_window_text" msgid="6318648868380652280">"新視窗"</string> <string name="manage_windows_text" msgid="5567366688493093920">"管理視窗"</string> + <string name="change_aspect_ratio_text" msgid="9104456064548212806">"變更長寬比"</string> <string name="close_text" msgid="4986518933445178928">"關閉"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"關閉選單"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"打開選單"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"畫面最大化"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"貼齊畫面"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"應用程式無法移至這裡"</string> - <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) --> - <skip /> - <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) --> - <skip /> + <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"身歷其境"</string> + <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"還原"</string> <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"最大化"</string> <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"還原"</string> <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"貼齊左邊"</string> @@ -146,4 +145,5 @@ <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"在應用程式內"</string> <string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"在瀏覽器中"</string> <string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"確定"</string> + <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"在此透過瀏覽器快速開啟應用程式"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml b/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml index 8083e378bf29..b2227deeccc3 100644 --- a/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml +++ b/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml @@ -127,16 +127,15 @@ <string name="open_in_browser_text" msgid="9181692926376072904">"在瀏覽器中開啟"</string> <string name="new_window_text" msgid="6318648868380652280">"新視窗"</string> <string name="manage_windows_text" msgid="5567366688493093920">"管理視窗"</string> + <string name="change_aspect_ratio_text" msgid="9104456064548212806">"變更顯示比例"</string> <string name="close_text" msgid="4986518933445178928">"關閉"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"關閉選單"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"開啟選單"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"畫面最大化"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"貼齊畫面"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"應用程式無法移至此處"</string> - <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) --> - <skip /> - <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) --> - <skip /> + <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"沉浸"</string> + <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"還原"</string> <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"最大化"</string> <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"還原"</string> <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"靠左對齊"</string> @@ -146,4 +145,5 @@ <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"使用應用程式"</string> <string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"使用瀏覽器"</string> <string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"確定"</string> + <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"在這個瀏覽器中快速開啟應用程式"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-zu/strings.xml b/libs/WindowManager/Shell/res/values-zu/strings.xml index 092efd6593fc..10d904fa17d2 100644 --- a/libs/WindowManager/Shell/res/values-zu/strings.xml +++ b/libs/WindowManager/Shell/res/values-zu/strings.xml @@ -127,16 +127,15 @@ <string name="open_in_browser_text" msgid="9181692926376072904">"Vula kubhrawuza"</string> <string name="new_window_text" msgid="6318648868380652280">"Iwindi Elisha"</string> <string name="manage_windows_text" msgid="5567366688493093920">"Phatha Amawindi"</string> + <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Shintsha ukubukeka kwesilinganiselo"</string> <string name="close_text" msgid="4986518933445178928">"Vala"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Vala Imenyu"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Vula Imenyu"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Khulisa Isikrini Sifike Ekugcineni"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Thwebula Isikrini"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"I-app ayikwazi ukuhanjiswa lapha"</string> - <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) --> - <skip /> - <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) --> - <skip /> + <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Okugxilile"</string> + <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Buyisela"</string> <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Khulisa"</string> <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Buyisela"</string> <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Chofoza kwesobunxele"</string> @@ -146,4 +145,5 @@ <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Ku-app"</string> <string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"Kubhrawuza yakho"</string> <string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"KULUNGILE"</string> + <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Ngokushesha vula ama-app ebhrawuzeni yakho lapha"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml index 8f1ef6c7e49e..012579a6d40c 100644 --- a/libs/WindowManager/Shell/res/values/strings.xml +++ b/libs/WindowManager/Shell/res/values/strings.xml @@ -301,6 +301,8 @@ <string name="screenshot_text">Screenshot</string> <!-- Accessibility text for the handle menu open in browser button [CHAR LIMIT=NONE] --> <string name="open_in_browser_text">Open in browser</string> + <!-- Accessibility text for the handle menu open in app button [CHAR LIMIT=NONE] --> + <string name="open_in_app_text">Open in App</string> <!-- Accessibility text for the handle menu new window button [CHAR LIMIT=NONE] --> <string name="new_window_text">New Window</string> <!-- Accessibility text for the handle menu new window button [CHAR LIMIT=NONE] --> diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.aidl b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedTaskInfo.aidl index e21bf8fb723c..93e635dd937c 100644 --- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.aidl +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedTaskInfo.aidl @@ -16,4 +16,4 @@ package com.android.wm.shell.shared; -parcelable GroupedRecentTaskInfo;
\ No newline at end of file +parcelable GroupedTaskInfo;
\ No newline at end of file diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedTaskInfo.java index 65e079ef4f72..03e0ab0591a1 100644 --- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.java +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedTaskInfo.java @@ -17,7 +17,8 @@ package com.android.wm.shell.shared; import android.annotation.IntDef; -import android.app.ActivityManager; +import android.app.ActivityManager.RecentTaskInfo; +import android.app.TaskInfo; import android.app.WindowConfiguration; import android.os.Parcel; import android.os.Parcelable; @@ -27,69 +28,91 @@ import androidx.annotation.Nullable; import com.android.wm.shell.shared.split.SplitBounds; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Objects; import java.util.Set; /** - * Simple container for recent tasks. May contain either a single or pair of tasks. + * Simple container for recent tasks which should be presented as a single task within the + * Overview UI. */ -public class GroupedRecentTaskInfo implements Parcelable { +public class GroupedTaskInfo implements Parcelable { - public static final int TYPE_SINGLE = 1; + public static final int TYPE_FULLSCREEN = 1; public static final int TYPE_SPLIT = 2; public static final int TYPE_FREEFORM = 3; @IntDef(prefix = {"TYPE_"}, value = { - TYPE_SINGLE, + TYPE_FULLSCREEN, TYPE_SPLIT, TYPE_FREEFORM }) public @interface GroupType {} + /** + * The type of this particular task info, can be one of TYPE_FULLSCREEN, TYPE_SPLIT or + * TYPE_FREEFORM. + */ + @GroupType + protected final int mType; + + /** + * The list of tasks associated with this single recent task info. + * TYPE_FULLSCREEN: Contains the stack of tasks associated with a single "task" in overview + * TYPE_SPLIT: Contains the two split roots of each side + * TYPE_FREEFORM: Contains the set of tasks currently in freeform mode + */ @NonNull - private final ActivityManager.RecentTaskInfo[] mTasks; + protected final List<TaskInfo> mTasks; + + /** + * Only set for TYPE_SPLIT. + * + * Information about the split bounds. + */ @Nullable - private final SplitBounds mSplitBounds; - @GroupType - private final int mType; - // TODO(b/348332802): move isMinimized inside each Task object instead once we have a - // replacement for RecentTaskInfo - private final int[] mMinimizedTaskIds; + protected final SplitBounds mSplitBounds; /** - * Create new for a single task + * Only set for TYPE_FREEFORM. + * + * TODO(b/348332802): move isMinimized inside each Task object instead once we have a + * replacement for RecentTaskInfo */ - public static GroupedRecentTaskInfo forSingleTask( - @NonNull ActivityManager.RecentTaskInfo task) { - return new GroupedRecentTaskInfo(new ActivityManager.RecentTaskInfo[]{task}, null, - TYPE_SINGLE, null /* minimizedFreeformTasks */); + @Nullable + protected final int[] mMinimizedTaskIds; + + /** + * Create new for a stack of fullscreen tasks + */ + public static GroupedTaskInfo forFullscreenTasks(@NonNull TaskInfo task) { + return new GroupedTaskInfo(List.of(task), null, TYPE_FULLSCREEN, + null /* minimizedFreeformTasks */); } /** * Create new for a pair of tasks in split screen */ - public static GroupedRecentTaskInfo forSplitTasks(@NonNull ActivityManager.RecentTaskInfo task1, - @NonNull ActivityManager.RecentTaskInfo task2, @Nullable SplitBounds splitBounds) { - return new GroupedRecentTaskInfo(new ActivityManager.RecentTaskInfo[]{task1, task2}, - splitBounds, TYPE_SPLIT, null /* minimizedFreeformTasks */); + public static GroupedTaskInfo forSplitTasks(@NonNull TaskInfo task1, + @NonNull TaskInfo task2, @Nullable SplitBounds splitBounds) { + return new GroupedTaskInfo(List.of(task1, task2), splitBounds, TYPE_SPLIT, + null /* minimizedFreeformTasks */); } /** * Create new for a group of freeform tasks */ - public static GroupedRecentTaskInfo forFreeformTasks( - @NonNull ActivityManager.RecentTaskInfo[] tasks, - @NonNull Set<Integer> minimizedFreeformTasks) { - return new GroupedRecentTaskInfo( - tasks, - null /* splitBounds */, - TYPE_FREEFORM, + public static GroupedTaskInfo forFreeformTasks( + @NonNull List<TaskInfo> tasks, + @NonNull Set<Integer> minimizedFreeformTasks) { + return new GroupedTaskInfo(tasks, null /* splitBounds */, TYPE_FREEFORM, minimizedFreeformTasks.stream().mapToInt(i -> i).toArray()); } - private GroupedRecentTaskInfo( - @NonNull ActivityManager.RecentTaskInfo[] tasks, + private GroupedTaskInfo( + @NonNull List<TaskInfo> tasks, @Nullable SplitBounds splitBounds, @GroupType int type, @Nullable int[] minimizedFreeformTaskIds) { @@ -100,52 +123,56 @@ public class GroupedRecentTaskInfo implements Parcelable { ensureAllMinimizedIdsPresent(tasks, minimizedFreeformTaskIds); } - private static void ensureAllMinimizedIdsPresent( - @NonNull ActivityManager.RecentTaskInfo[] tasks, + private void ensureAllMinimizedIdsPresent( + @NonNull List<TaskInfo> tasks, @Nullable int[] minimizedFreeformTaskIds) { if (minimizedFreeformTaskIds == null) { return; } if (!Arrays.stream(minimizedFreeformTaskIds).allMatch( - taskId -> Arrays.stream(tasks).anyMatch(task -> task.taskId == taskId))) { + taskId -> tasks.stream().anyMatch(task -> task.taskId == taskId))) { throw new IllegalArgumentException("Minimized task IDs contain non-existent Task ID."); } } - GroupedRecentTaskInfo(Parcel parcel) { - mTasks = parcel.createTypedArray(ActivityManager.RecentTaskInfo.CREATOR); + protected GroupedTaskInfo(@NonNull Parcel parcel) { + mTasks = new ArrayList(); + final int numTasks = parcel.readInt(); + for (int i = 0; i < numTasks; i++) { + mTasks.add(new TaskInfo(parcel)); + } mSplitBounds = parcel.readTypedObject(SplitBounds.CREATOR); mType = parcel.readInt(); mMinimizedTaskIds = parcel.createIntArray(); } /** - * Get primary {@link ActivityManager.RecentTaskInfo} + * Get primary {@link RecentTaskInfo} */ @NonNull - public ActivityManager.RecentTaskInfo getTaskInfo1() { - return mTasks[0]; + public TaskInfo getTaskInfo1() { + return mTasks.getFirst(); } /** - * Get secondary {@link ActivityManager.RecentTaskInfo}. + * Get secondary {@link RecentTaskInfo}. * * Used in split screen. */ @Nullable - public ActivityManager.RecentTaskInfo getTaskInfo2() { - if (mTasks.length > 1) { - return mTasks[1]; + public TaskInfo getTaskInfo2() { + if (mTasks.size() > 1) { + return mTasks.get(1); } return null; } /** - * Get all {@link ActivityManager.RecentTaskInfo}s grouped together. + * Get all {@link RecentTaskInfo}s grouped together. */ @NonNull - public List<ActivityManager.RecentTaskInfo> getTaskInfoList() { - return Arrays.asList(mTasks); + public List<TaskInfo> getTaskInfoList() { + return mTasks; } /** @@ -164,28 +191,46 @@ public class GroupedRecentTaskInfo implements Parcelable { return mType; } + @Nullable public int[] getMinimizedTaskIds() { return mMinimizedTaskIds; } @Override + public boolean equals(Object obj) { + if (!(obj instanceof GroupedTaskInfo)) { + return false; + } + GroupedTaskInfo other = (GroupedTaskInfo) obj; + return mType == other.mType + && Objects.equals(mTasks, other.mTasks) + && Objects.equals(mSplitBounds, other.mSplitBounds) + && Arrays.equals(mMinimizedTaskIds, other.mMinimizedTaskIds); + } + + @Override + public int hashCode() { + return Objects.hash(mType, mTasks, mSplitBounds, Arrays.hashCode(mMinimizedTaskIds)); + } + + @Override public String toString() { StringBuilder taskString = new StringBuilder(); - for (int i = 0; i < mTasks.length; i++) { + for (int i = 0; i < mTasks.size(); i++) { if (i == 0) { taskString.append("Task"); } else { taskString.append(", Task"); } - taskString.append(i + 1).append(": ").append(getTaskInfo(mTasks[i])); + taskString.append(i + 1).append(": ").append(getTaskInfo(mTasks.get(i))); } if (mSplitBounds != null) { taskString.append(", SplitBounds: ").append(mSplitBounds); } taskString.append(", Type="); switch (mType) { - case TYPE_SINGLE: - taskString.append("TYPE_SINGLE"); + case TYPE_FULLSCREEN: + taskString.append("TYPE_FULLSCREEN"); break; case TYPE_SPLIT: taskString.append("TYPE_SPLIT"); @@ -199,7 +244,7 @@ public class GroupedRecentTaskInfo implements Parcelable { return taskString.toString(); } - private String getTaskInfo(ActivityManager.RecentTaskInfo taskInfo) { + private String getTaskInfo(TaskInfo taskInfo) { if (taskInfo == null) { return null; } @@ -213,7 +258,12 @@ public class GroupedRecentTaskInfo implements Parcelable { @Override public void writeToParcel(Parcel parcel, int flags) { - parcel.writeTypedArray(mTasks, flags); + // We don't use the parcel list methods because we want to only write the TaskInfo state + // and not the subclasses (Recents/RunningTaskInfo) whose fields are all deprecated + parcel.writeInt(mTasks.size()); + for (int i = 0; i < mTasks.size(); i++) { + mTasks.get(i).writeTaskToParcel(parcel, flags); + } parcel.writeTypedObject(mSplitBounds, flags); parcel.writeInt(mType); parcel.writeIntArray(mMinimizedTaskIds); @@ -224,13 +274,15 @@ public class GroupedRecentTaskInfo implements Parcelable { return 0; } - public static final @android.annotation.NonNull Creator<GroupedRecentTaskInfo> CREATOR = - new Creator<GroupedRecentTaskInfo>() { - public GroupedRecentTaskInfo createFromParcel(Parcel source) { - return new GroupedRecentTaskInfo(source); + public static final Creator<GroupedTaskInfo> CREATOR = new Creator() { + @Override + public GroupedTaskInfo createFromParcel(Parcel in) { + return new GroupedTaskInfo(in); } - public GroupedRecentTaskInfo[] newArray(int size) { - return new GroupedRecentTaskInfo[size]; + + @Override + public GroupedTaskInfo[] newArray(int size) { + return new GroupedTaskInfo[size]; } }; -}
\ No newline at end of file +} diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/animation/PhysicsAnimatorTestUtils.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/animation/PhysicsAnimatorTestUtils.kt index fc3dc1465dff..f93b35e868f6 100644 --- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/animation/PhysicsAnimatorTestUtils.kt +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/animation/PhysicsAnimatorTestUtils.kt @@ -20,7 +20,7 @@ import android.os.Looper import android.util.ArrayMap import androidx.dynamicanimation.animation.FloatPropertyCompat import com.android.wm.shell.shared.animation.PhysicsAnimatorTestUtils.prepareForTest -import java.util.* +import java.util.ArrayDeque import java.util.concurrent.CountDownLatch import java.util.concurrent.TimeUnit import kotlin.collections.ArrayList @@ -74,14 +74,17 @@ object PhysicsAnimatorTestUtils { @JvmStatic fun tearDown() { - val latch = CountDownLatch(1) - animationThreadHandler.post { + if (Looper.myLooper() == animationThreadHandler.looper) { animatorTestHelpers.keys.forEach { it.cancel() } - latch.countDown() + } else { + val latch = CountDownLatch(1) + animationThreadHandler.post { + animatorTestHelpers.keys.forEach { it.cancel() } + latch.countDown() + } + latch.await(5, TimeUnit.SECONDS) } - latch.await() - animatorTestHelpers.clear() animators.clear() allAnimatedObjects.clear() @@ -348,8 +351,9 @@ object PhysicsAnimatorTestUtils { * Returns all of the values that have ever been reported to update listeners, per property. */ @Suppress("UNCHECKED_CAST") - fun <T : Any> getAnimationUpdateFrames(animator: PhysicsAnimator<T>): - UpdateFramesPerProperty<T> { + fun <T : Any> getAnimationUpdateFrames( + animator: PhysicsAnimator<T> + ): UpdateFramesPerProperty<T> { return animatorTestHelpers[animator]?.getUpdates() as UpdateFramesPerProperty<T> } diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BaseBubblePinController.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BaseBubblePinController.kt index 7086691e7431..bd129a28f049 100644 --- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BaseBubblePinController.kt +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BaseBubblePinController.kt @@ -56,6 +56,7 @@ abstract class BaseBubblePinController(private val screenSizeProvider: () -> Poi onLeft = initialLocationOnLeft screenCenterX = screenSizeProvider.invoke().x / 2 dismissZone = getExclusionRect() + listener?.onStart(if (initialLocationOnLeft) LEFT else RIGHT) } /** View has moved to [x] and [y] screen coordinates */ @@ -109,6 +110,7 @@ abstract class BaseBubblePinController(private val screenSizeProvider: () -> Poi /** Get width for exclusion rect where dismiss takes over drag */ protected abstract fun getExclusionRectWidth(): Float + /** Get height for exclusion rect where dismiss takes over drag */ protected abstract fun getExclusionRectHeight(): Float @@ -184,6 +186,9 @@ abstract class BaseBubblePinController(private val screenSizeProvider: () -> Poi /** Receive updates on location changes */ interface LocationChangeListener { + /** Bubble bar dragging has started. Includes the initial location of the bar */ + fun onStart(location: BubbleBarLocation) {} + /** * Bubble bar has been dragged to a new [BubbleBarLocation]. And the drag is still in * progress. diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubbleBarLocation.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubbleBarLocation.kt index 191875d38daf..84a22b873aaf 100644 --- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubbleBarLocation.kt +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubbleBarLocation.kt @@ -15,6 +15,7 @@ */ package com.android.wm.shell.shared.bubbles +import android.annotation.IntDef import android.os.Parcel import android.os.Parcelable @@ -60,4 +61,36 @@ enum class BubbleBarLocation : Parcelable { override fun newArray(size: Int) = arrayOfNulls<BubbleBarLocation>(size) } } + + /** Define set of constants that allow to determine why location changed. */ + @IntDef( + UpdateSource.DRAG_BAR, + UpdateSource.DRAG_BUBBLE, + UpdateSource.DRAG_EXP_VIEW, + UpdateSource.A11Y_ACTION_BAR, + UpdateSource.A11Y_ACTION_BUBBLE, + UpdateSource.A11Y_ACTION_EXP_VIEW, + ) + @Retention(AnnotationRetention.SOURCE) + annotation class UpdateSource { + companion object { + /** Location changed from dragging the bar */ + const val DRAG_BAR = 1 + + /** Location changed from dragging the bubble */ + const val DRAG_BUBBLE = 2 + + /** Location changed from dragging the expanded view */ + const val DRAG_EXP_VIEW = 3 + + /** Location changed via a11y action on the bar */ + const val A11Y_ACTION_BAR = 4 + + /** Location changed via a11y action on the bubble */ + const val A11Y_ACTION_BUBBLE = 5 + + /** Location changed via a11y action on the expanded view */ + const val A11Y_ACTION_EXP_VIEW = 6 + } + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/AppToWebUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/AppToWebUtils.kt index 65132fe89063..7243ea36b137 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/AppToWebUtils.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/AppToWebUtils.kt @@ -20,7 +20,9 @@ package com.android.wm.shell.apptoweb import android.content.Context import android.content.Intent +import android.content.Intent.ACTION_VIEW import android.content.Intent.FLAG_ACTIVITY_NEW_TASK +import android.content.Intent.FLAG_ACTIVITY_REQUIRE_NON_BROWSER import android.content.pm.PackageManager import android.content.pm.verify.domain.DomainVerificationManager import android.content.pm.verify.domain.DomainVerificationUserState @@ -31,7 +33,7 @@ import com.android.wm.shell.protolog.ShellProtoLogGroup private const val TAG = "AppToWebUtils" private val GenericBrowserIntent = Intent() - .setAction(Intent.ACTION_VIEW) + .setAction(ACTION_VIEW) .addCategory(Intent.CATEGORY_BROWSABLE) .setData(Uri.parse("http:")) @@ -67,6 +69,20 @@ fun getBrowserIntent(uri: Uri, packageManager: PackageManager): Intent? { } /** + * Returns intent if there is a non-browser application available to handle the uri. Otherwise, + * returns null. + */ +fun getAppIntent(uri: Uri, packageManager: PackageManager): Intent? { + val intent = Intent(ACTION_VIEW, uri).apply { + flags = FLAG_ACTIVITY_NEW_TASK or FLAG_ACTIVITY_REQUIRE_NON_BROWSER + } + // If there is no application available to handle intent, return null + val component = intent.resolveActivity(packageManager) ?: return null + intent.setComponent(component) + return intent +} + +/** * Returns the [DomainVerificationUserState] of the user associated with the given * [DomainVerificationManager] and the given package. */ 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 b9a305062b46..ce7a97703f44 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 @@ -21,6 +21,7 @@ import static android.view.RemoteAnimationTarget.MODE_CLOSING; import static android.view.RemoteAnimationTarget.MODE_OPENING; import static android.view.WindowManager.TRANSIT_CHANGE; import static android.view.WindowManager.TRANSIT_CLOSE_PREPARE_BACK_NAVIGATION; +import static android.window.BackEvent.EDGE_NONE; import static android.window.TransitionInfo.FLAG_BACK_GESTURE_ANIMATED; import static android.window.TransitionInfo.FLAG_IS_WALLPAPER; import static android.window.TransitionInfo.FLAG_MOVED_TO_TOP; @@ -533,7 +534,15 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont if (keyAction == MotionEvent.ACTION_DOWN) { if (!mBackGestureStarted) { - mShouldStartOnNextMoveEvent = true; + if (swipeEdge == EDGE_NONE) { + // start animation immediately for non-gestural sources (without ACTION_MOVE + // events) + mThresholdCrossed = true; + onGestureStarted(touchX, touchY, swipeEdge); + mShouldStartOnNextMoveEvent = false; + } else { + mShouldStartOnNextMoveEvent = true; + } } } else if (keyAction == MotionEvent.ACTION_MOVE) { if (!mBackGestureStarted && mShouldStartOnNextMoveEvent) { @@ -1074,6 +1083,11 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont mCurrentTracker.updateStartLocation(); BackMotionEvent startEvent = mCurrentTracker.createStartEvent(mApps[0]); dispatchOnBackStarted(mActiveCallback, startEvent); + // TODO(b/373544911): onBackStarted is dispatched here so that + // WindowOnBackInvokedDispatcher knows about the back navigation and intercepts touch + // events while it's active. It would be cleaner and safer to disable multitouch + // altogether (same as in gesture-nav). + dispatchOnBackStarted(mBackNavigationInfo.getOnBackInvokedCallback(), startEvent); } } @@ -1108,7 +1122,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont final BackMotionEvent backFinish = mCurrentTracker .createProgressEvent(); dispatchOnBackProgressed(mActiveCallback, backFinish); - if (!mBackGestureStarted) { + if (mCurrentTracker.isFinished()) { // if the down -> up gesture happened before animation // start, we have to trigger the uninterruptible transition // to finish the back animation. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java index 14f8cc74bfc5..39dc26797a81 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java @@ -740,8 +740,10 @@ public class BubbleController implements ConfigurationChangeListener, /** * Update bubble bar location and trigger and update to listeners */ - public void setBubbleBarLocation(BubbleBarLocation bubbleBarLocation) { + public void setBubbleBarLocation(BubbleBarLocation bubbleBarLocation, + @BubbleBarLocation.UpdateSource int source) { if (canShowAsBubbleBar()) { + BubbleBarLocation previousLocation = mBubblePositioner.getBubbleBarLocation(); mBubblePositioner.setBubbleBarLocation(bubbleBarLocation); if (mLayerView != null && !mLayerView.isExpandedViewDragged()) { mLayerView.updateExpandedView(); @@ -749,13 +751,47 @@ public class BubbleController implements ConfigurationChangeListener, BubbleBarUpdate bubbleBarUpdate = new BubbleBarUpdate(); bubbleBarUpdate.bubbleBarLocation = bubbleBarLocation; mBubbleStateListener.onBubbleStateChange(bubbleBarUpdate); + + logBubbleBarLocationIfChanged(bubbleBarLocation, previousLocation, source); + } + } + + private void logBubbleBarLocationIfChanged(BubbleBarLocation location, + BubbleBarLocation previous, + @BubbleBarLocation.UpdateSource int source) { + if (mLayerView == null) { + return; + } + boolean isRtl = mLayerView.isLayoutRtl(); + boolean wasLeft = previous.isOnLeft(isRtl); + boolean onLeft = location.isOnLeft(isRtl); + if (wasLeft == onLeft) { + // No changes, skip logging + return; + } + switch (source) { + case BubbleBarLocation.UpdateSource.DRAG_BAR: + case BubbleBarLocation.UpdateSource.A11Y_ACTION_BAR: + mLogger.log(onLeft ? BubbleLogger.Event.BUBBLE_BAR_MOVED_LEFT_DRAG_BAR + : BubbleLogger.Event.BUBBLE_BAR_MOVED_RIGHT_DRAG_BAR); + break; + case BubbleBarLocation.UpdateSource.DRAG_BUBBLE: + case BubbleBarLocation.UpdateSource.A11Y_ACTION_BUBBLE: + mLogger.log(onLeft ? BubbleLogger.Event.BUBBLE_BAR_MOVED_LEFT_DRAG_BUBBLE + : BubbleLogger.Event.BUBBLE_BAR_MOVED_RIGHT_DRAG_BUBBLE); + break; + case BubbleBarLocation.UpdateSource.DRAG_EXP_VIEW: + case BubbleBarLocation.UpdateSource.A11Y_ACTION_EXP_VIEW: + // TODO(b/349845968): move logging from BubbleBarLayerView to here + break; } } /** * Animate bubble bar to the given location. The location change is transient. It does not * update the state of the bubble bar. - * To update bubble bar pinned location, use {@link #setBubbleBarLocation(BubbleBarLocation)}. + * To update bubble bar pinned location, use + * {@link #setBubbleBarLocation(BubbleBarLocation, int)}. */ public void animateBubbleBarLocation(BubbleBarLocation bubbleBarLocation) { if (canShowAsBubbleBar()) { @@ -1259,6 +1295,14 @@ public class BubbleController implements ConfigurationChangeListener, // We still have bubbles, if we dragged an individual bubble to dismiss we were expanded // so re-expand to whatever is selected. showExpandedViewForBubbleBar(); + if (bubbleKey.equals(selectedBubbleKey)) { + // We dragged the selected bubble to dismiss, log switch event + if (mBubbleData.getSelectedBubble() instanceof Bubble) { + // Log only bubbles as overflow can't be dragged + mLogger.log((Bubble) mBubbleData.getSelectedBubble(), + BubbleLogger.Event.BUBBLE_BAR_BUBBLE_SWITCHED); + } + } } } @@ -1301,10 +1345,16 @@ public class BubbleController implements ConfigurationChangeListener, public void expandStackAndSelectBubbleFromLauncher(String key, int topOnScreen) { mBubblePositioner.setBubbleBarTopOnScreen(topOnScreen); + boolean wasExpanded = (mLayerView != null && mLayerView.isExpanded()); + if (BubbleOverflow.KEY.equals(key)) { mBubbleData.setSelectedBubbleFromLauncher(mBubbleData.getOverflow()); mLayerView.showExpandedView(mBubbleData.getOverflow()); - mLogger.log(BubbleLogger.Event.BUBBLE_BAR_EXPANDED); + if (wasExpanded) { + mLogger.log(BubbleLogger.Event.BUBBLE_BAR_BUBBLE_SWITCHED); + } else { + mLogger.log(BubbleLogger.Event.BUBBLE_BAR_EXPANDED); + } return; } @@ -1316,7 +1366,11 @@ public class BubbleController implements ConfigurationChangeListener, // already in the stack mBubbleData.setSelectedBubbleFromLauncher(b); mLayerView.showExpandedView(b); - mLogger.log(b, BubbleLogger.Event.BUBBLE_BAR_EXPANDED); + if (wasExpanded) { + mLogger.log(b, BubbleLogger.Event.BUBBLE_BAR_BUBBLE_SWITCHED); + } else { + mLogger.log(b, BubbleLogger.Event.BUBBLE_BAR_EXPANDED); + } } else if (mBubbleData.hasOverflowBubbleWithKey(b.getKey())) { // TODO: (b/271468319) handle overflow } else { @@ -2045,6 +2099,9 @@ public class BubbleController implements ConfigurationChangeListener, // Only need to update the layer view if we're currently expanded for selection changes. if (mLayerView != null && mLayerView.isExpanded()) { mLayerView.showExpandedView(selectedBubble); + if (selectedBubble instanceof Bubble bubble) { + mLogger.log(bubble, BubbleLogger.Event.BUBBLE_BAR_BUBBLE_SWITCHED); + } } } }; @@ -2568,9 +2625,10 @@ public class BubbleController implements ConfigurationChangeListener, } @Override - public void setBubbleBarLocation(BubbleBarLocation location) { + public void setBubbleBarLocation(BubbleBarLocation location, + @BubbleBarLocation.UpdateSource int source) { mMainExecutor.execute(() -> - mController.setBubbleBarLocation(location)); + mController.setBubbleBarLocation(location, source)); } @Override diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedViewManager.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedViewManager.kt index ec4854b47aff..6423eed59165 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedViewManager.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedViewManager.kt @@ -32,7 +32,10 @@ interface BubbleExpandedViewManager { fun isStackExpanded(): Boolean fun isShowingAsBubbleBar(): Boolean fun hideCurrentInputMethod() - fun updateBubbleBarLocation(location: BubbleBarLocation) + fun updateBubbleBarLocation( + location: BubbleBarLocation, + @BubbleBarLocation.UpdateSource source: Int, + ) companion object { /** @@ -82,8 +85,11 @@ interface BubbleExpandedViewManager { controller.hideCurrentInputMethod() } - override fun updateBubbleBarLocation(location: BubbleBarLocation) { - controller.bubbleBarLocation = location + override fun updateBubbleBarLocation( + location: BubbleBarLocation, + @BubbleBarLocation.UpdateSource source: Int, + ) { + controller.setBubbleBarLocation(location, source) } } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl index 1855b938f48e..9c2d35431554 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl @@ -44,7 +44,7 @@ interface IBubbles { oneway void showUserEducation(in int positionX, in int positionY) = 8; - oneway void setBubbleBarLocation(in BubbleBarLocation location) = 9; + oneway void setBubbleBarLocation(in BubbleBarLocation location, in int source) = 9; oneway void updateBubbleBarTopOnScreen(in int topOnScreen) = 10; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java index 0ce651c3f1fe..3764bcd42ac6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java @@ -222,7 +222,7 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView mHandleView.setAccessibilityDelegate(new HandleViewAccessibilityDelegate()); } - mMenuViewController = new BubbleBarMenuViewController(mContext, this); + mMenuViewController = new BubbleBarMenuViewController(mContext, mHandleView, this); mMenuViewController.setListener(new BubbleBarMenuViewController.Listener() { @Override public void onMenuVisibilityChanged(boolean visible) { @@ -241,12 +241,14 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView if (mListener != null) { mListener.onUnBubbleConversation(bubble.getKey()); } + mBubbleLogger.log(bubble, BubbleLogger.Event.BUBBLE_BAR_APP_MENU_OPT_OUT); } @Override public void onOpenAppSettings(Bubble bubble) { mManager.collapseStack(); mContext.startActivityAsUser(bubble.getSettingsIntent(mContext), bubble.getUser()); + mBubbleLogger.log(bubble, BubbleLogger.Event.BUBBLE_BAR_APP_MENU_GO_TO_SETTINGS); } @Override @@ -635,11 +637,13 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView return true; } if (action == R.id.action_move_bubble_bar_left) { - mManager.updateBubbleBarLocation(BubbleBarLocation.LEFT); + mManager.updateBubbleBarLocation(BubbleBarLocation.LEFT, + BubbleBarLocation.UpdateSource.A11Y_ACTION_EXP_VIEW); return true; } if (action == R.id.action_move_bubble_bar_right) { - mManager.updateBubbleBarLocation(BubbleBarLocation.RIGHT); + mManager.updateBubbleBarLocation(BubbleBarLocation.RIGHT, + BubbleBarLocation.UpdateSource.A11Y_ACTION_EXP_VIEW); return true; } return false; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java index e781c07f01a7..712e41b0b3c5 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java @@ -17,17 +17,18 @@ package com.android.wm.shell.bubbles.bar; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; +import android.animation.ArgbEvaluator; import android.animation.ObjectAnimator; import android.annotation.Nullable; import android.content.Context; -import android.graphics.Outline; -import android.graphics.Path; -import android.graphics.RectF; +import android.graphics.Canvas; +import android.graphics.Paint; import android.util.AttributeSet; import android.view.View; -import android.view.ViewOutlineProvider; import androidx.annotation.ColorInt; +import androidx.annotation.VisibleForTesting; +import androidx.core.animation.IntProperty; import androidx.core.content.ContextCompat; import com.android.wm.shell.R; @@ -37,14 +38,33 @@ import com.android.wm.shell.R; */ public class BubbleBarHandleView extends View { private static final long COLOR_CHANGE_DURATION = 120; - // Path used to draw the dots - private final Path mPath = new Path(); + /** Custom property to set handle color. */ + private static final IntProperty<BubbleBarHandleView> HANDLE_COLOR = new IntProperty<>( + "handleColor") { + @Override + public void setValue(BubbleBarHandleView bubbleBarHandleView, int color) { + bubbleBarHandleView.setHandleColor(color); + } + + @Override + public Integer get(BubbleBarHandleView bubbleBarHandleView) { + return bubbleBarHandleView.getHandleColor(); + } + }; + + @VisibleForTesting + final Paint mHandlePaint = new Paint(); private final @ColorInt int mHandleLightColor; private final @ColorInt int mHandleDarkColor; - private @ColorInt int mCurrentColor; + private final ArgbEvaluator mArgbEvaluator = ArgbEvaluator.getInstance(); + private final float mHandleHeight; + private final float mHandleWidth; + private float mCurrentHandleHeight; + private float mCurrentHandleWidth; @Nullable private ObjectAnimator mColorChangeAnim; + private @ColorInt int mRegionSamplerColor; public BubbleBarHandleView(Context context) { this(context, null /* attrs */); @@ -61,30 +81,52 @@ public class BubbleBarHandleView extends View { public BubbleBarHandleView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); - final int handleHeight = getResources().getDimensionPixelSize( + mHandlePaint.setFlags(Paint.ANTI_ALIAS_FLAG); + mHandlePaint.setStyle(Paint.Style.FILL); + mHandlePaint.setColor(0); + mHandleHeight = getResources().getDimensionPixelSize( R.dimen.bubble_bar_expanded_view_handle_height); + mHandleWidth = getResources().getDimensionPixelSize( + R.dimen.bubble_bar_expanded_view_caption_width); mHandleLightColor = ContextCompat.getColor(getContext(), R.color.bubble_bar_expanded_view_handle_light); mHandleDarkColor = ContextCompat.getColor(getContext(), R.color.bubble_bar_expanded_view_handle_dark); - - setClipToOutline(true); - setOutlineProvider(new ViewOutlineProvider() { - @Override - public void getOutline(View view, Outline outline) { - final int handleCenterY = view.getHeight() / 2; - final int handleTop = handleCenterY - handleHeight / 2; - final int handleBottom = handleTop + handleHeight; - final int radius = handleHeight / 2; - RectF handle = new RectF(/* left = */ 0, handleTop, view.getWidth(), handleBottom); - mPath.reset(); - mPath.addRoundRect(handle, radius, radius, Path.Direction.CW); - outline.setPath(mPath); - } - }); + mCurrentHandleHeight = mHandleHeight; + mCurrentHandleWidth = mHandleWidth; setContentDescription(getResources().getString(R.string.handle_text)); } + private void setHandleColor(int color) { + mHandlePaint.setColor(color); + invalidate(); + } + + private int getHandleColor() { + return mHandlePaint.getColor(); + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + float handleLeft = (getWidth() - mCurrentHandleWidth) / 2; + float handleRight = handleLeft + mCurrentHandleWidth; + float handleCenterY = (float) getHeight() / 2; + float handleTop = (int) (handleCenterY - mCurrentHandleHeight / 2); + float handleBottom = handleTop + mCurrentHandleHeight; + float cornerRadius = mCurrentHandleHeight / 2; + canvas.drawRoundRect(handleLeft, handleTop, handleRight, handleBottom, cornerRadius, + cornerRadius, mHandlePaint); + } + + /** Sets handle width, height and color. Does not change the layout properties */ + private void setHandleProperties(float width, float height, int color) { + mCurrentHandleHeight = height; + mCurrentHandleWidth = width; + mHandlePaint.setColor(color); + invalidate(); + } + /** * Updates the handle color. * @@ -94,15 +136,15 @@ public class BubbleBarHandleView extends View { */ public void updateHandleColor(boolean isRegionDark, boolean animated) { int newColor = isRegionDark ? mHandleLightColor : mHandleDarkColor; - if (newColor == mCurrentColor) { + if (newColor == mRegionSamplerColor) { return; } + mRegionSamplerColor = newColor; if (mColorChangeAnim != null) { mColorChangeAnim.cancel(); } - mCurrentColor = newColor; if (animated) { - mColorChangeAnim = ObjectAnimator.ofArgb(this, "backgroundColor", newColor); + mColorChangeAnim = ObjectAnimator.ofArgb(this, HANDLE_COLOR, newColor); mColorChangeAnim.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { @@ -112,7 +154,39 @@ public class BubbleBarHandleView extends View { mColorChangeAnim.setDuration(COLOR_CHANGE_DURATION); mColorChangeAnim.start(); } else { - setBackgroundColor(newColor); + setHandleColor(newColor); } } + + /** Returns handle padding top. */ + public int getHandlePaddingTop() { + return (getHeight() - getResources().getDimensionPixelSize( + R.dimen.bubble_bar_expanded_view_handle_height)) / 2; + } + + /** Animates handle for the bubble menu. */ + public void animateHandleForMenu(float progress, float widthDelta, float heightDelta, + int menuColor) { + float currentWidth = mHandleWidth + widthDelta * progress; + float currentHeight = mHandleHeight + heightDelta * progress; + int color = (int) mArgbEvaluator.evaluate(progress, mRegionSamplerColor, menuColor); + setHandleProperties(currentWidth, currentHeight, color); + setTranslationY(heightDelta * progress / 2); + } + + /** Restores all the properties that were animated to the default values. */ + public void restoreAnimationDefaults() { + setHandleProperties(mHandleWidth, mHandleHeight, mRegionSamplerColor); + setTranslationY(0); + } + + /** Returns the handle height. */ + public int getHandleHeight() { + return (int) mHandleHeight; + } + + /** Returns the handle width. */ + public int getHandleWidth() { + return (int) mHandleWidth; + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java index 402818c80b01..0c05e3c5115c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java @@ -19,6 +19,7 @@ package com.android.wm.shell.bubbles.bar; import static com.android.wm.shell.shared.animation.Interpolators.ALPHA_IN; import static com.android.wm.shell.shared.animation.Interpolators.ALPHA_OUT; import static com.android.wm.shell.bubbles.Bubbles.DISMISS_USER_GESTURE; +import static com.android.wm.shell.shared.bubbles.BubbleConstants.BUBBLE_EXPANDED_SCRIM_ALPHA; import android.annotation.Nullable; import android.content.Context; @@ -66,8 +67,6 @@ public class BubbleBarLayerView extends FrameLayout private static final String TAG = BubbleBarLayerView.class.getSimpleName(); - private static final float SCRIM_ALPHA = 0.2f; - private final BubbleController mBubbleController; private final BubbleData mBubbleData; private final BubblePositioner mPositioner; @@ -124,18 +123,7 @@ public class BubbleBarLayerView extends FrameLayout mBubbleExpandedViewPinController = new BubbleExpandedViewPinController( context, this, mPositioner); - mBubbleExpandedViewPinController.setListener( - new BaseBubblePinController.LocationChangeListener() { - @Override - public void onChange(@NonNull BubbleBarLocation bubbleBarLocation) { - mBubbleController.animateBubbleBarLocation(bubbleBarLocation); - } - - @Override - public void onRelease(@NonNull BubbleBarLocation location) { - mBubbleController.setBubbleBarLocation(location); - } - }); + mBubbleExpandedViewPinController.setListener(new LocationChangeListener()); setOnClickListener(view -> hideModalOrCollapse()); } @@ -238,11 +226,7 @@ public class BubbleBarLayerView extends FrameLayout DragListener dragListener = inDismiss -> { if (inDismiss && mExpandedBubble != null) { mBubbleController.dismissBubble(mExpandedBubble.getKey(), DISMISS_USER_GESTURE); - if (mExpandedBubble instanceof Bubble) { - // Only a bubble can be dragged to dismiss - mBubbleLogger.log((Bubble) mExpandedBubble, - BubbleLogger.Event.BUBBLE_BAR_BUBBLE_DISMISSED_DRAG_EXP_VIEW); - } + logBubbleEvent(BubbleLogger.Event.BUBBLE_BAR_BUBBLE_DISMISSED_DRAG_EXP_VIEW); } }; mDragController = new BubbleBarExpandedViewDragController( @@ -401,7 +385,7 @@ public class BubbleBarLayerView extends FrameLayout if (show) { mScrimView.animate() .setInterpolator(ALPHA_IN) - .alpha(SCRIM_ALPHA) + .alpha(BUBBLE_EXPANDED_SCRIM_ALPHA) .start(); } else { mScrimView.animate() @@ -423,10 +407,48 @@ public class BubbleBarLayerView extends FrameLayout } } + /** + * Log the event only if {@link #mExpandedBubble} is a {@link Bubble}. + * <p> + * Skips logging if it is {@link BubbleOverflow}. + */ + private void logBubbleEvent(BubbleLogger.Event event) { + if (mExpandedBubble != null && mExpandedBubble instanceof Bubble bubble) { + mBubbleLogger.log(bubble, event); + } + } + @Nullable @VisibleForTesting public BubbleBarExpandedViewDragController getDragController() { return mDragController; } + private class LocationChangeListener implements + BaseBubblePinController.LocationChangeListener { + + private BubbleBarLocation mInitialLocation; + + @Override + public void onStart(@NonNull BubbleBarLocation location) { + mInitialLocation = location; + } + + @Override + public void onChange(@NonNull BubbleBarLocation bubbleBarLocation) { + mBubbleController.animateBubbleBarLocation(bubbleBarLocation); + } + + @Override + public void onRelease(@NonNull BubbleBarLocation location) { + mBubbleController.setBubbleBarLocation(location, + BubbleBarLocation.UpdateSource.DRAG_EXP_VIEW); + if (location != mInitialLocation) { + BubbleLogger.Event event = location.isOnLeft(isLayoutRtl()) + ? BubbleLogger.Event.BUBBLE_BAR_MOVED_LEFT_DRAG_EXP_VIEW + : BubbleLogger.Event.BUBBLE_BAR_MOVED_RIGHT_DRAG_EXP_VIEW; + logBubbleEvent(event); + } + } + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuView.java index 52b807abddd6..99e20097e61c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuView.java @@ -16,7 +16,6 @@ package com.android.wm.shell.bubbles.bar; import android.annotation.ColorInt; -import android.annotation.Nullable; import android.content.Context; import android.content.res.ColorStateList; import android.content.res.TypedArray; @@ -43,13 +42,15 @@ import java.util.ArrayList; */ public class BubbleBarMenuView extends LinearLayout { - public static final Object DISMISS_ACTION_TAG = new Object(); - private ViewGroup mBubbleSectionView; private ViewGroup mActionsSectionView; private ImageView mBubbleIconView; private ImageView mBubbleDismissIconView; private TextView mBubbleTitleView; + // The animation has three stages. Each stage transition lasts until the animation ends. In + // stage 1, the title item content fades in. In stage 2, the background of the option items + // fades in. In stage 3, the option item content fades in. + private static final int SHOW_MENU_STAGES_COUNT = 3; public BubbleBarMenuView(Context context) { this(context, null /* attrs */); @@ -100,6 +101,35 @@ public class BubbleBarMenuView extends LinearLayout { } } + /** Animates the menu from the specified start scale. */ + public void animateFromStartScale(float currentScale, float progress) { + int menuItemElevation = getResources().getDimensionPixelSize( + R.dimen.bubble_manage_menu_elevation); + setScaleX(currentScale); + setScaleY(currentScale); + setAlphaForTitleViews(progress); + mBubbleSectionView.setElevation(menuItemElevation * progress); + float actionsBackgroundAlpha = Math.max(0, + (progress - (float) 1 / SHOW_MENU_STAGES_COUNT) * (SHOW_MENU_STAGES_COUNT - 1)); + float actionItemsAlpha = Math.max(0, + (progress - (float) 2 / SHOW_MENU_STAGES_COUNT) * SHOW_MENU_STAGES_COUNT); + mActionsSectionView.setAlpha(actionsBackgroundAlpha); + mActionsSectionView.setElevation(menuItemElevation * actionsBackgroundAlpha); + setMenuItemViewsAlpha(actionItemsAlpha); + } + + private void setAlphaForTitleViews(float alpha) { + mBubbleIconView.setAlpha(alpha); + mBubbleTitleView.setAlpha(alpha); + mBubbleDismissIconView.setAlpha(alpha); + } + + private void setMenuItemViewsAlpha(float alpha) { + for (int i = mActionsSectionView.getChildCount() - 1; i >= 0; i--) { + mActionsSectionView.getChildAt(i).setAlpha(alpha); + } + } + /** Update menu details with bubble info */ void updateInfo(Bubble bubble) { if (bubble.getIcon() != null) { @@ -123,9 +153,6 @@ public class BubbleBarMenuView extends LinearLayout { R.layout.bubble_bar_menu_item, mActionsSectionView, false); itemView.update(action.mIcon, action.mTitle, action.mTint); itemView.setOnClickListener(action.mOnClick); - if (action.mTag != null) { - itemView.setTag(action.mTag); - } mActionsSectionView.addView(itemView); } } @@ -159,6 +186,11 @@ public class BubbleBarMenuView extends LinearLayout { return mBubbleSectionView.getAlpha(); } + /** Return title menu item height. */ + public float getTitleItemHeight() { + return mBubbleSectionView.getHeight(); + } + /** * Menu action details used to create menu items */ @@ -166,8 +198,6 @@ public class BubbleBarMenuView extends LinearLayout { private Icon mIcon; private @ColorInt int mTint; private String mTitle; - @Nullable - private Object mTag; private OnClickListener mOnClick; MenuAction(Icon icon, String title, OnClickListener onClick) { @@ -180,14 +210,5 @@ public class BubbleBarMenuView extends LinearLayout { this.mTint = tint; this.mOnClick = onClick; } - - MenuAction(Icon icon, String title, @ColorInt int tint, @Nullable Object tag, - OnClickListener onClick) { - this.mIcon = icon; - this.mTitle = title; - this.mTint = tint; - this.mTag = tag; - this.mOnClick = onClick; - } } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuViewController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuViewController.java index 5ed01b66ec67..9dd0cae20370 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuViewController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuViewController.java @@ -15,6 +15,9 @@ */ package com.android.wm.shell.bubbles.bar; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ValueAnimator; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; @@ -26,13 +29,10 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import androidx.dynamicanimation.animation.DynamicAnimation; -import androidx.dynamicanimation.animation.SpringForce; - +import com.android.app.animation.Interpolators; import com.android.wm.shell.Flags; import com.android.wm.shell.R; import com.android.wm.shell.bubbles.Bubble; -import com.android.wm.shell.shared.animation.PhysicsAnimator; import java.util.ArrayList; @@ -40,22 +40,26 @@ import java.util.ArrayList; * Manages bubble bar expanded view menu presentation and animations */ class BubbleBarMenuViewController { - private static final float MENU_INITIAL_SCALE = 0.5f; + + private static final float WIDTH_SWAP_FRACTION = 0.4F; + private static final long MENU_ANIMATION_DURATION = 600; + private final Context mContext; private final ViewGroup mRootView; + private final BubbleBarHandleView mHandleView; private @Nullable Listener mListener; private @Nullable Bubble mBubble; private @Nullable BubbleBarMenuView mMenuView; /** A transparent view used to intercept touches to collapse menu when presented */ private @Nullable View mScrimView; - private @Nullable PhysicsAnimator<BubbleBarMenuView> mMenuAnimator; - private PhysicsAnimator.SpringConfig mMenuSpringConfig; + private @Nullable ValueAnimator mMenuAnimator; + - BubbleBarMenuViewController(Context context, ViewGroup rootView) { + BubbleBarMenuViewController(Context context, BubbleBarHandleView handleView, + ViewGroup rootView) { mContext = context; mRootView = rootView; - mMenuSpringConfig = new PhysicsAnimator.SpringConfig( - SpringForce.STIFFNESS_MEDIUM, SpringForce.DAMPING_RATIO_LOW_BOUNCY); + mHandleView = handleView; } /** Tells if the menu is visible or being animated */ @@ -81,20 +85,21 @@ class BubbleBarMenuViewController { if (mMenuView == null || mScrimView == null) { setupMenu(); } - cancelAnimations(); - mMenuView.setVisibility(View.VISIBLE); - mScrimView.setVisibility(View.VISIBLE); - Runnable endActions = () -> { - mMenuView.getChildAt(0).requestAccessibilityFocus(); - if (mListener != null) { - mListener.onMenuVisibilityChanged(true /* isShown */); + runOnMenuIsMeasured(() -> { + mMenuView.setVisibility(View.VISIBLE); + mScrimView.setVisibility(View.VISIBLE); + Runnable endActions = () -> { + mMenuView.getChildAt(0).requestAccessibilityFocus(); + if (mListener != null) { + mListener.onMenuVisibilityChanged(true /* isShown */); + } + }; + if (animated) { + animateTransition(true /* show */, endActions); + } else { + endActions.run(); } - }; - if (animated) { - animateTransition(true /* show */, endActions); - } else { - endActions.run(); - } + }); } /** @@ -103,18 +108,30 @@ class BubbleBarMenuViewController { */ void hideMenu(boolean animated) { if (mMenuView == null || mScrimView == null) return; - cancelAnimations(); - Runnable endActions = () -> { - mMenuView.setVisibility(View.GONE); - mScrimView.setVisibility(View.GONE); - if (mListener != null) { - mListener.onMenuVisibilityChanged(false /* isShown */); + runOnMenuIsMeasured(() -> { + Runnable endActions = () -> { + mHandleView.restoreAnimationDefaults(); + mMenuView.setVisibility(View.GONE); + mScrimView.setVisibility(View.GONE); + mHandleView.setVisibility(View.VISIBLE); + if (mListener != null) { + mListener.onMenuVisibilityChanged(false /* isShown */); + } + }; + if (animated) { + animateTransition(false /* show */, endActions); + } else { + endActions.run(); } - }; - if (animated) { - animateTransition(false /* show */, endActions); + }); + } + + private void runOnMenuIsMeasured(Runnable action) { + if (mMenuView.getWidth() == 0 || mMenuView.getHeight() == 0) { + // the menu view is not yet measured, postpone showing the animation + mMenuView.post(() -> runOnMenuIsMeasured(action)); } else { - endActions.run(); + action.run(); } } @@ -125,24 +142,63 @@ class BubbleBarMenuViewController { */ private void animateTransition(boolean show, Runnable endActions) { if (mMenuView == null) return; - mMenuAnimator = PhysicsAnimator.getInstance(mMenuView); - mMenuAnimator.setDefaultSpringConfig(mMenuSpringConfig); - mMenuAnimator - .spring(DynamicAnimation.ALPHA, show ? 1f : 0f) - .spring(DynamicAnimation.SCALE_Y, show ? 1f : MENU_INITIAL_SCALE) - .withEndActions(() -> { - mMenuAnimator = null; - endActions.run(); - }) - .start(); + float startValue = show ? 0 : 1; + if (mMenuAnimator != null && mMenuAnimator.isRunning()) { + startValue = (float) mMenuAnimator.getAnimatedValue(); + mMenuAnimator.cancel(); + } + ValueAnimator showMenuAnimation = ValueAnimator.ofFloat(startValue, show ? 1 : 0); + showMenuAnimation.setDuration(MENU_ANIMATION_DURATION); + showMenuAnimation.setInterpolator(Interpolators.EMPHASIZED); + showMenuAnimation.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mMenuAnimator = null; + endActions.run(); + } + }); + mMenuAnimator = showMenuAnimation; + setupAnimatorListener(showMenuAnimation); + showMenuAnimation.start(); } - /** Cancel running animations */ - private void cancelAnimations() { - if (mMenuAnimator != null) { - mMenuAnimator.cancel(); - mMenuAnimator = null; + /** Setup listener that orchestrates the animation. */ + private void setupAnimatorListener(ValueAnimator showMenuAnimation) { + // Getting views properties start values + int widthDiff = mMenuView.getWidth() - mHandleView.getHandleWidth(); + int handleHeight = mHandleView.getHandleHeight(); + float targetWidth = mHandleView.getHandleWidth() + widthDiff * WIDTH_SWAP_FRACTION; + float targetHeight = targetWidth * mMenuView.getTitleItemHeight() / mMenuView.getWidth(); + int menuColor; + try (TypedArray ta = mContext.obtainStyledAttributes(new int[]{ + com.android.internal.R.attr.materialColorSurfaceBright, + })) { + menuColor = ta.getColor(0, Color.WHITE); } + // Calculating deltas + float swapScale = targetWidth / mMenuView.getWidth(); + float handleWidthDelta = targetWidth - mHandleView.getHandleWidth(); + float handleHeightDelta = targetHeight - handleHeight; + // Setting update listener that will orchestrate the animation + showMenuAnimation.addUpdateListener(animator -> { + float animationProgress = (float) animator.getAnimatedValue(); + boolean showHandle = animationProgress <= WIDTH_SWAP_FRACTION; + mHandleView.setVisibility(showHandle ? View.VISIBLE : View.GONE); + mMenuView.setVisibility(showHandle ? View.GONE : View.VISIBLE); + if (showHandle) { + float handleAnimationProgress = animationProgress / WIDTH_SWAP_FRACTION; + mHandleView.animateHandleForMenu(handleAnimationProgress, handleWidthDelta, + handleHeightDelta, menuColor); + } else { + mMenuView.setTranslationY(mHandleView.getHandlePaddingTop()); + mMenuView.setPivotY(0); + mMenuView.setPivotX((float) mMenuView.getWidth() / 2); + float menuAnimationProgress = + (animationProgress - WIDTH_SWAP_FRACTION) / (1 - WIDTH_SWAP_FRACTION); + float currentMenuScale = swapScale + (1 - swapScale) * menuAnimationProgress; + mMenuView.animateFromStartScale(currentMenuScale, menuAnimationProgress); + } + }); } /** Sets up and inflate menu views */ @@ -150,9 +206,6 @@ class BubbleBarMenuViewController { // Menu view setup mMenuView = (BubbleBarMenuView) LayoutInflater.from(mContext).inflate( R.layout.bubble_bar_menu_view, mRootView, false); - mMenuView.setAlpha(0f); - mMenuView.setPivotY(0f); - mMenuView.setScaleY(MENU_INITIAL_SCALE); mMenuView.setOnCloseListener(() -> hideMenu(true /* animated */)); if (mBubble != null) { mMenuView.updateInfo(mBubble); @@ -212,7 +265,6 @@ class BubbleBarMenuViewController { Icon.createWithResource(resources, R.drawable.ic_remove_no_shadow), resources.getString(R.string.bubble_dismiss_text), tintColor, - BubbleBarMenuView.DISMISS_ACTION_TAG, view -> { hideMenu(true /* animated */); if (mListener != null) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipAppOpsListener.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipAppOpsListener.kt index 4abb35c2a428..193c593e2ab2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipAppOpsListener.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipAppOpsListener.kt @@ -16,8 +16,11 @@ package com.android.wm.shell.common.pip import android.app.AppOpsManager +import android.content.ComponentName import android.content.Context import android.content.pm.PackageManager +import android.util.Pair +import com.android.internal.annotations.VisibleForTesting import com.android.wm.shell.common.ShellExecutor class PipAppOpsListener( @@ -27,10 +30,12 @@ class PipAppOpsListener( ) { private val mAppOpsManager: AppOpsManager = checkNotNull( mContext.getSystemService(Context.APP_OPS_SERVICE) as AppOpsManager) + private var mTopPipActivityInfoSupplier: (Context) -> Pair<ComponentName?, Int> = + PipUtils::getTopPipActivity private val mAppOpsChangedListener = AppOpsManager.OnOpChangedListener { _, packageName -> try { // Dismiss the PiP once the user disables the app ops setting for that package - val topPipActivityInfo = PipUtils.getTopPipActivity(mContext) + val topPipActivityInfo = mTopPipActivityInfoSupplier.invoke(mContext) val componentName = topPipActivityInfo.first ?: return@OnOpChangedListener val userId = topPipActivityInfo.second val appInfo = mContext.packageManager @@ -75,4 +80,9 @@ class PipAppOpsListener( /** Dismisses the PIP window. */ fun dismissPip() } + + @VisibleForTesting + fun setTopPipActivityInfoSupplier(supplier: (Context) -> Pair<ComponentName?, Int>) { + mTopPipActivityInfoSupplier = supplier + } }
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt index b83b5f341dda..8ef20d1d6b93 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt @@ -44,7 +44,8 @@ object PipUtils { private const val TAG = "PipUtils" // Minimum difference between two floats (e.g. aspect ratios) to consider them not equal. - private const val EPSILON = 1e-7 + // TODO b/377530560: Restore epsilon once a long term fix is merged for non-config-at-end issue. + private const val EPSILON = 0.05f /** * @return the ComponentName and user id of the top non-SystemUI activity in the pinned stack. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java index 886330f3264a..c99d9ba862c1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java @@ -188,6 +188,8 @@ public class CompatUIController implements OnDisplaysChangedListener, */ private boolean mIsFirstReachabilityEducationRunning; + private boolean mIsInDesktopMode; + @NonNull private final CompatUIStatusManager mCompatUIStatusManager; @@ -253,18 +255,19 @@ public class CompatUIController implements OnDisplaysChangedListener, if (taskInfo != null && !taskInfo.appCompatTaskInfo.isTopActivityInSizeCompat()) { mSetOfTaskIdsShowingRestartDialog.remove(taskInfo.taskId); } - - if (taskInfo != null && taskListener != null) { - updateActiveTaskInfo(taskInfo); - } - - // We close all the Compat UI educations in case we're in desktop mode. - if (taskInfo.configuration == null || taskListener == null - || isInDesktopMode(taskInfo.displayId)) { + mIsInDesktopMode = isInDesktopMode(taskInfo); + // We close all the Compat UI educations in case TaskInfo has no configuration or + // TaskListener or in desktop mode. + if (taskInfo.configuration == null || taskListener == null || mIsInDesktopMode) { // Null token means the current foreground activity is not in compatibility mode. removeLayouts(taskInfo.taskId); return; } + if (taskInfo != null && taskListener != null) { + updateActiveTaskInfo(taskInfo); + } + + // We're showing the first reachability education so we ignore incoming TaskInfo // until the education flow has completed or we double tap. The double-tap // basically cancel all the onboarding flow. We don't have to ignore events in case @@ -286,7 +289,7 @@ public class CompatUIController implements OnDisplaysChangedListener, // we need to ignore all the incoming TaskInfo until the education // completes. If we come from a double tap we follow the normal flow. final boolean topActivityPillarboxed = - taskInfo.appCompatTaskInfo.isTopActivityPillarboxed(); + taskInfo.appCompatTaskInfo.isTopActivityPillarboxShaped(); final boolean isFirstTimeHorizontalReachabilityEdu = topActivityPillarboxed && !mCompatUIConfiguration.hasSeenHorizontalReachabilityEducation(taskInfo); final boolean isFirstTimeVerticalReachabilityEdu = !topActivityPillarboxed @@ -443,7 +446,7 @@ public class CompatUIController implements OnDisplaysChangedListener, @Nullable ShellTaskOrganizer.TaskListener taskListener) { CompatUIWindowManager layout = mActiveCompatLayouts.get(taskInfo.taskId); if (layout != null) { - if (layout.needsToBeRecreated(taskInfo, taskListener)) { + if (layout.needsToBeRecreated(taskInfo, taskListener) || mIsInDesktopMode) { mActiveCompatLayouts.remove(taskInfo.taskId); layout.release(); } else { @@ -456,7 +459,10 @@ public class CompatUIController implements OnDisplaysChangedListener, return; } } - + if (mIsInDesktopMode) { + // Return if in desktop mode. + return; + } // Create a new UI layout. final Context context = getOrCreateDisplayContext(taskInfo.displayId); if (context == null) { @@ -494,7 +500,8 @@ public class CompatUIController implements OnDisplaysChangedListener, private void createOrUpdateLetterboxEduLayout(@NonNull TaskInfo taskInfo, @Nullable ShellTaskOrganizer.TaskListener taskListener) { if (mActiveLetterboxEduLayout != null) { - if (mActiveLetterboxEduLayout.needsToBeRecreated(taskInfo, taskListener)) { + if (mActiveLetterboxEduLayout.needsToBeRecreated(taskInfo, taskListener) + || mIsInDesktopMode) { mActiveLetterboxEduLayout.release(); mActiveLetterboxEduLayout = null; } else { @@ -507,6 +514,10 @@ public class CompatUIController implements OnDisplaysChangedListener, return; } } + if (mIsInDesktopMode) { + // Return if in desktop mode. + return; + } // Create a new UI layout. final Context context = getOrCreateDisplayContext(taskInfo.displayId); if (context == null) { @@ -541,7 +552,7 @@ public class CompatUIController implements OnDisplaysChangedListener, RestartDialogWindowManager layout = mTaskIdToRestartDialogWindowManagerMap.get(taskInfo.taskId); if (layout != null) { - if (layout.needsToBeRecreated(taskInfo, taskListener)) { + if (layout.needsToBeRecreated(taskInfo, taskListener) || mIsInDesktopMode) { mTaskIdToRestartDialogWindowManagerMap.remove(taskInfo.taskId); layout.release(); } else { @@ -556,6 +567,10 @@ public class CompatUIController implements OnDisplaysChangedListener, return; } } + if (mIsInDesktopMode) { + // Return if in desktop mode. + return; + } // Create a new UI layout. final Context context = getOrCreateDisplayContext(taskInfo.displayId); if (context == null) { @@ -594,7 +609,8 @@ public class CompatUIController implements OnDisplaysChangedListener, private void createOrUpdateReachabilityEduLayout(@NonNull TaskInfo taskInfo, @Nullable ShellTaskOrganizer.TaskListener taskListener) { if (mActiveReachabilityEduLayout != null) { - if (mActiveReachabilityEduLayout.needsToBeRecreated(taskInfo, taskListener)) { + if (mActiveReachabilityEduLayout.needsToBeRecreated(taskInfo, taskListener) + || mIsInDesktopMode) { mActiveReachabilityEduLayout.release(); mActiveReachabilityEduLayout = null; } else { @@ -608,6 +624,10 @@ public class CompatUIController implements OnDisplaysChangedListener, return; } } + if (mIsInDesktopMode) { + // Return if in desktop mode. + return; + } // Create a new UI layout. final Context context = getOrCreateDisplayContext(taskInfo.displayId); if (context == null) { @@ -647,7 +667,8 @@ public class CompatUIController implements OnDisplaysChangedListener, private void createOrUpdateUserAspectRatioSettingsLayout(@NonNull TaskInfo taskInfo, @Nullable ShellTaskOrganizer.TaskListener taskListener) { if (mUserAspectRatioSettingsLayout != null) { - if (mUserAspectRatioSettingsLayout.needsToBeRecreated(taskInfo, taskListener)) { + if (mUserAspectRatioSettingsLayout.needsToBeRecreated(taskInfo, taskListener) + || mIsInDesktopMode) { mUserAspectRatioSettingsLayout.release(); mUserAspectRatioSettingsLayout = null; } else { @@ -660,7 +681,10 @@ public class CompatUIController implements OnDisplaysChangedListener, return; } } - + if (mIsInDesktopMode) { + // Return if in desktop mode. + return; + } // Create a new UI layout. final Context context = getOrCreateDisplayContext(taskInfo.displayId); if (context == null) { @@ -840,8 +864,8 @@ public class CompatUIController implements OnDisplaysChangedListener, boolean mHasShownUserAspectRatioSettingsButtonHint; } - private boolean isInDesktopMode(int displayId) { - return Flags.skipCompatUiEducationInDesktopMode() - && mInDesktopModePredicate.test(displayId); + private boolean isInDesktopMode(@Nullable TaskInfo taskInfo) { + return taskInfo != null && Flags.skipCompatUiEducationInDesktopMode() + && mInDesktopModePredicate.test(taskInfo.displayId); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java index 706a67821a30..601cf70b93ed 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java @@ -67,6 +67,7 @@ import com.android.wm.shell.dagger.pip.PipModule; import com.android.wm.shell.desktopmode.CloseDesktopTaskTransitionHandler; import com.android.wm.shell.desktopmode.DefaultDragToDesktopTransitionHandler; import com.android.wm.shell.desktopmode.DesktopActivityOrientationChangeHandler; +import com.android.wm.shell.desktopmode.DesktopBackNavigationTransitionHandler; import com.android.wm.shell.desktopmode.DesktopDisplayEventHandler; import com.android.wm.shell.desktopmode.DesktopImmersiveController; import com.android.wm.shell.desktopmode.DesktopMixedTransitionHandler; @@ -779,7 +780,8 @@ public abstract class WMShellModule { ShellTaskOrganizer shellTaskOrganizer, ToggleResizeDesktopTaskTransitionHandler toggleResizeDesktopTaskTransitionHandler, ReturnToDragStartAnimator returnToDragStartAnimator, - @DynamicOverride DesktopRepository desktopRepository) { + @DynamicOverride DesktopRepository desktopRepository, + DesktopModeEventLogger desktopModeEventLogger) { return new DesktopTilingDecorViewModel( context, displayController, @@ -789,7 +791,8 @@ public abstract class WMShellModule { shellTaskOrganizer, toggleResizeDesktopTaskTransitionHandler, returnToDragStartAnimator, - desktopRepository + desktopRepository, + desktopModeEventLogger ); } @@ -834,14 +837,21 @@ public abstract class WMShellModule { @Provides static Optional<DesktopImmersiveController> provideDesktopImmersiveController( Context context, + ShellInit shellInit, Transitions transitions, @DynamicOverride DesktopRepository desktopRepository, DisplayController displayController, - ShellTaskOrganizer shellTaskOrganizer) { + ShellTaskOrganizer shellTaskOrganizer, + ShellCommandHandler shellCommandHandler) { if (DesktopModeStatus.canEnterDesktopMode(context)) { return Optional.of( new DesktopImmersiveController( - transitions, desktopRepository, displayController, shellTaskOrganizer)); + shellInit, + transitions, + desktopRepository, + displayController, + shellTaskOrganizer, + shellCommandHandler)); } return Optional.empty(); } @@ -906,6 +916,16 @@ public abstract class WMShellModule { @WMSingleton @Provides + static DesktopBackNavigationTransitionHandler provideDesktopBackNavigationTransitionHandler( + @ShellMainThread ShellExecutor mainExecutor, + @ShellAnimationThread ShellExecutor animExecutor, + DisplayController displayController) { + return new DesktopBackNavigationTransitionHandler(mainExecutor, animExecutor, + displayController); + } + + @WMSingleton + @Provides static DesktopModeDragAndDropTransitionHandler provideDesktopModeDragAndDropTransitionHandler( Transitions transitions) { return new DesktopModeDragAndDropTransitionHandler(transitions); @@ -955,6 +975,7 @@ public abstract class WMShellModule { Optional<DesktopRepository> desktopRepository, Transitions transitions, ShellTaskOrganizer shellTaskOrganizer, + Optional<DesktopMixedTransitionHandler> desktopMixedTransitionHandler, ShellInit shellInit) { return desktopRepository.flatMap( repository -> @@ -964,6 +985,7 @@ public abstract class WMShellModule { repository, transitions, shellTaskOrganizer, + desktopMixedTransitionHandler.get(), shellInit))); } @@ -976,6 +998,7 @@ public abstract class WMShellModule { FreeformTaskTransitionHandler freeformTaskTransitionHandler, CloseDesktopTaskTransitionHandler closeDesktopTaskTransitionHandler, Optional<DesktopImmersiveController> desktopImmersiveController, + DesktopBackNavigationTransitionHandler desktopBackNavigationTransitionHandler, InteractionJankMonitor interactionJankMonitor, @ShellMainThread Handler handler, ShellInit shellInit, @@ -992,6 +1015,7 @@ public abstract class WMShellModule { freeformTaskTransitionHandler, closeDesktopTaskTransitionHandler, desktopImmersiveController.get(), + desktopBackNavigationTransitionHandler, interactionJankMonitor, handler, shellInit, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java index 3a4764d45f2c..3cd5df3121c1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java @@ -19,6 +19,7 @@ package com.android.wm.shell.dagger.pip; import android.content.Context; import android.os.Handler; +import com.android.wm.shell.RootTaskDisplayAreaOrganizer; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.WindowManagerShellWrapper; import com.android.wm.shell.common.DisplayController; @@ -41,6 +42,7 @@ import com.android.wm.shell.common.pip.PipUiEventLogger; import com.android.wm.shell.common.pip.SizeSpecSource; import com.android.wm.shell.dagger.WMShellBaseModule; import com.android.wm.shell.dagger.WMSingleton; +import com.android.wm.shell.desktopmode.DesktopRepository; import com.android.wm.shell.onehanded.OneHandedController; import com.android.wm.shell.pip.PipAnimationController; import com.android.wm.shell.pip.PipParamsChangedForwarder; @@ -169,6 +171,8 @@ public abstract class Pip1Module { PipParamsChangedForwarder pipParamsChangedForwarder, Optional<SplitScreenController> splitScreenControllerOptional, Optional<PipPerfHintController> pipPerfHintControllerOptional, + Optional<DesktopRepository> desktopRepositoryOptional, + RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer, DisplayController displayController, PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer, @ShellMainThread ShellExecutor mainExecutor) { @@ -176,7 +180,8 @@ public abstract class Pip1Module { syncTransactionQueue, pipTransitionState, pipBoundsState, pipDisplayLayoutState, pipBoundsAlgorithm, menuPhoneController, pipAnimationController, pipSurfaceTransactionHelper, pipTransitionController, pipParamsChangedForwarder, - splitScreenControllerOptional, pipPerfHintControllerOptional, displayController, + splitScreenControllerOptional, pipPerfHintControllerOptional, + desktopRepositoryOptional, rootTaskDisplayAreaOrganizer, displayController, pipUiEventLogger, shellTaskOrganizer, mainExecutor); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java index 8d1b15c1e631..78e676f8cd45 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java @@ -22,6 +22,7 @@ import android.os.SystemClock; import androidx.annotation.NonNull; +import com.android.wm.shell.RootTaskDisplayAreaOrganizer; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.WindowManagerShellWrapper; import com.android.wm.shell.common.DisplayController; @@ -214,6 +215,7 @@ public abstract class TvPipModule { PipSurfaceTransactionHelper pipSurfaceTransactionHelper, Optional<SplitScreenController> splitScreenControllerOptional, Optional<PipPerfHintController> pipPerfHintControllerOptional, + RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer, DisplayController displayController, PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer, @ShellMainThread ShellExecutor mainExecutor) { @@ -221,8 +223,9 @@ public abstract class TvPipModule { syncTransactionQueue, pipTransitionState, tvPipBoundsState, pipDisplayLayoutState, tvPipBoundsAlgorithm, tvPipMenuController, pipAnimationController, pipSurfaceTransactionHelper, tvPipTransition, pipParamsChangedForwarder, - splitScreenControllerOptional, pipPerfHintControllerOptional, displayController, - pipUiEventLogger, shellTaskOrganizer, mainExecutor); + splitScreenControllerOptional, pipPerfHintControllerOptional, + rootTaskDisplayAreaOrganizer, displayController, pipUiEventLogger, + shellTaskOrganizer, mainExecutor); } @WMSingleton diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopBackNavigationTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopBackNavigationTransitionHandler.kt new file mode 100644 index 000000000000..83b0f8413a28 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopBackNavigationTransitionHandler.kt @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2024 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.desktopmode + +import android.animation.Animator +import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM +import android.os.IBinder +import android.util.DisplayMetrics +import android.view.SurfaceControl.Transaction +import android.window.TransitionInfo +import android.window.TransitionRequestInfo +import android.window.WindowContainerTransaction +import com.android.wm.shell.common.DisplayController +import com.android.wm.shell.common.ShellExecutor +import com.android.wm.shell.shared.TransitionUtil +import com.android.wm.shell.shared.animation.MinimizeAnimator.create +import com.android.wm.shell.transition.Transitions + +/** + * The [Transitions.TransitionHandler] that handles transitions for tasks that are closing or going + * to back as part of back navigation. This handler is used only for animating transitions. + */ +class DesktopBackNavigationTransitionHandler( + private val mainExecutor: ShellExecutor, + private val animExecutor: ShellExecutor, + private val displayController: DisplayController, +) : Transitions.TransitionHandler { + + /** Shouldn't handle anything */ + override fun handleRequest( + transition: IBinder, + request: TransitionRequestInfo, + ): WindowContainerTransaction? = null + + /** Animates a transition with minimizing tasks */ + override fun startAnimation( + transition: IBinder, + info: TransitionInfo, + startTransaction: Transaction, + finishTransaction: Transaction, + finishCallback: Transitions.TransitionFinishCallback, + ): Boolean { + if (!TransitionUtil.isClosingType(info.type)) return false + + val animations = mutableListOf<Animator>() + val onAnimFinish: (Animator) -> Unit = { animator -> + mainExecutor.execute { + // Animation completed + animations.remove(animator) + if (animations.isEmpty()) { + // All animations completed, finish the transition + finishCallback.onTransitionFinished(/* wct= */ null) + } + } + } + + animations += + info.changes + .filter { + it.mode == info.type && + it.taskInfo?.windowingMode == WINDOWING_MODE_FREEFORM + } + .mapNotNull { createMinimizeAnimation(it, finishTransaction, onAnimFinish) } + if (animations.isEmpty()) return false + animExecutor.execute { animations.forEach(Animator::start) } + return true + } + + private fun createMinimizeAnimation( + change: TransitionInfo.Change, + finishTransaction: Transaction, + onAnimFinish: (Animator) -> Unit + ): Animator? { + val t = Transaction() + val sc = change.leash + finishTransaction.hide(sc) + val displayMetrics: DisplayMetrics? = + change.taskInfo?.let { + displayController.getDisplayContext(it.displayId)?.getResources()?.displayMetrics + } + return displayMetrics?.let { create(it, change, t, onAnimFinish) } + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopImmersiveController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopImmersiveController.kt index f69aa6df6a1d..1acde73e68dc 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopImmersiveController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopImmersiveController.kt @@ -34,10 +34,13 @@ import com.android.window.flags.Flags import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.common.DisplayController import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE +import com.android.wm.shell.sysui.ShellCommandHandler +import com.android.wm.shell.sysui.ShellInit import com.android.wm.shell.transition.Transitions import com.android.wm.shell.transition.Transitions.TransitionHandler import com.android.wm.shell.transition.Transitions.TransitionObserver import com.android.wm.shell.windowdecor.OnTaskResizeAnimationListener +import java.io.PrintWriter /** * A controller to move tasks in/out of desktop's full immersive state where the task @@ -45,27 +48,34 @@ import com.android.wm.shell.windowdecor.OnTaskResizeAnimationListener * be transient below the status bar like in fullscreen immersive mode. */ class DesktopImmersiveController( + shellInit: ShellInit, private val transitions: Transitions, private val desktopRepository: DesktopRepository, private val displayController: DisplayController, private val shellTaskOrganizer: ShellTaskOrganizer, + private val shellCommandHandler: ShellCommandHandler, private val transactionSupplier: () -> SurfaceControl.Transaction, ) : TransitionHandler, TransitionObserver { constructor( + shellInit: ShellInit, transitions: Transitions, desktopRepository: DesktopRepository, displayController: DisplayController, shellTaskOrganizer: ShellTaskOrganizer, + shellCommandHandler: ShellCommandHandler, ) : this( + shellInit, transitions, desktopRepository, displayController, shellTaskOrganizer, + shellCommandHandler, { SurfaceControl.Transaction() } ) - private var state: TransitionState? = null + @VisibleForTesting + var state: TransitionState? = null @VisibleForTesting val pendingExternalExitTransitions = mutableListOf<ExternalPendingExit>() @@ -79,10 +89,21 @@ class DesktopImmersiveController( /** A listener to invoke on animation changes during entry/exit. */ var onTaskResizeAnimationListener: OnTaskResizeAnimationListener? = null + init { + shellInit.addInitCallback({ onInit() }, this) + } + + fun onInit() { + shellCommandHandler.addDumpCallback(this::dump, this) + } + /** Starts a transition to enter full immersive state inside the desktop. */ fun moveTaskToImmersive(taskInfo: RunningTaskInfo) { if (inProgress) { - logV("Cannot start entry because transition already in progress.") + logV( + "Cannot start entry because transition(s) already in progress: %s", + getRunningTransitions() + ) return } val wct = WindowContainerTransaction().apply { @@ -100,7 +121,10 @@ class DesktopImmersiveController( fun moveTaskToNonImmersive(taskInfo: RunningTaskInfo) { if (inProgress) { - logV("Cannot start exit because transition already in progress.") + logV( + "Cannot start exit because transition(s) already in progress: %s", + getRunningTransitions() + ) return } @@ -225,14 +249,19 @@ class DesktopImmersiveController( finishCallback: Transitions.TransitionFinishCallback ): Boolean { val state = requireState() - if (transition != state.transition) return false + check(state.transition == transition) { + "Transition $transition did not match expected state=$state" + } logD("startAnimation transition=%s", transition) animateResize( targetTaskId = state.taskId, info = info, startTransaction = startTransaction, finishTransaction = finishTransaction, - finishCallback = finishCallback + finishCallback = { + finishCallback.onTransitionFinished(/* wct= */ null) + clearState() + }, ) return true } @@ -242,12 +271,18 @@ class DesktopImmersiveController( info: TransitionInfo, startTransaction: SurfaceControl.Transaction, finishTransaction: SurfaceControl.Transaction, - finishCallback: Transitions.TransitionFinishCallback + finishCallback: Transitions.TransitionFinishCallback, ) { logD("animateResize for task#%d", targetTaskId) - val change = info.changes.first { c -> + val change = info.changes.firstOrNull { c -> val taskInfo = c.taskInfo - return@first taskInfo != null && taskInfo.taskId == targetTaskId + return@firstOrNull taskInfo != null && taskInfo.taskId == targetTaskId + } + if (change == null) { + logD("Did not find change for task#%d to animate", targetTaskId) + startTransaction.apply() + finishCallback.onTransitionFinished(/* wct= */ null) + return } animateResizeChange(change, startTransaction, finishTransaction, finishCallback) } @@ -288,7 +323,6 @@ class DesktopImmersiveController( .apply() onTaskResizeAnimationListener?.onAnimationEnd(taskId) finishCallback.onTransitionFinished(null /* wct */) - clearState() } ) addUpdateListener { animation -> @@ -357,8 +391,17 @@ class DesktopImmersiveController( // Check if this is a direct immersive enter/exit transition. if (transition == state?.transition) { val state = requireState() - val startBounds = info.changes.first { c -> c.taskInfo?.taskId == state.taskId } - .startAbsBounds + val immersiveChange = info.changes.firstOrNull { c -> + c.taskInfo?.taskId == state.taskId + } + if (immersiveChange == null) { + logV( + "Direct move for task#%d in %s direction missing immersive change.", + state.taskId, state.direction + ) + return + } + val startBounds = immersiveChange.startAbsBounds logV("Direct move for task#%d in %s direction verified", state.taskId, state.direction) when (state.direction) { Direction.ENTER -> { @@ -446,11 +489,30 @@ class DesktopImmersiveController( private fun requireState(): TransitionState = state ?: error("Expected non-null transition state") + private fun getRunningTransitions(): List<IBinder> { + val running = mutableListOf<IBinder>() + state?.let { + running.add(it.transition) + } + pendingExternalExitTransitions.forEach { + running.add(it.transition) + } + return running + } + private fun TransitionInfo.hasTaskChange(taskId: Int): Boolean = changes.any { c -> c.taskInfo?.taskId == taskId } + private fun dump(pw: PrintWriter, prefix: String) { + val innerPrefix = "$prefix " + pw.println("${prefix}DesktopImmersiveController") + pw.println(innerPrefix + "state=" + state) + pw.println(innerPrefix + "pendingExternalExitTransitions=" + pendingExternalExitTransitions) + } + /** The state of the currently running transition. */ - private data class TransitionState( + @VisibleForTesting + data class TransitionState( val transition: IBinder, val displayId: Int, val taskId: Int, @@ -483,7 +545,8 @@ class DesktopImmersiveController( fun asExit(): Exit? = if (this is Exit) this else null } - private enum class Direction { + @VisibleForTesting + enum class Direction { ENTER, EXIT } @@ -495,9 +558,10 @@ class DesktopImmersiveController( ProtoLog.d(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments) } - private companion object { + companion object { private const val TAG = "DesktopImmersive" - private const val FULL_IMMERSIVE_ANIM_DURATION_MS = 336L + @VisibleForTesting + const val FULL_IMMERSIVE_ANIM_DURATION_MS = 336L } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt index 48bb2a8b4a74..2001f9743094 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt @@ -52,6 +52,7 @@ class DesktopMixedTransitionHandler( private val freeformTaskTransitionHandler: FreeformTaskTransitionHandler, private val closeDesktopTaskTransitionHandler: CloseDesktopTaskTransitionHandler, private val desktopImmersiveController: DesktopImmersiveController, + private val desktopBackNavigationTransitionHandler: DesktopBackNavigationTransitionHandler, private val interactionJankMonitor: InteractionJankMonitor, @ShellMainThread private val handler: Handler, shellInit: ShellInit, @@ -161,6 +162,14 @@ class DesktopMixedTransitionHandler( finishTransaction, finishCallback ) + is PendingMixedTransition.Minimize -> animateMinimizeTransition( + pending, + transition, + info, + startTransaction, + finishTransaction, + finishCallback + ) } } @@ -213,7 +222,12 @@ class DesktopMixedTransitionHandler( findDesktopTaskChange(info, minimizingTask) } val launchChange = findDesktopTaskChange(info, pending.launchingTask) - ?: error("Should have pending launching task change") + if (launchChange == null) { + check(minimizeChange == null) + check(immersiveExitChange == null) + logV("No launch Change, returning") + return false + } var subAnimationCount = -1 var combinedWct: WindowContainerTransaction? = null @@ -267,6 +281,42 @@ class DesktopMixedTransitionHandler( ) } + private fun animateMinimizeTransition( + pending: PendingMixedTransition.Minimize, + transition: IBinder, + info: TransitionInfo, + startTransaction: SurfaceControl.Transaction, + finishTransaction: SurfaceControl.Transaction, + finishCallback: TransitionFinishCallback, + ): Boolean { + if (!DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue) return false + + val minimizeChange = findDesktopTaskChange(info, pending.minimizingTask) + if (minimizeChange == null) { + logW("Should have minimizing desktop task") + return false + } + if (pending.isLastTask) { + // Dispatch close desktop task animation to the default transition handlers. + return dispatchToLeftoverHandler( + transition, + info, + startTransaction, + finishTransaction, + finishCallback + ) + } + + // Animate minimizing desktop task transition with [DesktopBackNavigationTransitionHandler]. + return desktopBackNavigationTransitionHandler.startAnimation( + transition, + info, + startTransaction, + finishTransaction, + finishCallback, + ) + } + override fun onTransitionConsumed( transition: IBinder, aborted: Boolean, @@ -395,6 +445,14 @@ class DesktopMixedTransitionHandler( val minimizingTask: Int?, val exitingImmersiveTask: Int?, ) : PendingMixedTransition() + + /** A task is minimizing. This should be used for task going to back and some closing cases + * with back navigation. */ + data class Minimize( + override val transition: IBinder, + val minimizingTask: Int, + val isLastTask: Boolean, + ) : PendingMixedTransition() } private fun logV(msg: String, vararg arguments: Any?) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt index bed484c7a532..39586e39fdd4 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt @@ -594,6 +594,10 @@ class DesktopModeEventLogger { FrameworkStatsLog .DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZE_TRIGGER__MAXIMIZE_MENU_RESIZE_TRIGGER ), + DRAG_TO_TOP_RESIZE_TRIGGER( + FrameworkStatsLog + .DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZE_TRIGGER__DRAG_TO_TOP_RESIZE_TRIGGER + ), } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt index edcc877ef58e..c7cf31081c8b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt @@ -275,7 +275,7 @@ private fun TaskInfo.hasPortraitTopActivity(): Boolean { } // Then check if the activity is portrait when letterboxed - appCompatTaskInfo.isTopActivityLetterboxed -> appCompatTaskInfo.isTopActivityPillarboxed + appCompatTaskInfo.isTopActivityLetterboxed -> appCompatTaskInfo.isTopActivityPillarboxShaped // Then check if the activity is portrait appBounds != null -> appBounds.height() > appBounds.width() diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt index fda709a4a2d7..08ca55f93e3f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt @@ -102,6 +102,9 @@ class DesktopRepository ( /* Tracks last bounds of task before toggled to stable bounds. */ private val boundsBeforeMaximizeByTaskId = SparseArray<Rect>() + /* Tracks last bounds of task before it is minimized. */ + private val boundsBeforeMinimizeByTaskId = SparseArray<Rect>() + /* Tracks last bounds of task before toggled to immersive state. */ private val boundsBeforeFullImmersiveByTaskId = SparseArray<Rect>() @@ -462,6 +465,14 @@ class DesktopRepository ( fun saveBoundsBeforeMaximize(taskId: Int, bounds: Rect) = boundsBeforeMaximizeByTaskId.set(taskId, Rect(bounds)) + /** Removes and returns the bounds saved before minimizing the given task. */ + fun removeBoundsBeforeMinimize(taskId: Int): Rect? = + boundsBeforeMinimizeByTaskId.removeReturnOld(taskId) + + /** Saves the bounds of the given task before minimizing. */ + fun saveBoundsBeforeMinimize(taskId: Int, bounds: Rect?) = + boundsBeforeMinimizeByTaskId.set(taskId, Rect(bounds)) + /** Removes and returns the bounds saved before entering immersive with the given task. */ fun removeBoundsBeforeFullImmersive(taskId: Int): Rect? = boundsBeforeFullImmersiveByTaskId.removeReturnOld(taskId) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt index 162879c97a16..6928c255edde 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt @@ -42,6 +42,7 @@ import android.os.Binder import android.os.Handler import android.os.IBinder import android.os.SystemProperties +import android.os.UserHandle import android.util.Size import android.view.Display.DEFAULT_DISPLAY import android.view.DragEvent @@ -871,11 +872,10 @@ class DesktopTasksController( return } - // TODO(b/375356605): Introduce a new ResizeTrigger for drag-to-top. desktopModeEventLogger.logTaskResizingStarted( - ResizeTrigger.UNKNOWN_RESIZE_TRIGGER, motionEvent, taskInfo, displayController + ResizeTrigger.DRAG_TO_TOP_RESIZE_TRIGGER, motionEvent, taskInfo, displayController ) - toggleDesktopTaskSize(taskInfo, ResizeTrigger.UNKNOWN_RESIZE_TRIGGER, motionEvent) + toggleDesktopTaskSize(taskInfo, ResizeTrigger.DRAG_TO_TOP_RESIZE_TRIGGER, motionEvent) } private fun getMaximizeBounds(taskInfo: RunningTaskInfo, stableBounds: Rect): Rect { @@ -1175,7 +1175,11 @@ class DesktopTasksController( private fun addWallpaperActivity(wct: WindowContainerTransaction) { logV("addWallpaperActivity") - val intent = Intent(context, DesktopWallpaperActivity::class.java) + val userHandle = UserHandle.of(userId) + val userContext = + context.createContextAsUser(userHandle, /* flags= */ 0) + val intent = Intent(userContext, DesktopWallpaperActivity::class.java) + intent.putExtra(Intent.EXTRA_USER_HANDLE, userId) val options = ActivityOptions.makeBasic().apply { launchWindowingMode = WINDOWING_MODE_FULLSCREEN @@ -1183,11 +1187,13 @@ class DesktopTasksController( ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS } val pendingIntent = - PendingIntent.getActivity( - context, - /* requestCode = */ 0, + PendingIntent.getActivityAsUser( + userContext, + /* requestCode= */ 0, intent, - PendingIntent.FLAG_IMMUTABLE + PendingIntent.FLAG_IMMUTABLE, + /* bundle= */ null, + userHandle ) wct.sendPendingIntent(pendingIntent, intent, options.toBundle()) } @@ -1291,7 +1297,11 @@ class DesktopTasksController( // Check if freeform task launch during recents should be handled shouldHandleMidRecentsFreeformLaunch -> handleMidRecentsFreeformTaskLaunch(task) // Check if the closing task needs to be handled - TransitionUtil.isClosingType(request.type) -> handleTaskClosing(task) + TransitionUtil.isClosingType(request.type) -> handleTaskClosing( + task, + transition, + request.type + ) // Check if the top task shouldn't be allowed to enter desktop mode isIncompatibleTask(task) -> handleIncompatibleTaskLaunch(task) // Check if fullscreen task should be updated @@ -1621,7 +1631,7 @@ class DesktopTasksController( } /** Handle task closing by removing wallpaper activity if it's the last active task */ - private fun handleTaskClosing(task: RunningTaskInfo): WindowContainerTransaction? { + private fun handleTaskClosing(task: RunningTaskInfo, transition: IBinder, requestType: Int): WindowContainerTransaction? { logV("handleTaskClosing") if (!isDesktopModeShowing(task.displayId)) return null @@ -1637,8 +1647,15 @@ class DesktopTasksController( if (!DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue()) { taskRepository.addClosingTask(task.displayId, task.taskId) desktopTilingDecorViewModel.removeTaskIfTiled(task.displayId, task.taskId) + } else if (requestType == TRANSIT_CLOSE) { + // Handle closing tasks, tasks that are going to back are handled in + // [DesktopTasksTransitionObserver]. + desktopMixedTransitionHandler.addPendingMixedTransition( + DesktopMixedTransitionHandler.PendingMixedTransition.Minimize( + transition, task.taskId, taskRepository.getVisibleTaskCount(task.displayId) == 1 + ) + ) } - taskbarDesktopTaskListener?.onTaskbarCornerRoundingUpdate( doesAnyTaskRequireTaskbarRounding( task.displayId, @@ -1770,9 +1787,13 @@ class DesktopTasksController( transition: IBinder, taskIdToMinimize: Int, ) { - val taskToMinimize = shellTaskOrganizer.getRunningTaskInfo(taskIdToMinimize) ?: return + val taskToMinimize = shellTaskOrganizer.getRunningTaskInfo(taskIdToMinimize) desktopTasksLimiter.ifPresent { - it.addPendingMinimizeChange(transition, taskToMinimize.displayId, taskToMinimize.taskId) + it.addPendingMinimizeChange( + transition = transition, + displayId = taskToMinimize?.displayId ?: DEFAULT_DISPLAY, + taskId = taskIdToMinimize + ) } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt index f0e3a2bd8ffc..77af627a948a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt @@ -92,6 +92,12 @@ class DesktopTasksLimiter ( } taskToMinimize.transitionInfo = info activeTransitionTokensAndTasks[transition] = taskToMinimize + + // Save current bounds before minimizing in case we need to restore to it later. + val boundsBeforeMinimize = info.changes.find { change -> + change.taskInfo?.taskId == taskToMinimize.taskId }?.startAbsBounds + taskRepository.saveBoundsBeforeMinimize(taskToMinimize.taskId, boundsBeforeMinimize) + this@DesktopTasksLimiter.minimizeTask( taskToMinimize.displayId, taskToMinimize.taskId) } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt index d1534da9a078..c39c715e685c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt @@ -46,6 +46,7 @@ class DesktopTasksTransitionObserver( private val desktopRepository: DesktopRepository, private val transitions: Transitions, private val shellTaskOrganizer: ShellTaskOrganizer, + private val desktopMixedTransitionHandler: DesktopMixedTransitionHandler, shellInit: ShellInit ) : Transitions.TransitionObserver { @@ -71,7 +72,7 @@ class DesktopTasksTransitionObserver( // TODO: b/332682201 Update repository state updateWallpaperToken(info) if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue()) { - handleBackNavigation(info) + handleBackNavigation(transition, info) removeTaskIfNeeded(info) } removeWallpaperOnLastTaskClosingIfNeeded(transition, info) @@ -95,7 +96,7 @@ class DesktopTasksTransitionObserver( } } - private fun handleBackNavigation(info: TransitionInfo) { + private fun handleBackNavigation(transition: IBinder, info: TransitionInfo) { // When default back navigation happens, transition type is TO_BACK and the change is // TO_BACK. Mark the task going to back as minimized. if (info.type == TRANSIT_TO_BACK) { @@ -105,10 +106,14 @@ class DesktopTasksTransitionObserver( continue } - if (desktopRepository.getVisibleTaskCount(taskInfo.displayId) > 0 && + val visibleTaskCount = desktopRepository.getVisibleTaskCount(taskInfo.displayId) + if (visibleTaskCount > 0 && change.mode == TRANSIT_TO_BACK && taskInfo.windowingMode == WINDOWING_MODE_FREEFORM) { desktopRepository.minimizeTask(taskInfo.displayId, taskInfo.taskId) + desktopMixedTransitionHandler.addPendingMixedTransition( + DesktopMixedTransitionHandler.PendingMixedTransition.Minimize( + transition, taskInfo.taskId, visibleTaskCount == 1)) } } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopWallpaperActivity.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopWallpaperActivity.kt index 1c2415c236ad..e835b2fec232 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopWallpaperActivity.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopWallpaperActivity.kt @@ -36,7 +36,6 @@ import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE class DesktopWallpaperActivity : Activity() { override fun onCreate(savedInstanceState: Bundle?) { - ProtoLog.d(WM_SHELL_DESKTOP_MODE, "DesktopWallpaperActivity: onCreate") super.onCreate(savedInstanceState) window.addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java index dcbdfa349687..a611fe1db2ce 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java @@ -17,6 +17,7 @@ package com.android.wm.shell.draganddrop; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; +import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.content.ClipDescription.EXTRA_HIDE_DRAG_SOURCE_TASK_ID; @@ -94,25 +95,30 @@ public class DragSession { void updateRunningTask() { final boolean hideDragSourceTask = hideDragSourceTaskId != -1; final List<ActivityManager.RunningTaskInfo> tasks = - mActivityTaskManager.getTasks(hideDragSourceTask ? 2 : 1, - false /* filterOnlyVisibleRecents */); - if (!tasks.isEmpty()) { - for (int i = tasks.size() - 1; i >= 0; i--) { - final ActivityManager.RunningTaskInfo task = tasks.get(i); - if (hideDragSourceTask && hideDragSourceTaskId == task.taskId) { - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, - "Skipping running task: id=%d component=%s", task.taskId, - task.baseIntent != null ? task.baseIntent.getComponent() : "null"); - continue; - } - runningTaskInfo = task; - runningTaskWinMode = task.getWindowingMode(); - runningTaskActType = task.getActivityType(); + mActivityTaskManager.getTasks(5, false /* filterOnlyVisibleRecents */); + for (int i = 0; i < tasks.size(); i++) { + final ActivityManager.RunningTaskInfo task = tasks.get(i); + if (hideDragSourceTask && hideDragSourceTaskId == task.taskId) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, - "Running task: id=%d component=%s", task.taskId, + "Skipping running task: id=%d component=%s", task.taskId, task.baseIntent != null ? task.baseIntent.getComponent() : "null"); - break; + continue; } + if (!task.isVisible) { + // Skip invisible tasks + continue; + } + if (task.configuration.windowConfiguration.isAlwaysOnTop()) { + // Skip always-on-top floating tasks + continue; + } + runningTaskInfo = task; + runningTaskWinMode = task.getWindowingMode(); + runningTaskActType = task.getActivityType(); + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, + "Running task: id=%d component=%s", task.taskId, + task.baseIntent != null ? task.baseIntent.getComponent() : "null"); + break; } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java index f8d2011d0934..b618bf1215ac 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java @@ -53,6 +53,7 @@ import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; import com.android.internal.protolog.ProtoLog; +import com.android.window.flags.Flags; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.TaskStackListenerCallback; import com.android.wm.shell.common.TaskStackListenerImpl; @@ -72,6 +73,9 @@ import com.android.wm.shell.transition.Transitions.TransitionFinishCallback; public class KeyguardTransitionHandler implements Transitions.TransitionHandler, KeyguardChangeListener, TaskStackListenerCallback { + private static final boolean ENABLE_NEW_KEYGUARD_SHELL_TRANSITIONS = + Flags.ensureKeyguardDoesTransitionStarting(); + private static final String TAG = "KeyguardTransition"; private final Transitions mTransitions; @@ -194,7 +198,7 @@ public class KeyguardTransitionHandler // Occlude/unocclude animations are only played if the keyguard is locked. if ((info.getFlags() & TRANSIT_FLAG_KEYGUARD_LOCKED) != 0) { - if ((info.getFlags() & TRANSIT_FLAG_KEYGUARD_OCCLUDING) != 0) { + if (isKeyguardOccluding(info)) { if (hasOpeningDream(info)) { return startAnimation(mOccludeByDreamTransition, "occlude-by-dream", transition, info, startTransaction, finishTransaction, finishCallback); @@ -202,7 +206,7 @@ public class KeyguardTransitionHandler return startAnimation(mOccludeTransition, "occlude", transition, info, startTransaction, finishTransaction, finishCallback); } - } else if ((info.getFlags() & TRANSIT_FLAG_KEYGUARD_UNOCCLUDING) != 0) { + } else if (isKeyguardUnoccluding(info)) { return startAnimation(mUnoccludeTransition, "unocclude", transition, info, startTransaction, finishTransaction, finishCallback); } @@ -325,6 +329,36 @@ public class KeyguardTransitionHandler return false; } + private static boolean isKeyguardOccluding(@NonNull TransitionInfo info) { + if (!ENABLE_NEW_KEYGUARD_SHELL_TRANSITIONS) { + return (info.getFlags() & TRANSIT_FLAG_KEYGUARD_OCCLUDING) != 0; + } + + for (int i = 0; i < info.getChanges().size(); i++) { + TransitionInfo.Change change = info.getChanges().get(i); + if (change.hasFlags(TransitionInfo.FLAG_IS_TASK_DISPLAY_AREA) + && change.getMode() == TRANSIT_TO_FRONT) { + return true; + } + } + return false; + } + + private static boolean isKeyguardUnoccluding(@NonNull TransitionInfo info) { + if (!ENABLE_NEW_KEYGUARD_SHELL_TRANSITIONS) { + return (info.getFlags() & TRANSIT_FLAG_KEYGUARD_UNOCCLUDING) != 0; + } + + for (int i = 0; i < info.getChanges().size(); i++) { + TransitionInfo.Change change = info.getChanges().get(i); + if (change.hasFlags(TransitionInfo.FLAG_IS_TASK_DISPLAY_AREA) + && change.getMode() == TRANSIT_TO_BACK) { + return true; + } + } + return false; + } + private void finishAnimationImmediately(IBinder transition, StartedTransition playing) { final IBinder fakeTransition = new Binder(); final TransitionInfo fakeInfo = new TransitionInfo(TRANSIT_SLEEP, 0x0); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java index b27c428f1693..0154d0455e50 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java @@ -45,12 +45,17 @@ public interface Pip { } /** - * Set the callback when {@link PipTaskOrganizer#isInPip()} state is changed. + * Set the callback when isInPip state is changed. * - * @param callback The callback accepts the result of {@link PipTaskOrganizer#isInPip()} - * when it's changed. + * @param callback The callback accepts the state of isInPip when it's changed. */ - default void setOnIsInPipStateChangedListener(Consumer<Boolean> callback) {} + default void addOnIsInPipStateChangedListener(@NonNull Consumer<Boolean> callback) {} + + /** + * Remove the callback when isInPip state is changed. + * @param callback The callback accepts the state of isInPip when it's changed. + */ + default void removeOnIsInPipStateChangedListener(@NonNull Consumer<Boolean> callback) {} /** * Called when showing Pip menu. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java index 4aeecbec7dfb..5276d9d6a4df 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java @@ -27,6 +27,7 @@ import android.animation.RectEvaluator; import android.animation.ValueAnimator; import android.annotation.IntDef; import android.annotation.NonNull; +import android.app.AppCompatTaskInfo; import android.app.TaskInfo; import android.content.Context; import android.content.pm.ActivityInfo; @@ -176,12 +177,12 @@ public class PipAnimationController { public PipTransitionAnimator getAnimator(TaskInfo taskInfo, SurfaceControl leash, Rect baseBounds, Rect startBounds, Rect endBounds, Rect sourceHintRect, @PipAnimationController.TransitionDirection int direction, float startingAngle, - @Surface.Rotation int rotationDelta) { + @Surface.Rotation int rotationDelta, boolean alwaysAnimateTaskBounds) { if (mCurrentAnimator == null) { mCurrentAnimator = setupPipTransitionAnimator( PipTransitionAnimator.ofBounds(taskInfo, leash, startBounds, startBounds, endBounds, sourceHintRect, direction, 0 /* startingAngle */, - rotationDelta)); + rotationDelta, alwaysAnimateTaskBounds)); } else if (mCurrentAnimator.getAnimationType() == ANIM_TYPE_ALPHA && mCurrentAnimator.isRunning()) { // If we are still animating the fade into pip, then just move the surface and ensure @@ -197,7 +198,8 @@ public class PipAnimationController { mCurrentAnimator.cancel(); mCurrentAnimator = setupPipTransitionAnimator( PipTransitionAnimator.ofBounds(taskInfo, leash, baseBounds, startBounds, - endBounds, sourceHintRect, direction, startingAngle, rotationDelta)); + endBounds, sourceHintRect, direction, startingAngle, rotationDelta, + alwaysAnimateTaskBounds)); } return mCurrentAnimator; } @@ -585,28 +587,32 @@ public class PipAnimationController { static PipTransitionAnimator<Rect> ofBounds(TaskInfo taskInfo, SurfaceControl leash, Rect baseValue, Rect startValue, Rect endValue, Rect sourceRectHint, @PipAnimationController.TransitionDirection int direction, float startingAngle, - @Surface.Rotation int rotationDelta) { + @Surface.Rotation int rotationDelta, boolean alwaysAnimateTaskBounds) { final boolean isOutPipDirection = isOutPipDirection(direction); final boolean isInPipDirection = isInPipDirection(direction); // Just for simplicity we'll interpolate between the source rect hint insets and empty // insets to calculate the window crop final Rect initialSourceValue; final Rect mainWindowFrame = taskInfo.topActivityMainWindowFrame; - final boolean hasNonMatchFrame = mainWindowFrame != null; + final AppCompatTaskInfo compatInfo = taskInfo.appCompatTaskInfo; + final boolean isSizeCompatOrLetterboxed = compatInfo.isTopActivityInSizeCompat() + || compatInfo.isTopActivityLetterboxed(); + // For the animation to swipe PIP to home or restore a PIP task from home, we don't + // override to the main window frame since we should animate the whole task. + final boolean shouldUseMainWindowFrame = mainWindowFrame != null + && !alwaysAnimateTaskBounds && !isSizeCompatOrLetterboxed; final boolean changeOrientation = rotationDelta == ROTATION_90 || rotationDelta == ROTATION_270; final Rect baseBounds = new Rect(baseValue); final Rect startBounds = new Rect(startValue); final Rect endBounds = new Rect(endValue); if (isOutPipDirection) { - // TODO(b/356277166): handle rotation change with activity that provides main window - // frame. - if (hasNonMatchFrame && !changeOrientation) { + if (shouldUseMainWindowFrame && !changeOrientation) { endBounds.set(mainWindowFrame); } initialSourceValue = new Rect(endBounds); } else if (isInPipDirection) { - if (hasNonMatchFrame) { + if (shouldUseMainWindowFrame) { baseBounds.set(mainWindowFrame); if (startValue.equals(baseValue)) { // If the start value is at initial state as in PIP animation, also override @@ -635,9 +641,19 @@ public class PipAnimationController { if (changeOrientation) { lastEndRect = new Rect(endBounds); rotatedEndRect = new Rect(endBounds); - // Rotate the end bounds according to the rotation delta because the display will - // be rotated to the same orientation. - rotateBounds(rotatedEndRect, initialSourceValue, rotationDelta); + // TODO(b/375977163): polish the animation to restoring the PIP task back from + // swipe-pip-to-home. Ideally we should send the transitionInfo after reparenting + // the PIP activity back to the original task. + if (shouldUseMainWindowFrame) { + // If we should animate the main window frame, set it to the rotatedRect + // instead. The end bounds reported by transitionInfo is the bounds before + // rotation, while main window frame is calculated after the rotation. + rotatedEndRect.set(mainWindowFrame); + } else { + // Rotate the end bounds according to the rotation delta because the display + // will be rotated to the same orientation. + rotateBounds(rotatedEndRect, initialSourceValue, rotationDelta); + } // Use the rect that has the same orientation as the hint rect. initialContainerRect = isOutPipDirection ? rotatedEndRect : initialSourceValue; } else { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java index c4e63dfdade9..30f1948efa2d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java @@ -17,6 +17,7 @@ package com.android.wm.shell.pip; import static android.app.ActivityTaskManager.INVALID_TASK_ID; +import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; @@ -67,6 +68,7 @@ import android.view.Choreographer; import android.view.Display; import android.view.Surface; import android.view.SurfaceControl; +import android.window.DisplayAreaInfo; import android.window.TaskOrganizer; import android.window.TaskSnapshot; import android.window.WindowContainerToken; @@ -74,7 +76,9 @@ import android.window.WindowContainerTransaction; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.protolog.ProtoLog; +import com.android.window.flags.Flags; import com.android.wm.shell.R; +import com.android.wm.shell.RootTaskDisplayAreaOrganizer; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.ScreenshotUtils; @@ -87,6 +91,7 @@ import com.android.wm.shell.common.pip.PipMenuController; import com.android.wm.shell.common.pip.PipPerfHintController; import com.android.wm.shell.common.pip.PipUiEventLogger; import com.android.wm.shell.common.pip.PipUtils; +import com.android.wm.shell.desktopmode.DesktopRepository; import com.android.wm.shell.pip.phone.PipMotionHelper; import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.shared.animation.Interpolators; @@ -145,6 +150,8 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, private final PipSurfaceTransactionHelper mSurfaceTransactionHelper; private final Optional<SplitScreenController> mSplitScreenOptional; @Nullable private final PipPerfHintController mPipPerfHintController; + private final Optional<DesktopRepository> mDesktopRepositoryOptional; + private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer; protected final ShellTaskOrganizer mTaskOrganizer; protected final ShellExecutor mMainExecutor; @@ -388,6 +395,8 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, @NonNull PipParamsChangedForwarder pipParamsChangedForwarder, Optional<SplitScreenController> splitScreenOptional, Optional<PipPerfHintController> pipPerfHintControllerOptional, + Optional<DesktopRepository> desktopRepositoryOptional, + RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer, @NonNull DisplayController displayController, @NonNull PipUiEventLogger pipUiEventLogger, @NonNull ShellTaskOrganizer shellTaskOrganizer, @@ -414,6 +423,8 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, new PipSurfaceTransactionHelper.VsyncSurfaceControlTransactionFactory(); mSplitScreenOptional = splitScreenOptional; mPipPerfHintController = pipPerfHintControllerOptional.orElse(null); + mDesktopRepositoryOptional = desktopRepositoryOptional; + mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer; mTaskOrganizer = shellTaskOrganizer; mMainExecutor = mainExecutor; @@ -741,10 +752,23 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, } /** Returns the bounds to restore to when exiting PIP mode. */ + // TODO(b/377581840): Instead of manually tracking bounds, use bounds from Core. public Rect getExitDestinationBounds() { + if (isPipLaunchedInDesktopMode()) { + final Rect freeformBounds = mDesktopRepositoryOptional.get().removeBoundsBeforeMinimize( + mTaskInfo.taskId); + return Objects.requireNonNullElseGet(freeformBounds, mPipBoundsState::getDisplayBounds); + } return mPipBoundsState.getDisplayBounds(); } + /** Returns whether PiP was launched while in desktop mode. */ + // TODO(377581840): Update this check to include non-minimized cases, e.g. split to PiP etc. + private boolean isPipLaunchedInDesktopMode() { + return Flags.enableDesktopWindowingPip() && mDesktopRepositoryOptional.isPresent() + && mDesktopRepositoryOptional.get().isMinimizedTask(mTaskInfo.taskId); + } + private void exitLaunchIntoPipTask(WindowContainerTransaction wct) { wct.startTask(mTaskInfo.launchIntoPipHostTaskId, null /* ActivityOptions */); mTaskOrganizer.applyTransaction(wct); @@ -1808,7 +1832,25 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, * and can be overridden to restore to an alternate windowing mode. */ public int getOutPipWindowingMode() { - // By default, simply reset the windowing mode to undefined. + final DisplayAreaInfo tdaInfo = mRootTaskDisplayAreaOrganizer.getDisplayAreaInfo( + mTaskInfo.displayId); + + // If PiP was launched while in desktop mode (we should return the task to freeform + // windowing mode): + // 1) If the display windowing mode is freeform, set windowing mode to undefined so it will + // resolve the windowing mode to the display's windowing mode. + // 2) If the display windowing mode is not freeform, set windowing mode to freeform. + if (tdaInfo != null && isPipLaunchedInDesktopMode()) { + final int displayWindowingMode = + tdaInfo.configuration.windowConfiguration.getWindowingMode(); + if (displayWindowingMode == WINDOWING_MODE_FREEFORM) { + return WINDOWING_MODE_UNDEFINED; + } else { + return WINDOWING_MODE_FREEFORM; + } + } + + // By default, or if the task is going to fullscreen, reset the windowing mode to undefined. return WINDOWING_MODE_UNDEFINED; } @@ -1838,9 +1880,11 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, ? mPipBoundsState.getBounds() : currentBounds; final boolean existingAnimatorRunning = mPipAnimationController.getCurrentAnimator() != null && mPipAnimationController.getCurrentAnimator().isRunning(); + // For resize animation, we always animate the whole PIP task bounds. final PipAnimationController.PipTransitionAnimator<?> animator = mPipAnimationController .getAnimator(mTaskInfo, mLeash, baseBounds, currentBounds, destinationBounds, - sourceHintRect, direction, startingAngle, rotationDelta); + sourceHintRect, direction, startingAngle, rotationDelta, + true /* alwaysAnimateTaskBounds */); animator.setTransitionDirection(direction) .setPipTransactionHandler(mPipTransactionHandler) .setDuration(durationMs); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java index 28b91c6cb812..f7aed4401247 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java @@ -530,6 +530,13 @@ public class PipTransition extends PipTransitionController { if (mFixedRotationState != FIXED_ROTATION_TRANSITION && mFinishTransaction != null) { mFinishTransaction.merge(tx); + // Set window crop and position to destination bounds to avoid flickering. + if (hasValidLeash) { + mFinishTransaction.setWindowCrop(leash, destinationBounds.width(), + destinationBounds.height()); + mFinishTransaction.setPosition(leash, destinationBounds.left, + destinationBounds.top); + } } } else { wct = new WindowContainerTransaction(); @@ -884,7 +891,8 @@ public class PipTransition extends PipTransitionController { final PipAnimationController.PipTransitionAnimator animator = mPipAnimationController.getAnimator(taskInfo, pipChange.getLeash(), startBounds, startBounds, endBounds, null, TRANSITION_DIRECTION_LEAVE_PIP, - 0 /* startingAngle */, pipRotateDelta); + 0 /* startingAngle */, pipRotateDelta, + false /* alwaysAnimateTaskBounds */); animator.setTransitionDirection(TRANSITION_DIRECTION_LEAVE_PIP) .setPipAnimationCallback(mPipAnimationCallback) .setDuration(mEnterExitAnimationDuration) @@ -899,7 +907,7 @@ public class PipTransition extends PipTransitionController { final PipAnimationController.PipTransitionAnimator animator = mPipAnimationController.getAnimator(taskInfo, leash, baseBounds, startBounds, endBounds, sourceHintRect, TRANSITION_DIRECTION_LEAVE_PIP, - 0 /* startingAngle */, rotationDelta); + 0 /* startingAngle */, rotationDelta, false /* alwaysAnimateTaskBounds */); animator.setTransitionDirection(TRANSITION_DIRECTION_LEAVE_PIP) .setDuration(mEnterExitAnimationDuration); if (startTransaction != null) { @@ -1095,8 +1103,6 @@ public class PipTransition extends PipTransitionController { if (taskInfo.pictureInPictureParams != null && taskInfo.pictureInPictureParams.isAutoEnterEnabled() && mPipTransitionState.getInSwipePipToHomeTransition()) { - // TODO(b/356277166): add support to swipe PIP to home with - // non-match parent activity. handleSwipePipToHomeTransition(startTransaction, finishTransaction, leash, sourceHintRect, destinationBounds, taskInfo); return; @@ -1118,7 +1124,7 @@ public class PipTransition extends PipTransitionController { if (enterAnimationType == ANIM_TYPE_BOUNDS) { animator = mPipAnimationController.getAnimator(taskInfo, leash, currentBounds, currentBounds, destinationBounds, sourceHintRect, TRANSITION_DIRECTION_TO_PIP, - 0 /* startingAngle */, rotationDelta); + 0 /* startingAngle */, rotationDelta, false /* alwaysAnimateTaskBounds */); if (sourceHintRect == null) { // We use content overlay when there is no source rect hint to enter PiP use bounds // animation. We also temporarily disallow app icon overlay and use color overlay @@ -1241,10 +1247,14 @@ public class PipTransition extends PipTransitionController { // to avoid flicker. final Rect savedDisplayCutoutInsets = new Rect(pipTaskInfo.displayCutoutInsets); pipTaskInfo.displayCutoutInsets.setEmpty(); + // Always use the task bounds even if the PIP activity doesn't match parent because the app + // and the whole task will move behind. We should animate the whole task bounds in this + // case. final PipAnimationController.PipTransitionAnimator animator = mPipAnimationController.getAnimator(pipTaskInfo, leash, sourceBounds, sourceBounds, destinationBounds, sourceHintRect, TRANSITION_DIRECTION_TO_PIP, - 0 /* startingAngle */, ROTATION_0 /* rotationDelta */) + 0 /* startingAngle */, ROTATION_0 /* rotationDelta */, + true /* alwaysAnimateTaskBounds */) .setPipTransactionHandler(mTransactionConsumer) .setTransitionDirection(TRANSITION_DIRECTION_TO_PIP); // The start state is the end state for swipe-auto-pip. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java index 5ffc64f412f1..79a9ce5212c6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java @@ -47,7 +47,6 @@ import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.pip.PipBoundsAlgorithm; import com.android.wm.shell.common.pip.PipBoundsState; import com.android.wm.shell.common.pip.PipMenuController; -import com.android.wm.shell.common.split.SplitScreenUtils; import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.transition.DefaultMixedHandler; @@ -312,10 +311,10 @@ public abstract class PipTransitionController implements Transitions.TransitionH } /** Whether a particular package is same as current pip package. */ - public boolean isPackageActiveInPip(String packageName) { - final TaskInfo inPipTask = mPipOrganizer.getTaskInfo(); - return packageName != null && inPipTask != null && mPipOrganizer.isInPip() - && packageName.equals(SplitScreenUtils.getPackageName(inPipTask.baseIntent)); + public boolean isPackageActiveInPip(@Nullable String packageName) { + return packageName != null + && mPipBoundsState.getLastPipComponentName() != null + && packageName.equals(mPipBoundsState.getLastPipComponentName().getPackageName()); } /** Add PiP-related changes to `outWCT` for the given request. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java index 7f6118689dad..588b88753eb9 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java @@ -104,6 +104,7 @@ import com.android.wm.shell.sysui.UserChangeListener; import com.android.wm.shell.transition.Transitions; import java.io.PrintWriter; +import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.Optional; @@ -215,7 +216,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb private boolean mIsKeyguardShowingOrAnimating; - private Consumer<Boolean> mOnIsInPipStateChangedListener; + private final List<Consumer<Boolean>> mOnIsInPipStateChangedListeners = new ArrayList<>(); @VisibleForTesting interface PipAnimationListener { @@ -501,11 +502,11 @@ public class PipController implements PipTransitionController.PipTransitionCallb false /* saveRestoreSnapFraction */); }); mPipTransitionState.addOnPipTransitionStateChangedListener((oldState, newState) -> { - if (mOnIsInPipStateChangedListener != null) { - final boolean wasInPip = PipTransitionState.isInPip(oldState); - final boolean nowInPip = PipTransitionState.isInPip(newState); - if (nowInPip != wasInPip) { - mOnIsInPipStateChangedListener.accept(nowInPip); + final boolean wasInPip = PipTransitionState.isInPip(oldState); + final boolean nowInPip = PipTransitionState.isInPip(newState); + if (nowInPip != wasInPip) { + for (Consumer<Boolean> listener : mOnIsInPipStateChangedListeners) { + listener.accept(nowInPip); } } }); @@ -960,13 +961,19 @@ public class PipController implements PipTransitionController.PipTransitionCallb mPipBoundsState.getLauncherState().setAppIconSizePx(iconSizePx); } - private void setOnIsInPipStateChangedListener(Consumer<Boolean> callback) { - mOnIsInPipStateChangedListener = callback; - if (mOnIsInPipStateChangedListener != null) { + private void addOnIsInPipStateChangedListener(@NonNull Consumer<Boolean> callback) { + if (callback != null) { + mOnIsInPipStateChangedListeners.add(callback); callback.accept(mPipTransitionState.isInPip()); } } + private void removeOnIsInPipStateChangedListener(@NonNull Consumer<Boolean> callback) { + if (callback != null) { + mOnIsInPipStateChangedListeners.remove(callback); + } + } + private void setShelfHeightLocked(boolean visible, int height) { final int shelfHeight = visible ? height : 0; mPipBoundsState.setShelfVisibility(visible, shelfHeight); @@ -1222,9 +1229,16 @@ public class PipController implements PipTransitionController.PipTransitionCallb } @Override - public void setOnIsInPipStateChangedListener(Consumer<Boolean> callback) { + public void addOnIsInPipStateChangedListener(@NonNull Consumer<Boolean> callback) { + mMainExecutor.execute(() -> { + PipController.this.addOnIsInPipStateChangedListener(callback); + }); + } + + @Override + public void removeOnIsInPipStateChangedListener(@NonNull Consumer<Boolean> callback) { mMainExecutor.execute(() -> { - PipController.this.setOnIsInPipStateChangedListener(callback); + PipController.this.removeOnIsInPipStateChangedListener(callback); }); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java index 614ef2ab9831..fcba46108f67 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java @@ -21,6 +21,7 @@ import android.content.Context; import androidx.annotation.NonNull; +import com.android.wm.shell.RootTaskDisplayAreaOrganizer; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.ShellExecutor; @@ -61,6 +62,7 @@ public class TvPipTaskOrganizer extends PipTaskOrganizer { @NonNull PipParamsChangedForwarder pipParamsChangedForwarder, Optional<SplitScreenController> splitScreenOptional, Optional<PipPerfHintController> pipPerfHintControllerOptional, + @NonNull RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer, @NonNull DisplayController displayController, @NonNull PipUiEventLogger pipUiEventLogger, @NonNull ShellTaskOrganizer shellTaskOrganizer, @@ -68,8 +70,9 @@ public class TvPipTaskOrganizer extends PipTaskOrganizer { super(context, syncTransactionQueue, pipTransitionState, pipBoundsState, pipDisplayLayoutState, boundsHandler, pipMenuController, pipAnimationController, surfaceTransactionHelper, tvPipTransition, pipParamsChangedForwarder, - splitScreenOptional, pipPerfHintControllerOptional, displayController, - pipUiEventLogger, shellTaskOrganizer, mainExecutor); + splitScreenOptional, pipPerfHintControllerOptional, Optional.empty(), + rootTaskDisplayAreaOrganizer, displayController, pipUiEventLogger, + shellTaskOrganizer, mainExecutor); mTvPipTransition = tvPipTransition; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/PipSurfaceTransactionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/PipSurfaceTransactionHelper.java index 24077a35d41c..026482004d51 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/PipSurfaceTransactionHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/PipSurfaceTransactionHelper.java @@ -24,7 +24,6 @@ import android.view.Choreographer; import android.view.SurfaceControl; import com.android.wm.shell.R; -import com.android.wm.shell.transition.Transitions; /** * Abstracts the common operations on {@link SurfaceControl.Transaction} for PiP transition. @@ -180,8 +179,7 @@ public class PipSurfaceTransactionHelper { // destination are different. final float scale = srcW <= srcH ? (float) destW / srcW : (float) destH / srcH; final Rect crop = mTmpDestinationRect; - crop.set(0, 0, Transitions.SHELL_TRANSITIONS_ROTATION ? destH - : destW, Transitions.SHELL_TRANSITIONS_ROTATION ? destW : destH); + crop.set(0, 0, destW, destH); // Inverse scale for crop to fit in screen coordinates. crop.scale(1 / scale); crop.offset(insets.left, insets.top); @@ -200,8 +198,8 @@ public class PipSurfaceTransactionHelper { } } mTmpTransform.setScale(scale, scale); - mTmpTransform.postRotate(degrees); mTmpTransform.postTranslate(positionX, positionY); + mTmpTransform.postRotate(degrees); tx.setMatrix(leash, mTmpTransform, mTmpFloat9).setCrop(leash, crop); return this; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipExpandAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipExpandAnimator.java index 3f9b0c30e314..fb1aba399585 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipExpandAnimator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipExpandAnimator.java @@ -16,6 +16,8 @@ package com.android.wm.shell.pip2.animation; +import static android.view.Surface.ROTATION_90; + import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.RectEvaluator; @@ -73,6 +75,7 @@ public class PipExpandAnimator extends ValueAnimator { mAnimationStartCallback.run(); } if (mStartTransaction != null) { + onExpandAnimationUpdate(mStartTransaction, 0f); mStartTransaction.apply(); } } @@ -81,13 +84,7 @@ public class PipExpandAnimator extends ValueAnimator { public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); if (mFinishTransaction != null) { - // finishTransaction might override some state (eg. corner radii) so we want to - // manually set the state to the end of the animation - mPipSurfaceTransactionHelper.scaleAndCrop(mFinishTransaction, mLeash, - mSourceRectHint, mBaseBounds, mAnimatedRect, getInsets(1f), - false /* isInPipDirection */, 1f) - .round(mFinishTransaction, mLeash, false /* applyCornerRadius */) - .shadow(mFinishTransaction, mLeash, false /* applyCornerRadius */); + onExpandAnimationUpdate(mFinishTransaction, 1f); } if (mAnimationEndCallback != null) { mAnimationEndCallback.run(); @@ -102,14 +99,7 @@ public class PipExpandAnimator extends ValueAnimator { final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction(); final float fraction = getAnimatedFraction(); - - // TODO (b/350801661): implement fixed rotation - Rect insets = getInsets(fraction); - mPipSurfaceTransactionHelper.scaleAndCrop(tx, mLeash, mSourceRectHint, - mBaseBounds, mAnimatedRect, - insets, false /* isInPipDirection */, fraction) - .round(tx, mLeash, false /* applyCornerRadius */) - .shadow(tx, mLeash, false /* applyCornerRadius */); + onExpandAnimationUpdate(tx, fraction); tx.apply(); } }; @@ -167,6 +157,32 @@ public class PipExpandAnimator extends ValueAnimator { mAnimationEndCallback = runnable; } + private void onExpandAnimationUpdate(SurfaceControl.Transaction tx, float fraction) { + Rect insets = getInsets(fraction); + if (mRotation == Surface.ROTATION_0) { + mPipSurfaceTransactionHelper.scaleAndCrop(tx, mLeash, mSourceRectHint, mBaseBounds, + mAnimatedRect, insets, false /* isInPipDirection */, fraction); + } else { + // Fixed rotation case. + Rect start = mStartBounds; + Rect end = mEndBounds; + float degrees, x, y; + x = fraction * (end.left - start.left) + start.left; + y = fraction * (end.top - start.top) + start.top; + + if (mRotation == ROTATION_90) { + degrees = 90 * fraction; + } else { + degrees = -90 * fraction; + } + mPipSurfaceTransactionHelper.rotateAndScaleWithCrop(tx, mLeash, mBaseBounds, + mAnimatedRect, insets, degrees, x, y, + true /* isExpanding */, mRotation == ROTATION_90); + } + mPipSurfaceTransactionHelper.round(tx, mLeash, false /* applyCornerRadius */) + .shadow(tx, mLeash, false /* applyShadowRadius */); + } + private Rect getInsets(float fraction) { final Rect startInsets = mSourceRectHintInsets; final Rect endInsets = mZeroInsets; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipResizeAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipResizeAnimator.java index 012dabbbb9f8..4558a9f141c8 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipResizeAnimator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipResizeAnimator.java @@ -30,6 +30,7 @@ import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; import com.android.wm.shell.pip2.PipSurfaceTransactionHelper; +import com.android.wm.shell.shared.animation.Interpolators; /** * Animator that handles any resize related animation for PIP. @@ -128,6 +129,7 @@ public class PipResizeAnimator extends ValueAnimator { mRectEvaluator = new RectEvaluator(mAnimatedRect); setObjectValues(startBounds, endBounds); + setInterpolator(Interpolators.FAST_OUT_SLOW_IN); addListener(mAnimatorListener); addUpdateListener(mAnimatorUpdateListener); setEvaluator(mRectEvaluator); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PhonePipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PhonePipMenuController.java index 58d2a8577d8c..44900ce1db8a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PhonePipMenuController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PhonePipMenuController.java @@ -573,7 +573,7 @@ public class PhonePipMenuController implements PipMenuController, @PipTransitionState.TransitionState int newState, Bundle extra) { switch (newState) { case PipTransitionState.ENTERED_PIP: - attach(mPipTransitionState.mPinnedTaskLeash); + attach(mPipTransitionState.getPinnedTaskLeash()); break; case PipTransitionState.EXITED_PIP: detach(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java index 9a93371621c9..e901c39b8792 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java @@ -21,6 +21,7 @@ import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE; import static com.android.wm.shell.shared.ShellSharedConstants.KEY_EXTRA_SHELL_PIP; +import android.annotation.NonNull; import android.app.ActivityManager; import android.app.PictureInPictureParams; import android.content.ComponentName; @@ -66,6 +67,8 @@ import com.android.wm.shell.sysui.ShellController; import com.android.wm.shell.sysui.ShellInit; import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.List; import java.util.function.Consumer; /** @@ -94,7 +97,7 @@ public class PipController implements ConfigurationChangeListener, private final PipTouchHandler mPipTouchHandler; private final ShellExecutor mMainExecutor; private final PipImpl mImpl; - private Consumer<Boolean> mOnIsInPipStateChangedListener; + private final List<Consumer<Boolean>> mOnIsInPipStateChangedListeners = new ArrayList<>(); // Wrapper for making Binder calls into PiP animation listener hosted in launcher's Recents. private PipAnimationListener mPipRecentsAnimationListener; @@ -319,7 +322,7 @@ public class PipController implements ConfigurationChangeListener, mPipBoundsAlgorithm.applySnapFraction(toBounds, snapFraction); mPipBoundsState.setBounds(toBounds); } - t.setBounds(mPipTransitionState.mPipTaskToken, mPipBoundsState.getBounds()); + t.setBounds(mPipTransitionState.getPipTaskToken(), mPipBoundsState.getBounds()); } private void setDisplayLayout(DisplayLayout layout) { @@ -376,18 +379,20 @@ public class PipController implements ConfigurationChangeListener, private void setLauncherKeepClearAreaHeight(boolean visible, int height) { ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "setLauncherKeepClearAreaHeight: visible=%b, height=%d", visible, height); - if (visible) { - Rect rect = new Rect( - 0, mPipDisplayLayoutState.getDisplayBounds().bottom - height, - mPipDisplayLayoutState.getDisplayBounds().right, - mPipDisplayLayoutState.getDisplayBounds().bottom); - mPipBoundsState.setNamedUnrestrictedKeepClearArea( - PipBoundsState.NAMED_KCA_LAUNCHER_SHELF, rect); - } else { - mPipBoundsState.setNamedUnrestrictedKeepClearArea( - PipBoundsState.NAMED_KCA_LAUNCHER_SHELF, null); - } - mPipTouchHandler.onShelfVisibilityChanged(visible, height); + mPipTransitionState.setOnIdlePipTransitionStateRunnable(() -> { + if (visible) { + Rect rect = new Rect( + 0, mPipDisplayLayoutState.getDisplayBounds().bottom - height, + mPipDisplayLayoutState.getDisplayBounds().right, + mPipDisplayLayoutState.getDisplayBounds().bottom); + mPipBoundsState.setNamedUnrestrictedKeepClearArea( + PipBoundsState.NAMED_KCA_LAUNCHER_SHELF, rect); + } else { + mPipBoundsState.setNamedUnrestrictedKeepClearArea( + PipBoundsState.NAMED_KCA_LAUNCHER_SHELF, null); + } + mPipTouchHandler.onShelfVisibilityChanged(visible, height); + }); } @Override @@ -411,13 +416,13 @@ public class PipController implements ConfigurationChangeListener, if (mPipTransitionState.isInSwipePipToHomeTransition()) { mPipTransitionState.resetSwipePipToHomeState(); } - if (mOnIsInPipStateChangedListener != null) { - mOnIsInPipStateChangedListener.accept(true /* inPip */); + for (Consumer<Boolean> listener : mOnIsInPipStateChangedListeners) { + listener.accept(true /* inPip */); } break; case PipTransitionState.EXITED_PIP: - if (mOnIsInPipStateChangedListener != null) { - mOnIsInPipStateChangedListener.accept(false /* inPip */); + for (Consumer<Boolean> listener : mOnIsInPipStateChangedListeners) { + listener.accept(false /* inPip */); } break; } @@ -449,13 +454,19 @@ public class PipController implements ConfigurationChangeListener, mPipTransitionState.dump(pw, innerPrefix); } - private void setOnIsInPipStateChangedListener(Consumer<Boolean> callback) { - mOnIsInPipStateChangedListener = callback; - if (mOnIsInPipStateChangedListener != null) { + private void addOnIsInPipStateChangedListener(@NonNull Consumer<Boolean> callback) { + if (callback != null) { + mOnIsInPipStateChangedListeners.add(callback); callback.accept(mPipTransitionState.isInPip()); } } + private void removeOnIsInPipStateChangedListener(@NonNull Consumer<Boolean> callback) { + if (callback != null) { + mOnIsInPipStateChangedListeners.remove(callback); + } + } + private void setLauncherAppIconSize(int iconSizePx) { mPipBoundsState.getLauncherState().setAppIconSizePx(iconSizePx); } @@ -471,9 +482,16 @@ public class PipController implements ConfigurationChangeListener, public void onSystemUiStateChanged(boolean isSysUiStateValid, long flag) {} @Override - public void setOnIsInPipStateChangedListener(Consumer<Boolean> callback) { + public void addOnIsInPipStateChangedListener(@NonNull Consumer<Boolean> callback) { + mMainExecutor.execute(() -> { + PipController.this.addOnIsInPipStateChangedListener(callback); + }); + } + + @Override + public void removeOnIsInPipStateChangedListener(@NonNull Consumer<Boolean> callback) { mMainExecutor.execute(() -> { - PipController.this.setOnIsInPipStateChangedListener(callback); + PipController.this.removeOnIsInPipStateChangedListener(callback); }); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java index 17392bc521d8..3738353dd0a3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java @@ -785,7 +785,7 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, private void handleFlingTransition(SurfaceControl.Transaction startTx, SurfaceControl.Transaction finishTx, Rect destinationBounds) { - startTx.setPosition(mPipTransitionState.mPinnedTaskLeash, + startTx.setPosition(mPipTransitionState.getPinnedTaskLeash(), destinationBounds.left, destinationBounds.top); startTx.apply(); @@ -799,7 +799,7 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, private void startResizeAnimation(SurfaceControl.Transaction startTx, SurfaceControl.Transaction finishTx, Rect destinationBounds, int duration) { - SurfaceControl pipLeash = mPipTransitionState.mPinnedTaskLeash; + SurfaceControl pipLeash = mPipTransitionState.getPinnedTaskLeash(); Preconditions.checkState(pipLeash != null, "No leash cached by mPipTransitionState=" + mPipTransitionState); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java index 751175f0f3e9..d98be55f28e1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java @@ -531,7 +531,7 @@ public class PipResizeGestureHandler implements // If resize transition was scheduled from this component, handle leash updates. mWaitingForBoundsChangeTransition = false; - SurfaceControl pipLeash = mPipTransitionState.mPinnedTaskLeash; + SurfaceControl pipLeash = mPipTransitionState.getPinnedTaskLeash(); Preconditions.checkState(pipLeash != null, "No leash cached by mPipTransitionState=" + mPipTransitionState); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java index 8b25b11e3a47..5438a014af00 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java @@ -25,10 +25,13 @@ import android.content.Context; import android.graphics.Matrix; import android.graphics.Rect; import android.view.SurfaceControl; +import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.pip.PipBoundsState; @@ -48,11 +51,13 @@ public class PipScheduler { private final ShellExecutor mMainExecutor; private final PipTransitionState mPipTransitionState; private PipTransitionController mPipTransitionController; - private final PipSurfaceTransactionHelper.SurfaceControlTransactionFactory + private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory mSurfaceControlTransactionFactory; @Nullable private Runnable mUpdateMovementBoundsRunnable; + private PipAlphaAnimatorSupplier mPipAlphaAnimatorSupplier; + public PipScheduler(Context context, PipBoundsState pipBoundsState, ShellExecutor mainExecutor, @@ -64,10 +69,7 @@ public class PipScheduler { mSurfaceControlTransactionFactory = new PipSurfaceTransactionHelper.VsyncSurfaceControlTransactionFactory(); - } - - ShellExecutor getMainExecutor() { - return mMainExecutor; + mPipAlphaAnimatorSupplier = PipAlphaAnimator::new; } void setPipTransitionController(PipTransitionController pipTransitionController) { @@ -76,27 +78,29 @@ public class PipScheduler { @Nullable private WindowContainerTransaction getExitPipViaExpandTransaction() { - if (mPipTransitionState.mPipTaskToken == null) { + WindowContainerToken pipTaskToken = mPipTransitionState.getPipTaskToken(); + if (pipTaskToken == null) { return null; } WindowContainerTransaction wct = new WindowContainerTransaction(); // final expanded bounds to be inherited from the parent - wct.setBounds(mPipTransitionState.mPipTaskToken, null); + wct.setBounds(pipTaskToken, null); // if we are hitting a multi-activity case // windowing mode change will reparent to original host task - wct.setWindowingMode(mPipTransitionState.mPipTaskToken, WINDOWING_MODE_UNDEFINED); + wct.setWindowingMode(pipTaskToken, WINDOWING_MODE_UNDEFINED); return wct; } @Nullable private WindowContainerTransaction getRemovePipTransaction() { - if (mPipTransitionState.mPipTaskToken == null) { + WindowContainerToken pipTaskToken = mPipTransitionState.getPipTaskToken(); + if (pipTaskToken == null) { return null; } WindowContainerTransaction wct = new WindowContainerTransaction(); - wct.setBounds(mPipTransitionState.mPipTaskToken, null); - wct.setWindowingMode(mPipTransitionState.mPipTaskToken, WINDOWING_MODE_UNDEFINED); - wct.reorder(mPipTransitionState.mPipTaskToken, false); + wct.setBounds(pipTaskToken, null); + wct.setWindowingMode(pipTaskToken, WINDOWING_MODE_UNDEFINED); + wct.reorder(pipTaskToken, false); return wct; } @@ -117,8 +121,8 @@ public class PipScheduler { /** Runs remove PiP animation and schedules remove PiP transition after the animation ends. */ public void removePipAfterAnimation() { SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction(); - PipAlphaAnimator animator = new PipAlphaAnimator(mContext, - mPipTransitionState.mPinnedTaskLeash, tx, PipAlphaAnimator.FADE_OUT); + PipAlphaAnimator animator = mPipAlphaAnimatorSupplier.get(mContext, + mPipTransitionState.getPinnedTaskLeash(), tx, PipAlphaAnimator.FADE_OUT); animator.setAnimationEndCallback(this::scheduleRemovePipImmediately); animator.start(); } @@ -159,13 +163,14 @@ public class PipScheduler { * for running the animator will get this as an extra. */ public void scheduleAnimateResizePip(Rect toBounds, boolean configAtEnd, int duration) { - if (mPipTransitionState.mPipTaskToken == null || !mPipTransitionState.isInPip()) { + WindowContainerToken pipTaskToken = mPipTransitionState.getPipTaskToken(); + if (pipTaskToken == null || !mPipTransitionState.isInPip()) { return; } WindowContainerTransaction wct = new WindowContainerTransaction(); - wct.setBounds(mPipTransitionState.mPipTaskToken, toBounds); + wct.setBounds(pipTaskToken, toBounds); if (configAtEnd) { - wct.deferConfigToTransitionEnd(mPipTransitionState.mPipTaskToken); + wct.deferConfigToTransitionEnd(pipTaskToken); } mPipTransitionController.startResizeTransition(wct, duration); } @@ -203,8 +208,8 @@ public class PipScheduler { "%s: Attempted to user resize PIP to empty bounds, aborting.", TAG); return; } - SurfaceControl leash = mPipTransitionState.mPinnedTaskLeash; - final SurfaceControl.Transaction tx = new SurfaceControl.Transaction(); + SurfaceControl leash = mPipTransitionState.getPinnedTaskLeash(); + final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction(); Matrix transformTensor = new Matrix(); final float[] mMatrixTmp = new float[9]; @@ -218,7 +223,7 @@ public class PipScheduler { tx.apply(); } - void setUpdateMovementBoundsRunnable(Runnable updateMovementBoundsRunnable) { + void setUpdateMovementBoundsRunnable(@Nullable Runnable updateMovementBoundsRunnable) { mUpdateMovementBoundsRunnable = updateMovementBoundsRunnable; } @@ -235,4 +240,23 @@ public class PipScheduler { mPipBoundsState.setBounds(newBounds); maybeUpdateMovementBounds(); } + + @VisibleForTesting + void setSurfaceControlTransactionFactory( + @NonNull PipSurfaceTransactionHelper.SurfaceControlTransactionFactory factory) { + mSurfaceControlTransactionFactory = factory; + } + + @VisibleForTesting + interface PipAlphaAnimatorSupplier { + PipAlphaAnimator get(@NonNull Context context, + SurfaceControl leash, + SurfaceControl.Transaction tx, + @PipAlphaAnimator.Fade int direction); + } + + @VisibleForTesting + void setPipAlphaAnimatorSupplier(@NonNull PipAlphaAnimatorSupplier supplier) { + mPipAlphaAnimatorSupplier = supplier; + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java index 2c7584af1f07..2f9371536a16 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java @@ -29,6 +29,8 @@ import android.view.SurfaceControl; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.protolog.ProtoLog; import com.android.internal.util.Preconditions; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.ShellExecutor; @@ -36,6 +38,7 @@ import com.android.wm.shell.common.pip.PipBoundsAlgorithm; import com.android.wm.shell.common.pip.PipBoundsState; import com.android.wm.shell.common.pip.PipUtils; import com.android.wm.shell.pip2.animation.PipResizeAnimator; +import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.shared.annotations.ShellMainThread; import java.util.ArrayList; @@ -49,7 +52,8 @@ import java.util.List; public class PipTaskListener implements ShellTaskOrganizer.TaskListener, PipTransitionState.PipTransitionStateChangedListener { private static final int ASPECT_RATIO_CHANGE_DURATION = 250; - private static final String ANIMATING_ASPECT_RATIO_CHANGE = "animating_aspect_ratio_change"; + @VisibleForTesting + static final String ANIMATING_ASPECT_RATIO_CHANGE = "animating_aspect_ratio_change"; private final Context mContext; private final PipTransitionState mPipTransitionState; @@ -63,6 +67,8 @@ public class PipTaskListener implements ShellTaskOrganizer.TaskListener, private boolean mWaitingForAspectRatioChange = false; private final List<PipParamsChangedCallback> mPipParamsChangedListeners = new ArrayList<>(); + private PipResizeAnimatorSupplier mPipResizeAnimatorSupplier; + public PipTaskListener(Context context, ShellTaskOrganizer shellTaskOrganizer, PipTransitionState pipTransitionState, @@ -84,6 +90,7 @@ public class PipTaskListener implements ShellTaskOrganizer.TaskListener, ShellTaskOrganizer.TASK_LISTENER_TYPE_PIP); }); } + mPipResizeAnimatorSupplier = PipResizeAnimator::new; } void setPictureInPictureParams(@Nullable PictureInPictureParams params) { @@ -121,6 +128,9 @@ public class PipTaskListener implements ShellTaskOrganizer.TaskListener, if (mPictureInPictureParams.equals(params)) { return; } + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "onTaskInfoChanged: %s, state=%s oldParams=%s newParams=%s", + taskInfo.topActivity, mPipTransitionState, mPictureInPictureParams, params); setPictureInPictureParams(params); float newAspectRatio = mPictureInPictureParams.getAspectRatioFloat(); if (PipUtils.aspectRatioChanged(newAspectRatio, mPipBoundsState.getAspectRatio())) { @@ -167,18 +177,18 @@ public class PipTaskListener implements ShellTaskOrganizer.TaskListener, final int duration = extra.getInt(ANIMATING_BOUNDS_CHANGE_DURATION, PipTransition.BOUNDS_CHANGE_JUMPCUT_DURATION); - Preconditions.checkNotNull(mPipTransitionState.mPinnedTaskLeash, + Preconditions.checkNotNull(mPipTransitionState.getPinnedTaskLeash(), "Leash is null for bounds transition."); if (mWaitingForAspectRatioChange) { - PipResizeAnimator animator = new PipResizeAnimator(mContext, - mPipTransitionState.mPinnedTaskLeash, startTx, finishTx, + mWaitingForAspectRatioChange = false; + PipResizeAnimator animator = mPipResizeAnimatorSupplier.get(mContext, + mPipTransitionState.getPinnedTaskLeash(), startTx, finishTx, destinationBounds, mPipBoundsState.getBounds(), destinationBounds, duration, 0f /* delta */); - animator.setAnimationEndCallback(() -> { - mPipScheduler.scheduleFinishResizePip(destinationBounds); - }); + animator.setAnimationEndCallback( + () -> mPipScheduler.scheduleFinishResizePip(destinationBounds)); animator.start(); } break; @@ -192,4 +202,22 @@ public class PipTaskListener implements ShellTaskOrganizer.TaskListener, default void onActionsChanged(List<RemoteAction> actions, RemoteAction closeAction) { } } + + @VisibleForTesting + interface PipResizeAnimatorSupplier { + PipResizeAnimator get(@NonNull Context context, + @NonNull SurfaceControl leash, + @Nullable SurfaceControl.Transaction startTx, + @Nullable SurfaceControl.Transaction finishTx, + @NonNull Rect baseBounds, + @NonNull Rect startBounds, + @NonNull Rect endBounds, + int duration, + float delta); + } + + @VisibleForTesting + void setPipResizeAnimatorSupplier(@NonNull PipResizeAnimatorSupplier supplier) { + mPipResizeAnimatorSupplier = supplier; + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java index 19d293e829ad..65972fb7df48 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java @@ -231,17 +231,15 @@ public class PipTouchHandler implements PipTransitionState.PipTransitionStateCha // KCA triggered movement to wait for other transitions (e.g. due to IME changes). return; } - mPipTransitionState.setOnIdlePipTransitionStateRunnable(() -> { - boolean hasUserInteracted = (mPipBoundsState.hasUserMovedPip() - || mPipBoundsState.hasUserResizedPip()); - int delta = mPipBoundsAlgorithm.getEntryDestinationBounds().top - - mPipBoundsState.getBounds().top; + boolean hasUserInteracted = (mPipBoundsState.hasUserMovedPip() + || mPipBoundsState.hasUserResizedPip()); + int delta = mPipBoundsAlgorithm.getEntryDestinationBounds().top + - mPipBoundsState.getBounds().top; - if (!mIsImeShowing && !hasUserInteracted && delta != 0) { - // If the user hasn't interacted with PiP, we respect the keep clear areas - mMotionHelper.animateToOffset(mPipBoundsState.getBounds(), delta); - } - }); + if (!mIsImeShowing && !hasUserInteracted && delta != 0) { + // If the user hasn't interacted with PiP, we respect the keep clear areas + mMotionHelper.animateToOffset(mPipBoundsState.getBounds(), delta); + } }; if (PipUtils.isPip2ExperimentEnabled()) { @@ -877,7 +875,7 @@ public class PipTouchHandler implements PipTransitionState.PipTransitionStateCha mMovementWithinDismiss = touchState.getDownTouchPosition().y >= mPipBoundsState.getMovementBounds().bottom; mMotionHelper.setSpringingToTouch(false); - mPipDismissTargetHandler.setTaskLeash(mPipTransitionState.mPinnedTaskLeash); + mPipDismissTargetHandler.setTaskLeash(mPipTransitionState.getPinnedTaskLeash()); // If the menu is still visible then just poke the menu // so that it will timeout after the user stops touching it diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java index 6bf92f69cfb6..d415c10b0cf8 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java @@ -20,6 +20,7 @@ import static android.app.WindowConfiguration.ROTATION_UNDEFINED; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.view.Surface.ROTATION_0; import static android.view.Surface.ROTATION_270; +import static android.view.Surface.ROTATION_90; import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_OPEN; import static android.view.WindowManager.TRANSIT_PIP; @@ -230,6 +231,11 @@ public class PipTransition extends PipTransitionController implements // If there is no PiP change, exit this transition handler and potentially try others. if (pipChange == null) return false; + // Other targets might have default transforms applied that are not relevant when + // playing PiP transitions, so reset those transforms if needed. + prepareOtherTargetTransforms(info, startTransaction, finishTransaction); + + // Update the PipTransitionState while supplying the PiP leash and token to be cached. Bundle extra = new Bundle(); extra.putParcelable(PIP_TASK_TOKEN, pipChange.getContainer()); extra.putParcelable(PIP_TASK_LEASH, pipChange.getLeash()); @@ -325,9 +331,7 @@ public class PipTransition extends PipTransitionController implements return false; } - SurfaceControl pipLeash = pipChange.getLeash(); - Preconditions.checkNotNull(pipLeash, "Leash is null for swipe-up transition."); - + final SurfaceControl pipLeash = getLeash(pipChange); final Rect destinationBounds = pipChange.getEndAbsBounds(); final SurfaceControl swipePipToHomeOverlay = mPipTransitionState.getSwipePipToHomeOverlay(); if (swipePipToHomeOverlay != null) { @@ -343,17 +347,21 @@ public class PipTransition extends PipTransitionController implements (destinationBounds.height() - overlaySize) / 2f); } - final int startRotation = pipChange.getStartRotation(); - final int endRotation = mPipDisplayLayoutState.getRotation(); - final int delta = endRotation == ROTATION_UNDEFINED ? ROTATION_0 - : startRotation - endRotation; + final int delta = getFixedRotationDelta(info, pipChange); if (delta != ROTATION_0) { - mPipTransitionState.setInFixedRotation(true); - handleBoundsTypeFixedRotation(pipChange, pipActivityChange, endRotation); + // Update transition target changes in place to prepare for fixed rotation. + handleBoundsEnterFixedRotation(info, pipChange, pipActivityChange); } + // Update the src-rect-hint in params in place, to set up initial animator transform. + Rect sourceRectHint = getAdjustedSourceRectHint(info, pipChange, pipActivityChange); + pipChange.getTaskInfo().pictureInPictureParams.getSourceRectHint().set(sourceRectHint); + + // Config-at-end transitions need to have their activities transformed before starting + // the animation; this makes the buffer seem like it's been updated to final size. prepareConfigAtEndActivity(startTransaction, finishTransaction, pipChange, pipActivityChange); + startTransaction.merge(finishTransaction); PipEnterAnimator animator = new PipEnterAnimator(mContext, pipLeash, startTransaction, finishTransaction, destinationBounds, delta); @@ -389,55 +397,37 @@ public class PipTransition extends PipTransitionController implements return false; } + final SurfaceControl pipLeash = getLeash(pipChange); final Rect startBounds = pipChange.getStartAbsBounds(); final Rect endBounds = pipChange.getEndAbsBounds(); - final PictureInPictureParams params = pipChange.getTaskInfo().pictureInPictureParams; - final float aspectRatio = mPipBoundsAlgorithm.getAspectRatioOrDefault(params); - final Rect sourceRectHint = PipBoundsAlgorithm.getValidSourceHintRect(params, startBounds, - endBounds); - final Rect adjustedSourceRectHint = sourceRectHint != null ? new Rect(sourceRectHint) - : PipUtils.getEnterPipWithOverlaySrcRectHint(startBounds, aspectRatio); - - final SurfaceControl pipLeash = mPipTransitionState.mPinnedTaskLeash; - - // For opening type transitions, if there is a change of mode TO_FRONT/OPEN, - // make sure that change has alpha of 1f, since it's init state might be set to alpha=0f - // by the Transitions framework to simplify Task opening transitions. - if (TransitionUtil.isOpeningType(info.getType())) { - for (TransitionInfo.Change change : info.getChanges()) { - if (change.getLeash() == null) continue; - if (change.getMode() == TRANSIT_OPEN || change.getMode() == TRANSIT_TO_FRONT) { - startTransaction.setAlpha(change.getLeash(), 1f); - } - } - } - - final TransitionInfo.Change fixedRotationChange = findFixedRotationChange(info); - int startRotation = pipChange.getStartRotation(); - int endRotation = fixedRotationChange != null - ? fixedRotationChange.getEndFixedRotation() : ROTATION_UNDEFINED; - final int delta = endRotation == ROTATION_UNDEFINED ? ROTATION_0 - : startRotation - endRotation; + final Rect adjustedSourceRectHint = getAdjustedSourceRectHint(info, pipChange, + pipActivityChange); + final int delta = getFixedRotationDelta(info, pipChange); if (delta != ROTATION_0) { - mPipTransitionState.setInFixedRotation(true); - handleBoundsTypeFixedRotation(pipChange, pipActivityChange, - fixedRotationChange.getEndFixedRotation()); + // Update transition target changes in place to prepare for fixed rotation. + handleBoundsEnterFixedRotation(info, pipChange, pipActivityChange); } PipEnterAnimator animator = new PipEnterAnimator(mContext, pipLeash, startTransaction, finishTransaction, endBounds, delta); - if (sourceRectHint == null) { - // update the src-rect-hint in params in place, to set up initial animator transform. - params.getSourceRectHint().set(adjustedSourceRectHint); + if (PipBoundsAlgorithm.getValidSourceHintRect(params, startBounds, endBounds) == null) { + // If app provided src-rect-hint is invalid, use app icon overlay. animator.setAppIconContentOverlay( mContext, startBounds, endBounds, pipChange.getTaskInfo().topActivityInfo, mPipBoundsState.getLauncherState().getAppIconSizePx()); } + // Update the src-rect-hint in params in place, to set up initial animator transform. + params.copyOnlySet(new PictureInPictureParams.Builder() + .setSourceRectHint(adjustedSourceRectHint).build()); + + // Config-at-end transitions need to have their activities transformed before starting + // the animation; this makes the buffer seem like it's been updated to final size. prepareConfigAtEndActivity(startTransaction, finishTransaction, pipChange, pipActivityChange); + animator.setAnimationStartCallback(() -> animator.setEnterStartState(pipChange)); animator.setAnimationEndCallback(() -> { if (animator.getContentOverlayLeash() != null) { @@ -459,11 +449,22 @@ public class PipTransition extends PipTransitionController implements animator.start(); } - private void handleBoundsTypeFixedRotation(TransitionInfo.Change pipTaskChange, - TransitionInfo.Change pipActivityChange, int endRotation) { - final Rect endBounds = pipTaskChange.getEndAbsBounds(); - final Rect endActivityBounds = pipActivityChange.getEndAbsBounds(); - int startRotation = pipTaskChange.getStartRotation(); + private void handleBoundsEnterFixedRotation(TransitionInfo info, + TransitionInfo.Change outPipTaskChange, + TransitionInfo.Change outPipActivityChange) { + final TransitionInfo.Change fixedRotationChange = findFixedRotationChange(info); + final Rect endBounds = outPipTaskChange.getEndAbsBounds(); + final Rect endActivityBounds = outPipActivityChange.getEndAbsBounds(); + int startRotation = outPipTaskChange.getStartRotation(); + int endRotation = fixedRotationChange != null + ? fixedRotationChange.getEndFixedRotation() : mPipDisplayLayoutState.getRotation(); + + if (startRotation == endRotation) { + return; + } + + // This is used by display change listeners to respond properly to fixed rotation. + mPipTransitionState.setInFixedRotation(true); // Cache the task to activity offset to potentially restore later. Point activityEndOffset = new Point(endActivityBounds.left - endBounds.left, @@ -492,6 +493,26 @@ public class PipTransition extends PipTransitionController implements endBounds.top + activityEndOffset.y); } + private void handleExpandFixedRotation(TransitionInfo.Change outPipTaskChange, int delta) { + final Rect endBounds = outPipTaskChange.getEndAbsBounds(); + final int width = endBounds.width(); + final int height = endBounds.height(); + final int left = endBounds.left; + final int top = endBounds.top; + int newTop, newLeft; + + if (delta == Surface.ROTATION_90) { + newLeft = top; + newTop = -(left + width); + } else { + newLeft = -(height + top); + newTop = left; + } + // Modify the endBounds, rotating and placing them potentially off-screen, so that + // as we translate and rotate around the origin, we place them right into the target. + endBounds.set(newLeft, newTop, newLeft + height, newTop + width); + } + private boolean startAlphaTypeEnterAnimation(@NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @@ -503,7 +524,7 @@ public class PipTransition extends PipTransitionController implements } Rect destinationBounds = pipChange.getEndAbsBounds(); - SurfaceControl pipLeash = mPipTransitionState.mPinnedTaskLeash; + SurfaceControl pipLeash = mPipTransitionState.getPinnedTaskLeash(); Preconditions.checkNotNull(pipLeash, "Leash is null for alpha transition."); // Start transition with 0 alpha at the entry bounds. @@ -523,7 +544,7 @@ public class PipTransition extends PipTransitionController implements @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback) { - WindowContainerToken pipToken = mPipTransitionState.mPipTaskToken; + WindowContainerToken pipToken = mPipTransitionState.getPipTaskToken(); TransitionInfo.Change pipChange = getChangeByToken(info, pipToken); if (pipChange == null) { @@ -544,33 +565,47 @@ public class PipTransition extends PipTransitionController implements } } - // for multi activity, we need to manually set the leash layer - if (pipChange.getTaskInfo() == null) { - TransitionInfo.Change parent = getChangeByToken(info, pipChange.getParent()); - if (parent != null) { - startTransaction.setLayer(parent.getLeash(), Integer.MAX_VALUE - 1); - } + // The parent change if we were in a multi-activity PiP; null if single activity PiP. + final TransitionInfo.Change parentBeforePip = pipChange.getTaskInfo() == null + ? getChangeByToken(info, pipChange.getParent()) : null; + if (parentBeforePip != null) { + // For multi activity, we need to manually set the leash layer + startTransaction.setLayer(parentBeforePip.getLeash(), Integer.MAX_VALUE - 1); } - Rect startBounds = pipChange.getStartAbsBounds(); - Rect endBounds = pipChange.getEndAbsBounds(); - SurfaceControl pipLeash = pipChange.getLeash(); - Preconditions.checkNotNull(pipLeash, "Leash is null for exit transition."); + final Rect startBounds = pipChange.getStartAbsBounds(); + final Rect endBounds = pipChange.getEndAbsBounds(); + final SurfaceControl pipLeash = getLeash(pipChange); - Rect sourceRectHint = null; - if (pipChange.getTaskInfo() != null - && pipChange.getTaskInfo().pictureInPictureParams != null) { + PictureInPictureParams params = null; + if (pipChange.getTaskInfo() != null) { // single activity - sourceRectHint = pipChange.getTaskInfo().pictureInPictureParams.getSourceRectHint(); - } else if (mPipTaskListener.getPictureInPictureParams().hasSourceBoundsHint()) { + params = pipChange.getTaskInfo().pictureInPictureParams; + } else if (parentBeforePip != null && parentBeforePip.getTaskInfo() != null) { // multi activity - sourceRectHint = mPipTaskListener.getPictureInPictureParams().getSourceRectHint(); + params = parentBeforePip.getTaskInfo().pictureInPictureParams; + } + final Rect sourceRectHint = PipBoundsAlgorithm.getValidSourceHintRect(params, endBounds, + startBounds); + + // We define delta = startRotation - endRotation, so we need to flip the sign. + final int delta = -getFixedRotationDelta(info, pipChange); + if (delta != ROTATION_0) { + // Update PiP target change in place to prepare for fixed rotation; + handleExpandFixedRotation(pipChange, delta); } PipExpandAnimator animator = new PipExpandAnimator(mContext, pipLeash, startTransaction, finishTransaction, endBounds, startBounds, endBounds, - sourceRectHint, Surface.ROTATION_0); - animator.setAnimationEndCallback(this::finishTransition); + sourceRectHint, delta); + animator.setAnimationEndCallback(() -> { + if (parentBeforePip != null) { + // TODO b/377362511: Animate local leash instead to also handle letterbox case. + // For multi-activity, set the crop to be null + finishTransaction.setCrop(pipLeash, null); + } + finishTransition(); + }); animator.start(); return true; } @@ -625,6 +660,72 @@ public class PipTransition extends PipTransitionController implements return null; } + @NonNull + private Rect getAdjustedSourceRectHint(@NonNull TransitionInfo info, + @NonNull TransitionInfo.Change pipTaskChange, + @NonNull TransitionInfo.Change pipActivityChange) { + final Rect startBounds = pipTaskChange.getStartAbsBounds(); + final Rect endBounds = pipTaskChange.getEndAbsBounds(); + final PictureInPictureParams params = pipTaskChange.getTaskInfo().pictureInPictureParams; + + // Get the source-rect-hint provided by the app and check its validity; null if invalid. + final Rect sourceRectHint = PipBoundsAlgorithm.getValidSourceHintRect(params, startBounds, + endBounds); + + final Rect adjustedSourceRectHint = new Rect(); + if (sourceRectHint != null) { + adjustedSourceRectHint.set(sourceRectHint); + // If multi-activity PiP, use the parent task before PiP to retrieve display cutouts; + // then, offset the valid app provided source rect hint by the cutout insets. + // For single-activity PiP, just use the pinned task to get the cutouts instead. + TransitionInfo.Change parentBeforePip = pipActivityChange.getLastParent() != null + ? getChangeByToken(info, pipActivityChange.getLastParent()) : null; + Rect cutoutInsets = parentBeforePip != null + ? parentBeforePip.getTaskInfo().displayCutoutInsets + : pipTaskChange.getTaskInfo().displayCutoutInsets; + if (cutoutInsets != null + && getFixedRotationDelta(info, pipTaskChange) == ROTATION_90) { + adjustedSourceRectHint.offset(cutoutInsets.left, cutoutInsets.top); + } + } else { + // For non-valid app provided src-rect-hint, calculate one to crop into during + // app icon overlay animation. + float aspectRatio = mPipBoundsAlgorithm.getAspectRatioOrDefault(params); + adjustedSourceRectHint.set( + PipUtils.getEnterPipWithOverlaySrcRectHint(startBounds, aspectRatio)); + } + return adjustedSourceRectHint; + } + + @Surface.Rotation + private int getFixedRotationDelta(@NonNull TransitionInfo info, + @NonNull TransitionInfo.Change pipChange) { + TransitionInfo.Change fixedRotationChange = findFixedRotationChange(info); + int startRotation = pipChange.getStartRotation(); + int endRotation = fixedRotationChange != null + ? fixedRotationChange.getEndFixedRotation() : mPipDisplayLayoutState.getRotation(); + int delta = endRotation == ROTATION_UNDEFINED ? ROTATION_0 + : startRotation - endRotation; + return delta; + } + + private void prepareOtherTargetTransforms(TransitionInfo info, + SurfaceControl.Transaction startTransaction, + SurfaceControl.Transaction finishTransaction) { + // For opening type transitions, if there is a change of mode TO_FRONT/OPEN, + // make sure that change has alpha of 1f, since it's init state might be set to alpha=0f + // by the Transitions framework to simplify Task opening transitions. + if (TransitionUtil.isOpeningType(info.getType())) { + for (TransitionInfo.Change change : info.getChanges()) { + if (change.getLeash() == null) continue; + if (change.getMode() == TRANSIT_OPEN || change.getMode() == TRANSIT_TO_FRONT) { + startTransaction.setAlpha(change.getLeash(), 1f); + } + } + } + + } + private WindowContainerTransaction getEnterPipTransaction(@NonNull IBinder transition, @NonNull TransitionRequestInfo request) { // cache the original task token to check for multi-activity case later @@ -673,11 +774,11 @@ public class PipTransition extends PipTransitionController implements } private boolean isRemovePipTransition(@NonNull TransitionInfo info) { - if (mPipTransitionState.mPipTaskToken == null) { + if (mPipTransitionState.getPipTaskToken() == null) { // PiP removal makes sense if enter-PiP has cached a valid pinned task token. return false; } - TransitionInfo.Change pipChange = info.getChange(mPipTransitionState.mPipTaskToken); + TransitionInfo.Change pipChange = info.getChange(mPipTransitionState.getPipTaskToken()); if (pipChange == null) { // Search for the PiP change by token since the windowing mode might be FULLSCREEN now. return false; @@ -717,6 +818,13 @@ public class PipTransition extends PipTransitionController implements } } + @NonNull + private SurfaceControl getLeash(TransitionInfo.Change change) { + SurfaceControl leash = change.getLeash(); + Preconditions.checkNotNull(leash, "Leash is null for change=" + change); + return leash; + } + // // Miscellaneous callbacks and listeners // @@ -752,19 +860,19 @@ public class PipTransition extends PipTransitionController implements Preconditions.checkState(extra != null, "No extra bundle for " + mPipTransitionState); - mPipTransitionState.mPipTaskToken = extra.getParcelable( - PIP_TASK_TOKEN, WindowContainerToken.class); - mPipTransitionState.mPinnedTaskLeash = extra.getParcelable( - PIP_TASK_LEASH, SurfaceControl.class); - boolean hasValidTokenAndLeash = mPipTransitionState.mPipTaskToken != null - && mPipTransitionState.mPinnedTaskLeash != null; + mPipTransitionState.setPipTaskToken(extra.getParcelable( + PIP_TASK_TOKEN, WindowContainerToken.class)); + mPipTransitionState.setPinnedTaskLeash(extra.getParcelable( + PIP_TASK_LEASH, SurfaceControl.class)); + boolean hasValidTokenAndLeash = mPipTransitionState.getPipTaskToken() != null + && mPipTransitionState.getPinnedTaskLeash() != null; Preconditions.checkState(hasValidTokenAndLeash, "Unexpected bundle for " + mPipTransitionState); break; case PipTransitionState.EXITED_PIP: - mPipTransitionState.mPipTaskToken = null; - mPipTransitionState.mPinnedTaskLeash = null; + mPipTransitionState.setPipTaskToken(null); + mPipTransitionState.setPinnedTaskLeash(null); break; } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java index ccdd66b5d1a8..8e90bfee2636 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java @@ -138,11 +138,11 @@ public class PipTransitionState { // pinned PiP task's WC token @Nullable - WindowContainerToken mPipTaskToken; + private WindowContainerToken mPipTaskToken; // pinned PiP task's leash @Nullable - SurfaceControl mPinnedTaskLeash; + private SurfaceControl mPinnedTaskLeash; // Overlay leash potentially used during swipe PiP to home transition; // if null while mInSwipePipToHomeTransition is true, then srcRectHint was invalid. @@ -304,6 +304,22 @@ public class PipTransitionState { mSwipePipToHomeAppBounds.setEmpty(); } + @Nullable WindowContainerToken getPipTaskToken() { + return mPipTaskToken; + } + + public void setPipTaskToken(@Nullable WindowContainerToken token) { + mPipTaskToken = token; + } + + @Nullable SurfaceControl getPinnedTaskLeash() { + return mPinnedTaskLeash; + } + + void setPinnedTaskLeash(@Nullable SurfaceControl leash) { + mPinnedTaskLeash = leash; + } + /** * @return true if either in swipe or button-nav fixed rotation. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java index f739d65e63c3..49cf8ae81aa8 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java @@ -63,6 +63,8 @@ public enum ShellProtoLogGroup implements IProtoLogGroup { "Bubbles"), WM_SHELL_COMPAT_UI(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false, Consts.TAG_WM_COMPAT_UI), + WM_SHELL_APP_COMPAT(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false, + Consts.TAG_WM_APP_COMPAT), TEST_GROUP(true, true, false, "WindowManagerShellProtoLogTest"); private final boolean mEnabled; @@ -131,6 +133,7 @@ public enum ShellProtoLogGroup implements IProtoLogGroup { private static final String TAG_WM_SPLIT_SCREEN = "ShellSplitScreen"; private static final String TAG_WM_DESKTOP_MODE = "ShellDesktopMode"; private static final String TAG_WM_COMPAT_UI = "CompatUi"; + private static final String TAG_WM_APP_COMPAT = "AppCompat"; private static final boolean ENABLE_DEBUG = true; private static final boolean ENABLE_LOG_TO_PROTO_DEBUG = true; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl index 799028a5507a..4a301cc0b603 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl @@ -24,7 +24,7 @@ import android.os.Bundle; import com.android.wm.shell.recents.IRecentsAnimationRunner; import com.android.wm.shell.recents.IRecentTasksListener; -import com.android.wm.shell.shared.GroupedRecentTaskInfo; +import com.android.wm.shell.shared.GroupedTaskInfo; /** * Interface that is exposed to remote callers to fetch recent tasks. @@ -44,7 +44,7 @@ interface IRecentTasks { /** * Gets the set of recent tasks. */ - GroupedRecentTaskInfo[] getRecentTasks(int maxNum, int flags, int userId) = 3; + GroupedTaskInfo[] getRecentTasks(int maxNum, int flags, int userId) = 3; /** * Gets the set of running tasks. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasksListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasksListener.aidl index 371bdd5c6469..b58f0681c571 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasksListener.aidl +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasksListener.aidl @@ -18,6 +18,8 @@ package com.android.wm.shell.recents; import android.app.ActivityManager.RunningTaskInfo; +import com.android.wm.shell.shared.GroupedTaskInfo; + /** * Listener interface that Launcher attaches to SystemUI to get split-screen callbacks. */ @@ -44,8 +46,8 @@ oneway interface IRecentTasksListener { void onRunningTaskChanged(in RunningTaskInfo taskInfo); /** A task has moved to front. */ - oneway void onTaskMovedToFront(in RunningTaskInfo taskInfo); + void onTaskMovedToFront(in GroupedTaskInfo[] visibleTasks); /** A task info has changed. */ - oneway void onTaskInfoChanged(in RunningTaskInfo taskInfo); + void onTaskInfoChanged(in RunningTaskInfo taskInfo); }
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java index 8c5d1e7e069d..364a087211c5 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java @@ -19,7 +19,7 @@ package com.android.wm.shell.recents; import android.annotation.Nullable; import android.graphics.Color; -import com.android.wm.shell.shared.GroupedRecentTaskInfo; +import com.android.wm.shell.shared.GroupedTaskInfo; import com.android.wm.shell.shared.annotations.ExternalThread; import java.util.List; @@ -35,7 +35,7 @@ public interface RecentTasks { * Gets the set of recent tasks. */ default void getRecentTasks(int maxNum, int flags, int userId, Executor callbackExecutor, - Consumer<List<GroupedRecentTaskInfo>> callback) { + Consumer<List<GroupedTaskInfo>> callback) { } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java index faa20159f64a..9911669d2cb8 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java @@ -24,10 +24,12 @@ import static com.android.wm.shell.shared.ShellSharedConstants.KEY_EXTRA_SHELL_R import android.Manifest; import android.annotation.RequiresPermission; import android.app.ActivityManager; +import android.app.ActivityManager.RecentTaskInfo; import android.app.ActivityTaskManager; import android.app.IApplicationThread; import android.app.KeyguardManager; import android.app.PendingIntent; +import android.app.TaskInfo; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -55,7 +57,7 @@ import com.android.wm.shell.common.TaskStackListenerCallback; import com.android.wm.shell.common.TaskStackListenerImpl; import com.android.wm.shell.desktopmode.DesktopRepository; import com.android.wm.shell.protolog.ShellProtoLogGroup; -import com.android.wm.shell.shared.GroupedRecentTaskInfo; +import com.android.wm.shell.shared.GroupedTaskInfo; import com.android.wm.shell.shared.annotations.ExternalThread; import com.android.wm.shell.shared.annotations.ShellMainThread; import com.android.wm.shell.shared.desktopmode.DesktopModeStatus; @@ -379,7 +381,8 @@ public class RecentTasksController implements TaskStackListenerCallback, return; } try { - mListener.onTaskMovedToFront(taskInfo); + GroupedTaskInfo runningTask = GroupedTaskInfo.forFullscreenTasks(taskInfo); + mListener.onTaskMovedToFront(new GroupedTaskInfo[]{ runningTask }); } catch (RemoteException e) { Slog.w(TAG, "Failed call onTaskMovedToFront", e); } @@ -407,27 +410,27 @@ public class RecentTasksController implements TaskStackListenerCallback, } @VisibleForTesting - ArrayList<GroupedRecentTaskInfo> getRecentTasks(int maxNum, int flags, int userId) { + ArrayList<GroupedTaskInfo> getRecentTasks(int maxNum, int flags, int userId) { // Note: the returned task list is from the most-recent to least-recent order - final List<ActivityManager.RecentTaskInfo> rawList = mActivityTaskManager.getRecentTasks( + final List<RecentTaskInfo> rawList = mActivityTaskManager.getRecentTasks( maxNum, flags, userId); // Make a mapping of task id -> task info - final SparseArray<ActivityManager.RecentTaskInfo> rawMapping = new SparseArray<>(); + final SparseArray<TaskInfo> rawMapping = new SparseArray<>(); for (int i = 0; i < rawList.size(); i++) { - final ActivityManager.RecentTaskInfo taskInfo = rawList.get(i); + final TaskInfo taskInfo = rawList.get(i); rawMapping.put(taskInfo.taskId, taskInfo); } - ArrayList<ActivityManager.RecentTaskInfo> freeformTasks = new ArrayList<>(); + ArrayList<TaskInfo> freeformTasks = new ArrayList<>(); Set<Integer> minimizedFreeformTasks = new HashSet<>(); int mostRecentFreeformTaskIndex = Integer.MAX_VALUE; // Pull out the pairs as we iterate back in the list - ArrayList<GroupedRecentTaskInfo> recentTasks = new ArrayList<>(); + ArrayList<GroupedTaskInfo> recentTasks = new ArrayList<>(); for (int i = 0; i < rawList.size(); i++) { - final ActivityManager.RecentTaskInfo taskInfo = rawList.get(i); + final RecentTaskInfo taskInfo = rawList.get(i); if (!rawMapping.contains(taskInfo.taskId)) { // If it's not in the mapping, then it was already paired with another task continue; @@ -460,20 +463,20 @@ public class RecentTasksController implements TaskStackListenerCallback, final int pairedTaskId = mSplitTasks.get(taskInfo.taskId, INVALID_TASK_ID); if (pairedTaskId != INVALID_TASK_ID && rawMapping.contains( pairedTaskId)) { - final ActivityManager.RecentTaskInfo pairedTaskInfo = rawMapping.get(pairedTaskId); + final TaskInfo pairedTaskInfo = rawMapping.get(pairedTaskId); rawMapping.remove(pairedTaskId); - recentTasks.add(GroupedRecentTaskInfo.forSplitTasks(taskInfo, pairedTaskInfo, + recentTasks.add(GroupedTaskInfo.forSplitTasks(taskInfo, pairedTaskInfo, mTaskSplitBoundsMap.get(pairedTaskId))); } else { - recentTasks.add(GroupedRecentTaskInfo.forSingleTask(taskInfo)); + recentTasks.add(GroupedTaskInfo.forFullscreenTasks(taskInfo)); } } // Add a special entry for freeform tasks if (!freeformTasks.isEmpty()) { recentTasks.add(mostRecentFreeformTaskIndex, - GroupedRecentTaskInfo.forFreeformTasks( - freeformTasks.toArray(new ActivityManager.RecentTaskInfo[0]), + GroupedTaskInfo.forFreeformTasks( + freeformTasks, minimizedFreeformTasks)); } @@ -514,16 +517,16 @@ public class RecentTasksController implements TaskStackListenerCallback, * {@param ignoreTaskToken} if it is non-null. */ @Nullable - public ActivityManager.RecentTaskInfo findTaskInBackground(ComponentName componentName, + public RecentTaskInfo findTaskInBackground(ComponentName componentName, int userId, @Nullable WindowContainerToken ignoreTaskToken) { if (componentName == null) { return null; } - List<ActivityManager.RecentTaskInfo> tasks = mActivityTaskManager.getRecentTasks( + List<RecentTaskInfo> tasks = mActivityTaskManager.getRecentTasks( Integer.MAX_VALUE, ActivityManager.RECENT_IGNORE_UNAVAILABLE, ActivityManager.getCurrentUser()); for (int i = 0; i < tasks.size(); i++) { - final ActivityManager.RecentTaskInfo task = tasks.get(i); + final RecentTaskInfo task = tasks.get(i); if (task.isVisible) { continue; } @@ -541,12 +544,12 @@ public class RecentTasksController implements TaskStackListenerCallback, * Find the background task that match the given taskId. */ @Nullable - public ActivityManager.RecentTaskInfo findTaskInBackground(int taskId) { - List<ActivityManager.RecentTaskInfo> tasks = mActivityTaskManager.getRecentTasks( + public RecentTaskInfo findTaskInBackground(int taskId) { + List<RecentTaskInfo> tasks = mActivityTaskManager.getRecentTasks( Integer.MAX_VALUE, ActivityManager.RECENT_IGNORE_UNAVAILABLE, ActivityManager.getCurrentUser()); for (int i = 0; i < tasks.size(); i++) { - final ActivityManager.RecentTaskInfo task = tasks.get(i); + final RecentTaskInfo task = tasks.get(i); if (task.isVisible) { continue; } @@ -570,7 +573,7 @@ public class RecentTasksController implements TaskStackListenerCallback, pw.println(prefix + TAG); pw.println(prefix + " mListener=" + mListener); pw.println(prefix + "Tasks:"); - ArrayList<GroupedRecentTaskInfo> recentTasks = getRecentTasks(Integer.MAX_VALUE, + ArrayList<GroupedTaskInfo> recentTasks = getRecentTasks(Integer.MAX_VALUE, ActivityManager.RECENT_IGNORE_UNAVAILABLE, ActivityManager.getCurrentUser()); for (int i = 0; i < recentTasks.size(); i++) { pw.println(innerPrefix + recentTasks.get(i)); @@ -584,9 +587,9 @@ public class RecentTasksController implements TaskStackListenerCallback, private class RecentTasksImpl implements RecentTasks { @Override public void getRecentTasks(int maxNum, int flags, int userId, Executor executor, - Consumer<List<GroupedRecentTaskInfo>> callback) { + Consumer<List<GroupedTaskInfo>> callback) { mMainExecutor.execute(() -> { - List<GroupedRecentTaskInfo> tasks = + List<GroupedTaskInfo> tasks = RecentTasksController.this.getRecentTasks(maxNum, flags, userId); executor.execute(() -> callback.accept(tasks)); }); @@ -650,7 +653,7 @@ public class RecentTasksController implements TaskStackListenerCallback, } @Override - public void onTaskMovedToFront(ActivityManager.RunningTaskInfo taskInfo) { + public void onTaskMovedToFront(GroupedTaskInfo[] taskInfo) { mListener.call(l -> l.onTaskMovedToFront(taskInfo)); } @@ -692,17 +695,20 @@ public class RecentTasksController implements TaskStackListenerCallback, } @Override - public GroupedRecentTaskInfo[] getRecentTasks(int maxNum, int flags, int userId) + public GroupedTaskInfo[] getRecentTasks(int maxNum, int flags, int userId) throws RemoteException { if (mController == null) { // The controller is already invalidated -- just return an empty task list for now - return new GroupedRecentTaskInfo[0]; + return new GroupedTaskInfo[0]; } - final GroupedRecentTaskInfo[][] out = new GroupedRecentTaskInfo[][]{null}; + final GroupedTaskInfo[][] out = new GroupedTaskInfo[][]{null}; executeRemoteCallWithTaskPermission(mController, "getRecentTasks", - (controller) -> out[0] = controller.getRecentTasks(maxNum, flags, userId) - .toArray(new GroupedRecentTaskInfo[0]), + (controller) -> { + List<GroupedTaskInfo> tasks = controller.getRecentTasks( + maxNum, flags, userId); + out[0] = tasks.toArray(new GroupedTaskInfo[0]); + }, true /* blocking */); return out[0]; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java index 40065b9287a6..417a6558ffcc 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java @@ -34,6 +34,8 @@ import static android.window.TransitionInfo.FLAG_TRANSLUCENT; import static com.android.wm.shell.shared.ShellSharedConstants.KEY_EXTRA_SHELL_CAN_HAND_OFF_ANIMATION; import static com.android.wm.shell.shared.split.SplitBounds.KEY_EXTRA_SPLIT_BOUNDS; +import static com.android.wm.shell.transition.Transitions.TRANSIT_END_RECENTS_TRANSITION; +import static com.android.wm.shell.transition.Transitions.TRANSIT_START_RECENTS_TRANSITION; import android.annotation.Nullable; import android.annotation.SuppressLint; @@ -67,6 +69,7 @@ import androidx.annotation.NonNull; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.IResultReceiver; import com.android.internal.protolog.ProtoLog; +import com.android.wm.shell.Flags; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.pip.PipUtils; @@ -216,8 +219,11 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler, break; } } - final IBinder transition = mTransitions.startTransition(TRANSIT_TO_FRONT, wct, - mixer == null ? this : mixer); + final int transitionType = Flags.enableShellTopTaskTracking() + ? TRANSIT_START_RECENTS_TRANSITION + : TRANSIT_TO_FRONT; + final IBinder transition = mTransitions.startTransition(transitionType, + wct, mixer == null ? this : mixer); if (mixer != null) { setTransitionForMixer.accept(transition); } @@ -300,7 +306,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler, "RecentsTransitionHandler.mergeAnimation: no controller found"); return; } - controller.merge(info, t, finishCallback); + controller.merge(info, t, mergeTarget, finishCallback); } @Override @@ -367,6 +373,8 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler, private boolean mPausingSeparateHome = false; private ArrayMap<SurfaceControl, SurfaceControl> mLeashMap = null; private PictureInPictureSurfaceTransaction mPipTransaction = null; + // This is the transition that backs the entire recents transition, and the one that the + // pending finish transition below will be merged into private IBinder mTransition = null; private boolean mKeyguardLocked = false; private boolean mWillFinishToHome = false; @@ -386,6 +394,10 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler, // next called. private Pair<int[], TaskSnapshot[]> mPendingPauseSnapshotsForCancel; + // Used to track a pending finish transition + private IBinder mPendingFinishTransition; + private IResultReceiver mPendingRunnerFinishCb; + RecentsController(IRecentsAnimationRunner listener) { mInstanceId = System.identityHashCode(this); mListener = listener; @@ -523,6 +535,11 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler, mInfo = null; mTransition = null; mPendingPauseSnapshotsForCancel = null; + mPipTaskId = -1; + mPipTask = null; + mPipTransaction = null; + mPendingRunnerFinishCb = null; + mPendingFinishTransition = null; mControllers.remove(this); for (int i = 0; i < mStateListeners.size(); i++) { mStateListeners.get(i).onAnimationStateChanged(false); @@ -734,6 +751,8 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler, // the pausing apps. t.setLayer(target.leash, layer); } else if (taskInfo != null && taskInfo.topActivityType == ACTIVITY_TYPE_HOME) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, + " not handling home taskId=%d", taskInfo.taskId); // do nothing } else if (TransitionUtil.isOpeningType(change.getMode())) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, @@ -872,16 +891,35 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler, } } + /** + * Note: because we use a book-end transition to finish the recents transition, we must + * either always merge the incoming transition, or always cancel the recents transition + * if we don't handle the incoming transition to ensure that the end transition is queued + * before any unhandled transitions. + */ @SuppressLint("NewApi") - void merge(TransitionInfo info, SurfaceControl.Transaction t, + void merge(TransitionInfo info, SurfaceControl.Transaction t, IBinder mergeTarget, Transitions.TransitionFinishCallback finishCallback) { if (mFinishCB == null) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, "[%d] RecentsController.merge: skip, no finish callback", mInstanceId); - // This was no-op'd (likely a repeated start) and we've already sent finish. + // This was no-op'd (likely a repeated start) and we've already completed finish. return; } + + if (Flags.enableShellTopTaskTracking() + && info.getType() == TRANSIT_END_RECENTS_TRANSITION + && mergeTarget == mTransition) { + // This is a pending finish, so merge the end transition to trigger completing the + // cleanup of the recents transition + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, + "[%d] RecentsController.merge: TRANSIT_END_RECENTS_TRANSITION", + mInstanceId); + finishCallback.onTransitionFinished(null /* wct */); + return; + } + if (info.getType() == TRANSIT_SLEEP) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, "[%d] RecentsController.merge: transit_sleep", mInstanceId); @@ -1245,8 +1283,19 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler, return; } - if (mFinishCB == null) { + if (mFinishCB == null + || (Flags.enableShellTopTaskTracking() && mPendingFinishTransition != null)) { Slog.e(TAG, "Duplicate call to finish"); + if (runnerFinishCb != null) { + try { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, + "[%d] RecentsController.finishInner: calling finish callback", + mInstanceId); + runnerFinishCb.send(0, null); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to report transition finished", e); + } + } return; } @@ -1254,19 +1303,22 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler, && !mWillFinishToHome && mPausingTasks != null && mState == STATE_NORMAL; - if (returningToApp && allAppsAreTranslucent(mPausingTasks)) { - mHomeTransitionObserver.notifyHomeVisibilityChanged(true); - } else if (!toHome) { - // For some transitions, we may have notified home activity that it became visible. - // We need to notify the observer that we are no longer going home. - mHomeTransitionObserver.notifyHomeVisibilityChanged(false); + if (!Flags.enableShellTopTaskTracking()) { + // This is only necessary when the recents transition is finished using a finishWCT, + // otherwise a new transition will notify the relevant observers + if (returningToApp && allAppsAreTranslucent(mPausingTasks)) { + mHomeTransitionObserver.notifyHomeVisibilityChanged(true); + } else if (!toHome) { + // For some transitions, we may have notified home activity that it became + // visible. We need to notify the observer that we are no longer going home. + mHomeTransitionObserver.notifyHomeVisibilityChanged(false); + } } + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, "[%d] RecentsController.finishInner: toHome=%b userLeave=%b " - + "willFinishToHome=%b state=%d", - mInstanceId, toHome, sendUserLeaveHint, mWillFinishToHome, mState); - final Transitions.TransitionFinishCallback finishCB = mFinishCB; - mFinishCB = null; + + "willFinishToHome=%b state=%d reason=%s", + mInstanceId, toHome, sendUserLeaveHint, mWillFinishToHome, mState, reason); final SurfaceControl.Transaction t = mFinishTransaction; final WindowContainerTransaction wct = new WindowContainerTransaction(); @@ -1328,6 +1380,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler, for (int i = 0; i < mClosingTasks.size(); ++i) { cleanUpPausingOrClosingTask(mClosingTasks.get(i), wct, t, sendUserLeaveHint); } + if (mPipTransaction != null && sendUserLeaveHint) { SurfaceControl pipLeash = null; TransitionInfo.Change pipChange = null; @@ -1379,15 +1432,50 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler, mTransitions.startTransition(TRANSIT_PIP, wct, null /* handler */); // We need to clear the WCT to send finishWCT=null for Recents. wct.clear(); + + if (Flags.enableShellTopTaskTracking()) { + // In this case, we've already started the PIP transition, so we can + // clean up immediately + mPendingRunnerFinishCb = runnerFinishCb; + onFinishInner(null); + return; + } } } - mPipTaskId = -1; - mPipTask = null; - mPipTransaction = null; } } + + if (Flags.enableShellTopTaskTracking()) { + if (!wct.isEmpty()) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, + "[%d] RecentsController.finishInner: " + + "Queuing TRANSIT_END_RECENTS_TRANSITION", mInstanceId); + mPendingRunnerFinishCb = runnerFinishCb; + mPendingFinishTransition = mTransitions.startTransition( + TRANSIT_END_RECENTS_TRANSITION, wct, + new PendingFinishTransitionHandler()); + } else { + // If there's no work to do, just go ahead and clean up + mPendingRunnerFinishCb = runnerFinishCb; + onFinishInner(null /* wct */); + } + } else { + mPendingRunnerFinishCb = runnerFinishCb; + onFinishInner(wct); + } + } + + /** + * Runs the actual logic to finish the recents transition. + */ + private void onFinishInner(@Nullable WindowContainerTransaction wct) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, + "[%d] RecentsController.finishInner: Completing finish", mInstanceId); + final Transitions.TransitionFinishCallback finishCb = mFinishCB; + final IResultReceiver runnerFinishCb = mPendingRunnerFinishCb; + cleanUp(); - finishCB.onTransitionFinished(wct.isEmpty() ? null : wct); + finishCb.onTransitionFinished(wct); if (runnerFinishCb != null) { try { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, @@ -1472,6 +1560,40 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler, } }); } + + /** + * A temporary transition handler used with the pending finish transition, which runs the + * cleanup/finish logic once the pending transition is merged/handled. + * This is only initialized if Flags.enableShellTopTaskTracking() is enabled. + */ + private class PendingFinishTransitionHandler implements Transitions.TransitionHandler { + @Override + public boolean startAnimation(@NonNull IBinder transition, + @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction startTransaction, + @NonNull SurfaceControl.Transaction finishTransaction, + @NonNull Transitions.TransitionFinishCallback finishCallback) { + return false; + } + + @Nullable + @Override + public WindowContainerTransaction handleRequest(@NonNull IBinder transition, + @NonNull TransitionRequestInfo request) { + return null; + } + + @Override + public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted, + @Nullable SurfaceControl.Transaction finishTransaction) { + // Once we have merged (or not if the WCT didn't result in any changes), then we can + // run the pending finish logic + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, + "[%d] RecentsController.onTransitionConsumed: " + + "Consumed pending finish transition", mInstanceId); + onFinishInner(null /* wct */); + } + }; }; /** Utility class to track the state of a task as-seen by recents. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java index ec58292b352c..9fcf98b9efc2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java @@ -353,7 +353,7 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { boolean isSeamlessDisplayChange = false; if (mode == TRANSIT_CHANGE && change.hasFlags(FLAG_IS_DISPLAY)) { - if (info.getType() == TRANSIT_CHANGE) { + if (info.getType() == TRANSIT_CHANGE || isOnlyTranslucent) { final int anim = getRotationAnimationHint(change, info, mDisplayController); isSeamlessDisplayChange = anim == ROTATION_ANIMATION_SEAMLESS; if (!(isSeamlessDisplayChange || anim == ROTATION_ANIMATION_JUMPCUT)) { @@ -395,10 +395,17 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { continue; } // No default animation for this, so just update bounds/position. - final int rootIdx = TransitionUtil.rootIndexFor(change, info); - startTransaction.setPosition(change.getLeash(), - change.getEndAbsBounds().left - info.getRoot(rootIdx).getOffset().x, - change.getEndAbsBounds().top - info.getRoot(rootIdx).getOffset().y); + if (change.getParent() == null) { + // For independent change without a parent, we have reparented it to the root + // leash in Transitions#setupAnimHierarchy. + final int rootIdx = TransitionUtil.rootIndexFor(change, info); + startTransaction.setPosition(change.getLeash(), + change.getEndAbsBounds().left - info.getRoot(rootIdx).getOffset().x, + change.getEndAbsBounds().top - info.getRoot(rootIdx).getOffset().y); + } else { + startTransaction.setPosition(change.getLeash(), + change.getEndRelOffset().x, change.getEndRelOffset().y); + } // Seamless display transition doesn't need to animate. if (isSeamlessDisplayChange) continue; if (isTask || (change.hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java index 1d456aed5f4d..3f191497e1ed 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java @@ -203,6 +203,12 @@ public class Transitions implements RemoteCallable<Transitions>, /** Transition type to minimize a task. */ public static final int TRANSIT_MINIMIZE = WindowManager.TRANSIT_FIRST_CUSTOM + 20; + /** Transition to start the recents transition */ + public static final int TRANSIT_START_RECENTS_TRANSITION = TRANSIT_FIRST_CUSTOM + 21; + + /** Transition to end the recents transition */ + public static final int TRANSIT_END_RECENTS_TRANSITION = TRANSIT_FIRST_CUSTOM + 22; + /** Transition type for desktop mode transitions. */ public static final int TRANSIT_DESKTOP_MODE_TYPES = WindowManager.TRANSIT_FIRST_CUSTOM + 100; @@ -1875,6 +1881,8 @@ public class Transitions implements RemoteCallable<Transitions>, case TRANSIT_SPLIT_PASSTHROUGH -> "SPLIT_PASSTHROUGH"; case TRANSIT_CLEANUP_PIP_EXIT -> "CLEANUP_PIP_EXIT"; case TRANSIT_MINIMIZE -> "MINIMIZE"; + case TRANSIT_START_RECENTS_TRANSITION -> "START_RECENTS_TRANSITION"; + case TRANSIT_END_RECENTS_TRANSITION -> "END_RECENTS_TRANSITION"; default -> ""; }; return typeStr + "(FIRST_CUSTOM+" + (transitType - TRANSIT_FIRST_CUSTOM) + ")"; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java index be4fd7c5eeec..c9f2d2e8c0e2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java @@ -24,6 +24,9 @@ import static android.content.pm.PackageManager.FEATURE_PC; import static android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS; import static android.view.WindowManager.TRANSIT_CHANGE; +import static com.android.window.flags.Flags.enableDisplayFocusInShellTransitions; + +import android.annotation.NonNull; import android.app.ActivityManager.RunningTaskInfo; import android.content.ContentResolver; import android.content.Context; @@ -108,6 +111,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel, FocusT } mMainExecutor.execute(() -> { mExclusionRegion.set(systemGestureExclusion); + onExclusionRegionChanged(displayId, mExclusionRegion); }); } }; @@ -161,7 +165,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel, FocusT boolean isFocusedGlobally) { final WindowDecoration decor = mWindowDecorByTaskId.get(taskId); if (decor != null) { - decor.relayout(decor.mTaskInfo, isFocusedGlobally); + decor.relayout(decor.mTaskInfo, isFocusedGlobally, decor.mExclusionRegion); } } @@ -195,7 +199,12 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel, FocusT return; } - decoration.relayout(taskInfo, decoration.mHasGlobalFocus); + if (enableDisplayFocusInShellTransitions()) { + // Pass the current global focus status to avoid updates outside of a ShellTransition. + decoration.relayout(taskInfo, decoration.mHasGlobalFocus, decoration.mExclusionRegion); + } else { + decoration.relayout(taskInfo, taskInfo.isFocused, decoration.mExclusionRegion); + } } @Override @@ -233,7 +242,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel, FocusT } else { decoration.relayout(taskInfo, startT, finishT, false /* applyStartTransactionOnDraw */, false /* setTaskCropAndPosition */, - mFocusTransitionObserver.hasGlobalFocus(taskInfo)); + mFocusTransitionObserver.hasGlobalFocus(taskInfo), mExclusionRegion); } } @@ -247,7 +256,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel, FocusT decoration.relayout(taskInfo, startT, finishT, false /* applyStartTransactionOnDraw */, false /* setTaskCropAndPosition */, - mFocusTransitionObserver.hasGlobalFocus(taskInfo)); + mFocusTransitionObserver.hasGlobalFocus(taskInfo), mExclusionRegion); } @Override @@ -259,6 +268,15 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel, FocusT decoration.close(); } + private void onExclusionRegionChanged(int displayId, @NonNull Region exclusionRegion) { + final int decorCount = mWindowDecorByTaskId.size(); + for (int i = 0; i < decorCount; i++) { + final CaptionWindowDecoration decoration = mWindowDecorByTaskId.valueAt(i); + if (decoration.mTaskInfo.displayId != displayId) continue; + decoration.onExclusionRegionChanged(exclusionRegion); + } + } + private boolean shouldShowWindowDecor(RunningTaskInfo taskInfo) { if (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) { return true; @@ -326,7 +344,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel, FocusT windowDecoration.setTaskDragResizer(taskPositioner); windowDecoration.relayout(taskInfo, startT, finishT, false /* applyStartTransactionOnDraw */, false /* setTaskCropAndPosition */, - mFocusTransitionObserver.hasGlobalFocus(taskInfo)); + mFocusTransitionObserver.hasGlobalFocus(taskInfo), mExclusionRegion); } private class CaptionTouchEventListener implements @@ -496,4 +514,4 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel, FocusT return Settings.Global.getInt(resolver, DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS, 0) != 0; } -}
\ No newline at end of file +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java index c9546731a193..982fda0ddf36 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java @@ -36,6 +36,7 @@ import android.graphics.Color; import android.graphics.Insets; import android.graphics.Point; import android.graphics.Rect; +import android.graphics.Region; import android.graphics.drawable.GradientDrawable; import android.os.Handler; import android.util.Size; @@ -174,7 +175,8 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL } @Override - void relayout(RunningTaskInfo taskInfo, boolean hasGlobalFocus) { + void relayout(RunningTaskInfo taskInfo, boolean hasGlobalFocus, + @NonNull Region displayExclusionRegion) { final SurfaceControl.Transaction t = new SurfaceControl.Transaction(); // The crop and position of the task should only be set when a task is fluid resizing. In // all other cases, it is expected that the transition handler positions and crops the task @@ -186,7 +188,7 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL // synced with the buffer transaction (that draws the View). Both will be shown on screen // at the same, whereas applying them independently causes flickering. See b/270202228. relayout(taskInfo, t, t, true /* applyStartTransactionOnDraw */, - shouldSetTaskVisibilityPositionAndCrop, hasGlobalFocus); + shouldSetTaskVisibilityPositionAndCrop, hasGlobalFocus, displayExclusionRegion); } @VisibleForTesting @@ -198,7 +200,8 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL boolean isStatusBarVisible, boolean isKeyguardVisibleAndOccluded, InsetsState displayInsetsState, - boolean hasGlobalFocus) { + boolean hasGlobalFocus, + @NonNull Region globalExclusionRegion) { relayoutParams.reset(); relayoutParams.mRunningTaskInfo = taskInfo; relayoutParams.mLayoutResId = R.layout.caption_window_decor; @@ -210,6 +213,7 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL relayoutParams.mSetTaskVisibilityPositionAndCrop = shouldSetTaskVisibilityPositionAndCrop; relayoutParams.mIsCaptionVisible = taskInfo.isFreeform() || (isStatusBarVisible && !isKeyguardVisibleAndOccluded); + relayoutParams.mDisplayExclusionRegion.set(globalExclusionRegion); if (TaskInfoKt.isTransparentCaptionBarAppearance(taskInfo)) { // If the app is requesting to customize the caption bar, allow input to fall @@ -236,7 +240,8 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL void relayout(RunningTaskInfo taskInfo, SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT, boolean applyStartTransactionOnDraw, boolean shouldSetTaskVisibilityPositionAndCrop, - boolean hasGlobalFocus) { + boolean hasGlobalFocus, + @NonNull Region globalExclusionRegion) { final boolean isFreeform = taskInfo.getWindowingMode() == WindowConfiguration.WINDOWING_MODE_FREEFORM; final boolean isDragResizeable = ENABLE_WINDOWING_SCALED_RESIZING.isTrue() @@ -249,7 +254,8 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL updateRelayoutParams(mRelayoutParams, taskInfo, applyStartTransactionOnDraw, shouldSetTaskVisibilityPositionAndCrop, mIsStatusBarVisible, mIsKeyguardVisibleAndOccluded, - mDisplayController.getInsetsState(taskInfo.displayId), hasGlobalFocus); + mDisplayController.getInsetsState(taskInfo.displayId), hasGlobalFocus, + globalExclusionRegion); relayout(mRelayoutParams, startT, finishT, wct, oldRootView, mResult); // After this line, mTaskInfo is up-to-date and should be used instead of taskInfo diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java index 17e3dd2fc68e..d71e61a4c4de 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java @@ -31,6 +31,7 @@ import static android.view.MotionEvent.ACTION_UP; import static android.view.WindowInsets.Type.statusBars; import static com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_ENTER_MODE_APP_HANDLE_MENU; +import static com.android.window.flags.Flags.enableDisplayFocusInShellTransitions; import static com.android.wm.shell.compatui.AppCompatUtils.isTopActivityExemptFromDesktopWindowing; import static com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger; import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR; @@ -219,6 +220,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, } mMainExecutor.execute(() -> { mExclusionRegion.set(systemGestureExclusion); + onExclusionRegionChanged(displayId, mExclusionRegion); }); } }; @@ -431,7 +433,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, boolean isFocusedGlobally) { final WindowDecoration decor = mWindowDecorByTaskId.get(taskId); if (decor != null) { - decor.relayout(decor.mTaskInfo, isFocusedGlobally); + decor.relayout(decor.mTaskInfo, isFocusedGlobally, decor.mExclusionRegion); } } @@ -464,11 +466,16 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, final RunningTaskInfo oldTaskInfo = decoration.mTaskInfo; if (taskInfo.displayId != oldTaskInfo.displayId - && !Flags.enableHandleInputFix()) { + && !DesktopModeFlags.ENABLE_HANDLE_INPUT_FIX.isTrue()) { removeTaskFromEventReceiver(oldTaskInfo.displayId); incrementEventReceiverTasks(taskInfo.displayId); } - decoration.relayout(taskInfo, decoration.mHasGlobalFocus); + if (enableDisplayFocusInShellTransitions()) { + // Pass the current global focus status to avoid updates outside of a ShellTransition. + decoration.relayout(taskInfo, decoration.mHasGlobalFocus, decoration.mExclusionRegion); + } else { + decoration.relayout(taskInfo, taskInfo.isFocused, decoration.mExclusionRegion); + } mActivityOrientationChangeHandler.ifPresent(handler -> handler.handleActivityOrientationChange(oldTaskInfo, taskInfo)); } @@ -508,7 +515,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, } else { decoration.relayout(taskInfo, startT, finishT, false /* applyStartTransactionOnDraw */, false /* shouldSetTaskPositionAndCrop */, - mFocusTransitionObserver.hasGlobalFocus(taskInfo)); + mFocusTransitionObserver.hasGlobalFocus(taskInfo), + mExclusionRegion); } } @@ -522,7 +530,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, decoration.relayout(taskInfo, startT, finishT, false /* applyStartTransactionOnDraw */, false /* shouldSetTaskPositionAndCrop */, - mFocusTransitionObserver.hasGlobalFocus(taskInfo)); + mFocusTransitionObserver.hasGlobalFocus(taskInfo), + mExclusionRegion); } @Override @@ -533,7 +542,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, decoration.close(); final int displayId = taskInfo.displayId; if (mEventReceiversByDisplay.contains(displayId) - && !Flags.enableHandleInputFix()) { + && !DesktopModeFlags.ENABLE_HANDLE_INPUT_FIX.isTrue()) { removeTaskFromEventReceiver(displayId); } // Remove the decoration from the cache last because WindowDecoration#close could still @@ -542,6 +551,15 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, mWindowDecorByTaskId.remove(taskInfo.taskId); } + private void onExclusionRegionChanged(int displayId, @NonNull Region exclusionRegion) { + final int decorCount = mWindowDecorByTaskId.size(); + for (int i = 0; i < decorCount; i++) { + final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.valueAt(i); + if (decoration.mTaskInfo.displayId != displayId) continue; + decoration.onExclusionRegionChanged(exclusionRegion); + } + } + private void openHandleMenu(int taskId) { final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskId); decoration.createHandleMenu(checkNumberOfOtherInstances(decoration.mTaskInfo) @@ -744,10 +762,13 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, /** * Whether to pilfer the next motion event to send cancellations to the windows below. - * Useful when the caption window is spy and the gesture should be handle by the system + * Useful when the caption window is spy and the gesture should be handled by the system * instead of by the app for their custom header content. + * Should not have any effect when {@link Flags#enableAccessibleCustomHeaders()}, because + * a spy window is not used then. */ - private boolean mShouldPilferCaptionEvents; + private boolean mIsCustomHeaderGesture; + private boolean mIsResizeGesture; private boolean mIsDragging; private boolean mTouchscreenInUse; private boolean mHasLongClicked; @@ -761,7 +782,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, mTaskToken = taskInfo.token; mDragPositioningCallback = dragPositioningCallback; final int touchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop(); - final long appHandleHoldToDragDuration = Flags.enableHoldToDragAppHandle() + final long appHandleHoldToDragDuration = + DesktopModeFlags.ENABLE_HOLD_TO_DRAG_APP_HANDLE.isTrue() ? APP_HANDLE_HOLD_TO_DRAG_DURATION_MS : 0; mHandleDragDetector = new DragDetector(this, appHandleHoldToDragDuration, touchSlop); @@ -861,7 +883,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, // to offset position relative to caption as a whole. int[] viewLocation = new int[2]; v.getLocationInWindow(viewLocation); - final boolean isResizeEvent = decoration.shouldResizeListenerHandleEvent(e, + mIsResizeGesture = decoration.shouldResizeListenerHandleEvent(e, new Point(viewLocation[0], viewLocation[1])); // The caption window may be a spy window when the caption background is // transparent, which means events will fall through to the app window. Make @@ -869,21 +891,23 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, // customizable region and what the app reported as exclusion areas, because // the drag-move or other caption gestures should take priority outside those // regions. - mShouldPilferCaptionEvents = !(downInCustomizableCaptionRegion - && downInExclusionRegion && isTransparentCaption) && !isResizeEvent; + mIsCustomHeaderGesture = downInCustomizableCaptionRegion + && downInExclusionRegion && isTransparentCaption; } - if (!mShouldPilferCaptionEvents) { - // The event will be handled by a window below or pilfered by resize handler. + if (mIsCustomHeaderGesture || mIsResizeGesture) { + // The event will be handled by the custom window below or pilfered by resize + // handler. return false; } - // Otherwise pilfer so that windows below receive cancellations for this gesture, and - // continue normal handling as a caption gesture. - if (mInputManager != null) { + if (mInputManager != null + && !Flags.enableAccessibleCustomHeaders()) { + // Pilfer so that windows below receive cancellations for this gesture. mInputManager.pilferPointers(v.getViewRootImpl().getInputToken()); } if (isUpOrCancel) { // Gesture is finished, reset state. - mShouldPilferCaptionEvents = false; + mIsCustomHeaderGesture = false; + mIsResizeGesture = false; } if (isAppHandle) { return mHandleDragDetector.onMotionEvent(v, e); @@ -1228,7 +1252,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, relevantDecor.updateHoverAndPressStatus(ev); final int action = ev.getActionMasked(); if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) { - if (!mTransitionDragActive && !Flags.enableHandleInputFix()) { + if (!mTransitionDragActive && !DesktopModeFlags.ENABLE_HANDLE_INPUT_FIX.isTrue()) { relevantDecor.closeHandleMenuIfNeeded(ev); } } @@ -1271,7 +1295,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, } final boolean shouldStartTransitionDrag = relevantDecor.checkTouchEventInFocusedCaptionHandle(ev) - || Flags.enableHandleInputFix(); + || DesktopModeFlags.ENABLE_HANDLE_INPUT_FIX.isTrue(); if (dragFromStatusBarAllowed && shouldStartTransitionDrag) { mTransitionDragActive = true; } @@ -1586,8 +1610,9 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, windowDecoration.setDragPositioningCallback(taskPositioner); windowDecoration.relayout(taskInfo, startT, finishT, false /* applyStartTransactionOnDraw */, false /* shouldSetTaskPositionAndCrop */, - mFocusTransitionObserver.hasGlobalFocus(taskInfo)); - if (!Flags.enableHandleInputFix()) { + mFocusTransitionObserver.hasGlobalFocus(taskInfo), + mExclusionRegion); + if (!DesktopModeFlags.ENABLE_HANDLE_INPUT_FIX.isTrue()) { incrementEventReceiverTasks(taskInfo.displayId); } } @@ -1612,6 +1637,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, pw.println(innerPrefix + "mTransitionDragActive=" + mTransitionDragActive); pw.println(innerPrefix + "mEventReceiversByDisplay=" + mEventReceiversByDisplay); pw.println(innerPrefix + "mWindowDecorByTaskId=" + mWindowDecorByTaskId); + pw.println(innerPrefix + "mExclusionRegion=" + mExclusionRegion); } private class DesktopModeOnTaskRepositionAnimationListener @@ -1748,7 +1774,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, && Flags.enableDesktopWindowingImmersiveHandleHiding()) { decor.onInsetsStateChanged(insetsState); } - if (!Flags.enableHandleInputFix()) { + if (!DesktopModeFlags.ENABLE_HANDLE_INPUT_FIX.isTrue()) { // If status bar inset is visible, top task is not in immersive mode. // This value is only needed when the App Handle input is being handled // through the global input monitor (hence the flag check) to ignore gestures diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java index d97632a9428c..cdcf14e0cbf3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java @@ -394,7 +394,8 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin } @Override - void relayout(ActivityManager.RunningTaskInfo taskInfo, boolean hasGlobalFocus) { + void relayout(ActivityManager.RunningTaskInfo taskInfo, boolean hasGlobalFocus, + @NonNull Region displayExclusionRegion) { final SurfaceControl.Transaction t = mSurfaceControlTransactionSupplier.get(); // The visibility, crop and position of the task should only be set when a task is // fluid resizing. In all other cases, it is expected that the transition handler sets @@ -415,7 +416,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin // causes flickering. See b/270202228. final boolean applyTransactionOnDraw = taskInfo.isFreeform(); relayout(taskInfo, t, t, applyTransactionOnDraw, shouldSetTaskVisibilityPositionAndCrop, - hasGlobalFocus); + hasGlobalFocus, displayExclusionRegion); if (!applyTransactionOnDraw) { t.apply(); } @@ -442,18 +443,18 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin void relayout(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT, boolean applyStartTransactionOnDraw, boolean shouldSetTaskVisibilityPositionAndCrop, - boolean hasGlobalFocus) { + boolean hasGlobalFocus, @NonNull Region displayExclusionRegion) { Trace.beginSection("DesktopModeWindowDecoration#relayout"); if (taskInfo.isFreeform()) { // The Task is in Freeform mode -> show its header in sync since it's an integral part // of the window itself - a delayed header might cause bad UX. relayoutInSync(taskInfo, startT, finishT, applyStartTransactionOnDraw, - shouldSetTaskVisibilityPositionAndCrop, hasGlobalFocus); + shouldSetTaskVisibilityPositionAndCrop, hasGlobalFocus, displayExclusionRegion); } else { // The Task is outside Freeform mode -> allow the handle view to be delayed since the // handle is just a small addition to the window. relayoutWithDelayedViewHost(taskInfo, startT, finishT, applyStartTransactionOnDraw, - shouldSetTaskVisibilityPositionAndCrop, hasGlobalFocus); + shouldSetTaskVisibilityPositionAndCrop, hasGlobalFocus, displayExclusionRegion); } Trace.endSection(); } @@ -462,11 +463,11 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin private void relayoutInSync(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT, boolean applyStartTransactionOnDraw, boolean shouldSetTaskVisibilityPositionAndCrop, - boolean hasGlobalFocus) { + boolean hasGlobalFocus, @NonNull Region displayExclusionRegion) { // Clear the current ViewHost runnable as we will update the ViewHost here clearCurrentViewHostRunnable(); updateRelayoutParamsAndSurfaces(taskInfo, startT, finishT, applyStartTransactionOnDraw, - shouldSetTaskVisibilityPositionAndCrop, hasGlobalFocus); + shouldSetTaskVisibilityPositionAndCrop, hasGlobalFocus, displayExclusionRegion); if (mResult.mRootView != null) { updateViewHost(mRelayoutParams, startT, mResult); } @@ -489,7 +490,8 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin private void relayoutWithDelayedViewHost(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT, boolean applyStartTransactionOnDraw, boolean shouldSetTaskVisibilityPositionAndCrop, - boolean hasGlobalFocus) { + boolean hasGlobalFocus, + @NonNull Region displayExclusionRegion) { if (applyStartTransactionOnDraw) { throw new IllegalArgumentException( "We cannot both sync viewhost ondraw and delay viewhost creation."); @@ -498,7 +500,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin clearCurrentViewHostRunnable(); updateRelayoutParamsAndSurfaces(taskInfo, startT, finishT, false /* applyStartTransactionOnDraw */, shouldSetTaskVisibilityPositionAndCrop, - hasGlobalFocus); + hasGlobalFocus, displayExclusionRegion); if (mResult.mRootView == null) { // This means something blocks the window decor from showing, e.g. the task is hidden. // Nothing is set up in this case including the decoration surface. @@ -513,7 +515,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin private void updateRelayoutParamsAndSurfaces(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT, boolean applyStartTransactionOnDraw, boolean shouldSetTaskVisibilityPositionAndCrop, - boolean hasGlobalFocus) { + boolean hasGlobalFocus, @NonNull Region displayExclusionRegion) { Trace.beginSection("DesktopModeWindowDecoration#updateRelayoutParamsAndSurfaces"); if (Flags.enableDesktopWindowingAppToWeb()) { setCapturedLink(taskInfo.capturedLink, taskInfo.capturedLinkTimestamp); @@ -538,7 +540,8 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin updateRelayoutParams(mRelayoutParams, mContext, taskInfo, applyStartTransactionOnDraw, shouldSetTaskVisibilityPositionAndCrop, mIsStatusBarVisible, mIsKeyguardVisibleAndOccluded, inFullImmersive, - mDisplayController.getInsetsState(taskInfo.displayId), hasGlobalFocus); + mDisplayController.getInsetsState(taskInfo.displayId), hasGlobalFocus, + displayExclusionRegion); final WindowDecorLinearLayout oldRootView = mResult.mRootView; final SurfaceControl oldDecorationSurface = mDecorationContainerSurface; @@ -628,13 +631,6 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin @Nullable private Intent getBrowserLink() { - // Do not show browser link in browser applications - final ComponentName baseActivity = mTaskInfo.baseActivity; - if (baseActivity != null && AppToWebUtils.isBrowserApp(mContext, - baseActivity.getPackageName(), mUserContext.getUserId())) { - return null; - } - final Uri browserLink; // If the captured link is available and has not expired, return the captured link. // Otherwise, return the generic link which is set to null if a generic link is unavailable. @@ -651,6 +647,18 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin } + @Nullable + private Intent getAppLink() { + return mWebUri == null ? null + : AppToWebUtils.getAppIntent(mWebUri, mContext.getPackageManager()); + } + + private boolean isBrowserApp() { + final ComponentName baseActivity = mTaskInfo.baseActivity; + return baseActivity != null && AppToWebUtils.isBrowserApp(mContext, + baseActivity.getPackageName(), mUserContext.getUserId()); + } + UserHandle getUser() { return mUserContext.getUser(); } @@ -807,7 +815,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin */ void disposeStatusBarInputLayer() { if (!isAppHandle(mWindowDecorViewHolder) - || !Flags.enableHandleInputFix()) { + || !DesktopModeFlags.ENABLE_HANDLE_INPUT_FIX.isTrue()) { return; } asAppHandle(mWindowDecorViewHolder).disposeStatusBarInputLayer(); @@ -874,7 +882,8 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin boolean isKeyguardVisibleAndOccluded, boolean inFullImmersiveMode, @NonNull InsetsState displayInsetsState, - boolean hasGlobalFocus) { + boolean hasGlobalFocus, + @NonNull Region displayExclusionRegion) { final int captionLayoutId = getDesktopModeWindowDecorLayoutId(taskInfo.getWindowingMode()); final boolean isAppHeader = captionLayoutId == R.layout.desktop_mode_app_header; @@ -885,6 +894,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin relayoutParams.mCaptionHeightId = getCaptionHeightIdStatic(taskInfo.getWindowingMode()); relayoutParams.mCaptionWidthId = getCaptionWidthId(relayoutParams.mLayoutResId); relayoutParams.mHasGlobalFocus = hasGlobalFocus; + relayoutParams.mDisplayExclusionRegion.set(displayExclusionRegion); final boolean showCaption; if (Flags.enableFullyImmersiveInDesktop()) { @@ -910,10 +920,20 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin relayoutParams.mIsInsetSource = isAppHeader && !inFullImmersiveMode; if (isAppHeader) { if (TaskInfoKt.isTransparentCaptionBarAppearance(taskInfo)) { - // If the app is requesting to customize the caption bar, allow input to fall - // through to the windows below so that the app can respond to input events on - // their custom content. - relayoutParams.mInputFeatures |= WindowManager.LayoutParams.INPUT_FEATURE_SPY; + // The app is requesting to customize the caption bar, which means input on + // customizable/exclusion regions must go to the app instead of to the system. + // This may be accomplished with spy windows or custom touchable regions: + if (Flags.enableAccessibleCustomHeaders()) { + // Set the touchable region of the caption to only the areas where input should + // be handled by the system (i.e. non custom-excluded areas). The region will + // be calculated based on occluding caption elements and exclusion areas + // reported by the app. + relayoutParams.mLimitTouchRegionToSystemAreas = true; + } else { + // Allow input to fall through to the windows below so that the app can respond + // to input events on their custom content. + relayoutParams.mInputFeatures |= WindowManager.LayoutParams.INPUT_FEATURE_SPY; + } } else { if (ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION.isTrue()) { // Force-consume the caption bar insets when the app tries to hide the caption. @@ -951,7 +971,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin } controlsElement.mAlignment = RelayoutParams.OccludingCaptionElement.Alignment.END; relayoutParams.mOccludingCaptionElements.add(controlsElement); - } else if (isAppHandle && !Flags.enableHandleInputFix()) { + } else if (isAppHandle && !DesktopModeFlags.ENABLE_HANDLE_INPUT_FIX.isTrue()) { // The focused decor (fullscreen/split) does not need to handle input because input in // the App Handle is handled by the InputMonitor in DesktopModeWindowDecorViewModel. // Note: This does not apply with the above flag enabled as the status bar input layer @@ -1368,6 +1388,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin .shouldShowChangeAspectRatioButton(mTaskInfo); final boolean inDesktopImmersive = mDesktopRepository .isTaskInFullImmersiveState(mTaskInfo.taskId); + final boolean isBrowserApp = isBrowserApp(); mHandleMenu = mHandleMenuFactory.create( this, mWindowManagerWrapper, @@ -1379,7 +1400,8 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin supportsMultiInstance, shouldShowManageWindowsButton, shouldShowChangeAspectRatioButton, - getBrowserLink(), + isBrowserApp, + isBrowserApp ? getAppLink() : getBrowserLink(), mResult.mCaptionWidth, mResult.mCaptionHeight, mResult.mCaptionX, @@ -1560,13 +1582,13 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin */ boolean checkTouchEventInFocusedCaptionHandle(MotionEvent ev) { if (isHandleMenuActive() || !isAppHandle(mWindowDecorViewHolder) - || Flags.enableHandleInputFix()) { + || DesktopModeFlags.ENABLE_HANDLE_INPUT_FIX.isTrue()) { return false; } // The status bar input layer can only receive input in handle coordinates to begin with, // so checking coordinates is unnecessary as input is always within handle bounds. if (isAppHandle(mWindowDecorViewHolder) - && Flags.enableHandleInputFix() + && DesktopModeFlags.ENABLE_HANDLE_INPUT_FIX.isTrue() && isCaptionVisible()) { return true; } @@ -1603,7 +1625,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin * @param ev the MotionEvent to compare */ void checkTouchEvent(MotionEvent ev) { - if (mResult.mRootView == null || Flags.enableHandleInputFix()) return; + if (mResult.mRootView == null || DesktopModeFlags.ENABLE_HANDLE_INPUT_FIX.isTrue()) return; final View caption = mResult.mRootView.findViewById(R.id.desktop_mode_caption); final View handle = caption.findViewById(R.id.caption_handle); final boolean inHandle = !isHandleMenuActive() @@ -1616,7 +1638,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin // If the whole handle menu can be touched directly, rely on FLAG_WATCH_OUTSIDE_TOUCH. // This is for the case that some of the handle menu is underneath the status bar. if (isAppHandle(mWindowDecorViewHolder) - && !Flags.enableHandleInputFix()) { + && !DesktopModeFlags.ENABLE_HANDLE_INPUT_FIX.isTrue()) { mHandleMenu.checkMotionEvent(ev); closeHandleMenuIfNeeded(ev); } @@ -1630,7 +1652,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin * @param ev the MotionEvent to compare against. */ void updateHoverAndPressStatus(MotionEvent ev) { - if (mResult.mRootView == null || Flags.enableHandleInputFix()) return; + if (mResult.mRootView == null || DesktopModeFlags.ENABLE_HANDLE_INPUT_FIX.isTrue()) return; final View handle = mResult.mRootView.findViewById(R.id.caption_handle); final boolean inHandle = !isHandleMenuActive() && checkTouchEventInFocusedCaptionHandle(ev); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt index 2edc380756ac..54c247bff984 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt @@ -39,12 +39,14 @@ import android.widget.Button import android.widget.ImageButton import android.widget.ImageView import android.widget.TextView +import android.window.DesktopModeFlags import android.window.SurfaceSyncGroup +import androidx.annotation.StringRes import androidx.annotation.VisibleForTesting import androidx.compose.ui.graphics.toArgb import androidx.core.view.isGone -import com.android.window.flags.Flags import com.android.wm.shell.R +import com.android.wm.shell.apptoweb.isBrowserApp import com.android.wm.shell.shared.split.SplitScreenConstants import com.android.wm.shell.splitscreen.SplitScreenController import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer @@ -73,7 +75,8 @@ class HandleMenu( private val shouldShowNewWindowButton: Boolean, private val shouldShowManageWindowsButton: Boolean, private val shouldShowChangeAspectRatioButton: Boolean, - private val openInBrowserIntent: Intent?, + private val isBrowserApp: Boolean, + private val openInAppOrBrowserIntent: Intent?, private val captionWidth: Int, private val captionHeight: Int, captionX: Int, @@ -83,7 +86,7 @@ class HandleMenu( private val taskInfo: RunningTaskInfo = parentDecor.mTaskInfo private val isViewAboveStatusBar: Boolean - get() = (Flags.enableHandleInputFix() && !taskInfo.isFreeform) + get() = (DesktopModeFlags.ENABLE_HANDLE_INPUT_FIX.isTrue() && !taskInfo.isFreeform) private val pillElevation: Int = loadDimensionPixelSize( R.dimen.desktop_mode_handle_menu_pill_elevation) @@ -111,7 +114,7 @@ class HandleMenu( private val globalMenuPosition: Point = Point() private val shouldShowBrowserPill: Boolean - get() = openInBrowserIntent != null + get() = openInAppOrBrowserIntent != null private val shouldShowMoreActionsPill: Boolean get() = SHOULD_SHOW_SCREENSHOT_BUTTON || shouldShowNewWindowButton || @@ -128,7 +131,7 @@ class HandleMenu( onNewWindowClickListener: () -> Unit, onManageWindowsClickListener: () -> Unit, onChangeAspectRatioClickListener: () -> Unit, - openInBrowserClickListener: (Intent) -> Unit, + openInAppOrBrowserClickListener: (Intent) -> Unit, onOpenByDefaultClickListener: () -> Unit, onCloseMenuClickListener: () -> Unit, onOutsideTouchListener: () -> Unit, @@ -146,7 +149,7 @@ class HandleMenu( onNewWindowClickListener = onNewWindowClickListener, onManageWindowsClickListener = onManageWindowsClickListener, onChangeAspectRatioClickListener = onChangeAspectRatioClickListener, - openInBrowserClickListener = openInBrowserClickListener, + openInAppOrBrowserClickListener = openInAppOrBrowserClickListener, onOpenByDefaultClickListener = onOpenByDefaultClickListener, onCloseMenuClickListener = onCloseMenuClickListener, onOutsideTouchListener = onOutsideTouchListener, @@ -167,7 +170,7 @@ class HandleMenu( onNewWindowClickListener: () -> Unit, onManageWindowsClickListener: () -> Unit, onChangeAspectRatioClickListener: () -> Unit, - openInBrowserClickListener: (Intent) -> Unit, + openInAppOrBrowserClickListener: (Intent) -> Unit, onOpenByDefaultClickListener: () -> Unit, onCloseMenuClickListener: () -> Unit, onOutsideTouchListener: () -> Unit, @@ -181,7 +184,8 @@ class HandleMenu( shouldShowBrowserPill = shouldShowBrowserPill, shouldShowNewWindowButton = shouldShowNewWindowButton, shouldShowManageWindowsButton = shouldShowManageWindowsButton, - shouldShowChangeAspectRatioButton = shouldShowChangeAspectRatioButton + shouldShowChangeAspectRatioButton = shouldShowChangeAspectRatioButton, + isBrowserApp = isBrowserApp ).apply { bind(taskInfo, appIconBitmap, appName, shouldShowMoreActionsPill) this.onToDesktopClickListener = onToDesktopClickListener @@ -190,8 +194,8 @@ class HandleMenu( this.onNewWindowClickListener = onNewWindowClickListener this.onManageWindowsClickListener = onManageWindowsClickListener this.onChangeAspectRatioClickListener = onChangeAspectRatioClickListener - this.onOpenInBrowserClickListener = { - openInBrowserClickListener.invoke(openInBrowserIntent!!) + this.onOpenInAppOrBrowserClickListener = { + openInAppOrBrowserClickListener.invoke(openInAppOrBrowserIntent!!) } this.onOpenByDefaultClickListener = onOpenByDefaultClickListener this.onCloseMenuClickListener = onCloseMenuClickListener @@ -201,7 +205,8 @@ class HandleMenu( val x = handleMenuPosition.x.toInt() val y = handleMenuPosition.y.toInt() handleMenuViewContainer = - if ((!taskInfo.isFreeform && Flags.enableHandleInputFix()) || forceShowSystemBars) { + if ((!taskInfo.isFreeform && DesktopModeFlags.ENABLE_HANDLE_INPUT_FIX.isTrue()) + || forceShowSystemBars) { AdditionalSystemViewContainer( windowManagerWrapper = windowManagerWrapper, taskId = taskInfo.taskId, @@ -237,7 +242,7 @@ class HandleMenu( menuX = marginMenuStart menuY = captionY + marginMenuTop } else { - if (Flags.enableHandleInputFix()) { + if (DesktopModeFlags.ENABLE_HANDLE_INPUT_FIX.isTrue()) { // In a focused decor, we use global coordinates for handle menu. Therefore we // need to account for other factors like split stage and menu/handle width to // center the menu. @@ -435,14 +440,15 @@ class HandleMenu( /** The view within the Handle Menu, with options to change the windowing mode and more. */ @SuppressLint("ClickableViewAccessibility") class HandleMenuView( - context: Context, + private val context: Context, menuWidth: Int, captionHeight: Int, private val shouldShowWindowingPill: Boolean, private val shouldShowBrowserPill: Boolean, private val shouldShowNewWindowButton: Boolean, private val shouldShowManageWindowsButton: Boolean, - private val shouldShowChangeAspectRatioButton: Boolean + private val shouldShowChangeAspectRatioButton: Boolean, + private val isBrowserApp: Boolean ) { val rootView = LayoutInflater.from(context) .inflate(R.layout.desktop_mode_window_decor_handle_menu, null /* root */) as View @@ -472,11 +478,12 @@ class HandleMenu( private val changeAspectRatioBtn = moreActionsPill .requireViewById<Button>(R.id.change_aspect_ratio_button) - // Open in Browser Pill. - private val openInBrowserPill = rootView.requireViewById<View>(R.id.open_in_browser_pill) - private val browserBtn = openInBrowserPill.requireViewById<Button>( - R.id.open_in_browser_button) - private val openByDefaultBtn = openInBrowserPill.requireViewById<ImageButton>( + // Open in Browser/App Pill. + private val openInAppOrBrowserPill = rootView.requireViewById<View>( + R.id.open_in_app_or_browser_pill) + private val openInAppOrBrowserBtn = openInAppOrBrowserPill.requireViewById<Button>( + R.id.open_in_app_or_browser_button) + private val openByDefaultBtn = openInAppOrBrowserPill.requireViewById<ImageButton>( R.id.open_by_default_button) private val decorThemeUtil = DecorThemeUtil(context) private val animator = HandleMenuAnimator(rootView, menuWidth, captionHeight.toFloat()) @@ -490,7 +497,7 @@ class HandleMenu( var onNewWindowClickListener: (() -> Unit)? = null var onManageWindowsClickListener: (() -> Unit)? = null var onChangeAspectRatioClickListener: (() -> Unit)? = null - var onOpenInBrowserClickListener: (() -> Unit)? = null + var onOpenInAppOrBrowserClickListener: (() -> Unit)? = null var onOpenByDefaultClickListener: (() -> Unit)? = null var onCloseMenuClickListener: (() -> Unit)? = null var onOutsideTouchListener: (() -> Unit)? = null @@ -499,7 +506,7 @@ class HandleMenu( fullscreenBtn.setOnClickListener { onToFullscreenClickListener?.invoke() } splitscreenBtn.setOnClickListener { onToSplitScreenClickListener?.invoke() } desktopBtn.setOnClickListener { onToDesktopClickListener?.invoke() } - browserBtn.setOnClickListener { onOpenInBrowserClickListener?.invoke() } + openInAppOrBrowserBtn.setOnClickListener { onOpenInAppOrBrowserClickListener?.invoke() } openByDefaultBtn.setOnClickListener { onOpenByDefaultClickListener?.invoke() } @@ -535,10 +542,10 @@ class HandleMenu( if (shouldShowMoreActionsPill) { bindMoreActionsPill(style) } - bindOpenInBrowserPill(style) + bindOpenInAppOrBrowserPill(style) } - /** Animates the menu opening. */ + /** Animates the menu openInAppOrBrowserg. */ fun animateOpenMenu() { if (taskInfo.isFullscreen || taskInfo.isMultiWindow) { animator.animateCaptionHandleExpandToOpen() @@ -660,13 +667,20 @@ class HandleMenu( } } - private fun bindOpenInBrowserPill(style: MenuStyle) { - openInBrowserPill.apply { + private fun bindOpenInAppOrBrowserPill(style: MenuStyle) { + openInAppOrBrowserPill.apply { isGone = !shouldShowBrowserPill background.setTint(style.backgroundColor) } - browserBtn.apply { + val btnText = if (isBrowserApp) { + getString(R.string.open_in_app_text) + } else { + getString(R.string.open_in_browser_text) + } + openInAppOrBrowserBtn.apply { + text = btnText + contentDescription = btnText setTextColor(style.textColor) compoundDrawableTintList = ColorStateList.valueOf(style.textColor) } @@ -674,6 +688,8 @@ class HandleMenu( openByDefaultBtn.imageTintList = ColorStateList.valueOf(style.textColor) } + private fun getString(@StringRes resId: Int): String = context.resources.getString(resId) + private data class MenuStyle( @ColorInt val backgroundColor: Int, @ColorInt val textColor: Int, @@ -708,7 +724,8 @@ interface HandleMenuFactory { shouldShowNewWindowButton: Boolean, shouldShowManageWindowsButton: Boolean, shouldShowChangeAspectRatioButton: Boolean, - openInBrowserIntent: Intent?, + isBrowserApp: Boolean, + openInAppOrBrowserIntent: Intent?, captionWidth: Int, captionHeight: Int, captionX: Int, @@ -729,7 +746,8 @@ object DefaultHandleMenuFactory : HandleMenuFactory { shouldShowNewWindowButton: Boolean, shouldShowManageWindowsButton: Boolean, shouldShowChangeAspectRatioButton: Boolean, - openInBrowserIntent: Intent?, + isBrowserApp: Boolean, + openInAppOrBrowserIntent: Intent?, captionWidth: Int, captionHeight: Int, captionX: Int, @@ -746,7 +764,8 @@ object DefaultHandleMenuFactory : HandleMenuFactory { shouldShowNewWindowButton, shouldShowManageWindowsButton, shouldShowChangeAspectRatioButton, - openInBrowserIntent, + isBrowserApp, + openInAppOrBrowserIntent, captionWidth, captionHeight, captionX, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuAnimator.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuAnimator.kt index 0c475f12f53b..470e5a1d88b4 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuAnimator.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuAnimator.kt @@ -74,7 +74,8 @@ class HandleMenuAnimator( private val appInfoPill: ViewGroup = handleMenu.requireViewById(R.id.app_info_pill) private val windowingPill: ViewGroup = handleMenu.requireViewById(R.id.windowing_pill) private val moreActionsPill: ViewGroup = handleMenu.requireViewById(R.id.more_actions_pill) - private val openInBrowserPill: ViewGroup = handleMenu.requireViewById(R.id.open_in_browser_pill) + private val openInAppOrBrowserPill: ViewGroup = + handleMenu.requireViewById(R.id.open_in_app_or_browser_pill) /** Animates the opening of the handle menu. */ fun animateOpen() { @@ -83,7 +84,7 @@ class HandleMenuAnimator( animateAppInfoPillOpen() animateWindowingPillOpen() animateMoreActionsPillOpen() - animateOpenInBrowserPill() + animateOpenInAppOrBrowserPill() runAnimations { appInfoPill.post { appInfoPill.requireViewById<View>(R.id.collapse_menu_button).sendAccessibilityEvent( @@ -103,7 +104,7 @@ class HandleMenuAnimator( animateAppInfoPillOpen() animateWindowingPillOpen() animateMoreActionsPillOpen() - animateOpenInBrowserPill() + animateOpenInAppOrBrowserPill() runAnimations { appInfoPill.post { appInfoPill.requireViewById<View>(R.id.collapse_menu_button).sendAccessibilityEvent( @@ -124,7 +125,7 @@ class HandleMenuAnimator( animateAppInfoPillFadeOut() windowingPillClose() moreActionsPillClose() - openInBrowserPillClose() + openInAppOrBrowserPillClose() runAnimations(after) } @@ -141,7 +142,7 @@ class HandleMenuAnimator( animateAppInfoPillFadeOut() windowingPillClose() moreActionsPillClose() - openInBrowserPillClose() + openInAppOrBrowserPillClose() runAnimations(after) } @@ -154,7 +155,7 @@ class HandleMenuAnimator( appInfoPill.children.forEach { it.alpha = 0f } windowingPill.alpha = 0f moreActionsPill.alpha = 0f - openInBrowserPill.alpha = 0f + openInAppOrBrowserPill.alpha = 0f // Setup pivots. handleMenu.pivotX = menuWidth / 2f @@ -166,8 +167,8 @@ class HandleMenuAnimator( moreActionsPill.pivotX = menuWidth / 2f moreActionsPill.pivotY = appInfoPill.measuredHeight.toFloat() - openInBrowserPill.pivotX = menuWidth / 2f - openInBrowserPill.pivotY = appInfoPill.measuredHeight.toFloat() + openInAppOrBrowserPill.pivotX = menuWidth / 2f + openInAppOrBrowserPill.pivotY = appInfoPill.measuredHeight.toFloat() } private fun animateAppInfoPillOpen() { @@ -297,36 +298,36 @@ class HandleMenuAnimator( } } - private fun animateOpenInBrowserPill() { + private fun animateOpenInAppOrBrowserPill() { // Open in Browser X & Y Scaling Animation animators += - ObjectAnimator.ofFloat(openInBrowserPill, SCALE_X, HALF_INITIAL_SCALE, 1f).apply { + ObjectAnimator.ofFloat(openInAppOrBrowserPill, SCALE_X, HALF_INITIAL_SCALE, 1f).apply { startDelay = BODY_SCALE_OPEN_DELAY duration = BODY_SCALE_OPEN_DURATION } animators += - ObjectAnimator.ofFloat(openInBrowserPill, SCALE_Y, HALF_INITIAL_SCALE, 1f).apply { + ObjectAnimator.ofFloat(openInAppOrBrowserPill, SCALE_Y, HALF_INITIAL_SCALE, 1f).apply { startDelay = BODY_SCALE_OPEN_DELAY duration = BODY_SCALE_OPEN_DURATION } // Open in Browser Opacity Animation animators += - ObjectAnimator.ofFloat(openInBrowserPill, ALPHA, 1f).apply { + ObjectAnimator.ofFloat(openInAppOrBrowserPill, ALPHA, 1f).apply { startDelay = BODY_ALPHA_OPEN_DELAY duration = BODY_ALPHA_OPEN_DURATION } // Open in Browser Elevation Animation animators += - ObjectAnimator.ofFloat(openInBrowserPill, TRANSLATION_Z, 1f).apply { + ObjectAnimator.ofFloat(openInAppOrBrowserPill, TRANSLATION_Z, 1f).apply { startDelay = ELEVATION_OPEN_DELAY duration = BODY_ELEVATION_OPEN_DURATION } // Open in Browser Button Opacity Animation - val button = openInBrowserPill.requireViewById<Button>(R.id.open_in_browser_button) + val button = openInAppOrBrowserPill.requireViewById<Button>(R.id.open_in_app_or_browser_button) animators += ObjectAnimator.ofFloat(button, ALPHA, 1f).apply { startDelay = BODY_ALPHA_OPEN_DELAY @@ -438,33 +439,33 @@ class HandleMenuAnimator( } } - private fun openInBrowserPillClose() { + private fun openInAppOrBrowserPillClose() { // Open in Browser X & Y Scaling Animation animators += - ObjectAnimator.ofFloat(openInBrowserPill, SCALE_X, HALF_INITIAL_SCALE).apply { + ObjectAnimator.ofFloat(openInAppOrBrowserPill, SCALE_X, HALF_INITIAL_SCALE).apply { duration = BODY_CLOSE_DURATION } animators += - ObjectAnimator.ofFloat(openInBrowserPill, SCALE_Y, HALF_INITIAL_SCALE).apply { + ObjectAnimator.ofFloat(openInAppOrBrowserPill, SCALE_Y, HALF_INITIAL_SCALE).apply { duration = BODY_CLOSE_DURATION } // Open in Browser Opacity Animation animators += - ObjectAnimator.ofFloat(openInBrowserPill, ALPHA, 0f).apply { + ObjectAnimator.ofFloat(openInAppOrBrowserPill, ALPHA, 0f).apply { duration = BODY_CLOSE_DURATION } animators += - ObjectAnimator.ofFloat(openInBrowserPill, ALPHA, 0f).apply { + ObjectAnimator.ofFloat(openInAppOrBrowserPill, ALPHA, 0f).apply { duration = BODY_CLOSE_DURATION } // Upward Open in Browser y-translation Animation val yStart: Float = -captionHeight / 2 animators += - ObjectAnimator.ofFloat(openInBrowserPill, TRANSLATION_Y, yStart).apply { + ObjectAnimator.ofFloat(openInAppOrBrowserPill, TRANSLATION_Y, yStart).apply { duration = BODY_CLOSE_DURATION } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuImageButton.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuImageButton.kt index cf82bb4f9919..8bc56e0807a8 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuImageButton.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuImageButton.kt @@ -16,13 +16,12 @@ package com.android.wm.shell.windowdecor import android.app.ActivityManager.RunningTaskInfo -import com.android.window.flags.Flags -import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer - import android.content.Context import android.util.AttributeSet import android.view.MotionEvent import android.widget.ImageButton +import android.window.DesktopModeFlags +import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer /** * A custom [ImageButton] for buttons inside handle menu that intentionally doesn't handle hovers. @@ -39,7 +38,7 @@ class HandleMenuImageButton( lateinit var taskInfo: RunningTaskInfo override fun onHoverEvent(motionEvent: MotionEvent): Boolean { - if (Flags.enableHandleInputFix() || taskInfo.isFreeform) { + if (DesktopModeFlags.ENABLE_HANDLE_INPUT_FIX.isTrue() || taskInfo.isFreeform) { return super.onHoverEvent(motionEvent) } else { return false diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java index b016c755e323..a3c75bf33cde 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java @@ -127,7 +127,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> } mDisplayController.removeDisplayWindowListener(this); - relayout(mTaskInfo, mHasGlobalFocus); + relayout(mTaskInfo, mHasGlobalFocus, mExclusionRegion); } }; @@ -143,7 +143,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> SurfaceControl mDecorationContainerSurface; SurfaceControl mCaptionContainerSurface; - private WindowlessWindowManager mCaptionWindowManager; + private CaptionWindowlessWindowManager mCaptionWindowManager; private SurfaceControlViewHost mViewHost; private Configuration mWindowDecorConfig; TaskDragResizer mTaskDragResizer; @@ -152,6 +152,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> boolean mIsStatusBarVisible; boolean mIsKeyguardVisibleAndOccluded; boolean mHasGlobalFocus; + final Region mExclusionRegion = Region.obtain(); /** The most recent set of insets applied to this window decoration. */ private WindowDecorationInsets mWindowDecorationInsets; @@ -218,7 +219,8 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> * constructor. * @param hasGlobalFocus Whether the task is focused */ - abstract void relayout(RunningTaskInfo taskInfo, boolean hasGlobalFocus); + abstract void relayout(RunningTaskInfo taskInfo, boolean hasGlobalFocus, + @NonNull Region displayExclusionRegion); /** * Used by the {@link DragPositioningCallback} associated with the implementing class to @@ -244,6 +246,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> mTaskInfo = params.mRunningTaskInfo; } mHasGlobalFocus = params.mHasGlobalFocus; + mExclusionRegion.set(params.mDisplayExclusionRegion); final int oldLayoutResId = mLayoutResId; mLayoutResId = params.mLayoutResId; @@ -402,7 +405,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> final int elementWidthPx = resources.getDimensionPixelSize(element.mWidthResId); boundingRects[i] = - calculateBoundingRect(element, elementWidthPx, captionInsetsRect); + calculateBoundingRectLocal(element, elementWidthPx, captionInsetsRect); // Subtract the regions used by the caption elements, the rest is // customizable. if (params.hasInputFeatureSpy()) { @@ -477,9 +480,8 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> if (mCaptionWindowManager == null) { // Put caption under a container surface because ViewRootImpl sets the destination frame // of windowless window layers and BLASTBufferQueue#update() doesn't support offset. - mCaptionWindowManager = new WindowlessWindowManager( - mTaskInfo.getConfiguration(), mCaptionContainerSurface, - null /* hostInputToken */); + mCaptionWindowManager = new CaptionWindowlessWindowManager( + mTaskInfo.getConfiguration(), mCaptionContainerSurface); } mCaptionWindowManager.setConfiguration(mTaskInfo.getConfiguration()); final WindowManager.LayoutParams lp = @@ -492,6 +494,14 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> lp.setTitle("Caption of Task=" + mTaskInfo.taskId); lp.setTrustedOverlay(); lp.inputFeatures = params.mInputFeatures; + final Rect localCaptionBounds = new Rect( + outResult.mCaptionX, + outResult.mCaptionY, + outResult.mCaptionX + outResult.mCaptionWidth, + outResult.mCaptionY + outResult.mCaptionHeight); + final Region touchableRegion = params.mLimitTouchRegionToSystemAreas + ? calculateLimitedTouchableRegion(params, localCaptionBounds) + : null; if (mViewHost == null) { Trace.beginSection("CaptionViewHostLayout-new"); mViewHost = mSurfaceControlViewHostFactory.create(mDecorWindowContext, mDisplay, @@ -503,6 +513,9 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> mViewHost.getRootSurfaceControl().applyTransactionOnDraw(onDrawTransaction); } outResult.mRootView.setPadding(0, params.mCaptionTopPadding, 0, 0); + if (params.mLimitTouchRegionToSystemAreas) { + mCaptionWindowManager.setTouchRegion(mViewHost, touchableRegion); + } mViewHost.setView(outResult.mRootView, lp); Trace.endSection(); } else { @@ -514,13 +527,71 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> mViewHost.getRootSurfaceControl().applyTransactionOnDraw(onDrawTransaction); } outResult.mRootView.setPadding(0, params.mCaptionTopPadding, 0, 0); + if (params.mLimitTouchRegionToSystemAreas) { + mCaptionWindowManager.setTouchRegion(mViewHost, touchableRegion); + } mViewHost.relayout(lp); Trace.endSection(); } + if (touchableRegion != null) { + touchableRegion.recycle(); + } Trace.endSection(); // CaptionViewHostLayout } - private Rect calculateBoundingRect(@NonNull OccludingCaptionElement element, + @NonNull + private Region calculateLimitedTouchableRegion( + RelayoutParams params, + @NonNull Rect localCaptionBounds) { + // Make caption bounds relative to display to align with exclusion region. + final Point positionInParent = params.mRunningTaskInfo.positionInParent; + final Rect captionBoundsInDisplay = new Rect(localCaptionBounds); + captionBoundsInDisplay.offsetTo(positionInParent.x, positionInParent.y); + + final Region boundingRects = calculateBoundingRectsRegion(params, captionBoundsInDisplay); + + final Region customizedRegion = Region.obtain(); + customizedRegion.set(captionBoundsInDisplay); + customizedRegion.op(boundingRects, Region.Op.DIFFERENCE); + customizedRegion.op(params.mDisplayExclusionRegion, Region.Op.INTERSECT); + + final Region touchableRegion = Region.obtain(); + touchableRegion.set(captionBoundsInDisplay); + touchableRegion.op(customizedRegion, Region.Op.DIFFERENCE); + // Return resulting region back to window coordinates. + touchableRegion.translate(-positionInParent.x, -positionInParent.y); + + boundingRects.recycle(); + customizedRegion.recycle(); + return touchableRegion; + } + + @NonNull + private Region calculateBoundingRectsRegion( + @NonNull RelayoutParams params, + @NonNull Rect captionBoundsInDisplay) { + final int numOfElements = params.mOccludingCaptionElements.size(); + final Region region = Region.obtain(); + if (numOfElements == 0) { + // The entire caption is a bounding rect. + region.set(captionBoundsInDisplay); + return region; + } + final Resources resources = mDecorWindowContext.getResources(); + for (int i = 0; i < numOfElements; i++) { + final OccludingCaptionElement element = params.mOccludingCaptionElements.get(i); + final int elementWidthPx = resources.getDimensionPixelSize(element.mWidthResId); + final Rect boundingRect = calculateBoundingRectLocal(element, elementWidthPx, + captionBoundsInDisplay); + // Bounding rect is initially calculated relative to the caption, so offset it to make + // it relative to the display. + boundingRect.offset(captionBoundsInDisplay.left, captionBoundsInDisplay.top); + region.union(boundingRect); + } + return region; + } + + private Rect calculateBoundingRectLocal(@NonNull OccludingCaptionElement element, int elementWidthPx, @NonNull Rect captionRect) { switch (element.mAlignment) { case START -> { @@ -539,7 +610,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> mIsKeyguardVisibleAndOccluded = visible && occluded; final boolean changed = prevVisAndOccluded != mIsKeyguardVisibleAndOccluded; if (changed) { - relayout(mTaskInfo, mHasGlobalFocus); + relayout(mTaskInfo, mHasGlobalFocus, mExclusionRegion); } } @@ -549,10 +620,14 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> final boolean changed = prevStatusBarVisibility != mIsStatusBarVisible; if (changed) { - relayout(mTaskInfo, mHasGlobalFocus); + relayout(mTaskInfo, mHasGlobalFocus, mExclusionRegion); } } + void onExclusionRegionChanged(@NonNull Region exclusionRegion) { + relayout(mTaskInfo, mHasGlobalFocus, exclusionRegion); + } + /** * Update caption visibility state and views. */ @@ -751,9 +826,11 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> int mCaptionHeightId; int mCaptionWidthId; final List<OccludingCaptionElement> mOccludingCaptionElements = new ArrayList<>(); + boolean mLimitTouchRegionToSystemAreas; int mInputFeatures; boolean mIsInsetSource = true; @InsetsSource.Flags int mInsetSourceFlags; + final Region mDisplayExclusionRegion = Region.obtain(); int mShadowRadiusId; int mCornerRadius; @@ -772,9 +849,11 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> mCaptionHeightId = Resources.ID_NULL; mCaptionWidthId = Resources.ID_NULL; mOccludingCaptionElements.clear(); + mLimitTouchRegionToSystemAreas = false; mInputFeatures = 0; mIsInsetSource = true; mInsetSourceFlags = 0; + mDisplayExclusionRegion.setEmpty(); mShadowRadiusId = Resources.ID_NULL; mCornerRadius = 0; @@ -830,6 +909,19 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> } } + private static class CaptionWindowlessWindowManager extends WindowlessWindowManager { + CaptionWindowlessWindowManager( + @NonNull Configuration configuration, + @NonNull SurfaceControl rootSurface) { + super(configuration, rootSurface, /* hostInputToken= */ null); + } + + /** Set the view host's touchable region. */ + void setTouchRegion(@NonNull SurfaceControlViewHost viewHost, @NonNull Region region) { + setTouchRegion(viewHost.getWindowToken().asBinder(), region); + } + } + @VisibleForTesting public interface SurfaceControlViewHostFactory { default SurfaceControlViewHost create(Context c, Display d, WindowlessWindowManager wmm) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModel.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModel.kt index e43c3a613157..0e40a5350a43 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModel.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModel.kt @@ -30,6 +30,7 @@ import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.common.DisplayChangeController import com.android.wm.shell.common.DisplayController import com.android.wm.shell.common.SyncTransactionQueue +import com.android.wm.shell.desktopmode.DesktopModeEventLogger import com.android.wm.shell.desktopmode.DesktopRepository import com.android.wm.shell.desktopmode.DesktopTasksController import com.android.wm.shell.desktopmode.ReturnToDragStartAnimator @@ -48,6 +49,7 @@ class DesktopTilingDecorViewModel( private val toggleResizeDesktopTaskTransitionHandler: ToggleResizeDesktopTaskTransitionHandler, private val returnToDragStartAnimator: ReturnToDragStartAnimator, private val taskRepository: DesktopRepository, + private val desktopModeEventLogger: DesktopModeEventLogger, ) : DisplayChangeController.OnDisplayChangingListener { @VisibleForTesting var tilingTransitionHandlerByDisplayId = SparseArray<DesktopTilingWindowDecoration>() @@ -80,6 +82,7 @@ class DesktopTilingDecorViewModel( toggleResizeDesktopTaskTransitionHandler, returnToDragStartAnimator, taskRepository, + desktopModeEventLogger, ) tilingTransitionHandlerByDisplayId.put(displayId, newHandler) newHandler @@ -100,7 +103,7 @@ class DesktopTilingDecorViewModel( fun moveTaskToFrontIfTiled(taskInfo: RunningTaskInfo): Boolean { return tilingTransitionHandlerByDisplayId .get(taskInfo.displayId) - ?.moveTiledPairToFront(taskInfo) ?: false + ?.moveTiledPairToFront(taskInfo, isTaskFocused = true) ?: false } fun onOverviewAnimationStateChange(isRunning: Boolean) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManager.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManager.kt index 209eb5e501b2..6cdc517c9cb7 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManager.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManager.kt @@ -23,6 +23,7 @@ import android.graphics.Rect import android.graphics.Region import android.os.Binder import android.view.LayoutInflater +import android.view.MotionEvent import android.view.RoundedCorner import android.view.SurfaceControl import android.view.SurfaceControlViewHost @@ -39,6 +40,7 @@ import android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER import android.view.WindowlessWindowManager import com.android.wm.shell.R import com.android.wm.shell.common.SyncTransactionQueue +import com.android.wm.shell.desktopmode.DesktopModeEventLogger import java.util.function.Supplier /** @@ -141,8 +143,9 @@ class DesktopTilingDividerWindowManager( t.setRelativeLayer(leash, relativeLeash, 1) } - override fun onDividerMoveStart(pos: Int) { + override fun onDividerMoveStart(pos: Int, motionEvent: MotionEvent) { setSlippery(false) + transitionHandler.onDividerHandleDragStart(motionEvent) } /** @@ -161,13 +164,13 @@ class DesktopTilingDividerWindowManager( * Notifies the transition handler of tiling operations ending, which might result in resizing * WindowContainerTransactions if the sizes of the tiled tasks changed. */ - override fun onDividerMovedEnd(pos: Int) { + override fun onDividerMovedEnd(pos: Int, motionEvent: MotionEvent) { setSlippery(true) val t = transactionSupplier.get() t.setPosition(leash, pos.toFloat() - maxRoundedCornerRadius, dividerBounds.top.toFloat()) val dividerWidth = dividerBounds.width() dividerBounds.set(pos, dividerBounds.top, pos + dividerWidth, dividerBounds.bottom) - transitionHandler.onDividerHandleDragEnd(dividerBounds, t) + transitionHandler.onDividerHandleDragEnd(dividerBounds, t, motionEvent) } private fun getWindowManagerParams(): WindowManager.LayoutParams { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt index c46767c3a51d..418b8ecd5534 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt @@ -25,6 +25,7 @@ import android.graphics.Rect import android.os.IBinder import android.os.UserHandle import android.util.Slog +import android.view.MotionEvent import android.view.SurfaceControl import android.view.SurfaceControl.Transaction import android.view.WindowManager.TRANSIT_CHANGE @@ -44,6 +45,8 @@ import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.common.DisplayController import com.android.wm.shell.common.DisplayLayout import com.android.wm.shell.common.SyncTransactionQueue +import com.android.wm.shell.desktopmode.DesktopModeEventLogger +import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger import com.android.wm.shell.desktopmode.DesktopRepository import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition import com.android.wm.shell.desktopmode.ReturnToDragStartAnimator @@ -70,6 +73,7 @@ class DesktopTilingWindowDecoration( private val toggleResizeDesktopTaskTransitionHandler: ToggleResizeDesktopTaskTransitionHandler, private val returnToDragStartAnimator: ReturnToDragStartAnimator, private val taskRepository: DesktopRepository, + private val desktopModeEventLogger: DesktopModeEventLogger, private val transactionSupplier: Supplier<Transaction> = Supplier { Transaction() }, ) : Transitions.TransitionHandler, @@ -218,6 +222,25 @@ class DesktopTilingWindowDecoration( return tilingManager } + fun onDividerHandleDragStart(motionEvent: MotionEvent) { + val leftTiledTask = leftTaskResizingHelper ?: return + val rightTiledTask = rightTaskResizingHelper ?: return + + desktopModeEventLogger.logTaskResizingStarted( + ResizeTrigger.TILING_DIVIDER, + motionEvent, + leftTiledTask.taskInfo, + displayController, + ) + + desktopModeEventLogger.logTaskResizingStarted( + ResizeTrigger.TILING_DIVIDER, + motionEvent, + rightTiledTask.taskInfo, + displayController, + ) + } + fun onDividerHandleMoved(dividerBounds: Rect, t: SurfaceControl.Transaction): Boolean { val leftTiledTask = leftTaskResizingHelper ?: return false val rightTiledTask = rightTaskResizingHelper ?: return false @@ -266,10 +289,32 @@ class DesktopTilingWindowDecoration( return true } - fun onDividerHandleDragEnd(dividerBounds: Rect, t: SurfaceControl.Transaction) { + fun onDividerHandleDragEnd( + dividerBounds: Rect, + t: SurfaceControl.Transaction, + motionEvent: MotionEvent, + ) { val leftTiledTask = leftTaskResizingHelper ?: return val rightTiledTask = rightTaskResizingHelper ?: return + desktopModeEventLogger.logTaskResizingEnded( + ResizeTrigger.TILING_DIVIDER, + motionEvent, + leftTiledTask.taskInfo, + leftTiledTask.newBounds.height(), + leftTiledTask.newBounds.width(), + displayController, + ) + + desktopModeEventLogger.logTaskResizingEnded( + ResizeTrigger.TILING_DIVIDER, + motionEvent, + rightTiledTask.taskInfo, + rightTiledTask.newBounds.height(), + rightTiledTask.newBounds.width(), + displayController, + ) + if (leftTiledTask.newBounds == leftTiledTask.bounds) { leftTiledTask.hideVeil() rightTiledTask.hideVeil() @@ -426,9 +471,9 @@ class DesktopTilingWindowDecoration( } } + // Only called if [taskInfo] relates to a focused task private fun isTilingFocusRemoved(taskInfo: RunningTaskInfo): Boolean { - return taskInfo.isFocused && - isTilingFocused && + return isTilingFocused && taskInfo.taskId != leftTaskResizingHelper?.taskInfo?.taskId && taskInfo.taskId != rightTaskResizingHelper?.taskInfo?.taskId } @@ -439,9 +484,9 @@ class DesktopTilingWindowDecoration( } } + // Only called if [taskInfo] relates to a focused task private fun isTilingRefocused(taskInfo: RunningTaskInfo): Boolean { return !isTilingFocused && - taskInfo.isFocused && (taskInfo.taskId == leftTaskResizingHelper?.taskInfo?.taskId || taskInfo.taskId == rightTaskResizingHelper?.taskInfo?.taskId) } @@ -528,9 +573,19 @@ class DesktopTilingWindowDecoration( removeTaskIfTiled(taskId, taskVanished = true, shouldDelayUpdate = true) } - fun moveTiledPairToFront(taskInfo: RunningTaskInfo): Boolean { + /** + * Moves the tiled pair to the front of the task stack, if the [taskInfo] is focused and one of + * the two tiled tasks. + * + * If specified, [isTaskFocused] will override [RunningTaskInfo.isFocused]. This is to be used + * when called when the task will be focused, but the [taskInfo] hasn't been updated yet. + */ + fun moveTiledPairToFront(taskInfo: RunningTaskInfo, isTaskFocused: Boolean? = null): Boolean { if (!isTilingManagerInitialised) return false + val isFocused = isTaskFocused ?: taskInfo.isFocused + if (!isFocused) return false + // If a task that isn't tiled is being focused, let the generic handler do the work. if (isTilingFocusRemoved(taskInfo)) { isTilingFocused = false diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DividerMoveCallback.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DividerMoveCallback.kt index b3b30ad4c09e..9799d01afc9f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DividerMoveCallback.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DividerMoveCallback.kt @@ -15,14 +15,16 @@ */ package com.android.wm.shell.windowdecor.tiling +import android.view.MotionEvent + /** Divider move callback to whichever entity that handles the moving logic. */ interface DividerMoveCallback { /** Called on the divider move start gesture. */ - fun onDividerMoveStart(pos: Int) + fun onDividerMoveStart(pos: Int, motionEvent: MotionEvent) /** Called on the divider moved by dragging it. */ fun onDividerMove(pos: Int): Boolean /** Called on divider move gesture end. */ - fun onDividerMovedEnd(pos: Int) + fun onDividerMovedEnd(pos: Int, motionEvent: MotionEvent) } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/TilingDividerView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/TilingDividerView.kt index 89229051941c..111e28e450bd 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/TilingDividerView.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/TilingDividerView.kt @@ -206,7 +206,7 @@ class TilingDividerView : FrameLayout, View.OnTouchListener, DragDetector.Motion when (event.actionMasked) { MotionEvent.ACTION_DOWN -> { if (!isWithinHandleRegion(yTouchPosInDivider)) return true - callback.onDividerMoveStart(touchPos) + callback.onDividerMoveStart(touchPos, event) setTouching() canResize = true } @@ -230,7 +230,7 @@ class TilingDividerView : FrameLayout, View.OnTouchListener, DragDetector.Motion if (!canResize) return true if (moving && resized) { dividerBounds.left = dividerBounds.left + lastAcceptedPos - startPos - callback.onDividerMovedEnd(dividerBounds.left) + callback.onDividerMovedEnd(dividerBounds.left, event) } moving = false canResize = false diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt index b5700ffb046b..503ad92d4d71 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt @@ -34,15 +34,14 @@ import android.view.accessibility.AccessibilityEvent import android.view.accessibility.AccessibilityNodeInfo import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction import android.widget.ImageButton +import android.window.DesktopModeFlags import androidx.core.view.ViewCompat import androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat import com.android.internal.policy.SystemBarUtils -import com.android.window.flags.Flags import com.android.wm.shell.R import com.android.wm.shell.shared.animation.Interpolators import com.android.wm.shell.windowdecor.WindowManagerWrapper import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer -import com.android.wm.shell.windowdecor.viewholder.WindowDecorationViewHolder.Data /** * A desktop mode window decoration used when the window is in full "focus" (i.e. fullscreen/split). @@ -141,7 +140,7 @@ internal class AppHandleViewHolder( private fun createStatusBarInputLayer(handlePosition: Point, handleWidth: Int, handleHeight: Int) { - if (!Flags.enableHandleInputFix()) return + if (!DesktopModeFlags.ENABLE_HANDLE_INPUT_FIX.isTrue()) return statusBarInputLayer = AdditionalSystemViewContainer(context, windowManagerWrapper, taskInfo.taskId, handlePosition.x, handlePosition.y, handleWidth, handleHeight, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt index 4fe66f3357a3..4cddf31321d6 100644 --- a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt @@ -23,8 +23,9 @@ import android.tools.flicker.assertors.assertions.AppLayerIncreasesInSize import android.tools.flicker.assertors.assertions.AppLayerIsInvisibleAtEnd import android.tools.flicker.assertors.assertions.AppLayerIsVisibleAlways import android.tools.flicker.assertors.assertions.AppLayerIsVisibleAtStart -import android.tools.flicker.assertors.assertions.AppWindowBecomesVisible import android.tools.flicker.assertors.assertions.AppWindowAlignsWithOnlyOneDisplayCornerAtEnd +import android.tools.flicker.assertors.assertions.AppWindowBecomesInvisible +import android.tools.flicker.assertors.assertions.AppWindowBecomesVisible import android.tools.flicker.assertors.assertions.AppWindowCoversLeftHalfScreenAtEnd import android.tools.flicker.assertors.assertions.AppWindowCoversRightHalfScreenAtEnd import android.tools.flicker.assertors.assertions.AppWindowHasDesktopModeInitialBoundsAtTheEnd @@ -44,6 +45,7 @@ import android.tools.flicker.assertors.assertions.LauncherWindowReplacesAppAsTop import android.tools.flicker.config.AssertionTemplates import android.tools.flicker.config.FlickerConfigEntry import android.tools.flicker.config.ScenarioId +import android.tools.flicker.config.common.Components.LAUNCHER import android.tools.flicker.config.desktopmode.Components.DESKTOP_MODE_APP import android.tools.flicker.config.desktopmode.Components.DESKTOP_WALLPAPER import android.tools.flicker.config.desktopmode.Components.NON_RESIZABLE_APP @@ -365,5 +367,57 @@ class DesktopModeFlickerScenarios { AppWindowAlignsWithOnlyOneDisplayCornerAtEnd(DESKTOP_MODE_APP) ).associateBy({ it }, { AssertionInvocationGroup.BLOCKING }), ) + + val MINIMIZE_APP = + FlickerConfigEntry( + scenarioId = ScenarioId("MINIMIZE_APP"), + extractor = + ShellTransitionScenarioExtractor( + transitionMatcher = + object : ITransitionMatcher { + override fun findAll( + transitions: Collection<Transition> + ): Collection<Transition> { + return transitions + .filter { it.type == TransitionType.MINIMIZE } + .sortedByDescending { it.id } + .drop(1) + } + } + ), + assertions = + AssertionTemplates.COMMON_ASSERTIONS + + listOf( + AppWindowOnTopAtStart(DESKTOP_MODE_APP), + AppWindowBecomesInvisible(DESKTOP_MODE_APP), + ).associateBy({ it }, { AssertionInvocationGroup.BLOCKING }) + ) + + val MINIMIZE_LAST_APP = + FlickerConfigEntry( + scenarioId = ScenarioId("MINIMIZE_LAST_APP"), + extractor = + ShellTransitionScenarioExtractor( + transitionMatcher = + object : ITransitionMatcher { + override fun findAll( + transitions: Collection<Transition> + ): Collection<Transition> { + val lastTransition = + transitions + .filter { it.type == TransitionType.MINIMIZE } + .maxByOrNull { it.id }!! + return listOf(lastTransition) + } + } + ), + assertions = + AssertionTemplates.COMMON_ASSERTIONS + + listOf( + AppWindowOnTopAtStart(DESKTOP_MODE_APP), + AppWindowBecomesInvisible(DESKTOP_MODE_APP), + AppWindowOnTopAtEnd(LAUNCHER), + ).associateBy({ it }, { AssertionInvocationGroup.BLOCKING }) + ) } } diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MinimizeAppsLandscape.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MinimizeAppsLandscape.kt new file mode 100644 index 000000000000..58582b02c212 --- /dev/null +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MinimizeAppsLandscape.kt @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2024 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.flicker + +import android.tools.Rotation.ROTATION_90 +import android.tools.flicker.FlickerConfig +import android.tools.flicker.annotation.ExpectedScenarios +import android.tools.flicker.annotation.FlickerConfigProvider +import android.tools.flicker.config.FlickerConfig +import android.tools.flicker.config.FlickerServiceConfig +import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.MINIMIZE_APP +import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.MINIMIZE_LAST_APP +import com.android.wm.shell.scenarios.MinimizeAppWindows +import org.junit.Test +import org.junit.runner.RunWith + +/** + * Minimize app windows by pressing the minimize button. + * + * Assert that the app windows gets hidden. + */ +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class MinimizeAppsLandscape : MinimizeAppWindows(rotation = ROTATION_90) { + @ExpectedScenarios(["MINIMIZE_APP", "MINIMIZE_LAST_APP"]) + @Test + override fun minimizeAllAppWindows() = super.minimizeAllAppWindows() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig() + .use(FlickerServiceConfig.DEFAULT) + .use(MINIMIZE_APP) + .use(MINIMIZE_LAST_APP) + } +} diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MinimizeAppsPortrait.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MinimizeAppsPortrait.kt new file mode 100644 index 000000000000..7970426a6ee8 --- /dev/null +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MinimizeAppsPortrait.kt @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2024 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.flicker + +import android.tools.flicker.FlickerConfig +import android.tools.flicker.annotation.ExpectedScenarios +import android.tools.flicker.annotation.FlickerConfigProvider +import android.tools.flicker.config.FlickerConfig +import android.tools.flicker.config.FlickerServiceConfig +import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.MINIMIZE_APP +import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.MINIMIZE_LAST_APP +import com.android.wm.shell.scenarios.MinimizeAppWindows +import org.junit.Test +import org.junit.runner.RunWith + +/** + * Minimize app windows by pressing the minimize button. + * + * Assert that the app windows gets hidden. + */ +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class MinimizeAppsPortrait : MinimizeAppWindows() { + @ExpectedScenarios(["MINIMIZE_APP", "MINIMIZE_LAST_APP"]) + @Test + override fun minimizeAllAppWindows() = super.minimizeAllAppWindows() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig() + .use(FlickerServiceConfig.DEFAULT) + .use(MINIMIZE_APP) + .use(MINIMIZE_LAST_APP) + } +} diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/MaximizeAppWindowWithDragToTopDragZoneTest.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/MaximizeAppWindowWithDragToTopDragZoneTest.kt new file mode 100644 index 000000000000..7e0b81a783c0 --- /dev/null +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/MaximizeAppWindowWithDragToTopDragZoneTest.kt @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2024 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.functional + +import android.platform.test.annotations.Postsubmit +import com.android.wm.shell.scenarios.MaximizeAppWindowWithDragToTopDragZone +import org.junit.runner.RunWith +import org.junit.runners.BlockJUnit4ClassRunner + +/* Functional test for [MaximizeAppWindowWithDragToTopDragZone]. */ +@RunWith(BlockJUnit4ClassRunner::class) +@Postsubmit +class MaximizeAppWindowWithDragToTopDragZoneTest : MaximizeAppWindowWithDragToTopDragZone() diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ExitDesktopWithDragToTopDragZone.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ExitDesktopWithDragToTopDragZone.kt index 824c4482c1e6..f442fdb31592 100644 --- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ExitDesktopWithDragToTopDragZone.kt +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ExitDesktopWithDragToTopDragZone.kt @@ -18,6 +18,7 @@ package com.android.wm.shell.scenarios import android.tools.NavBar import android.tools.Rotation +import com.android.internal.R import com.android.window.flags.Flags import com.android.wm.shell.Utils import org.junit.After @@ -40,6 +41,9 @@ constructor( @Before fun setup() { Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet) + // Skip the test when the drag-to-maximize is enabled on this device. + Assume.assumeFalse(Flags.enableDragToMaximize() && + instrumentation.context.resources.getBoolean(R.bool.config_dragToMaximizeInDesktopMode)) tapl.setEnableRotation(true) tapl.setExpectedRotation(rotation.value) testApp.enterDesktopWithDrag(wmHelper, device) diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MaximizeAppWindowWithDragToTopDragZone.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MaximizeAppWindowWithDragToTopDragZone.kt new file mode 100644 index 000000000000..a2b88f278ff2 --- /dev/null +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MaximizeAppWindowWithDragToTopDragZone.kt @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2024 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.scenarios + +import android.app.Instrumentation +import android.tools.NavBar +import android.tools.Rotation +import android.tools.flicker.rules.ChangeDisplayOrientationRule +import android.tools.traces.parsers.WindowManagerStateHelper +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import com.android.internal.R +import com.android.launcher3.tapl.LauncherInstrumentation +import com.android.server.wm.flicker.helpers.DesktopModeAppHelper +import com.android.server.wm.flicker.helpers.SimpleAppHelper +import com.android.window.flags.Flags +import com.android.wm.shell.Utils +import org.junit.After +import org.junit.Assume +import org.junit.Before +import org.junit.Ignore +import org.junit.Rule +import org.junit.Test + +/** + * Base scenario test for maximizing a desktop app window by dragging it to the top drag zone. + */ +@Ignore("Test Base Class") +abstract class MaximizeAppWindowWithDragToTopDragZone +constructor(private val rotation: Rotation = Rotation.ROTATION_0) { + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val tapl = LauncherInstrumentation() + private val wmHelper = WindowManagerStateHelper(instrumentation) + private val device = UiDevice.getInstance(instrumentation) + private val testApp = DesktopModeAppHelper(SimpleAppHelper(instrumentation)) + + @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation) + + @Before + fun setup() { + Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet) + // Skip the test when the drag-to-maximize is disabled on this device. + Assume.assumeTrue(Flags.enableDragToMaximize() && + instrumentation.context.resources.getBoolean(R.bool.config_dragToMaximizeInDesktopMode)) + tapl.setEnableRotation(true) + tapl.setExpectedRotation(rotation.value) + ChangeDisplayOrientationRule.setRotation(rotation) + testApp.enterDesktopWithDrag(wmHelper, device) + } + + @Test + open fun maximizeAppWithDragToTopDragZone() { + testApp.maximizeAppWithDragToTopDragZone(wmHelper, device) + } + + @After + fun teardown() { + testApp.exit(wmHelper) + } +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java index 266e48482568..2ed7d07ac75e 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java @@ -62,6 +62,7 @@ public class BackProgressAnimatorTest { @Before public void setUp() throws Exception { + mTargetProgressCalled = new CountDownLatch(1); mMainThreadHandler = new Handler(Looper.getMainLooper()); final BackMotionEvent backEvent = backMotionEventFrom(0, 0); mMainThreadHandler.post( diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleViewTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleViewTest.java index d38b848fbb4d..329a10998f23 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleViewTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleViewTest.java @@ -16,9 +16,8 @@ package com.android.wm.shell.bubbles.bar; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertFalse; -import android.graphics.drawable.ColorDrawable; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; @@ -47,10 +46,9 @@ public class BubbleBarHandleViewTest extends ShellTestCase { public void testUpdateHandleColor_lightBg() { mHandleView.updateHandleColor(false /* isRegionDark */, false /* animated */); - assertTrue(mHandleView.getClipToOutline()); - assertTrue(mHandleView.getBackground() instanceof ColorDrawable); - ColorDrawable bgDrawable = (ColorDrawable) mHandleView.getBackground(); - assertEquals(bgDrawable.getColor(), + assertFalse(mHandleView.getClipToOutline()); + int handleColor = mHandleView.mHandlePaint.getColor(); + assertEquals(handleColor, ContextCompat.getColor(mContext, R.color.bubble_bar_expanded_view_handle_dark)); } @@ -58,10 +56,9 @@ public class BubbleBarHandleViewTest extends ShellTestCase { public void testUpdateHandleColor_darkBg() { mHandleView.updateHandleColor(true /* isRegionDark */, false /* animated */); - assertTrue(mHandleView.getClipToOutline()); - assertTrue(mHandleView.getBackground() instanceof ColorDrawable); - ColorDrawable bgDrawable = (ColorDrawable) mHandleView.getBackground(); - assertEquals(bgDrawable.getColor(), + assertFalse(mHandleView.getClipToOutline()); + int handleColor = mHandleView.mHandlePaint.getColor(); + assertEquals(handleColor, ContextCompat.getColor(mContext, R.color.bubble_bar_expanded_view_handle_light)); } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipAppOpsListenerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipAppOpsListenerTest.java new file mode 100644 index 000000000000..b9490b881d08 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipAppOpsListenerTest.java @@ -0,0 +1,191 @@ +/* + * Copyright (C) 2024 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.common.pip; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; + +import android.app.AppOpsManager; +import android.content.ComponentName; +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; +import android.util.Pair; + +import androidx.test.filters.SmallTest; + +import com.android.wm.shell.common.ShellExecutor; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Unit test against {@link PipAppOpsListener}. + */ +@SmallTest +@TestableLooper.RunWithLooper +@RunWith(AndroidTestingRunner.class) +public class PipAppOpsListenerTest { + + @Mock private Context mMockContext; + @Mock private PackageManager mMockPackageManager; + @Mock private AppOpsManager mMockAppOpsManager; + @Mock private PipAppOpsListener.Callback mMockCallback; + @Mock private ShellExecutor mMockExecutor; + + private PipAppOpsListener mPipAppOpsListener; + + private ArgumentCaptor<AppOpsManager.OnOpChangedListener> mOnOpChangedListenerCaptor; + private ArgumentCaptor<Runnable> mRunnableArgumentCaptor; + private Pair<ComponentName, Integer> mTopPipActivity; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager); + when(mMockContext.getSystemService(Context.APP_OPS_SERVICE)) + .thenReturn(mMockAppOpsManager); + mOnOpChangedListenerCaptor = ArgumentCaptor.forClass( + AppOpsManager.OnOpChangedListener.class); + mRunnableArgumentCaptor = ArgumentCaptor.forClass(Runnable.class); + } + + @Test + public void onActivityPinned_registerAppOpsListener() { + String packageName = "com.android.test.pip"; + mPipAppOpsListener = new PipAppOpsListener(mMockContext, mMockCallback, mMockExecutor); + + mPipAppOpsListener.onActivityPinned(packageName); + + verify(mMockAppOpsManager).startWatchingMode( + eq(AppOpsManager.OP_PICTURE_IN_PICTURE), eq(packageName), + any(AppOpsManager.OnOpChangedListener.class)); + } + + @Test + public void onActivityUnpinned_unregisterAppOpsListener() { + mPipAppOpsListener = new PipAppOpsListener(mMockContext, mMockCallback, mMockExecutor); + + mPipAppOpsListener.onActivityUnpinned(); + + verify(mMockAppOpsManager).stopWatchingMode(any(AppOpsManager.OnOpChangedListener.class)); + } + + @Test + public void disablePipAppOps_dismissPip() throws PackageManager.NameNotFoundException { + String packageName = "com.android.test.pip"; + mPipAppOpsListener = new PipAppOpsListener(mMockContext, mMockCallback, mMockExecutor); + // Set up the top pip activity info as mTopPipActivity + mTopPipActivity = new Pair<>(new ComponentName(packageName, "PipActivity"), 0); + mPipAppOpsListener.setTopPipActivityInfoSupplier(this::getTopPipActivity); + // Set up the application info as mApplicationInfo + ApplicationInfo applicationInfo = new ApplicationInfo(); + applicationInfo.packageName = packageName; + when(mMockPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), anyInt())) + .thenReturn(applicationInfo); + // Mock the mode to be **not** allowed + when(mMockAppOpsManager.checkOpNoThrow(anyInt(), anyInt(), eq(packageName))) + .thenReturn(AppOpsManager.MODE_DEFAULT); + // Set up the initial state + mPipAppOpsListener.onActivityPinned(packageName); + verify(mMockAppOpsManager).startWatchingMode( + eq(AppOpsManager.OP_PICTURE_IN_PICTURE), eq(packageName), + mOnOpChangedListenerCaptor.capture()); + AppOpsManager.OnOpChangedListener opChangedListener = mOnOpChangedListenerCaptor.getValue(); + + opChangedListener.onOpChanged(String.valueOf(AppOpsManager.OP_PICTURE_IN_PICTURE), + packageName); + + verify(mMockExecutor).execute(mRunnableArgumentCaptor.capture()); + Runnable runnable = mRunnableArgumentCaptor.getValue(); + runnable.run(); + verify(mMockCallback).dismissPip(); + } + + @Test + public void disablePipAppOps_differentPackage_doNothing() + throws PackageManager.NameNotFoundException { + String packageName = "com.android.test.pip"; + mPipAppOpsListener = new PipAppOpsListener(mMockContext, mMockCallback, mMockExecutor); + // Set up the top pip activity info as mTopPipActivity + mTopPipActivity = new Pair<>(new ComponentName(packageName, "PipActivity"), 0); + mPipAppOpsListener.setTopPipActivityInfoSupplier(this::getTopPipActivity); + // Set up the application info as mApplicationInfo + ApplicationInfo applicationInfo = new ApplicationInfo(); + applicationInfo.packageName = packageName + ".modified"; + when(mMockPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), anyInt())) + .thenReturn(applicationInfo); + // Mock the mode to be **not** allowed + when(mMockAppOpsManager.checkOpNoThrow(anyInt(), anyInt(), eq(packageName))) + .thenReturn(AppOpsManager.MODE_DEFAULT); + // Set up the initial state + mPipAppOpsListener.onActivityPinned(packageName); + verify(mMockAppOpsManager).startWatchingMode( + eq(AppOpsManager.OP_PICTURE_IN_PICTURE), eq(packageName), + mOnOpChangedListenerCaptor.capture()); + AppOpsManager.OnOpChangedListener opChangedListener = mOnOpChangedListenerCaptor.getValue(); + + opChangedListener.onOpChanged(String.valueOf(AppOpsManager.OP_PICTURE_IN_PICTURE), + packageName); + + verifyZeroInteractions(mMockExecutor); + } + + @Test + public void disablePipAppOps_nameNotFound_unregisterAppOpsListener() + throws PackageManager.NameNotFoundException { + String packageName = "com.android.test.pip"; + mPipAppOpsListener = new PipAppOpsListener(mMockContext, mMockCallback, mMockExecutor); + // Set up the top pip activity info as mTopPipActivity + mTopPipActivity = new Pair<>(new ComponentName(packageName, "PipActivity"), 0); + mPipAppOpsListener.setTopPipActivityInfoSupplier(this::getTopPipActivity); + // Set up the application info as mApplicationInfo + ApplicationInfo applicationInfo = new ApplicationInfo(); + applicationInfo.packageName = packageName; + when(mMockPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), anyInt())) + .thenThrow(PackageManager.NameNotFoundException.class); + // Mock the mode to be **not** allowed + when(mMockAppOpsManager.checkOpNoThrow(anyInt(), anyInt(), eq(packageName))) + .thenReturn(AppOpsManager.MODE_DEFAULT); + // Set up the initial state + mPipAppOpsListener.onActivityPinned(packageName); + verify(mMockAppOpsManager).startWatchingMode( + eq(AppOpsManager.OP_PICTURE_IN_PICTURE), eq(packageName), + mOnOpChangedListenerCaptor.capture()); + AppOpsManager.OnOpChangedListener opChangedListener = mOnOpChangedListenerCaptor.getValue(); + + opChangedListener.onOpChanged(String.valueOf(AppOpsManager.OP_PICTURE_IN_PICTURE), + packageName); + + verify(mMockAppOpsManager).stopWatchingMode(any(AppOpsManager.OnOpChangedListener.class)); + } + + private Pair<ComponentName, Integer> getTopPipActivity(Context context) { + return mTopPipActivity; + } +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopBackNavigationTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopBackNavigationTransitionHandlerTest.kt new file mode 100644 index 000000000000..6df8d6fd7717 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopBackNavigationTransitionHandlerTest.kt @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2024 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.desktopmode + +import android.app.ActivityManager.RunningTaskInfo +import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD +import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM +import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN +import android.app.WindowConfiguration.WindowingMode +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper.RunWithLooper +import android.view.SurfaceControl +import android.view.WindowManager +import android.view.WindowManager.TRANSIT_CLOSE +import android.window.TransitionInfo +import androidx.test.filters.SmallTest +import com.android.wm.shell.ShellTestCase +import com.android.wm.shell.TestRunningTaskInfoBuilder +import com.android.wm.shell.common.DisplayController +import com.android.wm.shell.common.ShellExecutor +import org.junit.Assert.assertFalse +import org.junit.Assert.assertNull +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.any +import org.mockito.kotlin.mock +import org.mockito.kotlin.whenever + +@SmallTest +@RunWithLooper +@RunWith(AndroidTestingRunner::class) +class DesktopBackNavigationTransitionHandlerTest : ShellTestCase() { + + private val testExecutor = mock<ShellExecutor>() + private val closingTaskLeash = mock<SurfaceControl>() + private val displayController = mock<DisplayController>() + + private lateinit var handler: DesktopBackNavigationTransitionHandler + + @Before + fun setUp() { + handler = + DesktopBackNavigationTransitionHandler( + testExecutor, + testExecutor, + displayController + ) + whenever(displayController.getDisplayContext(any())).thenReturn(mContext) + } + + @Test + fun handleRequest_returnsNull() { + assertNull(handler.handleRequest(mock(), mock())) + } + + @Test + fun startAnimation_openTransition_returnsFalse() { + val animates = + handler.startAnimation( + transition = mock(), + info = + createTransitionInfo( + type = WindowManager.TRANSIT_OPEN, + task = createTask(WINDOWING_MODE_FREEFORM) + ), + startTransaction = mock(), + finishTransaction = mock(), + finishCallback = {} + ) + + assertFalse("Should not animate open transition", animates) + } + + @Test + fun startAnimation_toBackTransitionFullscreenTask_returnsFalse() { + val animates = + handler.startAnimation( + transition = mock(), + info = createTransitionInfo(task = createTask(WINDOWING_MODE_FULLSCREEN)), + startTransaction = mock(), + finishTransaction = mock(), + finishCallback = {} + ) + + assertFalse("Should not animate fullscreen task to back transition", animates) + } + + @Test + fun startAnimation_toBackTransitionOpeningFreeformTask_returnsFalse() { + val animates = + handler.startAnimation( + transition = mock(), + info = + createTransitionInfo( + changeMode = WindowManager.TRANSIT_OPEN, + task = createTask(WINDOWING_MODE_FREEFORM) + ), + startTransaction = mock(), + finishTransaction = mock(), + finishCallback = {} + ) + + assertFalse("Should not animate opening freeform task to back transition", animates) + } + + @Test + fun startAnimation_toBackTransitionToBackFreeformTask_returnsTrue() { + val animates = + handler.startAnimation( + transition = mock(), + info = createTransitionInfo(task = createTask(WINDOWING_MODE_FREEFORM)), + startTransaction = mock(), + finishTransaction = mock(), + finishCallback = {} + ) + + assertTrue("Should animate going to back freeform task close transition", animates) + } + + @Test + fun startAnimation_closeTransitionClosingFreeformTask_returnsTrue() { + val animates = + handler.startAnimation( + transition = mock(), + info = createTransitionInfo( + type = TRANSIT_CLOSE, + changeMode = TRANSIT_CLOSE, + task = createTask(WINDOWING_MODE_FREEFORM) + ), + startTransaction = mock(), + finishTransaction = mock(), + finishCallback = {} + ) + + assertTrue("Should animate going to back freeform task close transition", animates) + } + private fun createTransitionInfo( + type: Int = WindowManager.TRANSIT_TO_BACK, + changeMode: Int = WindowManager.TRANSIT_TO_BACK, + task: RunningTaskInfo + ): TransitionInfo = + TransitionInfo(type, 0 /* flags */).apply { + addChange( + TransitionInfo.Change(mock(), closingTaskLeash).apply { + mode = changeMode + parent = null + taskInfo = task + } + ) + } + + private fun createTask(@WindowingMode windowingMode: Int): RunningTaskInfo = + TestRunningTaskInfoBuilder() + .setActivityType(ACTIVITY_TYPE_STANDARD) + .setWindowingMode(windowingMode) + .build() +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopImmersiveControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopImmersiveControllerTest.kt index e05a0b54fcf4..a4f4d05d2079 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopImmersiveControllerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopImmersiveControllerTest.kt @@ -15,6 +15,7 @@ */ package com.android.wm.shell.desktopmode +import android.animation.AnimatorTestRule import android.app.ActivityManager.RunningTaskInfo import android.app.WindowConfiguration.WINDOW_CONFIG_BOUNDS import android.graphics.Rect @@ -24,6 +25,7 @@ import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import android.platform.test.flag.junit.SetFlagsRule import android.testing.AndroidTestingRunner +import android.testing.TestableLooper import android.view.Display.DEFAULT_DISPLAY import android.view.Surface import android.view.SurfaceControl @@ -43,6 +45,7 @@ import com.android.wm.shell.common.DisplayLayout import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFreeformTask import com.android.wm.shell.sysui.ShellInit import com.android.wm.shell.transition.Transitions +import com.android.wm.shell.util.StubTransaction import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Rule @@ -64,17 +67,19 @@ import org.mockito.kotlin.whenever * Usage: atest WMShellUnitTests:DesktopImmersiveControllerTest */ @SmallTest +@TestableLooper.RunWithLooper @RunWith(AndroidTestingRunner::class) class DesktopImmersiveControllerTest : ShellTestCase() { @JvmField @Rule val setFlagsRule = SetFlagsRule() + @JvmField @Rule val animatorTestRule = AnimatorTestRule(this) @Mock private lateinit var mockTransitions: Transitions private lateinit var desktopRepository: DesktopRepository @Mock private lateinit var mockDisplayController: DisplayController @Mock private lateinit var mockShellTaskOrganizer: ShellTaskOrganizer @Mock private lateinit var mockDisplayLayout: DisplayLayout - private val transactionSupplier = { SurfaceControl.Transaction() } + private val transactionSupplier = { StubTransaction() } private lateinit var controller: DesktopImmersiveController @@ -89,10 +94,12 @@ class DesktopImmersiveControllerTest : ShellTestCase() { (invocation.getArgument(0) as Rect).set(STABLE_BOUNDS) } controller = DesktopImmersiveController( + shellInit = mock(), transitions = mockTransitions, desktopRepository = desktopRepository, displayController = mockDisplayController, shellTaskOrganizer = mockShellTaskOrganizer, + shellCommandHandler = mock(), transactionSupplier = transactionSupplier, ) } @@ -672,6 +679,60 @@ class DesktopImmersiveControllerTest : ShellTestCase() { assertThat(controller.isImmersiveChange(transition, change)).isTrue() } + @Test + @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP) + fun externalAnimateResizeChange_doesNotCleanUpPendingTransitionState() { + val task = createFreeformTask() + val mockBinder = mock(IBinder::class.java) + whenever(mockTransitions.startTransition(eq(TRANSIT_CHANGE), any(), eq(controller))) + .thenReturn(mockBinder) + desktopRepository.setTaskInFullImmersiveState( + displayId = task.displayId, + taskId = task.taskId, + immersive = true + ) + + controller.moveTaskToNonImmersive(task) + + controller.animateResizeChange( + change = TransitionInfo.Change(task.token, SurfaceControl()).apply { + taskInfo = task + }, + startTransaction = StubTransaction(), + finishTransaction = StubTransaction(), + finishCallback = { } + ) + animatorTestRule.advanceTimeBy(DesktopImmersiveController.FULL_IMMERSIVE_ANIM_DURATION_MS) + + assertThat(controller.state).isNotNull() + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP) + fun startAnimation_missingChange_clearsState() { + val task = createFreeformTask() + val mockBinder = mock(IBinder::class.java) + whenever(mockTransitions.startTransition(eq(TRANSIT_CHANGE), any(), eq(controller))) + .thenReturn(mockBinder) + desktopRepository.setTaskInFullImmersiveState( + displayId = task.displayId, + taskId = task.taskId, + immersive = false + ) + + controller.moveTaskToImmersive(task) + + controller.startAnimation( + transition = mockBinder, + info = createTransitionInfo(changes = emptyList()), + startTransaction = StubTransaction(), + finishTransaction = StubTransaction(), + finishCallback = {} + ) + + assertThat(controller.state).isNull() + } + private fun createTransitionInfo( @TransitionType type: Int = TRANSIT_CHANGE, @TransitionFlags flags: Int = 0, diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt index df061e368071..f21f26443748 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt @@ -32,6 +32,7 @@ import android.testing.TestableLooper.RunWithLooper import android.view.SurfaceControl import android.view.WindowManager import android.view.WindowManager.TRANSIT_OPEN +import android.view.WindowManager.TRANSIT_TO_BACK import android.view.WindowManager.TransitionType import android.window.TransitionInfo import android.window.WindowContainerTransaction @@ -77,16 +78,28 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() { @JvmField @Rule val setFlagsRule = SetFlagsRule() - @Mock lateinit var transitions: Transitions - @Mock lateinit var desktopRepository: DesktopRepository - @Mock lateinit var freeformTaskTransitionHandler: FreeformTaskTransitionHandler - @Mock lateinit var closeDesktopTaskTransitionHandler: CloseDesktopTaskTransitionHandler - @Mock lateinit var desktopImmersiveController: DesktopImmersiveController - @Mock lateinit var interactionJankMonitor: InteractionJankMonitor - @Mock lateinit var mockHandler: Handler - @Mock lateinit var closingTaskLeash: SurfaceControl - @Mock lateinit var shellInit: ShellInit - @Mock lateinit var rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer + @Mock + lateinit var transitions: Transitions + @Mock + lateinit var desktopRepository: DesktopRepository + @Mock + lateinit var freeformTaskTransitionHandler: FreeformTaskTransitionHandler + @Mock + lateinit var closeDesktopTaskTransitionHandler: CloseDesktopTaskTransitionHandler + @Mock + lateinit var desktopBackNavigationTransitionHandler: DesktopBackNavigationTransitionHandler + @Mock + lateinit var desktopImmersiveController: DesktopImmersiveController + @Mock + lateinit var interactionJankMonitor: InteractionJankMonitor + @Mock + lateinit var mockHandler: Handler + @Mock + lateinit var closingTaskLeash: SurfaceControl + @Mock + lateinit var shellInit: ShellInit + @Mock + lateinit var rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer private lateinit var mixedHandler: DesktopMixedTransitionHandler @@ -100,6 +113,7 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() { freeformTaskTransitionHandler, closeDesktopTaskTransitionHandler, desktopImmersiveController, + desktopBackNavigationTransitionHandler, interactionJankMonitor, mockHandler, shellInit, @@ -447,6 +461,37 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() { @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS) + fun startAnimation_pendingTransition_noLaunchChange_returnsFalse() { + val wct = WindowContainerTransaction() + val launchingTask = createTask(WINDOWING_MODE_FREEFORM) + val nonLaunchTaskChange = createChange(createTask(WINDOWING_MODE_FREEFORM)) + val transition = Binder() + whenever(transitions.startTransition(eq(TRANSIT_OPEN), eq(wct), anyOrNull())) + .thenReturn(transition) + mixedHandler.addPendingMixedTransition( + PendingMixedTransition.Launch( + transition = transition, + launchingTask = launchingTask.taskId, + minimizingTask = null, + exitingImmersiveTask = null, + ) + ) + + val started = mixedHandler.startAnimation( + transition, + createTransitionInfo( + TRANSIT_OPEN, + listOf(nonLaunchTaskChange) + ), + SurfaceControl.Transaction(), + SurfaceControl.Transaction(), + ) { } + + assertFalse("Should not start animation without launching desktop task", started) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS) fun addPendingAndAnimateLaunchTransition_noMinimizeChange_doesNotReparentMinimizeChange() { val wct = WindowContainerTransaction() val launchingTask = createTask(WINDOWING_MODE_FREEFORM) @@ -564,6 +609,87 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() { assertThat(mixedHandler.pendingMixedTransitions).isEmpty() } + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION) + fun startAnimation_withMinimizingDesktopTask_callsBackNavigationHandler() { + val minimizingTask = createTask(WINDOWING_MODE_FREEFORM) + val transition = Binder() + whenever(desktopRepository.getExpandedTaskCount(any())).thenReturn(2) + whenever( + desktopBackNavigationTransitionHandler.startAnimation(any(), any(), any(), any(), any()) + ) + .thenReturn(true) + mixedHandler.addPendingMixedTransition( + PendingMixedTransition.Minimize( + transition = transition, + minimizingTask = minimizingTask.taskId, + isLastTask = false, + ) + ) + + val minimizingTaskChange = createChange(minimizingTask) + val started = mixedHandler.startAnimation( + transition = transition, + info = + createTransitionInfo( + TRANSIT_TO_BACK, + listOf(minimizingTaskChange) + ), + startTransaction = mock(), + finishTransaction = mock(), + finishCallback = {} + ) + + assertTrue("Should delegate animation to back navigation transition handler", started) + verify(desktopBackNavigationTransitionHandler) + .startAnimation( + eq(transition), + argThat { info -> info.changes.contains(minimizingTaskChange) }, + any(), any(), any()) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION) + fun startAnimation_withMinimizingLastDesktopTask_dispatchesTransition() { + val minimizingTask = createTask(WINDOWING_MODE_FREEFORM) + val transition = Binder() + whenever(desktopRepository.getExpandedTaskCount(any())).thenReturn(2) + whenever( + desktopBackNavigationTransitionHandler.startAnimation(any(), any(), any(), any(), any()) + ) + .thenReturn(true) + mixedHandler.addPendingMixedTransition( + PendingMixedTransition.Minimize( + transition = transition, + minimizingTask = minimizingTask.taskId, + isLastTask = true, + ) + ) + + val minimizingTaskChange = createChange(minimizingTask) + mixedHandler.startAnimation( + transition = transition, + info = + createTransitionInfo( + TRANSIT_TO_BACK, + listOf(minimizingTaskChange) + ), + startTransaction = mock(), + finishTransaction = mock(), + finishCallback = {} + ) + + verify(transitions) + .dispatchTransition( + eq(transition), + argThat { info -> info.changes.contains(minimizingTaskChange) }, + any(), + any(), + any(), + eq(mixedHandler) + ) + } + private fun createTransitionInfo( type: Int = WindowManager.TRANSIT_CLOSE, changeMode: Int = WindowManager.TRANSIT_CLOSE, diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt index 414c1a658b95..7f790d574a7e 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt @@ -936,6 +936,28 @@ class DesktopRepositoryTest : ShellTestCase() { } @Test + fun saveBoundsBeforeMinimize_boundsSavedByTaskId() { + val taskId = 1 + val bounds = Rect(0, 0, 200, 200) + + repo.saveBoundsBeforeMinimize(taskId, bounds) + + assertThat(repo.removeBoundsBeforeMinimize(taskId)).isEqualTo(bounds) + } + + @Test + fun removeBoundsBeforeMinimize_returnsNullAfterBoundsRemoved() { + val taskId = 1 + val bounds = Rect(0, 0, 200, 200) + repo.saveBoundsBeforeMinimize(taskId, bounds) + repo.removeBoundsBeforeMinimize(taskId) + + val boundsBeforeMinimize = repo.removeBoundsBeforeMinimize(taskId) + + assertThat(boundsBeforeMinimize).isNull() + } + + @Test fun getExpandedTasksOrdered_returnsFreeformTasksInCorrectOrder() { repo.addTask(displayId = DEFAULT_DISPLAY, taskId = 3, isVisible = true) repo.addTask(displayId = DEFAULT_DISPLAY, taskId = 2, isVisible = true) diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt index 315a46fcbd7b..ad266ead774e 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt @@ -3038,6 +3038,21 @@ class DesktopTasksControllerTest : ShellTestCase() { // Assert bounds set to stable bounds val wct = getLatestToggleResizeDesktopTaskWct() assertThat(findBoundsChange(wct, task)).isEqualTo(STABLE_BOUNDS) + // Assert event is properly logged + verify(desktopModeEventLogger, times(1)).logTaskResizingStarted( + ResizeTrigger.DRAG_TO_TOP_RESIZE_TRIGGER, + motionEvent, + task, + displayController + ) + verify(desktopModeEventLogger, times(1)).logTaskResizingEnded( + ResizeTrigger.DRAG_TO_TOP_RESIZE_TRIGGER, + motionEvent, + task, + STABLE_BOUNDS.height(), + STABLE_BOUNDS.width(), + displayController + ) } @Test @@ -3082,6 +3097,13 @@ class DesktopTasksControllerTest : ShellTestCase() { eq(STABLE_BOUNDS), anyOrNull(), ) + // Assert no event is logged + verify(desktopModeEventLogger, never()).logTaskResizingStarted( + any(), any(), any(), any(), any() + ) + verify(desktopModeEventLogger, never()).logTaskResizingEnded( + any(), any(), any(), any(), any(), any(), any() + ) } @Test diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt index 01b69aed8465..456b50da095b 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt @@ -17,6 +17,7 @@ package com.android.wm.shell.desktopmode import android.app.ActivityManager.RunningTaskInfo +import android.graphics.Rect import android.os.Binder import android.os.Handler import android.platform.test.annotations.DisableFlags @@ -24,8 +25,10 @@ import android.platform.test.annotations.EnableFlags import android.platform.test.flag.junit.SetFlagsRule import android.testing.AndroidTestingRunner import android.view.Display.DEFAULT_DISPLAY +import android.view.SurfaceControl import android.view.WindowManager.TRANSIT_OPEN import android.view.WindowManager.TRANSIT_TO_BACK +import android.window.TransitionInfo import android.window.WindowContainerTransaction import android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REMOVE_TASK import android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER @@ -63,6 +66,7 @@ import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.Mockito.any +import org.mockito.Mockito.mock import org.mockito.Mockito.spy import org.mockito.Mockito.`when` import org.mockito.kotlin.eq @@ -235,6 +239,30 @@ class DesktopTasksLimiterTest : ShellTestCase() { } @Test + fun onTransitionReady_pendingTransition_changeTaskToBack_boundsSaved() { + val bounds = Rect(0, 0, 200, 200) + val transition = Binder() + val task = setUpFreeformTask() + desktopTasksLimiter.addPendingMinimizeChange( + transition, displayId = DEFAULT_DISPLAY, taskId = task.taskId) + + val change = TransitionInfo.Change(task.token, mock(SurfaceControl::class.java)).apply { + mode = TRANSIT_TO_BACK + taskInfo = task + setStartAbsBounds(bounds) + } + desktopTasksLimiter.getTransitionObserver().onTransitionReady( + transition, + TransitionInfo(TRANSIT_OPEN, TransitionInfo.FLAG_NONE).apply { addChange(change) }, + StubTransaction() /* startTransaction */, + StubTransaction() /* finishTransaction */) + + assertThat(desktopTaskRepo.isMinimizedTask(taskId = task.taskId)).isTrue() + assertThat(desktopTaskRepo.removeBoundsBeforeMinimize(taskId = task.taskId)).isEqualTo( + bounds) + } + + @Test fun onTransitionReady_transitionMergedFromPending_taskIsMinimized() { val mergedTransition = Binder() val newTransition = Binder() diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt index 737439ce3cfe..7f1c1db3207a 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt @@ -76,6 +76,7 @@ class DesktopTasksTransitionObserverTest { private val context = mock<Context>() private val shellTaskOrganizer = mock<ShellTaskOrganizer>() private val taskRepository = mock<DesktopRepository>() + private val mixedHandler = mock<DesktopMixedTransitionHandler>() private lateinit var transitionObserver: DesktopTasksTransitionObserver private lateinit var shellInit: ShellInit @@ -87,7 +88,7 @@ class DesktopTasksTransitionObserverTest { transitionObserver = DesktopTasksTransitionObserver( - context, taskRepository, transitions, shellTaskOrganizer, shellInit + context, taskRepository, transitions, shellTaskOrganizer, mixedHandler, shellInit ) } @@ -106,6 +107,7 @@ class DesktopTasksTransitionObserverTest { ) verify(taskRepository).minimizeTask(task.displayId, task.taskId) + verify(mixedHandler).addPendingMixedTransition(any()) } @Test diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java index 840126421c08..e40bbad7adda 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java @@ -20,6 +20,8 @@ import static android.content.ClipDescription.MIMETYPE_APPLICATION_SHORTCUT; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.DragEvent.ACTION_DRAG_STARTED; +import static com.android.wm.shell.draganddrop.DragTestUtils.createAppClipData; + import static org.junit.Assert.assertFalse; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doReturn; @@ -30,9 +32,7 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import android.content.ClipData; -import android.content.ClipDescription; import android.content.Context; -import android.content.Intent; import android.os.RemoteException; import android.view.Display; import android.view.DragEvent; @@ -128,7 +128,7 @@ public class DragAndDropControllerTest extends ShellTestCase { doReturn(display).when(dragLayout).getDisplay(); doReturn(DEFAULT_DISPLAY).when(display).getDisplayId(); - final ClipData clipData = createClipData(); + final ClipData clipData = createAppClipData(MIMETYPE_APPLICATION_SHORTCUT); final DragEvent event = mock(DragEvent.class); doReturn(ACTION_DRAG_STARTED).when(event).getAction(); doReturn(clipData).when(event).getClipData(); @@ -150,15 +150,4 @@ public class DragAndDropControllerTest extends ShellTestCase { mController.onDrag(dragLayout, event); verify(mDragAndDropListener, never()).onDragStarted(); } - - private ClipData createClipData() { - ClipDescription clipDescription = new ClipDescription(MIMETYPE_APPLICATION_SHORTCUT, - new String[] { MIMETYPE_APPLICATION_SHORTCUT }); - Intent i = new Intent(); - i.putExtra(Intent.EXTRA_PACKAGE_NAME, "pkg"); - i.putExtra(Intent.EXTRA_SHORTCUT_ID, "shortcutId"); - i.putExtra(Intent.EXTRA_USER, android.os.Process.myUserHandle()); - ClipData.Item item = new ClipData.Item(i); - return new ClipData(clipDescription, item); - } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragSessionTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragSessionTest.kt new file mode 100644 index 000000000000..3d59342f62d8 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragSessionTest.kt @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2024 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.draganddrop + +import android.app.ActivityTaskManager +import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD +import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN +import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW +import android.app.WindowConfiguration.WINDOWING_MODE_PINNED +import android.content.ClipDescription +import android.content.ClipDescription.EXTRA_HIDE_DRAG_SOURCE_TASK_ID +import android.os.PersistableBundle +import android.os.RemoteException +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.wm.shell.ShellTestCase +import com.android.wm.shell.common.DisplayLayout +import com.android.wm.shell.draganddrop.DragTestUtils.createTaskInfo +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.MockitoAnnotations +import org.mockito.kotlin.any +import org.mockito.kotlin.whenever + +/** + * Tests for DragSession. + * + * Usage: atest WMShellUnitTests:DragSessionTest + */ +@SmallTest +@RunWith(AndroidTestingRunner::class) +class DragSessionTest : ShellTestCase() { + @Mock + private lateinit var activityTaskManager: ActivityTaskManager + + @Mock + private lateinit var displayLayout: DisplayLayout + + @Before + @Throws(RemoteException::class) + fun setUp() { + MockitoAnnotations.initMocks(this) + } + + @Test + fun testGetRunningTask() { + // Set up running tasks + val runningTasks = listOf( + createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD), + ) + whenever(activityTaskManager.getTasks(any(), any())).thenReturn(runningTasks) + + // Simulate dragging an app + val data = DragTestUtils.createAppClipData(ClipDescription.MIMETYPE_APPLICATION_SHORTCUT) + + // Start a new drag session + val session = DragSession(activityTaskManager, displayLayout, data, 0) + session.updateRunningTask() + + assertThat(session.runningTaskInfo).isEqualTo(runningTasks.first()) + assertThat(session.runningTaskWinMode).isEqualTo(runningTasks.first().windowingMode) + assertThat(session.runningTaskActType).isEqualTo(runningTasks.first().activityType) + } + + @Test + fun testGetRunningTaskWithFloatingTasks() { + // Set up running tasks + val runningTasks = listOf( + createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD), + createTaskInfo(WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD), + createTaskInfo(WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD, alwaysOnTop=true), + ) + whenever(activityTaskManager.getTasks(any(), any())).thenReturn(runningTasks) + + // Simulate dragging an app + val data = DragTestUtils.createAppClipData(ClipDescription.MIMETYPE_APPLICATION_SHORTCUT) + + // Start a new drag session + val session = DragSession(activityTaskManager, displayLayout, data, 0) + session.updateRunningTask() + + // Ensure that we find the first non-floating task + assertThat(session.runningTaskInfo).isEqualTo(runningTasks.first()) + assertThat(session.runningTaskWinMode).isEqualTo(runningTasks.first().windowingMode) + assertThat(session.runningTaskActType).isEqualTo(runningTasks.first().activityType) + } + + @Test + fun testHideDragSource_readDragFlag() { + // Set up running tasks + val runningTasks = listOf( + createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD), + createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD), + ) + whenever(activityTaskManager.getTasks(any(), any())).thenReturn(runningTasks) + + // Simulate dragging an app with hide-drag-source set for the second (top most) app + val data = DragTestUtils.createAppClipData(ClipDescription.MIMETYPE_APPLICATION_SHORTCUT) + data.description.extras = + PersistableBundle().apply { + putInt( + EXTRA_HIDE_DRAG_SOURCE_TASK_ID, + runningTasks.last().taskId + ) + } + + // Start a new drag session + val session = DragSession(activityTaskManager, displayLayout, data, 0) + session.updateRunningTask() + + assertThat(session.hideDragSourceTaskId).isEqualTo(runningTasks.last().taskId) + } +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragTestUtils.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragTestUtils.kt new file mode 100644 index 000000000000..1680d9b9a86c --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragTestUtils.kt @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2024 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.draganddrop + +import android.app.ActivityManager +import android.app.PendingIntent +import android.content.ClipData +import android.content.ClipDescription +import android.content.ComponentName +import android.content.Intent +import android.content.pm.ActivityInfo +import android.content.pm.ApplicationInfo +import android.os.Process +import java.util.Random +import org.mockito.Mockito + +/** + * Convenience methods for drag tests. + */ +object DragTestUtils { + /** + * Creates an app-based clip data that is by default resizeable. + */ + @JvmStatic + fun createAppClipData(mimeType: String): ClipData { + val clipDescription = ClipDescription(mimeType, arrayOf(mimeType)) + val i = Intent() + when (mimeType) { + ClipDescription.MIMETYPE_APPLICATION_SHORTCUT -> { + i.putExtra(Intent.EXTRA_PACKAGE_NAME, "package") + i.putExtra(Intent.EXTRA_SHORTCUT_ID, "shortcut_id") + } + + ClipDescription.MIMETYPE_APPLICATION_TASK -> i.putExtra(Intent.EXTRA_TASK_ID, 12345) + ClipDescription.MIMETYPE_APPLICATION_ACTIVITY -> { + val pi = Mockito.mock(PendingIntent::class.java) + Mockito.doReturn(Process.myUserHandle()).`when`(pi).creatorUserHandle + i.putExtra(ClipDescription.EXTRA_PENDING_INTENT, pi) + } + } + i.putExtra(Intent.EXTRA_USER, Process.myUserHandle()) + val item = ClipData.Item(i) + item.activityInfo = ActivityInfo() + item.activityInfo.applicationInfo = ApplicationInfo() + val data = ClipData(clipDescription, item) + setClipDataResizeable(data, true) + return data + } + + /** + * Creates an intent-based clip data that is by default resizeable. + */ + @JvmStatic + fun createIntentClipData(intent: PendingIntent): ClipData { + val clipDescription = ClipDescription( + "Intent", + arrayOf(ClipDescription.MIMETYPE_TEXT_INTENT) + ) + val item = ClipData.Item.Builder() + .setIntentSender(intent.intentSender) + .build() + item.activityInfo = ActivityInfo() + item.activityInfo.applicationInfo = ApplicationInfo() + val data = ClipData(clipDescription, item) + setClipDataResizeable(data, true) + return data + } + + /** + * Sets the given clip data to be resizeable. + */ + @JvmStatic + fun setClipDataResizeable(data: ClipData, resizeable: Boolean) { + data.getItemAt(0).activityInfo.resizeMode = if (resizeable) + ActivityInfo.RESIZE_MODE_RESIZEABLE + else + ActivityInfo.RESIZE_MODE_UNRESIZEABLE + } + + /** + * Creates a task info with the given params. + */ + @JvmStatic + fun createTaskInfo(winMode: Int, actType: Int): ActivityManager.RunningTaskInfo { + return createTaskInfo(winMode, actType, false) + } + + /** + * Creates a task info with the given params. + */ + @JvmStatic + fun createTaskInfo(winMode: Int, actType: Int, alwaysOnTop: Boolean = false): + ActivityManager.RunningTaskInfo { + val info = ActivityManager.RunningTaskInfo() + info.taskId = Random().nextInt() + info.configuration.windowConfiguration.activityType = actType + info.configuration.windowConfiguration.windowingMode = winMode + info.configuration.windowConfiguration.isAlwaysOnTop = alwaysOnTop + info.isVisible = true + info.isResizeable = true + info.baseActivity = ComponentName( + "com.android.wm.shell", + ".ActivityWithMode$winMode" + ) + info.baseIntent = Intent() + info.baseIntent.setComponent(info.baseActivity) + val activityInfo = ActivityInfo() + activityInfo.packageName = info.baseActivity!!.packageName + activityInfo.name = info.baseActivity!!.className + info.topActivityInfo = activityInfo + return info + } +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/SplitDragPolicyTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/SplitDragPolicyTest.java index eb74218b866a..2cfce6933e1b 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/SplitDragPolicyTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/SplitDragPolicyTest.java @@ -22,11 +22,12 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.content.ClipDescription.MIMETYPE_APPLICATION_ACTIVITY; import static android.content.ClipDescription.MIMETYPE_APPLICATION_SHORTCUT; import static android.content.ClipDescription.MIMETYPE_APPLICATION_TASK; -import static android.content.ClipDescription.MIMETYPE_TEXT_INTENT; - -import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; +import static com.android.wm.shell.draganddrop.DragTestUtils.createAppClipData; +import static com.android.wm.shell.draganddrop.DragTestUtils.createIntentClipData; +import static com.android.wm.shell.draganddrop.DragTestUtils.createTaskInfo; +import static com.android.wm.shell.draganddrop.DragTestUtils.setClipDataResizeable; import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT; import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT; import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED; @@ -55,11 +56,7 @@ import android.app.ActivityManager; import android.app.ActivityTaskManager; import android.app.PendingIntent; import android.content.ClipData; -import android.content.ClipDescription; -import android.content.ComponentName; import android.content.Context; -import android.content.Intent; -import android.content.pm.ActivityInfo; import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Insets; @@ -176,74 +173,11 @@ public class SplitDragPolicyTest extends ShellTestCase { mMockitoSession.finishMocking(); } - /** - * Creates an app-based clip data that is by default resizeable. - */ - private ClipData createAppClipData(String mimeType) { - ClipDescription clipDescription = new ClipDescription(mimeType, new String[] { mimeType }); - Intent i = new Intent(); - switch (mimeType) { - case MIMETYPE_APPLICATION_SHORTCUT: - i.putExtra(Intent.EXTRA_PACKAGE_NAME, "package"); - i.putExtra(Intent.EXTRA_SHORTCUT_ID, "shortcut_id"); - break; - case MIMETYPE_APPLICATION_TASK: - i.putExtra(Intent.EXTRA_TASK_ID, 12345); - break; - case MIMETYPE_APPLICATION_ACTIVITY: - final PendingIntent pi = mock(PendingIntent.class); - doReturn(android.os.Process.myUserHandle()).when(pi).getCreatorUserHandle(); - i.putExtra(ClipDescription.EXTRA_PENDING_INTENT, pi); - break; - } - i.putExtra(Intent.EXTRA_USER, android.os.Process.myUserHandle()); - ClipData.Item item = new ClipData.Item(i); - item.setActivityInfo(new ActivityInfo()); - ClipData data = new ClipData(clipDescription, item); - setClipDataResizeable(data, true); - return data; - } - - /** - * Creates an intent-based clip data that is by default resizeable. - */ - private ClipData createIntentClipData(PendingIntent intent) { - ClipDescription clipDescription = new ClipDescription("Intent", - new String[] { MIMETYPE_TEXT_INTENT }); - ClipData.Item item = new ClipData.Item.Builder() - .setIntentSender(intent.getIntentSender()) - .build(); - ClipData data = new ClipData(clipDescription, item); - return data; - } - - private ActivityManager.RunningTaskInfo createTaskInfo(int winMode, int actType) { - ActivityManager.RunningTaskInfo info = new ActivityManager.RunningTaskInfo(); - info.configuration.windowConfiguration.setActivityType(actType); - info.configuration.windowConfiguration.setWindowingMode(winMode); - info.isResizeable = true; - info.baseActivity = new ComponentName(getInstrumentation().getContext(), - ".ActivityWithMode" + winMode); - info.baseIntent = new Intent(); - info.baseIntent.setComponent(info.baseActivity); - ActivityInfo activityInfo = new ActivityInfo(); - activityInfo.packageName = info.baseActivity.getPackageName(); - activityInfo.name = info.baseActivity.getClassName(); - info.topActivityInfo = activityInfo; - return info; - } - private void setRunningTask(ActivityManager.RunningTaskInfo task) { doReturn(Collections.singletonList(task)).when(mActivityTaskManager) .getTasks(anyInt(), anyBoolean()); } - private void setClipDataResizeable(ClipData data, boolean resizeable) { - data.getItemAt(0).getActivityInfo().resizeMode = resizeable - ? ActivityInfo.RESIZE_MODE_RESIZEABLE - : ActivityInfo.RESIZE_MODE_UNRESIZEABLE; - } - @Test public void testDragAppOverFullscreenHome_expectOnlyFullscreenTarget() { dragOverFullscreenHome_expectOnlyFullscreenTarget(mActivityClipData); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipAnimationControllerTest.java index 72950a8dc139..6d37ed766aef 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipAnimationControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipAnimationControllerTest.java @@ -28,8 +28,11 @@ import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTI import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; +import android.app.AppCompatTaskInfo; import android.app.TaskInfo; import android.graphics.Rect; import android.testing.AndroidTestingRunner; @@ -75,6 +78,7 @@ public class PipAnimationControllerTest extends ShellTestCase { .setContainerLayer() .setName("FakeLeash") .build(); + mTaskInfo.appCompatTaskInfo = mock(AppCompatTaskInfo.class); } @Test @@ -93,7 +97,8 @@ public class PipAnimationControllerTest extends ShellTestCase { final Rect endValue1 = new Rect(100, 100, 200, 200); final PipAnimationController.PipTransitionAnimator animator = mPipAnimationController .getAnimator(mTaskInfo, mLeash, baseValue, startValue, endValue1, null, - TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_0); + TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_0, + false /* alwaysAnimateTaskBounds */); assertEquals("Expect ANIM_TYPE_BOUNDS animation", animator.getAnimationType(), PipAnimationController.ANIM_TYPE_BOUNDS); @@ -107,14 +112,16 @@ public class PipAnimationControllerTest extends ShellTestCase { final Rect endValue2 = new Rect(200, 200, 300, 300); final PipAnimationController.PipTransitionAnimator oldAnimator = mPipAnimationController .getAnimator(mTaskInfo, mLeash, baseValue, startValue, endValue1, null, - TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_0); + TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_0, + false /* alwaysAnimateTaskBounds */); oldAnimator.setSurfaceControlTransactionFactory( MockSurfaceControlHelper::createMockSurfaceControlTransaction); oldAnimator.start(); final PipAnimationController.PipTransitionAnimator newAnimator = mPipAnimationController .getAnimator(mTaskInfo, mLeash, baseValue, startValue, endValue2, null, - TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_0); + TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_0, + false /* alwaysAnimateTaskBounds */); assertEquals("getAnimator with same type returns same animator", oldAnimator, newAnimator); @@ -145,7 +152,8 @@ public class PipAnimationControllerTest extends ShellTestCase { // Fullscreen to PiP. PipAnimationController.PipTransitionAnimator<?> animator = mPipAnimationController .getAnimator(mTaskInfo, mLeash, null, startBounds, endBounds, null, - TRANSITION_DIRECTION_LEAVE_PIP, 0, ROTATION_90); + TRANSITION_DIRECTION_LEAVE_PIP, 0, ROTATION_90, + false /* alwaysAnimateTaskBounds */); // Apply fraction 1 to compute the end value. animator.applySurfaceControlTransaction(mLeash, tx, 1); final Rect rotatedEndBounds = new Rect(endBounds); @@ -157,7 +165,8 @@ public class PipAnimationControllerTest extends ShellTestCase { startBounds.set(0, 0, 1000, 500); endBounds.set(200, 100, 400, 500); animator = mPipAnimationController.getAnimator(mTaskInfo, mLeash, startBounds, startBounds, - endBounds, null, TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_270); + endBounds, null, TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_270, + false /* alwaysAnimateTaskBounds */); animator.applySurfaceControlTransaction(mLeash, tx, 1); rotatedEndBounds.set(endBounds); rotateBounds(rotatedEndBounds, startBounds, ROTATION_270); @@ -166,6 +175,37 @@ public class PipAnimationControllerTest extends ShellTestCase { } @Test + public void pipTransitionAnimator_rotatedEndValue_overrideMainWindowFrame() { + final SurfaceControl.Transaction tx = createMockSurfaceControlTransaction(); + final Rect startBounds = new Rect(200, 700, 400, 800); + final Rect endBounds = new Rect(0, 0, 500, 1000); + mTaskInfo.topActivityMainWindowFrame = new Rect(0, 250, 1000, 500); + + // Fullscreen task to PiP. + PipAnimationController.PipTransitionAnimator<?> animator = mPipAnimationController + .getAnimator(mTaskInfo, mLeash, null, startBounds, endBounds, null, + TRANSITION_DIRECTION_LEAVE_PIP, 0, ROTATION_90, + false /* alwaysAnimateTaskBounds */); + // Apply fraction 1 to compute the end value. + animator.applySurfaceControlTransaction(mLeash, tx, 1); + + assertEquals("Expect use main window frame", mTaskInfo.topActivityMainWindowFrame, + animator.mCurrentValue); + + // PiP to fullscreen. + mTaskInfo.topActivityMainWindowFrame = new Rect(0, 250, 1000, 500); + startBounds.set(0, 0, 1000, 500); + endBounds.set(200, 100, 400, 500); + animator = mPipAnimationController.getAnimator(mTaskInfo, mLeash, startBounds, startBounds, + endBounds, null, TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_270, + false /* alwaysAnimateTaskBounds */); + animator.applySurfaceControlTransaction(mLeash, tx, 1); + + assertEquals("Expect use main window frame", mTaskInfo.topActivityMainWindowFrame, + animator.mCurrentValue); + } + + @Test @SuppressWarnings("unchecked") public void pipTransitionAnimator_updateEndValue() { final Rect baseValue = new Rect(0, 0, 100, 100); @@ -174,7 +214,8 @@ public class PipAnimationControllerTest extends ShellTestCase { final Rect endValue2 = new Rect(200, 200, 300, 300); final PipAnimationController.PipTransitionAnimator animator = mPipAnimationController .getAnimator(mTaskInfo, mLeash, baseValue, startValue, endValue1, null, - TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_0); + TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_0, + false /* alwaysAnimateTaskBounds */); animator.updateEndValue(endValue2); @@ -188,7 +229,8 @@ public class PipAnimationControllerTest extends ShellTestCase { final Rect endValue = new Rect(100, 100, 200, 200); final PipAnimationController.PipTransitionAnimator animator = mPipAnimationController .getAnimator(mTaskInfo, mLeash, baseValue, startValue, endValue, null, - TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_0); + TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_0, + false /* alwaysAnimateTaskBounds */); animator.setSurfaceControlTransactionFactory( MockSurfaceControlHelper::createMockSurfaceControlTransaction); @@ -207,4 +249,126 @@ public class PipAnimationControllerTest extends ShellTestCase { verify(mPipAnimationCallback).onPipAnimationEnd(eq(mTaskInfo), any(SurfaceControl.Transaction.class), eq(animator)); } + + @Test + public void pipTransitionAnimator_overrideMainWindowFrame() { + final Rect baseValue = new Rect(0, 0, 100, 100); + final Rect startValue = new Rect(0, 0, 100, 100); + final Rect endValue = new Rect(100, 100, 200, 200); + mTaskInfo.topActivityMainWindowFrame = new Rect(0, 50, 100, 100); + PipAnimationController.PipTransitionAnimator<?> animator = mPipAnimationController + .getAnimator(mTaskInfo, mLeash, baseValue, startValue, endValue, null, + TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_0, + false /* alwaysAnimateTaskBounds */); + + assertEquals("Expect base value is overridden for in-PIP transition", + mTaskInfo.topActivityMainWindowFrame, animator.getBaseValue()); + assertEquals("Expect start value is overridden for in-PIP transition", + mTaskInfo.topActivityMainWindowFrame, animator.getStartValue()); + assertEquals("Expect end value is not overridden for in-PIP transition", + endValue, animator.getEndValue()); + + animator = mPipAnimationController.getAnimator(mTaskInfo, mLeash, baseValue, startValue, + endValue, null, TRANSITION_DIRECTION_LEAVE_PIP, 0, ROTATION_0, + false /* alwaysAnimateTaskBounds */); + + assertEquals("Expect base value is not overridden for leave-PIP transition", + baseValue, animator.getBaseValue()); + assertEquals("Expect start value is not overridden for leave-PIP transition", + startValue, animator.getStartValue()); + assertEquals("Expect end value is overridden for leave-PIP transition", + mTaskInfo.topActivityMainWindowFrame, animator.getEndValue()); + } + + @Test + public void pipTransitionAnimator_animateTaskBounds() { + final Rect baseValue = new Rect(0, 0, 100, 100); + final Rect startValue = new Rect(0, 0, 100, 100); + final Rect endValue = new Rect(100, 100, 200, 200); + mTaskInfo.topActivityMainWindowFrame = new Rect(0, 50, 100, 100); + PipAnimationController.PipTransitionAnimator<?> animator = mPipAnimationController + .getAnimator(mTaskInfo, mLeash, baseValue, startValue, endValue, null, + TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_0, + true /* alwaysAnimateTaskBounds */); + + assertEquals("Expect base value is not overridden for in-PIP transition", + baseValue, animator.getBaseValue()); + assertEquals("Expect start value is not overridden for in-PIP transition", + startValue, animator.getStartValue()); + assertEquals("Expect end value is not overridden for in-PIP transition", + endValue, animator.getEndValue()); + + animator = mPipAnimationController.getAnimator(mTaskInfo, mLeash, baseValue, startValue, + endValue, null, TRANSITION_DIRECTION_LEAVE_PIP, 0, ROTATION_0, + true /* alwaysAnimateTaskBounds */); + + assertEquals("Expect base value is not overridden for leave-PIP transition", + baseValue, animator.getBaseValue()); + assertEquals("Expect start value is not overridden for leave-PIP transition", + startValue, animator.getStartValue()); + assertEquals("Expect end value is not overridden for leave-PIP transition", + endValue, animator.getEndValue()); + } + + @Test + public void pipTransitionAnimator_letterboxed_animateTaskBounds() { + final Rect baseValue = new Rect(0, 0, 100, 100); + final Rect startValue = new Rect(0, 0, 100, 100); + final Rect endValue = new Rect(100, 100, 200, 200); + mTaskInfo.topActivityMainWindowFrame = new Rect(0, 50, 100, 100); + doReturn(true).when(mTaskInfo.appCompatTaskInfo).isTopActivityLetterboxed(); + PipAnimationController.PipTransitionAnimator<?> animator = mPipAnimationController + .getAnimator(mTaskInfo, mLeash, baseValue, startValue, endValue, null, + TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_0, + false /* alwaysAnimateTaskBounds */); + + assertEquals("Expect base value is not overridden for in-PIP transition", + baseValue, animator.getBaseValue()); + assertEquals("Expect start value is not overridden for in-PIP transition", + startValue, animator.getStartValue()); + assertEquals("Expect end value is not overridden for in-PIP transition", + endValue, animator.getEndValue()); + + animator = mPipAnimationController.getAnimator(mTaskInfo, mLeash, baseValue, startValue, + endValue, null, TRANSITION_DIRECTION_LEAVE_PIP, 0, ROTATION_0, + false /* alwaysAnimateTaskBounds */); + + assertEquals("Expect base value is not overridden for leave-PIP transition", + baseValue, animator.getBaseValue()); + assertEquals("Expect start value is not overridden for leave-PIP transition", + startValue, animator.getStartValue()); + assertEquals("Expect end value is not overridden for leave-PIP transition", + endValue, animator.getEndValue()); + } + + @Test + public void pipTransitionAnimator_sizeCompat_animateTaskBounds() { + final Rect baseValue = new Rect(0, 0, 100, 100); + final Rect startValue = new Rect(0, 0, 100, 100); + final Rect endValue = new Rect(100, 100, 200, 200); + mTaskInfo.topActivityMainWindowFrame = new Rect(0, 50, 100, 100); + doReturn(true).when(mTaskInfo.appCompatTaskInfo).isTopActivityInSizeCompat(); + PipAnimationController.PipTransitionAnimator<?> animator = mPipAnimationController + .getAnimator(mTaskInfo, mLeash, baseValue, startValue, endValue, null, + TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_0, + false /* alwaysAnimateTaskBounds */); + + assertEquals("Expect base value is not overridden for in-PIP transition", + baseValue, animator.getBaseValue()); + assertEquals("Expect start value is not overridden for in-PIP transition", + startValue, animator.getStartValue()); + assertEquals("Expect end value is not overridden for in-PIP transition", + endValue, animator.getEndValue()); + + animator = mPipAnimationController.getAnimator(mTaskInfo, mLeash, baseValue, startValue, + endValue, null, TRANSITION_DIRECTION_LEAVE_PIP, 0, ROTATION_0, + false /* alwaysAnimateTaskBounds */); + + assertEquals("Expect base value is not overridden for leave-PIP transition", + baseValue, animator.getBaseValue()); + assertEquals("Expect start value is not overridden for leave-PIP transition", + startValue, animator.getStartValue()); + assertEquals("Expect end value is not overridden for leave-PIP transition", + endValue, animator.getEndValue()); + } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java index bcb7461bfae7..5f58265b45f5 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java @@ -47,6 +47,7 @@ import android.window.WindowContainerToken; import androidx.test.filters.SmallTest; import com.android.wm.shell.MockSurfaceControlHelper; +import com.android.wm.shell.RootTaskDisplayAreaOrganizer; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.TestShellExecutor; @@ -61,6 +62,7 @@ import com.android.wm.shell.common.pip.PipKeepClearAlgorithmInterface; import com.android.wm.shell.common.pip.PipSnapAlgorithm; import com.android.wm.shell.common.pip.PipUiEventLogger; import com.android.wm.shell.common.pip.SizeSpecSource; +import com.android.wm.shell.desktopmode.DesktopRepository; import com.android.wm.shell.pip.phone.PhonePipMenuController; import com.android.wm.shell.splitscreen.SplitScreenController; @@ -90,6 +92,8 @@ public class PipTaskOrganizerTest extends ShellTestCase { @Mock private PipSurfaceTransactionHelper mMockPipSurfaceTransactionHelper; @Mock private PipUiEventLogger mMockPipUiEventLogger; @Mock private Optional<SplitScreenController> mMockOptionalSplitScreen; + @Mock private Optional<DesktopRepository> mMockOptionalDesktopRepository; + @Mock private RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer; @Mock private ShellTaskOrganizer mMockShellTaskOrganizer; @Mock private PipParamsChangedForwarder mMockPipParamsChangedForwarder; private TestShellExecutor mMainExecutor; @@ -120,8 +124,10 @@ public class PipTaskOrganizerTest extends ShellTestCase { mPipBoundsAlgorithm, mMockPhonePipMenuController, mMockPipAnimationController, mMockPipSurfaceTransactionHelper, mMockPipTransitionController, mMockPipParamsChangedForwarder, mMockOptionalSplitScreen, - Optional.empty() /* pipPerfHintControllerOptional */, mMockDisplayController, - mMockPipUiEventLogger, mMockShellTaskOrganizer, mMainExecutor); + Optional.empty() /* pipPerfHintControllerOptional */, + mMockOptionalDesktopRepository, mRootTaskDisplayAreaOrganizer, + mMockDisplayController, mMockPipUiEventLogger, mMockShellTaskOrganizer, + mMainExecutor); mMainExecutor.flushAll(); preparePipTaskOrg(); preparePipSurfaceTransactionHelper(); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipExpandAnimatorTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipExpandAnimatorTest.java index e19a10a78417..b816f0ef041e 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipExpandAnimatorTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipExpandAnimatorTest.java @@ -93,6 +93,17 @@ public class PipExpandAnimatorTest { .thenReturn(mMockTransaction); when(mMockTransaction.setShadowRadius(any(SurfaceControl.class), anyFloat())) .thenReturn(mMockTransaction); + // No-op on the mMockStartTransaction + when(mMockStartTransaction.setAlpha(any(SurfaceControl.class), anyFloat())) + .thenReturn(mMockFinishTransaction); + when(mMockStartTransaction.setCrop(any(SurfaceControl.class), any(Rect.class))) + .thenReturn(mMockFinishTransaction); + when(mMockStartTransaction.setMatrix(any(SurfaceControl.class), any(Matrix.class), any())) + .thenReturn(mMockFinishTransaction); + when(mMockStartTransaction.setCornerRadius(any(SurfaceControl.class), anyFloat())) + .thenReturn(mMockFinishTransaction); + when(mMockStartTransaction.setShadowRadius(any(SurfaceControl.class), anyFloat())) + .thenReturn(mMockFinishTransaction); // Do the same for mMockFinishTransaction when(mMockFinishTransaction.setAlpha(any(SurfaceControl.class), anyFloat())) .thenReturn(mMockFinishTransaction); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipSchedulerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipSchedulerTest.java new file mode 100644 index 000000000000..cab625216236 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipSchedulerTest.java @@ -0,0 +1,270 @@ +/* + * Copyright (C) 2024 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.pip2.phone; + +import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP; +import static com.android.wm.shell.transition.Transitions.TRANSIT_REMOVE_PIP; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.when; +import static org.mockito.kotlin.MatchersKt.eq; +import static org.mockito.kotlin.VerificationKt.times; +import static org.mockito.kotlin.VerificationKt.verify; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Matrix; +import android.graphics.Rect; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; +import android.view.SurfaceControl; +import android.window.WindowContainerToken; +import android.window.WindowContainerTransaction; + +import androidx.test.filters.SmallTest; + +import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.common.pip.PipBoundsState; +import com.android.wm.shell.pip.PipTransitionController; +import com.android.wm.shell.pip2.PipSurfaceTransactionHelper; +import com.android.wm.shell.pip2.animation.PipAlphaAnimator; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Unit test against {@link PipScheduler} + */ + +@SmallTest +@TestableLooper.RunWithLooper +@RunWith(AndroidTestingRunner.class) +public class PipSchedulerTest { + private static final int TEST_RESIZE_DURATION = 1; + private static final Rect TEST_STARTING_BOUNDS = new Rect(0, 0, 10, 10); + private static final Rect TEST_BOUNDS = new Rect(0, 0, 20, 20); + + @Mock private Context mMockContext; + @Mock private Resources mMockResources; + @Mock private PipBoundsState mMockPipBoundsState; + @Mock private ShellExecutor mMockMainExecutor; + @Mock private PipTransitionState mMockPipTransitionState; + @Mock private PipTransitionController mMockPipTransitionController; + @Mock private Runnable mMockUpdateMovementBoundsRunnable; + @Mock private WindowContainerToken mMockPipTaskToken; + @Mock private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory mMockFactory; + @Mock private SurfaceControl.Transaction mMockTransaction; + @Mock private PipAlphaAnimator mMockAlphaAnimator; + + @Captor private ArgumentCaptor<Runnable> mRunnableArgumentCaptor; + @Captor private ArgumentCaptor<WindowContainerTransaction> mWctArgumentCaptor; + + private PipScheduler mPipScheduler; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + when(mMockContext.getResources()).thenReturn(mMockResources); + when(mMockResources.getInteger(anyInt())).thenReturn(0); + when(mMockPipBoundsState.getBounds()).thenReturn(TEST_STARTING_BOUNDS); + when(mMockFactory.getTransaction()).thenReturn(mMockTransaction); + when(mMockTransaction.setMatrix(any(SurfaceControl.class), any(Matrix.class), any())) + .thenReturn(mMockTransaction); + + mPipScheduler = new PipScheduler(mMockContext, mMockPipBoundsState, mMockMainExecutor, + mMockPipTransitionState); + mPipScheduler.setPipTransitionController(mMockPipTransitionController); + mPipScheduler.setSurfaceControlTransactionFactory(mMockFactory); + mPipScheduler.setPipAlphaAnimatorSupplier((context, leash, tx, direction) -> + mMockAlphaAnimator); + + SurfaceControl testLeash = new SurfaceControl.Builder() + .setContainerLayer() + .setName("PipSchedulerTest") + .setCallsite("PipSchedulerTest") + .build(); + when(mMockPipTransitionState.getPinnedTaskLeash()).thenReturn(testLeash); + } + + @Test + public void scheduleExitPipViaExpand_nullTaskToken_noop() { + setNullPipTaskToken(); + + mPipScheduler.scheduleExitPipViaExpand(); + + verify(mMockMainExecutor, never()).execute(any()); + } + + @Test + public void scheduleExitPipViaExpand_exitTransitionCalled() { + setMockPipTaskToken(); + + mPipScheduler.scheduleExitPipViaExpand(); + + verify(mMockMainExecutor, times(1)).execute(mRunnableArgumentCaptor.capture()); + assertNotNull(mRunnableArgumentCaptor.getValue()); + mRunnableArgumentCaptor.getValue().run(); + + verify(mMockPipTransitionController, times(1)) + .startExitTransition(eq(TRANSIT_EXIT_PIP), any(), isNull()); + } + + @Test + public void removePipAfterAnimation() { + //TODO: Update once this is changed to run animation as part of transition + setMockPipTaskToken(); + + mPipScheduler.removePipAfterAnimation(); + verify(mMockAlphaAnimator, times(1)) + .setAnimationEndCallback(mRunnableArgumentCaptor.capture()); + assertNotNull(mRunnableArgumentCaptor.getValue()); + verify(mMockAlphaAnimator, times(1)).start(); + + mRunnableArgumentCaptor.getValue().run(); + + verify(mMockMainExecutor, times(1)).execute(mRunnableArgumentCaptor.capture()); + assertNotNull(mRunnableArgumentCaptor.getValue()); + + mRunnableArgumentCaptor.getValue().run(); + + verify(mMockPipTransitionController, times(1)) + .startExitTransition(eq(TRANSIT_REMOVE_PIP), any(), isNull()); + } + + @Test + public void scheduleAnimateResizePip_bounds_nullTaskToken_noop() { + setNullPipTaskToken(); + + mPipScheduler.scheduleAnimateResizePip(TEST_BOUNDS); + + verify(mMockPipTransitionController, never()).startResizeTransition(any(), anyInt()); + } + + @Test + public void scheduleAnimateResizePip_boundsConfig_nullTaskToken_noop() { + setNullPipTaskToken(); + + mPipScheduler.scheduleAnimateResizePip(TEST_BOUNDS, true); + + verify(mMockPipTransitionController, never()).startResizeTransition(any(), anyInt()); + } + + @Test + public void scheduleAnimateResizePip_boundsConfig_setsConfigAtEnd() { + setMockPipTaskToken(); + when(mMockPipTransitionState.isInPip()).thenReturn(true); + + mPipScheduler.scheduleAnimateResizePip(TEST_BOUNDS, true); + + verify(mMockPipTransitionController, times(1)) + .startResizeTransition(mWctArgumentCaptor.capture(), anyInt()); + assertNotNull(mWctArgumentCaptor.getValue()); + assertNotNull(mWctArgumentCaptor.getValue().getChanges()); + boolean hasConfigAtEndChange = false; + for (WindowContainerTransaction.Change change : + mWctArgumentCaptor.getValue().getChanges().values()) { + if (change.getConfigAtTransitionEnd()) { + hasConfigAtEndChange = true; + break; + } + } + assertTrue(hasConfigAtEndChange); + } + + @Test + public void scheduleAnimateResizePip_boundsConfigDuration_nullTaskToken_noop() { + setNullPipTaskToken(); + + mPipScheduler.scheduleAnimateResizePip(TEST_BOUNDS, true, TEST_RESIZE_DURATION); + + verify(mMockPipTransitionController, never()).startResizeTransition(any(), anyInt()); + } + + @Test + public void scheduleAnimateResizePip_notInPip_noop() { + setMockPipTaskToken(); + when(mMockPipTransitionState.isInPip()).thenReturn(false); + + mPipScheduler.scheduleAnimateResizePip(TEST_BOUNDS, true, TEST_RESIZE_DURATION); + + verify(mMockPipTransitionController, never()).startResizeTransition(any(), anyInt()); + } + + @Test + public void scheduleAnimateResizePip_resizeTransition() { + setMockPipTaskToken(); + when(mMockPipTransitionState.isInPip()).thenReturn(true); + + mPipScheduler.scheduleAnimateResizePip(TEST_BOUNDS, true, TEST_RESIZE_DURATION); + + verify(mMockPipTransitionController, times(1)) + .startResizeTransition(any(), eq(TEST_RESIZE_DURATION)); + } + + @Test + public void scheduleUserResizePip_emptyBounds_noop() { + setMockPipTaskToken(); + + mPipScheduler.scheduleUserResizePip(new Rect()); + + verify(mMockTransaction, never()).apply(); + } + + @Test + public void scheduleUserResizePip_rotation_emptyBounds_noop() { + setMockPipTaskToken(); + + mPipScheduler.scheduleUserResizePip(new Rect(), 90); + + verify(mMockTransaction, never()).apply(); + } + + @Test + public void scheduleUserResizePip_applyTransaction() { + setMockPipTaskToken(); + + mPipScheduler.scheduleUserResizePip(TEST_BOUNDS, 90); + + verify(mMockTransaction, times(1)).apply(); + } + + @Test + public void finishResize_movementBoundsRunnableCalled() { + mPipScheduler.setUpdateMovementBoundsRunnable(mMockUpdateMovementBoundsRunnable); + mPipScheduler.scheduleFinishResizePip(TEST_BOUNDS); + + verify(mMockUpdateMovementBoundsRunnable, times(1)).run(); + } + + private void setNullPipTaskToken() { + when(mMockPipTransitionState.getPipTaskToken()).thenReturn(null); + } + + private void setMockPipTaskToken() { + when(mMockPipTransitionState.getPipTaskToken()).thenReturn(mMockPipTaskToken); + } +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipTaskListenerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipTaskListenerTest.java new file mode 100644 index 000000000000..89cb729d17b5 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipTaskListenerTest.java @@ -0,0 +1,331 @@ +/* + * Copyright (C) 2024 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.pip2.phone; + +import static com.android.wm.shell.pip2.phone.PipTaskListener.ANIMATING_ASPECT_RATIO_CHANGE; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.when; +import static org.mockito.kotlin.MatchersKt.eq; +import static org.mockito.kotlin.VerificationKt.clearInvocations; +import static org.mockito.kotlin.VerificationKt.times; +import static org.mockito.kotlin.VerificationKt.verify; +import static org.mockito.kotlin.VerificationKt.verifyZeroInteractions; + +import android.app.ActivityManager; +import android.app.PendingIntent; +import android.app.PictureInPictureParams; +import android.app.RemoteAction; +import android.content.Context; +import android.graphics.Rect; +import android.graphics.drawable.Icon; +import android.os.Bundle; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; +import android.util.Rational; +import android.view.SurfaceControl; + +import androidx.test.filters.SmallTest; + +import com.android.wm.shell.ShellTaskOrganizer; +import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.common.pip.PipBoundsAlgorithm; +import com.android.wm.shell.common.pip.PipBoundsState; +import com.android.wm.shell.pip2.animation.PipResizeAnimator; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.ArrayList; +import java.util.List; + +/** + * Unit test against {@link PipTaskListener}. + */ +@SmallTest +@TestableLooper.RunWithLooper +@RunWith(AndroidTestingRunner.class) +public class PipTaskListenerTest { + + @Mock private Context mMockContext; + @Mock private ShellTaskOrganizer mMockShellTaskOrganizer; + @Mock private PipTransitionState mMockPipTransitionState; + @Mock private SurfaceControl mMockLeash; + @Mock private PipScheduler mMockPipScheduler; + @Mock private PipBoundsState mMockPipBoundsState; + @Mock private PipBoundsAlgorithm mMockPipBoundsAlgorithm; + @Mock private ShellExecutor mMockShellExecutor; + + @Mock private Icon mMockIcon; + @Mock private PendingIntent mMockPendingIntent; + + @Mock private PipTaskListener.PipParamsChangedCallback mMockPipParamsChangedCallback; + + @Mock private PipResizeAnimator mMockPipResizeAnimator; + + private ArgumentCaptor<List<RemoteAction>> mRemoteActionListCaptor; + + private PipTaskListener mPipTaskListener; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mRemoteActionListCaptor = ArgumentCaptor.forClass(List.class); + when(mMockPipTransitionState.getPinnedTaskLeash()).thenReturn(mMockLeash); + } + + @Test + public void constructor_addPipTransitionStateChangedListener() { + mPipTaskListener = new PipTaskListener(mMockContext, mMockShellTaskOrganizer, + mMockPipTransitionState, mMockPipScheduler, mMockPipBoundsState, + mMockPipBoundsAlgorithm, mMockShellExecutor); + + verify(mMockPipTransitionState).addPipTransitionStateChangedListener(eq(mPipTaskListener)); + } + + @Test + public void setPictureInPictureParams_updatePictureInPictureParams() { + mPipTaskListener = new PipTaskListener(mMockContext, mMockShellTaskOrganizer, + mMockPipTransitionState, mMockPipScheduler, mMockPipBoundsState, + mMockPipBoundsAlgorithm, mMockShellExecutor); + Rational aspectRatio = new Rational(4, 3); + String action1 = "action1"; + + mPipTaskListener.setPictureInPictureParams(getPictureInPictureParams( + aspectRatio, action1)); + + PictureInPictureParams params = mPipTaskListener.getPictureInPictureParams(); + assertEquals(aspectRatio, params.getAspectRatio()); + assertTrue(params.hasSetActions()); + assertEquals(1, params.getActions().size()); + assertEquals(action1, params.getActions().get(0).getTitle()); + } + + @Test + public void setPictureInPictureParams_withActionsChanged_callbackActionsChanged() { + mPipTaskListener = new PipTaskListener(mMockContext, mMockShellTaskOrganizer, + mMockPipTransitionState, mMockPipScheduler, mMockPipBoundsState, + mMockPipBoundsAlgorithm, mMockShellExecutor); + mPipTaskListener.addParamsChangedListener(mMockPipParamsChangedCallback); + Rational aspectRatio = new Rational(4, 3); + String action1 = "action1"; + mPipTaskListener.setPictureInPictureParams(getPictureInPictureParams( + aspectRatio, action1)); + + clearInvocations(mMockPipParamsChangedCallback); + action1 = "modified action1"; + mPipTaskListener.setPictureInPictureParams(getPictureInPictureParams( + aspectRatio, action1)); + + verify(mMockPipParamsChangedCallback).onActionsChanged( + mRemoteActionListCaptor.capture(), any()); + assertEquals(1, mRemoteActionListCaptor.getValue().size()); + assertEquals(action1, mRemoteActionListCaptor.getValue().get(0).getTitle()); + } + + @Test + public void setPictureInPictureParams_withoutActionsChanged_doesNotCallbackActionsChanged() { + mPipTaskListener = new PipTaskListener(mMockContext, mMockShellTaskOrganizer, + mMockPipTransitionState, mMockPipScheduler, mMockPipBoundsState, + mMockPipBoundsAlgorithm, mMockShellExecutor); + mPipTaskListener.addParamsChangedListener(mMockPipParamsChangedCallback); + Rational aspectRatio = new Rational(4, 3); + String action1 = "action1"; + mPipTaskListener.setPictureInPictureParams(getPictureInPictureParams( + aspectRatio, action1)); + + clearInvocations(mMockPipParamsChangedCallback); + mPipTaskListener.setPictureInPictureParams(getPictureInPictureParams( + aspectRatio, action1)); + + verifyZeroInteractions(mMockPipParamsChangedCallback); + } + + @Test + public void onTaskInfoChanged_withActionsChanged_callbackActionsChanged() { + mPipTaskListener = new PipTaskListener(mMockContext, mMockShellTaskOrganizer, + mMockPipTransitionState, mMockPipScheduler, mMockPipBoundsState, + mMockPipBoundsAlgorithm, mMockShellExecutor); + mPipTaskListener.addParamsChangedListener(mMockPipParamsChangedCallback); + Rational aspectRatio = new Rational(4, 3); + when(mMockPipBoundsState.getAspectRatio()).thenReturn(aspectRatio.toFloat()); + String action1 = "action1"; + mPipTaskListener.onTaskInfoChanged(getTaskInfo(aspectRatio, action1)); + + clearInvocations(mMockPipParamsChangedCallback); + clearInvocations(mMockPipBoundsState); + action1 = "modified action1"; + mPipTaskListener.onTaskInfoChanged(getTaskInfo(aspectRatio, action1)); + + verify(mMockPipTransitionState, times(0)) + .setOnIdlePipTransitionStateRunnable(any(Runnable.class)); + verify(mMockPipParamsChangedCallback).onActionsChanged( + mRemoteActionListCaptor.capture(), any()); + assertEquals(1, mRemoteActionListCaptor.getValue().size()); + assertEquals(action1, mRemoteActionListCaptor.getValue().get(0).getTitle()); + } + + @Test + public void onTaskInfoChanged_withAspectRatioChanged_callbackAspectRatioChanged() { + mPipTaskListener = new PipTaskListener(mMockContext, mMockShellTaskOrganizer, + mMockPipTransitionState, mMockPipScheduler, mMockPipBoundsState, + mMockPipBoundsAlgorithm, mMockShellExecutor); + mPipTaskListener.addParamsChangedListener(mMockPipParamsChangedCallback); + Rational aspectRatio = new Rational(4, 3); + when(mMockPipBoundsState.getAspectRatio()).thenReturn(aspectRatio.toFloat()); + String action1 = "action1"; + mPipTaskListener.onTaskInfoChanged(getTaskInfo(aspectRatio, action1)); + + clearInvocations(mMockPipParamsChangedCallback); + clearInvocations(mMockPipBoundsState); + aspectRatio = new Rational(16, 9); + mPipTaskListener.onTaskInfoChanged(getTaskInfo(aspectRatio, action1)); + + verify(mMockPipTransitionState).setOnIdlePipTransitionStateRunnable(any(Runnable.class)); + verifyZeroInteractions(mMockPipParamsChangedCallback); + } + + @Test + public void onTaskInfoChanged_withoutParamsChanged_doesNotCallbackAspectRatioChanged() { + mPipTaskListener = new PipTaskListener(mMockContext, mMockShellTaskOrganizer, + mMockPipTransitionState, mMockPipScheduler, mMockPipBoundsState, + mMockPipBoundsAlgorithm, mMockShellExecutor); + mPipTaskListener.addParamsChangedListener(mMockPipParamsChangedCallback); + Rational aspectRatio = new Rational(4, 3); + when(mMockPipBoundsState.getAspectRatio()).thenReturn(aspectRatio.toFloat()); + String action1 = "action1"; + mPipTaskListener.onTaskInfoChanged(getTaskInfo(aspectRatio, action1)); + + clearInvocations(mMockPipParamsChangedCallback); + mPipTaskListener.onTaskInfoChanged(getTaskInfo(aspectRatio, action1)); + + verifyZeroInteractions(mMockPipParamsChangedCallback); + verify(mMockPipTransitionState, times(0)) + .setOnIdlePipTransitionStateRunnable(any(Runnable.class)); + } + + @Test + public void onPipTransitionStateChanged_scheduledBoundsChangeWithAspectRatioChange_schedule() { + mPipTaskListener = new PipTaskListener(mMockContext, mMockShellTaskOrganizer, + mMockPipTransitionState, mMockPipScheduler, mMockPipBoundsState, + mMockPipBoundsAlgorithm, mMockShellExecutor); + Bundle extras = new Bundle(); + extras.putBoolean(ANIMATING_ASPECT_RATIO_CHANGE, true); + + mPipTaskListener.onPipTransitionStateChanged( + PipTransitionState.UNDEFINED, PipTransitionState.SCHEDULED_BOUNDS_CHANGE, extras); + + verify(mMockPipScheduler).scheduleAnimateResizePip(any(), anyBoolean(), anyInt()); + } + + @Test + public void onPipTransitionStateChanged_scheduledBoundsChangeWithoutAspectRatioChange_noop() { + mPipTaskListener = new PipTaskListener(mMockContext, mMockShellTaskOrganizer, + mMockPipTransitionState, mMockPipScheduler, mMockPipBoundsState, + mMockPipBoundsAlgorithm, mMockShellExecutor); + Bundle extras = new Bundle(); + extras.putBoolean(ANIMATING_ASPECT_RATIO_CHANGE, false); + + mPipTaskListener.onPipTransitionStateChanged( + PipTransitionState.UNDEFINED, + PipTransitionState.SCHEDULED_BOUNDS_CHANGE, + extras); + + verifyZeroInteractions(mMockPipScheduler); + } + + @Test + public void onPipTransitionStateChanged_changingPipBoundsWaitAspectRatioChange_animate() { + mPipTaskListener = new PipTaskListener(mMockContext, mMockShellTaskOrganizer, + mMockPipTransitionState, mMockPipScheduler, mMockPipBoundsState, + mMockPipBoundsAlgorithm, mMockShellExecutor); + Bundle extras = new Bundle(); + extras.putBoolean(ANIMATING_ASPECT_RATIO_CHANGE, true); + extras.putParcelable(PipTransition.PIP_DESTINATION_BOUNDS, + new Rect(0, 0, 100, 100)); + when(mMockPipBoundsState.getBounds()).thenReturn(new Rect(0, 0, 200, 200)); + + mPipTaskListener.onPipTransitionStateChanged( + PipTransitionState.UNDEFINED, + PipTransitionState.SCHEDULED_BOUNDS_CHANGE, + extras); + mPipTaskListener.setPipResizeAnimatorSupplier( + (context, leash, startTx, finishTx, baseBounds, startBounds, endBounds, + duration, delta) -> mMockPipResizeAnimator); + mPipTaskListener.onPipTransitionStateChanged( + PipTransitionState.SCHEDULED_BOUNDS_CHANGE, + PipTransitionState.CHANGING_PIP_BOUNDS, + extras); + + verify(mMockPipResizeAnimator, times(1)).start(); + } + + @Test + public void onPipTransitionStateChanged_changingPipBoundsNotAspectRatioChange_noop() { + mPipTaskListener = new PipTaskListener(mMockContext, mMockShellTaskOrganizer, + mMockPipTransitionState, mMockPipScheduler, mMockPipBoundsState, + mMockPipBoundsAlgorithm, mMockShellExecutor); + Bundle extras = new Bundle(); + extras.putBoolean(ANIMATING_ASPECT_RATIO_CHANGE, false); + extras.putParcelable(PipTransition.PIP_DESTINATION_BOUNDS, + new Rect(0, 0, 100, 100)); + when(mMockPipBoundsState.getBounds()).thenReturn(new Rect(0, 0, 200, 200)); + + mPipTaskListener.onPipTransitionStateChanged( + PipTransitionState.UNDEFINED, + PipTransitionState.SCHEDULED_BOUNDS_CHANGE, + extras); + mPipTaskListener.setPipResizeAnimatorSupplier( + (context, leash, startTx, finishTx, baseBounds, startBounds, endBounds, + duration, delta) -> mMockPipResizeAnimator); + mPipTaskListener.onPipTransitionStateChanged( + PipTransitionState.SCHEDULED_BOUNDS_CHANGE, + PipTransitionState.CHANGING_PIP_BOUNDS, + extras); + + verify(mMockPipResizeAnimator, times(0)).start(); + } + + private PictureInPictureParams getPictureInPictureParams(Rational aspectRatio, + String... actions) { + final PictureInPictureParams.Builder builder = new PictureInPictureParams.Builder(); + builder.setAspectRatio(aspectRatio); + final List<RemoteAction> remoteActions = new ArrayList<>(); + for (String action : actions) { + remoteActions.add(new RemoteAction(mMockIcon, action, action, mMockPendingIntent)); + } + if (!remoteActions.isEmpty()) { + builder.setActions(remoteActions); + } + return builder.build(); + } + + private ActivityManager.RunningTaskInfo getTaskInfo(Rational aspectRatio, + String... actions) { + final ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo(); + taskInfo.pictureInPictureParams = getPictureInPictureParams(aspectRatio, actions); + return taskInfo; + } +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedRecentTaskInfoTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedTaskInfoTest.kt index 0c100fca2036..2b30bc360d06 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedRecentTaskInfoTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedTaskInfoTest.kt @@ -17,6 +17,7 @@ package com.android.wm.shell.recents import android.app.ActivityManager +import android.app.TaskInfo import android.graphics.Rect import android.os.Parcel import android.testing.AndroidTestingRunner @@ -24,11 +25,10 @@ import android.window.IWindowContainerToken import android.window.WindowContainerToken import androidx.test.filters.SmallTest import com.android.wm.shell.ShellTestCase -import com.android.wm.shell.shared.GroupedRecentTaskInfo -import com.android.wm.shell.shared.GroupedRecentTaskInfo.CREATOR -import com.android.wm.shell.shared.GroupedRecentTaskInfo.TYPE_FREEFORM -import com.android.wm.shell.shared.GroupedRecentTaskInfo.TYPE_SINGLE -import com.android.wm.shell.shared.GroupedRecentTaskInfo.TYPE_SPLIT +import com.android.wm.shell.shared.GroupedTaskInfo +import com.android.wm.shell.shared.GroupedTaskInfo.TYPE_FREEFORM +import com.android.wm.shell.shared.GroupedTaskInfo.TYPE_FULLSCREEN +import com.android.wm.shell.shared.GroupedTaskInfo.TYPE_SPLIT import com.android.wm.shell.shared.split.SplitBounds import com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50 import com.google.common.truth.Correspondence @@ -39,15 +39,15 @@ import org.junit.runner.RunWith import org.mockito.Mockito.mock /** - * Tests for [GroupedRecentTaskInfo] + * Tests for [GroupedTaskInfo] */ @SmallTest @RunWith(AndroidTestingRunner::class) -class GroupedRecentTaskInfoTest : ShellTestCase() { +class GroupedTaskInfoTest : ShellTestCase() { @Test fun testSingleTask_hasCorrectType() { - assertThat(singleTaskGroupInfo().type).isEqualTo(TYPE_SINGLE) + assertThat(singleTaskGroupInfo().type).isEqualTo(TYPE_FULLSCREEN) } @Test @@ -117,8 +117,9 @@ class GroupedRecentTaskInfoTest : ShellTestCase() { recentTaskInfo.writeToParcel(parcel, 0) parcel.setDataPosition(0) // Read the object back from the parcel - val recentTaskInfoParcel = CREATOR.createFromParcel(parcel) - assertThat(recentTaskInfoParcel.type).isEqualTo(TYPE_SINGLE) + val recentTaskInfoParcel: GroupedTaskInfo = + GroupedTaskInfo.CREATOR.createFromParcel(parcel) + assertThat(recentTaskInfoParcel.type).isEqualTo(TYPE_FULLSCREEN) assertThat(recentTaskInfoParcel.taskInfo1.taskId).isEqualTo(1) assertThat(recentTaskInfoParcel.taskInfo2).isNull() } @@ -130,7 +131,8 @@ class GroupedRecentTaskInfoTest : ShellTestCase() { recentTaskInfo.writeToParcel(parcel, 0) parcel.setDataPosition(0) // Read the object back from the parcel - val recentTaskInfoParcel = CREATOR.createFromParcel(parcel) + val recentTaskInfoParcel: GroupedTaskInfo = + GroupedTaskInfo.CREATOR.createFromParcel(parcel) assertThat(recentTaskInfoParcel.type).isEqualTo(TYPE_SPLIT) assertThat(recentTaskInfoParcel.taskInfo1.taskId).isEqualTo(1) assertThat(recentTaskInfoParcel.taskInfo2).isNotNull() @@ -146,11 +148,12 @@ class GroupedRecentTaskInfoTest : ShellTestCase() { recentTaskInfo.writeToParcel(parcel, 0) parcel.setDataPosition(0) // Read the object back from the parcel - val recentTaskInfoParcel = CREATOR.createFromParcel(parcel) + val recentTaskInfoParcel: GroupedTaskInfo = + GroupedTaskInfo.CREATOR.createFromParcel(parcel) assertThat(recentTaskInfoParcel.type).isEqualTo(TYPE_FREEFORM) assertThat(recentTaskInfoParcel.taskInfoList).hasSize(3) // Only compare task ids - val taskIdComparator = Correspondence.transforming<ActivityManager.RecentTaskInfo, Int>( + val taskIdComparator = Correspondence.transforming<TaskInfo, Int>( { it?.taskId }, "has taskId of" ) assertThat(recentTaskInfoParcel.taskInfoList).comparingElementsUsing(taskIdComparator) @@ -167,7 +170,8 @@ class GroupedRecentTaskInfoTest : ShellTestCase() { parcel.setDataPosition(0) // Read the object back from the parcel - val recentTaskInfoParcel = CREATOR.createFromParcel(parcel) + val recentTaskInfoParcel: GroupedTaskInfo = + GroupedTaskInfo.CREATOR.createFromParcel(parcel) assertThat(recentTaskInfoParcel.type).isEqualTo(TYPE_FREEFORM) assertThat(recentTaskInfoParcel.minimizedTaskIds).isEqualTo(arrayOf(2).toIntArray()) } @@ -177,24 +181,24 @@ class GroupedRecentTaskInfoTest : ShellTestCase() { token = WindowContainerToken(mock(IWindowContainerToken::class.java)) } - private fun singleTaskGroupInfo(): GroupedRecentTaskInfo { + private fun singleTaskGroupInfo(): GroupedTaskInfo { val task = createTaskInfo(id = 1) - return GroupedRecentTaskInfo.forSingleTask(task) + return GroupedTaskInfo.forFullscreenTasks(task) } - private fun splitTasksGroupInfo(): GroupedRecentTaskInfo { + private fun splitTasksGroupInfo(): GroupedTaskInfo { val task1 = createTaskInfo(id = 1) val task2 = createTaskInfo(id = 2) val splitBounds = SplitBounds(Rect(), Rect(), 1, 2, SNAP_TO_2_50_50) - return GroupedRecentTaskInfo.forSplitTasks(task1, task2, splitBounds) + return GroupedTaskInfo.forSplitTasks(task1, task2, splitBounds) } private fun freeformTasksGroupInfo( freeformTaskIds: Array<Int>, minimizedTaskIds: Array<Int> = emptyArray() - ): GroupedRecentTaskInfo { - return GroupedRecentTaskInfo.forFreeformTasks( - freeformTaskIds.map { createTaskInfo(it) }.toTypedArray(), + ): GroupedTaskInfo { + return GroupedTaskInfo.forFreeformTasks( + freeformTaskIds.map { createTaskInfo(it) }.toList(), minimizedTaskIds.toSet()) } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java index 9b73d53e0639..dede583ca970 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java @@ -46,6 +46,7 @@ import static org.mockito.Mockito.when; import static java.lang.Integer.MAX_VALUE; import android.app.ActivityManager; +import android.app.ActivityManager.RecentTaskInfo; import android.app.ActivityTaskManager; import android.app.KeyguardManager; import android.content.ComponentName; @@ -71,7 +72,7 @@ import com.android.wm.shell.TestShellExecutor; import com.android.wm.shell.common.DisplayInsetsController; import com.android.wm.shell.common.TaskStackListenerImpl; import com.android.wm.shell.desktopmode.DesktopRepository; -import com.android.wm.shell.shared.GroupedRecentTaskInfo; +import com.android.wm.shell.shared.GroupedTaskInfo; import com.android.wm.shell.shared.ShellSharedConstants; import com.android.wm.shell.shared.desktopmode.DesktopModeStatus; import com.android.wm.shell.shared.split.SplitBounds; @@ -193,8 +194,8 @@ public class RecentTasksControllerTest extends ShellTestCase { @Test public void testAddRemoveSplitNotifyChange() { - ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1); - ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2); + RecentTaskInfo t1 = makeTaskInfo(1); + RecentTaskInfo t2 = makeTaskInfo(2); setRawList(t1, t2); mRecentTasksController.addSplitPair(t1.taskId, t2.taskId, mock(SplitBounds.class)); @@ -207,8 +208,8 @@ public class RecentTasksControllerTest extends ShellTestCase { @Test public void testAddSameSplitBoundsInfoSkipNotifyChange() { - ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1); - ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2); + RecentTaskInfo t1 = makeTaskInfo(1); + RecentTaskInfo t2 = makeTaskInfo(2); setRawList(t1, t2); // Verify only one update if the split info is the same @@ -223,13 +224,13 @@ public class RecentTasksControllerTest extends ShellTestCase { @Test public void testGetRecentTasks() { - ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1); - ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2); - ActivityManager.RecentTaskInfo t3 = makeTaskInfo(3); + RecentTaskInfo t1 = makeTaskInfo(1); + RecentTaskInfo t2 = makeTaskInfo(2); + RecentTaskInfo t3 = makeTaskInfo(3); setRawList(t1, t2, t3); - ArrayList<GroupedRecentTaskInfo> recentTasks = mRecentTasksController.getRecentTasks( - MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0); + ArrayList<GroupedTaskInfo> recentTasks = + mRecentTasksController.getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0); assertGroupedTasksListEquals(recentTasks, t1.taskId, -1, t2.taskId, -1, @@ -238,12 +239,12 @@ public class RecentTasksControllerTest extends ShellTestCase { @Test public void testGetRecentTasks_withPairs() { - ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1); - ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2); - ActivityManager.RecentTaskInfo t3 = makeTaskInfo(3); - ActivityManager.RecentTaskInfo t4 = makeTaskInfo(4); - ActivityManager.RecentTaskInfo t5 = makeTaskInfo(5); - ActivityManager.RecentTaskInfo t6 = makeTaskInfo(6); + RecentTaskInfo t1 = makeTaskInfo(1); + RecentTaskInfo t2 = makeTaskInfo(2); + RecentTaskInfo t3 = makeTaskInfo(3); + RecentTaskInfo t4 = makeTaskInfo(4); + RecentTaskInfo t5 = makeTaskInfo(5); + RecentTaskInfo t6 = makeTaskInfo(6); setRawList(t1, t2, t3, t4, t5, t6); // Mark a couple pairs [t2, t4], [t3, t5] @@ -255,8 +256,8 @@ public class RecentTasksControllerTest extends ShellTestCase { mRecentTasksController.addSplitPair(t2.taskId, t4.taskId, pair1Bounds); mRecentTasksController.addSplitPair(t3.taskId, t5.taskId, pair2Bounds); - ArrayList<GroupedRecentTaskInfo> recentTasks = mRecentTasksController.getRecentTasks( - MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0); + ArrayList<GroupedTaskInfo> recentTasks = + mRecentTasksController.getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0); assertGroupedTasksListEquals(recentTasks, t1.taskId, -1, t2.taskId, t4.taskId, @@ -267,14 +268,14 @@ public class RecentTasksControllerTest extends ShellTestCase { @Test public void testGetRecentTasks_ReturnsRecentTasksAsynchronously() { @SuppressWarnings("unchecked") - final List<GroupedRecentTaskInfo>[] recentTasks = new List[1]; - Consumer<List<GroupedRecentTaskInfo>> consumer = argument -> recentTasks[0] = argument; - ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1); - ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2); - ActivityManager.RecentTaskInfo t3 = makeTaskInfo(3); - ActivityManager.RecentTaskInfo t4 = makeTaskInfo(4); - ActivityManager.RecentTaskInfo t5 = makeTaskInfo(5); - ActivityManager.RecentTaskInfo t6 = makeTaskInfo(6); + final List<GroupedTaskInfo>[] recentTasks = new List[1]; + Consumer<List<GroupedTaskInfo>> consumer = argument -> recentTasks[0] = argument; + RecentTaskInfo t1 = makeTaskInfo(1); + RecentTaskInfo t2 = makeTaskInfo(2); + RecentTaskInfo t3 = makeTaskInfo(3); + RecentTaskInfo t4 = makeTaskInfo(4); + RecentTaskInfo t5 = makeTaskInfo(5); + RecentTaskInfo t6 = makeTaskInfo(6); setRawList(t1, t2, t3, t4, t5, t6); // Mark a couple pairs [t2, t4], [t3, t5] @@ -287,7 +288,8 @@ public class RecentTasksControllerTest extends ShellTestCase { mRecentTasksController.addSplitPair(t3.taskId, t5.taskId, pair2Bounds); mRecentTasksController.asRecentTasks() - .getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0, Runnable::run, consumer); + .getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0, Runnable::run, + consumer); mMainExecutor.flushAll(); assertGroupedTasksListEquals(recentTasks[0], @@ -299,28 +301,28 @@ public class RecentTasksControllerTest extends ShellTestCase { @Test public void testGetRecentTasks_hasActiveDesktopTasks_proto2Enabled_groupFreeformTasks() { - ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1); - ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2); - ActivityManager.RecentTaskInfo t3 = makeTaskInfo(3); - ActivityManager.RecentTaskInfo t4 = makeTaskInfo(4); + RecentTaskInfo t1 = makeTaskInfo(1); + RecentTaskInfo t2 = makeTaskInfo(2); + RecentTaskInfo t3 = makeTaskInfo(3); + RecentTaskInfo t4 = makeTaskInfo(4); setRawList(t1, t2, t3, t4); when(mDesktopRepository.isActiveTask(1)).thenReturn(true); when(mDesktopRepository.isActiveTask(3)).thenReturn(true); - ArrayList<GroupedRecentTaskInfo> recentTasks = mRecentTasksController.getRecentTasks( - MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0); + ArrayList<GroupedTaskInfo> recentTasks = + mRecentTasksController.getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0); // 2 freeform tasks should be grouped into one, 3 total recents entries assertEquals(3, recentTasks.size()); - GroupedRecentTaskInfo freeformGroup = recentTasks.get(0); - GroupedRecentTaskInfo singleGroup1 = recentTasks.get(1); - GroupedRecentTaskInfo singleGroup2 = recentTasks.get(2); + GroupedTaskInfo freeformGroup = recentTasks.get(0); + GroupedTaskInfo singleGroup1 = recentTasks.get(1); + GroupedTaskInfo singleGroup2 = recentTasks.get(2); // Check that groups have expected types - assertEquals(GroupedRecentTaskInfo.TYPE_FREEFORM, freeformGroup.getType()); - assertEquals(GroupedRecentTaskInfo.TYPE_SINGLE, singleGroup1.getType()); - assertEquals(GroupedRecentTaskInfo.TYPE_SINGLE, singleGroup2.getType()); + assertEquals(GroupedTaskInfo.TYPE_FREEFORM, freeformGroup.getType()); + assertEquals(GroupedTaskInfo.TYPE_FULLSCREEN, singleGroup1.getType()); + assertEquals(GroupedTaskInfo.TYPE_FULLSCREEN, singleGroup2.getType()); // Check freeform group entries assertEquals(t1, freeformGroup.getTaskInfoList().get(0)); @@ -333,11 +335,11 @@ public class RecentTasksControllerTest extends ShellTestCase { @Test public void testGetRecentTasks_hasActiveDesktopTasks_proto2Enabled_freeformTaskOrder() { - ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1); - ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2); - ActivityManager.RecentTaskInfo t3 = makeTaskInfo(3); - ActivityManager.RecentTaskInfo t4 = makeTaskInfo(4); - ActivityManager.RecentTaskInfo t5 = makeTaskInfo(5); + RecentTaskInfo t1 = makeTaskInfo(1); + RecentTaskInfo t2 = makeTaskInfo(2); + RecentTaskInfo t3 = makeTaskInfo(3); + RecentTaskInfo t4 = makeTaskInfo(4); + RecentTaskInfo t5 = makeTaskInfo(5); setRawList(t1, t2, t3, t4, t5); SplitBounds pair1Bounds = @@ -347,19 +349,19 @@ public class RecentTasksControllerTest extends ShellTestCase { when(mDesktopRepository.isActiveTask(3)).thenReturn(true); when(mDesktopRepository.isActiveTask(5)).thenReturn(true); - ArrayList<GroupedRecentTaskInfo> recentTasks = mRecentTasksController.getRecentTasks( - MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0); + ArrayList<GroupedTaskInfo> recentTasks = + mRecentTasksController.getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0); // 2 split screen tasks grouped, 2 freeform tasks grouped, 3 total recents entries assertEquals(3, recentTasks.size()); - GroupedRecentTaskInfo splitGroup = recentTasks.get(0); - GroupedRecentTaskInfo freeformGroup = recentTasks.get(1); - GroupedRecentTaskInfo singleGroup = recentTasks.get(2); + GroupedTaskInfo splitGroup = recentTasks.get(0); + GroupedTaskInfo freeformGroup = recentTasks.get(1); + GroupedTaskInfo singleGroup = recentTasks.get(2); // Check that groups have expected types - assertEquals(GroupedRecentTaskInfo.TYPE_SPLIT, splitGroup.getType()); - assertEquals(GroupedRecentTaskInfo.TYPE_FREEFORM, freeformGroup.getType()); - assertEquals(GroupedRecentTaskInfo.TYPE_SINGLE, singleGroup.getType()); + assertEquals(GroupedTaskInfo.TYPE_SPLIT, splitGroup.getType()); + assertEquals(GroupedTaskInfo.TYPE_FREEFORM, freeformGroup.getType()); + assertEquals(GroupedTaskInfo.TYPE_FULLSCREEN, singleGroup.getType()); // Check freeform group entries assertEquals(t3, freeformGroup.getTaskInfoList().get(0)); @@ -378,24 +380,24 @@ public class RecentTasksControllerTest extends ShellTestCase { ExtendedMockito.doReturn(false) .when(() -> DesktopModeStatus.canEnterDesktopMode(any())); - ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1); - ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2); - ActivityManager.RecentTaskInfo t3 = makeTaskInfo(3); - ActivityManager.RecentTaskInfo t4 = makeTaskInfo(4); + RecentTaskInfo t1 = makeTaskInfo(1); + RecentTaskInfo t2 = makeTaskInfo(2); + RecentTaskInfo t3 = makeTaskInfo(3); + RecentTaskInfo t4 = makeTaskInfo(4); setRawList(t1, t2, t3, t4); when(mDesktopRepository.isActiveTask(1)).thenReturn(true); when(mDesktopRepository.isActiveTask(3)).thenReturn(true); - ArrayList<GroupedRecentTaskInfo> recentTasks = mRecentTasksController.getRecentTasks( - MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0); + ArrayList<GroupedTaskInfo> recentTasks = + mRecentTasksController.getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0); // Expect no grouping of tasks assertEquals(4, recentTasks.size()); - assertEquals(GroupedRecentTaskInfo.TYPE_SINGLE, recentTasks.get(0).getType()); - assertEquals(GroupedRecentTaskInfo.TYPE_SINGLE, recentTasks.get(1).getType()); - assertEquals(GroupedRecentTaskInfo.TYPE_SINGLE, recentTasks.get(2).getType()); - assertEquals(GroupedRecentTaskInfo.TYPE_SINGLE, recentTasks.get(3).getType()); + assertEquals(GroupedTaskInfo.TYPE_FULLSCREEN, recentTasks.get(0).getType()); + assertEquals(GroupedTaskInfo.TYPE_FULLSCREEN, recentTasks.get(1).getType()); + assertEquals(GroupedTaskInfo.TYPE_FULLSCREEN, recentTasks.get(2).getType()); + assertEquals(GroupedTaskInfo.TYPE_FULLSCREEN, recentTasks.get(3).getType()); assertEquals(t1, recentTasks.get(0).getTaskInfo1()); assertEquals(t2, recentTasks.get(1).getTaskInfo1()); @@ -405,11 +407,11 @@ public class RecentTasksControllerTest extends ShellTestCase { @Test public void testGetRecentTasks_proto2Enabled_includesMinimizedFreeformTasks() { - ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1); - ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2); - ActivityManager.RecentTaskInfo t3 = makeTaskInfo(3); - ActivityManager.RecentTaskInfo t4 = makeTaskInfo(4); - ActivityManager.RecentTaskInfo t5 = makeTaskInfo(5); + RecentTaskInfo t1 = makeTaskInfo(1); + RecentTaskInfo t2 = makeTaskInfo(2); + RecentTaskInfo t3 = makeTaskInfo(3); + RecentTaskInfo t4 = makeTaskInfo(4); + RecentTaskInfo t5 = makeTaskInfo(5); setRawList(t1, t2, t3, t4, t5); when(mDesktopRepository.isActiveTask(1)).thenReturn(true); @@ -417,19 +419,19 @@ public class RecentTasksControllerTest extends ShellTestCase { when(mDesktopRepository.isActiveTask(5)).thenReturn(true); when(mDesktopRepository.isMinimizedTask(3)).thenReturn(true); - ArrayList<GroupedRecentTaskInfo> recentTasks = mRecentTasksController.getRecentTasks( - MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0); + ArrayList<GroupedTaskInfo> recentTasks = + mRecentTasksController.getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0); // 3 freeform tasks should be grouped into one, 2 single tasks, 3 total recents entries assertEquals(3, recentTasks.size()); - GroupedRecentTaskInfo freeformGroup = recentTasks.get(0); - GroupedRecentTaskInfo singleGroup1 = recentTasks.get(1); - GroupedRecentTaskInfo singleGroup2 = recentTasks.get(2); + GroupedTaskInfo freeformGroup = recentTasks.get(0); + GroupedTaskInfo singleGroup1 = recentTasks.get(1); + GroupedTaskInfo singleGroup2 = recentTasks.get(2); // Check that groups have expected types - assertEquals(GroupedRecentTaskInfo.TYPE_FREEFORM, freeformGroup.getType()); - assertEquals(GroupedRecentTaskInfo.TYPE_SINGLE, singleGroup1.getType()); - assertEquals(GroupedRecentTaskInfo.TYPE_SINGLE, singleGroup2.getType()); + assertEquals(GroupedTaskInfo.TYPE_FREEFORM, freeformGroup.getType()); + assertEquals(GroupedTaskInfo.TYPE_FULLSCREEN, singleGroup1.getType()); + assertEquals(GroupedTaskInfo.TYPE_FULLSCREEN, singleGroup2.getType()); // Check freeform group entries assertEquals(3, freeformGroup.getTaskInfoList().size()); @@ -445,8 +447,8 @@ public class RecentTasksControllerTest extends ShellTestCase { @Test @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE) public void testGetRecentTasks_hasDesktopTasks_persistenceEnabled_freeformTaskHaveBoundsSet() { - ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1); - ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2); + RecentTaskInfo t1 = makeTaskInfo(1); + RecentTaskInfo t2 = makeTaskInfo(2); t1.lastNonFullscreenBounds = new Rect(100, 200, 300, 400); t2.lastNonFullscreenBounds = new Rect(150, 250, 350, 450); @@ -455,11 +457,11 @@ public class RecentTasksControllerTest extends ShellTestCase { when(mDesktopRepository.isActiveTask(1)).thenReturn(true); when(mDesktopRepository.isActiveTask(2)).thenReturn(true); - ArrayList<GroupedRecentTaskInfo> recentTasks = mRecentTasksController.getRecentTasks( - MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0); + ArrayList<GroupedTaskInfo> recentTasks = + mRecentTasksController.getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0); assertEquals(1, recentTasks.size()); - GroupedRecentTaskInfo freeformGroup = recentTasks.get(0); + GroupedTaskInfo freeformGroup = recentTasks.get(0); // Check bounds assertEquals(t1.lastNonFullscreenBounds, freeformGroup.getTaskInfoList().get( @@ -478,9 +480,9 @@ public class RecentTasksControllerTest extends ShellTestCase { @Test public void testRemovedTaskRemovesSplit() { - ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1); - ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2); - ActivityManager.RecentTaskInfo t3 = makeTaskInfo(3); + RecentTaskInfo t1 = makeTaskInfo(1); + RecentTaskInfo t2 = makeTaskInfo(2); + RecentTaskInfo t3 = makeTaskInfo(3); setRawList(t1, t2, t3); // Add a pair @@ -500,7 +502,7 @@ public class RecentTasksControllerTest extends ShellTestCase { @Test public void testTaskWindowingModeChangedNotifiesChange() { - ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1); + RecentTaskInfo t1 = makeTaskInfo(1); setRawList(t1); // Remove one of the tasks and ensure the pair is removed @@ -607,7 +609,8 @@ public class RecentTasksControllerTest extends ShellTestCase { mRecentTasksControllerReal.onTaskMovedToFrontThroughTransition(taskInfo); - verify(mRecentTasksListener).onTaskMovedToFront(taskInfo); + GroupedTaskInfo runningTask = GroupedTaskInfo.forFullscreenTasks(taskInfo); + verify(mRecentTasksListener).onTaskMovedToFront(eq(new GroupedTaskInfo[] { runningTask })); } @Test @@ -656,8 +659,8 @@ public class RecentTasksControllerTest extends ShellTestCase { /** * Helper to create a task with a given task id. */ - private ActivityManager.RecentTaskInfo makeTaskInfo(int taskId) { - ActivityManager.RecentTaskInfo info = new ActivityManager.RecentTaskInfo(); + private RecentTaskInfo makeTaskInfo(int taskId) { + RecentTaskInfo info = new RecentTaskInfo(); info.taskId = taskId; info.lastNonFullscreenBounds = new Rect(); return info; @@ -676,10 +679,10 @@ public class RecentTasksControllerTest extends ShellTestCase { /** * Helper to set the raw task list on the controller. */ - private ArrayList<ActivityManager.RecentTaskInfo> setRawList( - ActivityManager.RecentTaskInfo... tasks) { - ArrayList<ActivityManager.RecentTaskInfo> rawList = new ArrayList<>(); - for (ActivityManager.RecentTaskInfo task : tasks) { + private ArrayList<RecentTaskInfo> setRawList( + RecentTaskInfo... tasks) { + ArrayList<RecentTaskInfo> rawList = new ArrayList<>(); + for (RecentTaskInfo task : tasks) { rawList.add(task); } doReturn(rawList).when(mActivityTaskManager).getRecentTasks(anyInt(), anyInt(), @@ -693,11 +696,11 @@ public class RecentTasksControllerTest extends ShellTestCase { * @param expectedTaskIds list of task ids that map to the flattened task ids of the tasks in * the grouped task list */ - private void assertGroupedTasksListEquals(List<GroupedRecentTaskInfo> recentTasks, + private void assertGroupedTasksListEquals(List<GroupedTaskInfo> recentTasks, int... expectedTaskIds) { int[] flattenedTaskIds = new int[recentTasks.size() * 2]; for (int i = 0; i < recentTasks.size(); i++) { - GroupedRecentTaskInfo pair = recentTasks.get(i); + GroupedTaskInfo pair = recentTasks.get(i); int taskId1 = pair.getTaskInfo1().taskId; flattenedTaskIds[2 * i] = taskId1; flattenedTaskIds[2 * i + 1] = pair.getTaskInfo2() != null diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java index ef3af8e7bdac..966651f19711 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java @@ -217,7 +217,6 @@ public class SplitScreenControllerTests extends ShellTestCase { // Put the same component to the top running task ActivityManager.RunningTaskInfo topRunningTask = createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, startIntent); - doReturn(topRunningTask).when(mRecentTasks).getTopRunningTask(); doReturn(topRunningTask).when(mRecentTasks).getTopRunningTask(any()); mSplitScreenController.startIntent(pendingIntent, mContext.getUserId(), null, @@ -238,7 +237,6 @@ public class SplitScreenControllerTests extends ShellTestCase { // Put the same component to the top running task ActivityManager.RunningTaskInfo topRunningTask = createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, startIntent); - doReturn(topRunningTask).when(mRecentTasks).getTopRunningTask(); doReturn(topRunningTask).when(mRecentTasks).getTopRunningTask(any()); // Put the same component into a task in the background ActivityManager.RecentTaskInfo sameTaskInfo = new ActivityManager.RecentTaskInfo(); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java index 6cde0569796d..2442a55d78d0 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java @@ -19,6 +19,7 @@ package com.android.wm.shell.transition; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; +import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; @@ -363,6 +364,25 @@ public class ShellTransitionTests extends ShellTestCase { } @Test + public void testTransitionFilterWindowingMode() { + TransitionFilter filter = new TransitionFilter(); + filter.mRequirements = + new TransitionFilter.Requirement[]{new TransitionFilter.Requirement()}; + filter.mRequirements[0].mWindowingMode = WINDOWING_MODE_FREEFORM; + filter.mRequirements[0].mModes = new int[]{TRANSIT_OPEN, TRANSIT_TO_FRONT}; + + final TransitionInfo fullscreenStd = new TransitionInfoBuilder(TRANSIT_OPEN) + .addChange(TRANSIT_OPEN, createTaskInfo( + 1, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD)).build(); + assertFalse(filter.matches(fullscreenStd)); + + final TransitionInfo freeformStd = new TransitionInfoBuilder(TRANSIT_OPEN) + .addChange(TRANSIT_OPEN, createTaskInfo( + 1, WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD)).build(); + assertTrue(filter.matches(freeformStd)); + } + + @Test public void testTransitionFilterMultiRequirement() { // filter that requires at-least one opening and one closing app TransitionFilter filter = new TransitionFilter(); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorationTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorationTests.kt index 5ebf5170bf86..59141ca39487 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorationTests.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorationTests.kt @@ -19,6 +19,7 @@ package com.android.wm.shell.windowdecor import android.app.ActivityManager import android.app.WindowConfiguration import android.content.ComponentName +import android.graphics.Region import android.testing.AndroidTestingRunner import android.view.Display import android.view.InsetsState @@ -33,6 +34,9 @@ import org.junit.runner.RunWith @SmallTest @RunWith(AndroidTestingRunner::class) class CaptionWindowDecorationTests : ShellTestCase() { + + private val exclusionRegion = Region.obtain() + @Test fun updateRelayoutParams_freeformAndTransparentAppearance_allowsInputFallthrough() { val taskInfo = createTaskInfo() @@ -50,7 +54,8 @@ class CaptionWindowDecorationTests : ShellTestCase() { true /* isStatusBarVisible */, false /* isKeyguardVisibleAndOccluded */, InsetsState(), - true /* hasGlobalFocus */ + true /* hasGlobalFocus */, + exclusionRegion ) Truth.assertThat(relayoutParams.hasInputFeatureSpy()).isTrue() @@ -72,7 +77,8 @@ class CaptionWindowDecorationTests : ShellTestCase() { true /* isStatusBarVisible */, false /* isKeyguardVisibleAndOccluded */, InsetsState(), - true /* hasGlobalFocus */ + true /* hasGlobalFocus */, + exclusionRegion ) Truth.assertThat(relayoutParams.hasInputFeatureSpy()).isFalse() @@ -90,7 +96,8 @@ class CaptionWindowDecorationTests : ShellTestCase() { true /* isStatusBarVisible */, false /* isKeyguardVisibleAndOccluded */, InsetsState(), - true /* hasGlobalFocus */ + true /* hasGlobalFocus */, + exclusionRegion ) Truth.assertThat(relayoutParams.mOccludingCaptionElements.size).isEqualTo(2) Truth.assertThat(relayoutParams.mOccludingCaptionElements[0].mAlignment).isEqualTo( diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt index 56267174ba75..be664f86e9f5 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt @@ -30,6 +30,7 @@ import android.content.Intent import android.content.Intent.ACTION_MAIN import android.content.pm.ActivityInfo import android.graphics.Rect +import android.graphics.Region import android.hardware.display.DisplayManager import android.hardware.display.VirtualDisplay import android.hardware.input.InputManager @@ -48,6 +49,7 @@ import android.testing.TestableLooper.RunWithLooper import android.util.SparseArray import android.view.Choreographer import android.view.Display.DEFAULT_DISPLAY +import android.view.ISystemGestureExclusionListener import android.view.IWindowManager import android.view.InputChannel import android.view.InputMonitor @@ -84,7 +86,6 @@ import com.android.wm.shell.common.DisplayController import com.android.wm.shell.common.DisplayInsetsController import com.android.wm.shell.common.DisplayLayout import com.android.wm.shell.common.MultiInstanceHelper -import com.android.wm.shell.common.ShellExecutor import com.android.wm.shell.common.SyncTransactionQueue import com.android.wm.shell.desktopmode.DesktopActivityOrientationChangeHandler import com.android.wm.shell.desktopmode.DesktopModeEventLogger @@ -131,6 +132,7 @@ import org.mockito.Mockito.times import org.mockito.kotlin.KArgumentCaptor import org.mockito.kotlin.verify import org.mockito.kotlin.any +import org.mockito.kotlin.anyOrNull import org.mockito.kotlin.argThat import org.mockito.kotlin.argumentCaptor import org.mockito.kotlin.doNothing @@ -175,7 +177,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { @Mock private lateinit var mockInputMonitorFactory: DesktopModeWindowDecorViewModel.InputMonitorFactory @Mock private lateinit var mockShellController: ShellController - @Mock private lateinit var mockShellExecutor: ShellExecutor + private val testShellExecutor = TestShellExecutor() @Mock private lateinit var mockAppHeaderViewHolderFactory: AppHeaderViewHolder.Factory @Mock private lateinit var mockRootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer @Mock private lateinit var mockShellCommandHandler: ShellCommandHandler @@ -230,13 +232,13 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { spyContext = spy(mContext) doNothing().`when`(spyContext).startActivity(any()) - shellInit = ShellInit(mockShellExecutor) + shellInit = ShellInit(testShellExecutor) windowDecorByTaskIdSpy.clear() spyContext.addMockSystemService(InputManager::class.java, mockInputManager) desktopModeEventLogger = mock<DesktopModeEventLogger>() desktopModeWindowDecorViewModel = DesktopModeWindowDecorViewModel( spyContext, - mockShellExecutor, + testShellExecutor, mockMainHandler, mockMainChoreographer, bgExecutor, @@ -1307,10 +1309,101 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { verify(decor).closeMaximizeMenu() } + @Test + @EnableFlags(Flags.FLAG_ENABLE_DISPLAY_FOCUS_IN_SHELL_TRANSITIONS) + fun testOnTaskInfoChanged_enableShellTransitionsFlag() { + val task = createTask( + windowingMode = WINDOWING_MODE_FREEFORM + ) + val taskSurface = SurfaceControl() + val decoration = setUpMockDecorationForTask(task) + + onTaskOpening(task, taskSurface) + assertTrue(windowDecorByTaskIdSpy.contains(task.taskId)) + + decoration.mHasGlobalFocus = true + desktopModeWindowDecorViewModel.onTaskInfoChanged(task) + verify(decoration).relayout(eq(task), eq(true), anyOrNull()) + + decoration.mHasGlobalFocus = false + desktopModeWindowDecorViewModel.onTaskInfoChanged(task) + verify(decoration).relayout(eq(task), eq(false), anyOrNull()) + } + + @Test + @DisableFlags(Flags.FLAG_ENABLE_DISPLAY_FOCUS_IN_SHELL_TRANSITIONS) + fun testOnTaskInfoChanged_disableShellTransitionsFlag() { + val task = createTask( + windowingMode = WINDOWING_MODE_FREEFORM + ) + val taskSurface = SurfaceControl() + val decoration = setUpMockDecorationForTask(task) + + onTaskOpening(task, taskSurface) + assertTrue(windowDecorByTaskIdSpy.contains(task.taskId)) + + task.isFocused = true + desktopModeWindowDecorViewModel.onTaskInfoChanged(task) + verify(decoration).relayout(eq(task), eq(true), anyOrNull()) + + task.isFocused = false + desktopModeWindowDecorViewModel.onTaskInfoChanged(task) + verify(decoration).relayout(eq(task), eq(false), anyOrNull()) + } + + @Test + fun testGestureExclusionChanged_updatesDecorations() { + val captor = argumentCaptor<ISystemGestureExclusionListener>() + verify(mockWindowManager) + .registerSystemGestureExclusionListener(captor.capture(), eq(DEFAULT_DISPLAY)) + val task = createOpenTaskDecoration( + windowingMode = WINDOWING_MODE_FREEFORM, + displayId = DEFAULT_DISPLAY + ) + val task2 = createOpenTaskDecoration( + windowingMode = WINDOWING_MODE_FREEFORM, + displayId = DEFAULT_DISPLAY + ) + val newRegion = Region.obtain().apply { + set(Rect(0, 0, 1600, 80)) + } + + captor.firstValue.onSystemGestureExclusionChanged(DEFAULT_DISPLAY, newRegion, newRegion) + testShellExecutor.flushAll() + + verify(task).onExclusionRegionChanged(newRegion) + verify(task2).onExclusionRegionChanged(newRegion) + } + + @Test + fun testGestureExclusionChanged_otherDisplay_skipsDecorationUpdate() { + val captor = argumentCaptor<ISystemGestureExclusionListener>() + verify(mockWindowManager) + .registerSystemGestureExclusionListener(captor.capture(), eq(DEFAULT_DISPLAY)) + val task = createOpenTaskDecoration( + windowingMode = WINDOWING_MODE_FREEFORM, + displayId = DEFAULT_DISPLAY + ) + val task2 = createOpenTaskDecoration( + windowingMode = WINDOWING_MODE_FREEFORM, + displayId = 2 + ) + val newRegion = Region.obtain().apply { + set(Rect(0, 0, 1600, 80)) + } + + captor.firstValue.onSystemGestureExclusionChanged(DEFAULT_DISPLAY, newRegion, newRegion) + testShellExecutor.flushAll() + + verify(task).onExclusionRegionChanged(newRegion) + verify(task2, never()).onExclusionRegionChanged(newRegion) + } + private fun createOpenTaskDecoration( @WindowingMode windowingMode: Int, taskSurface: SurfaceControl = SurfaceControl(), requestingImmersive: Boolean = false, + displayId: Int = DEFAULT_DISPLAY, onMaxOrRestoreListenerCaptor: ArgumentCaptor<Function0<Unit>> = forClass(Function0::class.java) as ArgumentCaptor<Function0<Unit>>, onImmersiveOrRestoreListenerCaptor: KArgumentCaptor<() -> Unit> = @@ -1334,6 +1427,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { ): DesktopModeWindowDecoration { val decor = setUpMockDecorationForTask(createTask( windowingMode = windowingMode, + displayId = displayId, requestingImmersive = requestingImmersive )) onTaskOpening(decor.mTaskInfo, taskSurface) diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java index 41f57ae0fd97..1d2d0f078817 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java @@ -64,6 +64,7 @@ import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.PointF; import android.graphics.Rect; +import android.graphics.Region; import android.net.Uri; import android.os.Handler; import android.os.SystemProperties; @@ -224,6 +225,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { private TestableContext mTestableContext; private final ShellExecutor mBgExecutor = new TestShellExecutor(); private final AssistContent mAssistContent = new AssistContent(); + private final Region mExclusionRegion = Region.obtain(); /** Set up run before test class. */ @BeforeClass @@ -262,8 +264,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { doReturn(defaultDisplay).when(mMockDisplayController).getDisplay(Display.DEFAULT_DISPLAY); doReturn(mInsetsState).when(mMockDisplayController).getInsetsState(anyInt()); when(mMockHandleMenuFactory.create(any(), any(), anyInt(), any(), any(), any(), - anyBoolean(), anyBoolean(), anyBoolean(), anyBoolean(), any(), anyInt(), anyInt(), - anyInt(), anyInt())) + anyBoolean(), anyBoolean(), anyBoolean(), anyBoolean(), anyBoolean(), any(), + anyInt(), anyInt(), anyInt(), anyInt())) .thenReturn(mMockHandleMenu); when(mMockMultiInstanceHelper.supportsMultiInstanceSplit(any())).thenReturn(false); when(mMockAppHeaderViewHolderFactory.create(any(), any(), any(), any(), any(), any(), any(), @@ -283,7 +285,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo)); - spyWindowDecor.relayout(taskInfo, false /* hasGlobalFocus */); + spyWindowDecor.relayout(taskInfo, false /* hasGlobalFocus */, mExclusionRegion); // Menus should close if open before the task being invisible causes relayout to return. verify(spyWindowDecor).closeHandleMenu(); @@ -303,7 +305,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { /* isKeyguardVisibleAndOccluded */ false, /* inFullImmersiveMode */ false, new InsetsState(), - /* hasGlobalFocus= */ true); + /* hasGlobalFocus= */ true, + mExclusionRegion); assertThat(relayoutParams.mShadowRadiusId).isNotEqualTo(Resources.ID_NULL); } @@ -324,7 +327,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { /* isKeyguardVisibleAndOccluded */ false, /* inFullImmersiveMode */ false, new InsetsState(), - /* hasGlobalFocus= */ true); + /* hasGlobalFocus= */ true, + mExclusionRegion); assertThat(relayoutParams.mCornerRadius).isGreaterThan(0); } @@ -350,7 +354,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { /* isKeyguardVisibleAndOccluded */ false, /* inFullImmersiveMode */ false, new InsetsState(), - /* hasGlobalFocus= */ true); + /* hasGlobalFocus= */ true, + mExclusionRegion); assertThat(relayoutParams.mWindowDecorConfig.densityDpi).isEqualTo(customTaskDensity); } @@ -377,12 +382,14 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { /* isKeyguardVisibleAndOccluded */ false, /* inFullImmersiveMode */ false, new InsetsState(), - /* hasGlobalFocus= */ true); + /* hasGlobalFocus= */ true, + mExclusionRegion); assertThat(relayoutParams.mWindowDecorConfig.densityDpi).isEqualTo(systemDensity); } @Test + @DisableFlags(Flags.FLAG_ENABLE_ACCESSIBLE_CUSTOM_HEADERS) public void updateRelayoutParams_freeformAndTransparentAppearance_allowsInputFallthrough() { final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true); taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM); @@ -400,12 +407,39 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { /* isKeyguardVisibleAndOccluded */ false, /* inFullImmersiveMode */ false, new InsetsState(), - /* hasGlobalFocus= */ true); + /* hasGlobalFocus= */ true, + mExclusionRegion); assertThat(relayoutParams.hasInputFeatureSpy()).isTrue(); } @Test + @EnableFlags(Flags.FLAG_ENABLE_ACCESSIBLE_CUSTOM_HEADERS) + public void updateRelayoutParams_freeformAndTransparentAppearance_limitedTouchRegion() { + final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true); + taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM); + taskInfo.taskDescription.setTopOpaqueSystemBarsAppearance( + APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND); + final RelayoutParams relayoutParams = new RelayoutParams(); + + DesktopModeWindowDecoration.updateRelayoutParams( + relayoutParams, + mTestableContext, + taskInfo, + /* applyStartTransactionOnDraw= */ true, + /* shouldSetTaskPositionAndCrop */ false, + /* isStatusBarVisible */ true, + /* isKeyguardVisibleAndOccluded */ false, + /* inFullImmersiveMode */ false, + new InsetsState(), + /* hasGlobalFocus= */ true, + mExclusionRegion); + + assertThat(relayoutParams.mLimitTouchRegionToSystemAreas).isTrue(); + } + + @Test + @DisableFlags(Flags.FLAG_ENABLE_ACCESSIBLE_CUSTOM_HEADERS) public void updateRelayoutParams_freeformButOpaqueAppearance_disallowsInputFallthrough() { final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true); taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM); @@ -422,12 +456,38 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { /* isKeyguardVisibleAndOccluded */ false, /* inFullImmersiveMode */ false, new InsetsState(), - /* hasGlobalFocus= */ true); + /* hasGlobalFocus= */ true, + mExclusionRegion); assertThat(relayoutParams.hasInputFeatureSpy()).isFalse(); } @Test + @EnableFlags(Flags.FLAG_ENABLE_ACCESSIBLE_CUSTOM_HEADERS) + public void updateRelayoutParams_freeformButOpaqueAppearance_unlimitedTouchRegion() { + final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true); + taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM); + taskInfo.taskDescription.setTopOpaqueSystemBarsAppearance(0); + final RelayoutParams relayoutParams = new RelayoutParams(); + + DesktopModeWindowDecoration.updateRelayoutParams( + relayoutParams, + mTestableContext, + taskInfo, + /* applyStartTransactionOnDraw= */ true, + /* shouldSetTaskPositionAndCrop */ false, + /* isStatusBarVisible */ true, + /* isKeyguardVisibleAndOccluded */ false, + /* inFullImmersiveMode */ false, + new InsetsState(), + /* hasGlobalFocus= */ true, + mExclusionRegion); + + assertThat(relayoutParams.mLimitTouchRegionToSystemAreas).isFalse(); + } + + @Test + @DisableFlags(Flags.FLAG_ENABLE_ACCESSIBLE_CUSTOM_HEADERS) public void updateRelayoutParams_fullscreen_disallowsInputFallthrough() { final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true); taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN); @@ -443,12 +503,36 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { /* isKeyguardVisibleAndOccluded */ false, /* inFullImmersiveMode */ false, new InsetsState(), - /* hasGlobalFocus= */ true); + /* hasGlobalFocus= */ true, + mExclusionRegion); assertThat(relayoutParams.hasInputFeatureSpy()).isFalse(); } @Test + @EnableFlags(Flags.FLAG_ENABLE_ACCESSIBLE_CUSTOM_HEADERS) + public void updateRelayoutParams_fullscreen_unlimitedTouchRegion() { + final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true); + taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN); + final RelayoutParams relayoutParams = new RelayoutParams(); + + DesktopModeWindowDecoration.updateRelayoutParams( + relayoutParams, + mTestableContext, + taskInfo, + /* applyStartTransactionOnDraw= */ true, + /* shouldSetTaskPositionAndCrop */ false, + /* isStatusBarVisible */ true, + /* isKeyguardVisibleAndOccluded */ false, + /* inFullImmersiveMode */ false, + new InsetsState(), + /* hasGlobalFocus= */ true, + mExclusionRegion); + + assertThat(relayoutParams.mLimitTouchRegionToSystemAreas).isFalse(); + } + + @Test public void updateRelayoutParams_freeform_inputChannelNeeded() { final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true); taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM); @@ -464,7 +548,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { /* isKeyguardVisibleAndOccluded */ false, /* inFullImmersiveMode */ false, new InsetsState(), - /* hasGlobalFocus= */ true); + /* hasGlobalFocus= */ true, + mExclusionRegion); assertThat(hasNoInputChannelFeature(relayoutParams)).isFalse(); } @@ -486,7 +571,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { /* isKeyguardVisibleAndOccluded */ false, /* inFullImmersiveMode */ false, new InsetsState(), - /* hasGlobalFocus= */ true); + /* hasGlobalFocus= */ true, + mExclusionRegion); assertThat(hasNoInputChannelFeature(relayoutParams)).isTrue(); } @@ -508,7 +594,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { /* isKeyguardVisibleAndOccluded */ false, /* inFullImmersiveMode */ false, new InsetsState(), - /* hasGlobalFocus= */ true); + /* hasGlobalFocus= */ true, + mExclusionRegion); assertThat(hasNoInputChannelFeature(relayoutParams)).isTrue(); } @@ -531,7 +618,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { /* isKeyguardVisibleAndOccluded */ false, /* inFullImmersiveMode */ false, new InsetsState(), - /* hasGlobalFocus= */ true); + /* hasGlobalFocus= */ true, + mExclusionRegion); assertThat((relayoutParams.mInsetSourceFlags & FLAG_FORCE_CONSUMING) != 0).isTrue(); } @@ -555,7 +643,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { /* isKeyguardVisibleAndOccluded */ false, /* inFullImmersiveMode */ false, new InsetsState(), - /* hasGlobalFocus= */ true); + /* hasGlobalFocus= */ true, + mExclusionRegion); assertThat((relayoutParams.mInsetSourceFlags & FLAG_FORCE_CONSUMING) == 0).isTrue(); } @@ -577,7 +666,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { /* isKeyguardVisibleAndOccluded */ false, /* inFullImmersiveMode */ false, new InsetsState(), - /* hasGlobalFocus= */ true); + /* hasGlobalFocus= */ true, + mExclusionRegion); assertThat( (relayoutParams.mInsetSourceFlags & FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR) != 0) @@ -601,7 +691,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { /* isKeyguardVisibleAndOccluded */ false, /* inFullImmersiveMode */ false, new InsetsState(), - /* hasGlobalFocus= */ true); + /* hasGlobalFocus= */ true, + mExclusionRegion); assertThat( (relayoutParams.mInsetSourceFlags & FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR) == 0) @@ -631,7 +722,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { /* isKeyguardVisibleAndOccluded */ false, /* inFullImmersiveMode */ true, insetsState, - /* hasGlobalFocus= */ true); + /* hasGlobalFocus= */ true, + mExclusionRegion); // Takes status bar inset as padding, ignores caption bar inset. assertThat(relayoutParams.mCaptionTopPadding).isEqualTo(50); @@ -654,7 +746,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { /* isKeyguardVisibleAndOccluded */ false, /* inFullImmersiveMode */ true, new InsetsState(), - /* hasGlobalFocus= */ true); + /* hasGlobalFocus= */ true, + mExclusionRegion); assertThat(relayoutParams.mIsInsetSource).isFalse(); } @@ -676,7 +769,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { /* isKeyguardVisibleAndOccluded */ false, /* inFullImmersiveMode */ false, new InsetsState(), - /* hasGlobalFocus= */ true); + /* hasGlobalFocus= */ true, + mExclusionRegion); // Header is always shown because it's assumed the status bar is always visible. assertThat(relayoutParams.mIsCaptionVisible).isTrue(); @@ -698,7 +792,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { /* isKeyguardVisibleAndOccluded */ false, /* inFullImmersiveMode */ false, new InsetsState(), - /* hasGlobalFocus= */ true); + /* hasGlobalFocus= */ true, + mExclusionRegion); assertThat(relayoutParams.mIsCaptionVisible).isTrue(); } @@ -719,7 +814,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { /* isKeyguardVisibleAndOccluded */ false, /* inFullImmersiveMode */ false, new InsetsState(), - /* hasGlobalFocus= */ true); + /* hasGlobalFocus= */ true, + mExclusionRegion); assertThat(relayoutParams.mIsCaptionVisible).isFalse(); } @@ -740,7 +836,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { /* isKeyguardVisibleAndOccluded */ true, /* inFullImmersiveMode */ false, new InsetsState(), - /* hasGlobalFocus= */ true); + /* hasGlobalFocus= */ true, + mExclusionRegion); assertThat(relayoutParams.mIsCaptionVisible).isFalse(); } @@ -762,7 +859,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { /* isKeyguardVisibleAndOccluded */ false, /* inFullImmersiveMode */ true, new InsetsState(), - /* hasGlobalFocus= */ true); + /* hasGlobalFocus= */ true, + mExclusionRegion); assertThat(relayoutParams.mIsCaptionVisible).isTrue(); @@ -776,7 +874,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { /* isKeyguardVisibleAndOccluded */ false, /* inFullImmersiveMode */ true, new InsetsState(), - /* hasGlobalFocus= */ true); + /* hasGlobalFocus= */ true, + mExclusionRegion); assertThat(relayoutParams.mIsCaptionVisible).isFalse(); } @@ -798,7 +897,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { /* isKeyguardVisibleAndOccluded */ true, /* inFullImmersiveMode */ true, new InsetsState(), - /* hasGlobalFocus= */ true); + /* hasGlobalFocus= */ true, + mExclusionRegion); assertThat(relayoutParams.mIsCaptionVisible).isFalse(); } @@ -809,7 +909,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo)); taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN); - spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */); + spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */, mExclusionRegion); verify(mMockTransaction).apply(); verify(mMockRootSurfaceControl, never()).applyTransactionOnDraw(any()); @@ -824,7 +924,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { // Make non-resizable to avoid dealing with input-permissions (MONITOR_INPUT) taskInfo.isResizeable = false; - spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */); + spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */, mExclusionRegion); verify(mMockTransaction, never()).apply(); verify(mMockRootSurfaceControl).applyTransactionOnDraw(mMockTransaction); @@ -836,7 +936,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo)); taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN); - spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */); + spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */, mExclusionRegion); verify(mMockSurfaceControlViewHostFactory, never()).create(any(), any(), any()); } @@ -848,7 +948,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN); ArgumentCaptor<Runnable> runnableArgument = ArgumentCaptor.forClass(Runnable.class); - spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */); + spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */, mExclusionRegion); // Once for view host, the other for the AppHandle input layer. verify(mMockHandler, times(2)).post(runnableArgument.capture()); @@ -865,7 +965,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { // Make non-resizable to avoid dealing with input-permissions (MONITOR_INPUT) taskInfo.isResizeable = false; - spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */); + spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */, mExclusionRegion); verify(mMockSurfaceControlViewHostFactory).create(any(), any(), any()); verify(mMockHandler, never()).post(any()); @@ -877,11 +977,11 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo)); taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN); ArgumentCaptor<Runnable> runnableArgument = ArgumentCaptor.forClass(Runnable.class); - spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */); + spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */, mExclusionRegion); // Once for view host, the other for the AppHandle input layer. verify(mMockHandler, times(2)).post(runnableArgument.capture()); - spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */); + spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */, mExclusionRegion); verify(mMockHandler).removeCallbacks(runnableArgument.getValue()); } @@ -892,7 +992,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo)); taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN); ArgumentCaptor<Runnable> runnableArgument = ArgumentCaptor.forClass(Runnable.class); - spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */); + spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */, mExclusionRegion); // Once for view host, the other for the AppHandle input layer. verify(mMockHandler, times(2)).post(runnableArgument.capture()); @@ -1132,7 +1232,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { runnableArgument.getValue().run(); // Relayout decor with same captured link - decor.relayout(taskInfo, true /* hasGlobalFocus */); + decor.relayout(taskInfo, true /* hasGlobalFocus */, mExclusionRegion); // Verify handle menu's browser link not set to captured link since link is expired createHandleMenu(decor); @@ -1313,7 +1413,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo)); taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN); - spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */); + spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */, mExclusionRegion); verify(mMockCaptionHandleRepository, never()).notifyCaptionChanged(any()); } @@ -1330,7 +1430,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { ArgumentCaptor<CaptionState> captionStateArgumentCaptor = ArgumentCaptor.forClass( CaptionState.class); - spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */); + spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */, mExclusionRegion); verify(mMockCaptionHandleRepository, atLeastOnce()).notifyCaptionChanged( captionStateArgumentCaptor.capture()); @@ -1357,7 +1457,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { ArgumentCaptor<CaptionState> captionStateArgumentCaptor = ArgumentCaptor.forClass( CaptionState.class); - spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */); + spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */, mExclusionRegion); verify(mMockAppHeaderViewHolder, atLeastOnce()).runOnAppChipGlobalLayout( runnableArgumentCaptor.capture()); runnableArgumentCaptor.getValue().invoke(); @@ -1380,7 +1480,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { ArgumentCaptor<CaptionState> captionStateArgumentCaptor = ArgumentCaptor.forClass( CaptionState.class); - spyWindowDecor.relayout(taskInfo, false /* hasGlobalFocus */); + spyWindowDecor.relayout(taskInfo, false /* hasGlobalFocus */, mExclusionRegion); verify(mMockCaptionHandleRepository, atLeastOnce()).notifyCaptionChanged( captionStateArgumentCaptor.capture()); @@ -1400,7 +1500,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { ArgumentCaptor<CaptionState> captionStateArgumentCaptor = ArgumentCaptor.forClass( CaptionState.class); - spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */); + spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */, mExclusionRegion); createHandleMenu(spyWindowDecor); verify(mMockCaptionHandleRepository, atLeastOnce()).notifyCaptionChanged( @@ -1425,7 +1525,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { ArgumentCaptor<CaptionState> captionStateArgumentCaptor = ArgumentCaptor.forClass( CaptionState.class); - spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */); + spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */, mExclusionRegion); createHandleMenu(spyWindowDecor); spyWindowDecor.closeHandleMenu(); @@ -1440,9 +1540,30 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { } + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB) + public void browserApp_webUriUsedForBrowserApp() { + // Make {@link AppToWebUtils#isBrowserApp} return true + ResolveInfo resolveInfo = new ResolveInfo(); + resolveInfo.handleAllWebDataURI = true; + resolveInfo.activityInfo = createActivityInfo(); + when(mMockPackageManager.queryIntentActivitiesAsUser(any(), anyInt(), anyInt())) + .thenReturn(List.of(resolveInfo)); + + final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */); + final DesktopModeWindowDecoration decor = createWindowDecoration( + taskInfo, TEST_URI1 /* captured link */, TEST_URI2 /* web uri */, + TEST_URI3 /* generic link */); + + // Verify web uri used for browser applications + createHandleMenu(decor); + verifyHandleMenuCreated(TEST_URI2); + } + + private void verifyHandleMenuCreated(@Nullable Uri uri) { verify(mMockHandleMenuFactory).create(any(), any(), anyInt(), any(), any(), - any(), anyBoolean(), anyBoolean(), anyBoolean(), anyBoolean(), + any(), anyBoolean(), anyBoolean(), anyBoolean(), anyBoolean(), anyBoolean(), argThat(intent -> (uri == null && intent == null) || intent.getData().equals(uri)), anyInt(), anyInt(), anyInt(), anyInt()); } @@ -1522,7 +1643,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { windowDecor.setOpenInBrowserClickListener(mMockOpenInBrowserClickListener); windowDecor.mDecorWindowContext = mContext; if (relayout) { - windowDecor.relayout(taskInfo, true /* hasGlobalFocus */); + windowDecor.relayout(taskInfo, true /* hasGlobalFocus */, mExclusionRegion); } return windowDecor; } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt index ade17c61eda1..7ec2cbf9460e 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt @@ -242,7 +242,7 @@ class HandleMenuTest : ShellTestCase() { private fun createAndShowHandleMenu( splitPosition: Int? = null, - forceShowSystemBars: Boolean = false, + forceShowSystemBars: Boolean = false ): HandleMenu { val layoutId = if (mockDesktopWindowDecoration.mTaskInfo.isFreeform) { R.layout.desktop_mode_app_header @@ -266,8 +266,9 @@ class HandleMenuTest : ShellTestCase() { WindowManagerWrapper(mockWindowManager), layoutId, appIcon, appName, splitScreenController, shouldShowWindowingPill = true, shouldShowNewWindowButton = true, shouldShowManageWindowsButton = false, - shouldShowChangeAspectRatioButton = false, - null /* openInBrowserLink */, captionWidth = HANDLE_WIDTH, captionHeight = 50, + shouldShowChangeAspectRatioButton = false, isBrowserApp = false, + null /* openInAppOrBrowserIntent */, captionWidth = HANDLE_WIDTH, + captionHeight = 50, captionX = captionX, captionY = 0, ) @@ -278,7 +279,7 @@ class HandleMenuTest : ShellTestCase() { onNewWindowClickListener = mock(), onManageWindowsClickListener = mock(), onChangeAspectRatioClickListener = mock(), - openInBrowserClickListener = mock(), + openInAppOrBrowserClickListener = mock(), onOpenByDefaultClickListener = mock(), onCloseMenuClickListener = mock(), onOutsideTouchListener = mock(), diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java index 8e0434cb28f7..534803db5fe0 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java @@ -60,6 +60,7 @@ import android.content.res.Resources; import android.graphics.Color; import android.graphics.Point; import android.graphics.Rect; +import android.graphics.Region; import android.platform.test.flag.junit.SetFlagsRule; import android.testing.AndroidTestingRunner; import android.util.DisplayMetrics; @@ -508,7 +509,7 @@ public class WindowDecorationTests extends ShellTestCase { final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo); windowDecor.relayout(taskInfo, true /* applyStartTransactionOnDraw */, - true /* hasGlobalFocus */); + true /* hasGlobalFocus */, Region.obtain()); verify(mMockRootSurfaceControl).applyTransactionOnDraw(mMockSurfaceControlStartT); } @@ -525,7 +526,7 @@ public class WindowDecorationTests extends ShellTestCase { mRelayoutParams.mCaptionTopPadding = 50; windowDecor.relayout(taskInfo, false /* applyStartTransactionOnDraw */, - true /* hasGlobalFocus */); + true /* hasGlobalFocus */, Region.obtain()); assertEquals(50, mRelayoutResult.mCaptionTopPadding); } @@ -944,7 +945,7 @@ public class WindowDecorationTests extends ShellTestCase { decor.onInsetsStateChanged(createInsetsState(statusBars(), false /* visible */)); - verify(decor, times(2)).relayout(task, true /* hasGlobalFocus */); + verify(decor, times(2)).relayout(any(), any(), any(), any(), any(), any()); } @Test @@ -958,7 +959,7 @@ public class WindowDecorationTests extends ShellTestCase { decor.onInsetsStateChanged(createInsetsState(statusBars(), true /* visible */)); - verify(decor, times(1)).relayout(task, true /* hasGlobalFocus */); + verify(decor, times(1)).relayout(any(), any(), any(), any(), any(), any()); } @Test @@ -973,7 +974,7 @@ public class WindowDecorationTests extends ShellTestCase { decor.onKeyguardStateChanged(true /* visible */, true /* occluding */); assertTrue(decor.mIsKeyguardVisibleAndOccluded); - verify(decor, times(2)).relayout(task, true /* hasGlobalFocus */); + verify(decor, times(2)).relayout(any(), any(), any(), any(), any(), any()); } @Test @@ -987,7 +988,7 @@ public class WindowDecorationTests extends ShellTestCase { decor.onKeyguardStateChanged(false /* visible */, true /* occluding */); - verify(decor, times(1)).relayout(task, true /* hasGlobalFocus */); + verify(decor, times(1)).relayout(any(), any(), any(), any(), any(), any()); } private ActivityManager.RunningTaskInfo createTaskInfo() { @@ -1061,9 +1062,16 @@ public class WindowDecorationTests extends ShellTestCase { surfaceControlViewHostFactory, desktopModeEventLogger); } - @Override void relayout(ActivityManager.RunningTaskInfo taskInfo, boolean hasGlobalFocus) { - relayout(taskInfo, false /* applyStartTransactionOnDraw */, hasGlobalFocus); + relayout(taskInfo, false /* applyStartTransactionOnDraw */, hasGlobalFocus, + Region.obtain()); + } + + @Override + void relayout(ActivityManager.RunningTaskInfo taskInfo, boolean hasGlobalFocus, + @NonNull Region displayExclusionRegion) { + relayout(taskInfo, false /* applyStartTransactionOnDraw */, hasGlobalFocus, + displayExclusionRegion); } @Override @@ -1085,11 +1093,13 @@ public class WindowDecorationTests extends ShellTestCase { } void relayout(ActivityManager.RunningTaskInfo taskInfo, - boolean applyStartTransactionOnDraw, boolean hasGlobalFocus) { + boolean applyStartTransactionOnDraw, boolean hasGlobalFocus, + @NonNull Region displayExclusionRegion) { mRelayoutParams.mRunningTaskInfo = taskInfo; mRelayoutParams.mApplyStartTransactionOnDraw = applyStartTransactionOnDraw; mRelayoutParams.mLayoutResId = R.layout.caption_layout; mRelayoutParams.mHasGlobalFocus = hasGlobalFocus; + mRelayoutParams.mDisplayExclusionRegion.set(displayExclusionRegion); relayout(mRelayoutParams, mMockSurfaceControlStartT, mMockSurfaceControlFinishT, mMockWindowContainerTransaction, mMockView, mRelayoutResult); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModelTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModelTest.kt index 80ad1df44a1b..d8c1a11de452 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModelTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModelTest.kt @@ -24,6 +24,7 @@ import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.ShellTestCase import com.android.wm.shell.common.DisplayController import com.android.wm.shell.common.SyncTransactionQueue +import com.android.wm.shell.desktopmode.DesktopModeEventLogger import com.android.wm.shell.desktopmode.DesktopRepository import com.android.wm.shell.desktopmode.DesktopTasksController import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFreeformTask @@ -52,6 +53,7 @@ class DesktopTilingDecorViewModelTest : ShellTestCase() { private val transitionsMock: Transitions = mock() private val shellTaskOrganizerMock: ShellTaskOrganizer = mock() private val desktopRepository: DesktopRepository = mock() + private val desktopModeEventLogger: DesktopModeEventLogger = mock() private val toggleResizeDesktopTaskTransitionHandlerMock: ToggleResizeDesktopTaskTransitionHandler = mock() @@ -74,6 +76,7 @@ class DesktopTilingDecorViewModelTest : ShellTestCase() { toggleResizeDesktopTaskTransitionHandlerMock, returnToDragStartAnimatorMock, desktopRepository, + desktopModeEventLogger, ) whenever(contextMock.createContextAsUser(any(), any())).thenReturn(contextMock) } @@ -127,7 +130,8 @@ class DesktopTilingDecorViewModelTest : ShellTestCase() { ) desktopTilingDecorViewModel.moveTaskToFrontIfTiled(task1) - verify(desktopTilingDecoration, times(1)).moveTiledPairToFront(any()) + verify(desktopTilingDecoration, times(1)) + .moveTiledPairToFront(any(), isTaskFocused = eq(true)) } @Test diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecorationTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecorationTest.kt index f371f5223419..d7b971de94ac 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecorationTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecorationTest.kt @@ -21,6 +21,7 @@ import android.content.res.Resources import android.graphics.Rect import android.os.IBinder import android.testing.AndroidTestingRunner +import android.view.MotionEvent import android.view.SurfaceControl import android.view.WindowManager.TRANSIT_CHANGE import android.view.WindowManager.TRANSIT_TO_FRONT @@ -33,6 +34,8 @@ import com.android.wm.shell.ShellTestCase import com.android.wm.shell.common.DisplayController import com.android.wm.shell.common.DisplayLayout import com.android.wm.shell.common.SyncTransactionQueue +import com.android.wm.shell.desktopmode.DesktopModeEventLogger +import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger import com.android.wm.shell.desktopmode.DesktopRepository import com.android.wm.shell.desktopmode.DesktopTasksController import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFreeformTask @@ -91,7 +94,9 @@ class DesktopTilingWindowDecorationTest : ShellTestCase() { private val info: TransitionInfo = mock() private val finishCallback: Transitions.TransitionFinishCallback = mock() private val desktopRepository: DesktopRepository = mock() + private val desktopModeEventLogger: DesktopModeEventLogger = mock() private val desktopTilingDividerWindowManager: DesktopTilingDividerWindowManager = mock() + private val motionEvent: MotionEvent = mock() private lateinit var tilingDecoration: DesktopTilingWindowDecoration private val split_divider_width = 10 @@ -112,6 +117,7 @@ class DesktopTilingWindowDecorationTest : ShellTestCase() { toggleResizeDesktopTaskTransitionHandler, returnToDragStartAnimator, desktopRepository, + desktopModeEventLogger, ) whenever(context.createContextAsUser(any(), any())).thenReturn(context) } @@ -322,6 +328,37 @@ class DesktopTilingWindowDecorationTest : ShellTestCase() { } @Test + fun taskTiled_broughtToFront_taskInfoNotUpdated_bringToFront() { + val task1 = createFreeformTask() + val task2 = createFreeformTask() + val task3 = createFreeformTask() + val stableBounds = STABLE_BOUNDS_MOCK + whenever(displayLayout.getStableBounds(any())).thenAnswer { i -> + (i.arguments.first() as Rect).set(stableBounds) + } + whenever(context.resources).thenReturn(resources) + whenever(resources.getDimensionPixelSize(any())).thenReturn(split_divider_width) + whenever(desktopWindowDecoration.getLeash()).thenReturn(surfaceControlMock) + whenever(desktopRepository.isVisibleTask(any())).thenReturn(true) + tilingDecoration.onAppTiled( + task1, + desktopWindowDecoration, + DesktopTasksController.SnapPosition.RIGHT, + BOUNDS, + ) + tilingDecoration.onAppTiled( + task2, + desktopWindowDecoration, + DesktopTasksController.SnapPosition.LEFT, + BOUNDS, + ) + + assertThat(tilingDecoration.moveTiledPairToFront(task3, isTaskFocused = true)).isFalse() + assertThat(tilingDecoration.moveTiledPairToFront(task1, isTaskFocused = true)).isTrue() + verify(transitions, times(1)).startTransition(eq(TRANSIT_TO_FRONT), any(), eq(null)) + } + + @Test fun taskTiledTasks_NotResized_BeforeTouchEndArrival() { // Setup val task1 = createFreeformTask() @@ -371,13 +408,13 @@ class DesktopTilingWindowDecorationTest : ShellTestCase() { // End moving, no startTransition because bounds did not change. tiledTaskHelper.newBounds.set(BOUNDS) - tilingDecoration.onDividerHandleDragEnd(BOUNDS, transaction) + tilingDecoration.onDividerHandleDragEnd(BOUNDS, transaction, motionEvent) verify(tiledTaskHelper, times(2)).hideVeil() verify(transitions, never()).startTransition(any(), any(), any()) // Move then end again with bounds changing to ensure startTransition is called. tilingDecoration.onDividerHandleMoved(BOUNDS, transaction) - tilingDecoration.onDividerHandleDragEnd(BOUNDS, transaction) + tilingDecoration.onDividerHandleDragEnd(BOUNDS, transaction, motionEvent) verify(transitions, times(1)) .startTransition(eq(TRANSIT_CHANGE), any(), eq(tilingDecoration)) // No hide veil until start animation is called. @@ -389,6 +426,64 @@ class DesktopTilingWindowDecorationTest : ShellTestCase() { } @Test + fun tiledTasksResizedUsingDividerHandle_shouldLogResizingEvents() { + // Setup + val task1 = createFreeformTask() + val task2 = createFreeformTask() + val stableBounds = STABLE_BOUNDS_MOCK + whenever(displayController.getDisplayLayout(any())).thenReturn(displayLayout) + whenever(displayLayout.getStableBounds(any())).thenAnswer { i -> + (i.arguments.first() as Rect).set(stableBounds) + } + whenever(context.resources).thenReturn(resources) + whenever(resources.getDimensionPixelSize(any())).thenReturn(split_divider_width) + desktopWindowDecoration.mTaskInfo = task1 + task1.minWidth = 0 + task1.minHeight = 0 + initTiledTaskHelperMock(task1) + desktopWindowDecoration.mDecorWindowContext = context + whenever(resources.getBoolean(any())).thenReturn(true) + + // Act + tilingDecoration.onAppTiled( + task1, + desktopWindowDecoration, + DesktopTasksController.SnapPosition.RIGHT, + BOUNDS, + ) + tilingDecoration.onAppTiled( + task2, + desktopWindowDecoration, + DesktopTasksController.SnapPosition.LEFT, + BOUNDS, + ) + tilingDecoration.leftTaskResizingHelper = tiledTaskHelper + tilingDecoration.rightTaskResizingHelper = tiledTaskHelper + tilingDecoration.onDividerHandleDragStart(motionEvent) + // Log start event for task1 and task2, but the tasks are the same in + // this test, so we verify the same log twice. + verify(desktopModeEventLogger, times(2)).logTaskResizingStarted( + ResizeTrigger.TILING_DIVIDER, + motionEvent, + task1, + displayController, + ) + + tilingDecoration.onDividerHandleMoved(BOUNDS, transaction) + tilingDecoration.onDividerHandleDragEnd(BOUNDS, transaction, motionEvent) + // Log end event for task1 and task2, but the tasks are the same in + // this test, so we verify the same log twice. + verify(desktopModeEventLogger, times(2)).logTaskResizingEnded( + ResizeTrigger.TILING_DIVIDER, + motionEvent, + task1, + BOUNDS.height(), + BOUNDS.width(), + displayController, + ) + } + + @Test fun taskTiled_shouldBeRemoved_whenTileBroken() { val task1 = createFreeformTask() val stableBounds = STABLE_BOUNDS_MOCK diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/TilingDividerViewTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/TilingDividerViewTest.kt index c96ce955f217..734815cdd915 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/TilingDividerViewTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/TilingDividerViewTest.kt @@ -68,7 +68,7 @@ class TilingDividerViewTest : ShellTestCase() { val downMotionEvent = getMotionEvent(downTime, MotionEvent.ACTION_DOWN, x.toFloat(), y.toFloat()) tilingDividerView.handleMotionEvent(viewMock, downMotionEvent) - verify(dividerMoveCallbackMock, times(1)).onDividerMoveStart(any()) + verify(dividerMoveCallbackMock, times(1)).onDividerMoveStart(any(), any()) whenever(dividerMoveCallbackMock.onDividerMove(any())).thenReturn(true) val motionEvent = @@ -79,7 +79,7 @@ class TilingDividerViewTest : ShellTestCase() { val upMotionEvent = getMotionEvent(downTime, MotionEvent.ACTION_UP, x.toFloat(), y.toFloat()) tilingDividerView.handleMotionEvent(viewMock, upMotionEvent) - verify(dividerMoveCallbackMock, times(1)).onDividerMovedEnd(any()) + verify(dividerMoveCallbackMock, times(1)).onDividerMovedEnd(any(), any()) } @Test @@ -92,12 +92,12 @@ class TilingDividerViewTest : ShellTestCase() { val downMotionEvent = getMotionEvent(downTime, MotionEvent.ACTION_DOWN, x.toFloat(), y.toFloat()) tilingDividerView.handleMotionEvent(viewMock, downMotionEvent) - verify(dividerMoveCallbackMock, times(1)).onDividerMoveStart(any()) + verify(dividerMoveCallbackMock, times(1)).onDividerMoveStart(any(), any()) val upMotionEvent = getMotionEvent(downTime, MotionEvent.ACTION_UP, x.toFloat(), y.toFloat()) tilingDividerView.handleMotionEvent(viewMock, upMotionEvent) - verify(dividerMoveCallbackMock, never()).onDividerMovedEnd(any()) + verify(dividerMoveCallbackMock, never()).onDividerMovedEnd(any(), any()) } private fun getMotionEvent(eventTime: Long, action: Int, x: Float, y: Float): MotionEvent { diff --git a/libs/appfunctions/Android.bp b/libs/appfunctions/Android.bp index c6cee07d1946..5ab5a7a59c2a 100644 --- a/libs/appfunctions/Android.bp +++ b/libs/appfunctions/Android.bp @@ -18,10 +18,10 @@ package { } java_sdk_library { - name: "com.google.android.appfunctions.sidecar", + name: "com.android.extensions.appfunctions", owner: "google", srcs: ["java/**/*.java"], - api_packages: ["com.google.android.appfunctions.sidecar"], + api_packages: ["com.android.extensions.appfunctions"], dex_preopt: { enabled: false, }, @@ -31,9 +31,9 @@ java_sdk_library { } prebuilt_etc { - name: "appfunctions.sidecar.xml", + name: "appfunctions.extension.xml", system_ext_specific: true, sub_dir: "permissions", - src: "appfunctions.sidecar.xml", + src: "appfunctions.extension.xml", filename_from_src: true, } diff --git a/libs/appfunctions/api/current.txt b/libs/appfunctions/api/current.txt index faf84a8ab5ac..de402095e195 100644 --- a/libs/appfunctions/api/current.txt +++ b/libs/appfunctions/api/current.txt @@ -1,9 +1,29 @@ // Signature format: 2.0 -package com.google.android.appfunctions.sidecar { +package com.android.extensions.appfunctions { + + public final class AppFunctionException extends java.lang.Exception { + ctor public AppFunctionException(int, @Nullable String); + ctor public AppFunctionException(int, @Nullable String, @NonNull android.os.Bundle); + method public int getErrorCategory(); + method public int getErrorCode(); + method @Nullable public String getErrorMessage(); + method @NonNull public android.os.Bundle getExtras(); + field public static final int ERROR_APP_UNKNOWN_ERROR = 3000; // 0xbb8 + field public static final int ERROR_CANCELLED = 2001; // 0x7d1 + field public static final int ERROR_CATEGORY_APP = 3; // 0x3 + field public static final int ERROR_CATEGORY_REQUEST_ERROR = 1; // 0x1 + field public static final int ERROR_CATEGORY_SYSTEM = 2; // 0x2 + field public static final int ERROR_CATEGORY_UNKNOWN = 0; // 0x0 + field public static final int ERROR_DENIED = 1000; // 0x3e8 + field public static final int ERROR_DISABLED = 1002; // 0x3ea + field public static final int ERROR_FUNCTION_NOT_FOUND = 1003; // 0x3eb + field public static final int ERROR_INVALID_ARGUMENT = 1001; // 0x3e9 + field public static final int ERROR_SYSTEM_ERROR = 2000; // 0x7d0 + } public final class AppFunctionManager { ctor public AppFunctionManager(android.content.Context); - method @RequiresPermission(anyOf={android.Manifest.permission.EXECUTE_APP_FUNCTIONS_TRUSTED, android.Manifest.permission.EXECUTE_APP_FUNCTIONS}, conditional=true) public void executeAppFunction(@NonNull com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest, @NonNull java.util.concurrent.Executor, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer<com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse>); + method @RequiresPermission(anyOf={android.Manifest.permission.EXECUTE_APP_FUNCTIONS_TRUSTED, android.Manifest.permission.EXECUTE_APP_FUNCTIONS}, conditional=true) public void executeAppFunction(@NonNull com.android.extensions.appfunctions.ExecuteAppFunctionRequest, @NonNull java.util.concurrent.Executor, @NonNull android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<com.android.extensions.appfunctions.ExecuteAppFunctionResponse,com.android.extensions.appfunctions.AppFunctionException>); method @RequiresPermission(anyOf={android.Manifest.permission.EXECUTE_APP_FUNCTIONS_TRUSTED, android.Manifest.permission.EXECUTE_APP_FUNCTIONS}, conditional=true) public void isAppFunctionEnabled(@NonNull String, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,java.lang.Exception>); method public void isAppFunctionEnabled(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,java.lang.Exception>); method public void setAppFunctionEnabled(@NonNull String, int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,java.lang.Exception>); @@ -15,7 +35,7 @@ package com.google.android.appfunctions.sidecar { public abstract class AppFunctionService extends android.app.Service { ctor public AppFunctionService(); method @NonNull public final android.os.IBinder onBind(@Nullable android.content.Intent); - method @MainThread public abstract void onExecuteFunction(@NonNull com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest, @NonNull String, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer<com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse>); + method @MainThread public abstract void onExecuteFunction(@NonNull com.android.extensions.appfunctions.ExecuteAppFunctionRequest, @NonNull String, @NonNull android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<com.android.extensions.appfunctions.ExecuteAppFunctionResponse,com.android.extensions.appfunctions.AppFunctionException>); field @NonNull public static final String BIND_APP_FUNCTION_SERVICE = "android.permission.BIND_APP_FUNCTION_SERVICE"; field @NonNull public static final String SERVICE_INTERFACE = "android.app.appfunctions.AppFunctionService"; } @@ -29,33 +49,17 @@ package com.google.android.appfunctions.sidecar { public static final class ExecuteAppFunctionRequest.Builder { ctor public ExecuteAppFunctionRequest.Builder(@NonNull String, @NonNull String); - method @NonNull public com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest build(); - method @NonNull public com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest.Builder setExtras(@NonNull android.os.Bundle); - method @NonNull public com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest.Builder setParameters(@NonNull android.app.appsearch.GenericDocument); + method @NonNull public com.android.extensions.appfunctions.ExecuteAppFunctionRequest build(); + method @NonNull public com.android.extensions.appfunctions.ExecuteAppFunctionRequest.Builder setExtras(@NonNull android.os.Bundle); + method @NonNull public com.android.extensions.appfunctions.ExecuteAppFunctionRequest.Builder setParameters(@NonNull android.app.appsearch.GenericDocument); } public final class ExecuteAppFunctionResponse { - method public int getErrorCategory(); - method @Nullable public String getErrorMessage(); + ctor public ExecuteAppFunctionResponse(@NonNull android.app.appsearch.GenericDocument); + ctor public ExecuteAppFunctionResponse(@NonNull android.app.appsearch.GenericDocument, @NonNull android.os.Bundle); method @NonNull public android.os.Bundle getExtras(); - method public int getResultCode(); method @NonNull public android.app.appsearch.GenericDocument getResultDocument(); - method public boolean isSuccess(); - method @NonNull public static com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse newFailure(int, @Nullable String, @Nullable android.os.Bundle); - method @NonNull public static com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse newSuccess(@NonNull android.app.appsearch.GenericDocument, @Nullable android.os.Bundle); - field public static final int ERROR_CATEGORY_APP = 3; // 0x3 - field public static final int ERROR_CATEGORY_REQUEST_ERROR = 1; // 0x1 - field public static final int ERROR_CATEGORY_SYSTEM = 2; // 0x2 - field public static final int ERROR_CATEGORY_UNKNOWN = 0; // 0x0 - field public static final String PROPERTY_RETURN_VALUE = "returnValue"; - field public static final int RESULT_APP_UNKNOWN_ERROR = 3000; // 0xbb8 - field public static final int RESULT_CANCELLED = 2001; // 0x7d1 - field public static final int RESULT_DENIED = 1000; // 0x3e8 - field public static final int RESULT_DISABLED = 1002; // 0x3ea - field public static final int RESULT_FUNCTION_NOT_FOUND = 1003; // 0x3eb - field public static final int RESULT_INVALID_ARGUMENT = 1001; // 0x3e9 - field public static final int RESULT_OK = 0; // 0x0 - field public static final int RESULT_SYSTEM_ERROR = 2000; // 0x7d0 + field public static final String PROPERTY_RETURN_VALUE = "androidAppfunctionsReturnValue"; } } diff --git a/libs/appfunctions/appfunctions.sidecar.xml b/libs/appfunctions/appfunctions.extension.xml index bef8b6ec7ce6..dd09cc39d12f 100644 --- a/libs/appfunctions/appfunctions.sidecar.xml +++ b/libs/appfunctions/appfunctions.extension.xml @@ -16,6 +16,6 @@ --> <permissions> <library - name="com.google.android.appfunctions.sidecar" - file="/system_ext/framework/com.google.android.appfunctions.sidecar.jar"/> + name="com.android.extensions.appfunctions" + file="/system_ext/framework/com.android.extensions.appfunctions.jar"/> </permissions>
\ No newline at end of file diff --git a/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionException.java b/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionException.java new file mode 100644 index 000000000000..2540236f2ce5 --- /dev/null +++ b/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionException.java @@ -0,0 +1,212 @@ +/* + * Copyright (C) 2024 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.extensions.appfunctions; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Bundle; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** Represents an app function related errors. */ +public final class AppFunctionException extends Exception { + /** + * The caller does not have the permission to execute an app function. + * + * <p>This error is in the {@link #ERROR_CATEGORY_REQUEST_ERROR} category. + */ + public static final int ERROR_DENIED = 1000; + + /** + * The caller supplied invalid arguments to the execution request. + * + * <p>This error may be considered similar to {@link IllegalArgumentException}. + * + * <p>This error is in the {@link #ERROR_CATEGORY_REQUEST_ERROR} category. + */ + public static final int ERROR_INVALID_ARGUMENT = 1001; + + /** + * The caller tried to execute a disabled app function. + * + * <p>This error is in the {@link #ERROR_CATEGORY_REQUEST_ERROR} category. + */ + public static final int ERROR_DISABLED = 1002; + + /** + * The caller tried to execute a function that does not exist. + * + * <p>This error is in the {@link #ERROR_CATEGORY_REQUEST_ERROR} category. + */ + public static final int ERROR_FUNCTION_NOT_FOUND = 1003; + + /** + * An internal unexpected error coming from the system. + * + * <p>This error is in the {@link #ERROR_CATEGORY_SYSTEM} category. + */ + public static final int ERROR_SYSTEM_ERROR = 2000; + + /** + * The operation was cancelled. Use this error code to report that a cancellation is done after + * receiving a cancellation signal. + * + * <p>This error is in the {@link #ERROR_CATEGORY_SYSTEM} category. + */ + public static final int ERROR_CANCELLED = 2001; + + /** + * An unknown error occurred while processing the call in the AppFunctionService. + * + * <p>This error is thrown when the service is connected in the remote application but an + * unexpected error is thrown from the bound application. + * + * <p>This error is in the {@link #ERROR_CATEGORY_APP} category. + */ + public static final int ERROR_APP_UNKNOWN_ERROR = 3000; + + /** + * The error category is unknown. + * + * <p>This is the default value for {@link #getErrorCategory}. + */ + public static final int ERROR_CATEGORY_UNKNOWN = 0; + + /** + * The error is caused by the app requesting a function execution. + * + * <p>For example, the caller provided invalid parameters in the execution request e.g. an + * invalid function ID. + * + * <p>Errors in the category fall in the range 1000-1999 inclusive. + */ + public static final int ERROR_CATEGORY_REQUEST_ERROR = 1; + + /** + * The error is caused by an issue in the system. + * + * <p>For example, the AppFunctionService implementation is not found by the system. + * + * <p>Errors in the category fall in the range 2000-2999 inclusive. + */ + public static final int ERROR_CATEGORY_SYSTEM = 2; + + /** + * The error is caused by the app providing the function. + * + * <p>For example, the app crashed when the system is executing the request. + * + * <p>Errors in the category fall in the range 3000-3999 inclusive. + */ + public static final int ERROR_CATEGORY_APP = 3; + + private final int mErrorCode; + @Nullable private final String mErrorMessage; + @NonNull private final Bundle mExtras; + + public AppFunctionException(int errorCode, @Nullable String errorMessage) { + this(errorCode, errorMessage, Bundle.EMPTY); + } + + public AppFunctionException( + int errorCode, @Nullable String errorMessage, @NonNull Bundle extras) { + super(errorMessage); + mErrorCode = errorCode; + mErrorMessage = errorMessage; + mExtras = extras; + } + + /** Returns one of the {@code ERROR} constants. */ + @ErrorCode + public int getErrorCode() { + return mErrorCode; + } + + /** Returns the error message. */ + @Nullable + public String getErrorMessage() { + return mErrorMessage; + } + + /** + * Returns the error category. + * + * <p>This method categorizes errors based on their underlying cause, allowing developers to + * implement targeted error handling and provide more informative error messages to users. It + * maps ranges of error codes to specific error categories. + * + * <p>This method returns {@code ERROR_CATEGORY_UNKNOWN} if the error code does not belong to + * any error category. + * + * <p>See {@link ErrorCategory} for a complete list of error categories and their corresponding + * error code ranges. + */ + @ErrorCategory + public int getErrorCategory() { + if (mErrorCode >= 1000 && mErrorCode < 2000) { + return ERROR_CATEGORY_REQUEST_ERROR; + } + if (mErrorCode >= 2000 && mErrorCode < 3000) { + return ERROR_CATEGORY_SYSTEM; + } + if (mErrorCode >= 3000 && mErrorCode < 4000) { + return ERROR_CATEGORY_APP; + } + return ERROR_CATEGORY_UNKNOWN; + } + + @NonNull + public Bundle getExtras() { + return mExtras; + } + + /** + * Error codes. + * + * @hide + */ + @IntDef( + prefix = {"ERROR_"}, + value = { + ERROR_DENIED, + ERROR_APP_UNKNOWN_ERROR, + ERROR_FUNCTION_NOT_FOUND, + ERROR_SYSTEM_ERROR, + ERROR_INVALID_ARGUMENT, + ERROR_DISABLED, + ERROR_CANCELLED + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ErrorCode {} + + /** + * Error categories. + * + * @hide + */ + @IntDef( + prefix = {"ERROR_CATEGORY_"}, + value = { + ERROR_CATEGORY_UNKNOWN, + ERROR_CATEGORY_REQUEST_ERROR, + ERROR_CATEGORY_APP, + ERROR_CATEGORY_SYSTEM + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ErrorCategory {} +} diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionManager.java b/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionManager.java index 2075104ff868..9eb66a33fedc 100644 --- a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionManager.java +++ b/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionManager.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.google.android.appfunctions.sidecar; +package com.android.extensions.appfunctions; import android.Manifest; import android.annotation.CallbackExecutor; @@ -31,7 +31,6 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Objects; import java.util.concurrent.Executor; -import java.util.function.Consumer; /** * Provides app functions related functionalities. @@ -115,7 +114,9 @@ public final class AppFunctionManager { @NonNull ExecuteAppFunctionRequest sidecarRequest, @NonNull @CallbackExecutor Executor executor, @NonNull CancellationSignal cancellationSignal, - @NonNull Consumer<ExecuteAppFunctionResponse> callback) { + @NonNull + OutcomeReceiver<ExecuteAppFunctionResponse, AppFunctionException> + callback) { Objects.requireNonNull(sidecarRequest); Objects.requireNonNull(executor); Objects.requireNonNull(callback); @@ -126,10 +127,20 @@ public final class AppFunctionManager { platformRequest, executor, cancellationSignal, - (platformResponse) -> { - callback.accept( - SidecarConverter.getSidecarExecuteAppFunctionResponse( - platformResponse)); + new OutcomeReceiver<>() { + @Override + public void onResult( + android.app.appfunctions.ExecuteAppFunctionResponse result) { + callback.onResult( + SidecarConverter.getSidecarExecuteAppFunctionResponse(result)); + } + + @Override + public void onError( + android.app.appfunctions.AppFunctionException exception) { + callback.onError( + SidecarConverter.getSidecarAppFunctionException(exception)); + } }); } diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionService.java b/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionService.java index 0dc87e45b7e3..55f579138218 100644 --- a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionService.java +++ b/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionService.java @@ -14,9 +14,10 @@ * limitations under the License. */ -package com.google.android.appfunctions.sidecar; +package com.android.extensions.appfunctions; -import static android.Manifest.permission.BIND_APP_FUNCTION_SERVICE; +import static com.android.extensions.appfunctions.SidecarConverter.getPlatformAppFunctionException; +import static com.android.extensions.appfunctions.SidecarConverter.getPlatformExecuteAppFunctionResponse; import android.annotation.MainThread; import android.annotation.NonNull; @@ -26,9 +27,7 @@ import android.content.Intent; import android.os.Binder; import android.os.CancellationSignal; import android.os.IBinder; -import android.util.Log; - -import java.util.function.Consumer; +import android.os.OutcomeReceiver; /** * Abstract base class to provide app functions to the system. @@ -80,10 +79,18 @@ public abstract class AppFunctionService extends Service { platformRequest), callingPackage, cancellationSignal, - (sidecarResponse) -> { - callback.accept( - SidecarConverter.getPlatformExecuteAppFunctionResponse( - sidecarResponse)); + new OutcomeReceiver<>() { + @Override + public void onResult(ExecuteAppFunctionResponse result) { + callback.onResult( + getPlatformExecuteAppFunctionResponse(result)); + } + + @Override + public void onError(AppFunctionException exception) { + callback.onError( + getPlatformAppFunctionException(exception)); + } }); }); @@ -116,12 +123,14 @@ public abstract class AppFunctionService extends Service { * @param request The function execution request. * @param callingPackage The package name of the app that is requesting the execution. * @param cancellationSignal A signal to cancel the execution. - * @param callback A callback to report back the result. + * @param callback A callback to report back the result or error. */ @MainThread public abstract void onExecuteFunction( @NonNull ExecuteAppFunctionRequest request, @NonNull String callingPackage, @NonNull CancellationSignal cancellationSignal, - @NonNull Consumer<ExecuteAppFunctionResponse> callback); + @NonNull + OutcomeReceiver<ExecuteAppFunctionResponse, AppFunctionException> + callback); } diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionRequest.java b/libs/appfunctions/java/com/android/extensions/appfunctions/ExecuteAppFunctionRequest.java index 593c5213dd52..baddc245f0f1 100644 --- a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionRequest.java +++ b/libs/appfunctions/java/com/android/extensions/appfunctions/ExecuteAppFunctionRequest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.google.android.appfunctions.sidecar; +package com.android.extensions.appfunctions; import android.annotation.NonNull; import android.app.appsearch.GenericDocument; @@ -91,8 +91,8 @@ public final class ExecuteAppFunctionRequest { * Returns the function parameters. The key is the parameter name, and the value is the * parameter value. * - * <p>The bundle may have missing parameters. Developers are advised to implement defensive - * handling measures. + * <p>The {@link GenericDocument} may have missing parameters. Developers are advised to + * implement defensive handling measures. * * <p>Similar to {@link #getFunctionIdentifier()} the parameters required by a function can be * obtained by querying AppSearch for the corresponding {@code AppFunctionStaticMetadata}. This diff --git a/libs/appfunctions/java/com/android/extensions/appfunctions/ExecuteAppFunctionResponse.java b/libs/appfunctions/java/com/android/extensions/appfunctions/ExecuteAppFunctionResponse.java new file mode 100644 index 000000000000..0826f04a50dd --- /dev/null +++ b/libs/appfunctions/java/com/android/extensions/appfunctions/ExecuteAppFunctionResponse.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2024 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.extensions.appfunctions; + +import android.annotation.NonNull; +import android.app.appfunctions.AppFunctionManager; +import android.app.appsearch.GenericDocument; +import android.os.Bundle; + +import java.util.Objects; + +/** The response to an app function execution. */ +public final class ExecuteAppFunctionResponse { + /** + * The name of the property that stores the function return value within the {@code + * resultDocument}. + * + * <p>See {@link GenericDocument#getProperty(String)} for more information. + * + * <p>If the function returns {@code void} or throws an error, the {@code resultDocument} will + * be empty {@link GenericDocument}. + * + * <p>If the {@code resultDocument} is empty, {@link GenericDocument#getProperty(String)} will + * return {@code null}. + * + * <p>See {@link #getResultDocument} for more information on extracting the return value. + */ + public static final String PROPERTY_RETURN_VALUE = "androidAppfunctionsReturnValue"; + + /** + * Returns the return value of the executed function. + * + * <p>The return value is stored in a {@link GenericDocument} with the key {@link + * #PROPERTY_RETURN_VALUE}. + * + * <p>See {@link #getResultDocument} for more information on extracting the return value. + */ + @NonNull private final GenericDocument mResultDocument; + + /** Returns the additional metadata data relevant to this function execution response. */ + @NonNull private final Bundle mExtras; + + /** + * @param resultDocument The return value of the executed function. + */ + public ExecuteAppFunctionResponse(@NonNull GenericDocument resultDocument) { + this(resultDocument, Bundle.EMPTY); + } + + /** + * @param resultDocument The return value of the executed function. + * @param extras The additional metadata for this function execution response. + */ + public ExecuteAppFunctionResponse( + @NonNull GenericDocument resultDocument, @NonNull Bundle extras) { + mResultDocument = Objects.requireNonNull(resultDocument); + mExtras = Objects.requireNonNull(extras); + } + + /** + * Returns a generic document containing the return value of the executed function. + * + * <p>The {@link #PROPERTY_RETURN_VALUE} key can be used to obtain the return value. + * + * <p>Sample code for extracting the return value: + * + * <pre> + * GenericDocument resultDocument = response.getResultDocument(); + * Object returnValue = resultDocument.getProperty(PROPERTY_RETURN_VALUE); + * if (returnValue != null) { + * // Cast returnValue to expected type, or use {@link GenericDocument#getPropertyString}, + * // {@link GenericDocument#getPropertyLong} etc. + * // Do something with the returnValue + * } + * </pre> + * + * @see AppFunctionManager on how to determine the expected function return. + */ + @NonNull + public GenericDocument getResultDocument() { + return mResultDocument; + } + + /** Returns the additional metadata for this function execution response. */ + @NonNull + public Bundle getExtras() { + return mExtras; + } +} diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/SidecarConverter.java b/libs/appfunctions/java/com/android/extensions/appfunctions/SidecarConverter.java index b1b05f79f33f..5e1fc7e684e2 100644 --- a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/SidecarConverter.java +++ b/libs/appfunctions/java/com/android/extensions/appfunctions/SidecarConverter.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.google.android.appfunctions.sidecar; +package com.android.extensions.appfunctions; import android.annotation.NonNull; @@ -28,46 +28,50 @@ public final class SidecarConverter { private SidecarConverter() {} /** - * Converts sidecar's {@link com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest} - * into platform's {@link android.app.appfunctions.ExecuteAppFunctionRequest} + * Converts sidecar's {@link ExecuteAppFunctionRequest} into platform's {@link + * android.app.appfunctions.ExecuteAppFunctionRequest} * * @hide */ @NonNull public static android.app.appfunctions.ExecuteAppFunctionRequest getPlatformExecuteAppFunctionRequest(@NonNull ExecuteAppFunctionRequest request) { - return new - android.app.appfunctions.ExecuteAppFunctionRequest.Builder( - request.getTargetPackageName(), - request.getFunctionIdentifier()) + return new android.app.appfunctions.ExecuteAppFunctionRequest.Builder( + request.getTargetPackageName(), request.getFunctionIdentifier()) .setExtras(request.getExtras()) .setParameters(request.getParameters()) .build(); } /** - * Converts sidecar's {@link com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse} - * into platform's {@link android.app.appfunctions.ExecuteAppFunctionResponse} + * Converts sidecar's {@link ExecuteAppFunctionResponse} into platform's {@link + * android.app.appfunctions.ExecuteAppFunctionResponse} * * @hide */ @NonNull public static android.app.appfunctions.ExecuteAppFunctionResponse getPlatformExecuteAppFunctionResponse(@NonNull ExecuteAppFunctionResponse response) { - if (response.isSuccess()) { - return android.app.appfunctions.ExecuteAppFunctionResponse.newSuccess( - response.getResultDocument(), response.getExtras()); - } else { - return android.app.appfunctions.ExecuteAppFunctionResponse.newFailure( - response.getResultCode(), - response.getErrorMessage(), - response.getExtras()); - } + return new android.app.appfunctions.ExecuteAppFunctionResponse( + response.getResultDocument(), response.getExtras()); } /** - * Converts platform's {@link android.app.appfunctions.ExecuteAppFunctionRequest} - * into sidecar's {@link com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest} + * Converts sidecar's {@link AppFunctionException} into platform's {@link + * android.app.appfunctions.AppFunctionException} + * + * @hide + */ + @NonNull + public static android.app.appfunctions.AppFunctionException + getPlatformAppFunctionException(@NonNull AppFunctionException exception) { + return new android.app.appfunctions.AppFunctionException( + exception.getErrorCode(), exception.getErrorMessage(), exception.getExtras()); + } + + /** + * Converts platform's {@link android.app.appfunctions.ExecuteAppFunctionRequest} into sidecar's + * {@link ExecuteAppFunctionRequest} * * @hide */ @@ -75,30 +79,34 @@ public final class SidecarConverter { public static ExecuteAppFunctionRequest getSidecarExecuteAppFunctionRequest( @NonNull android.app.appfunctions.ExecuteAppFunctionRequest request) { return new ExecuteAppFunctionRequest.Builder( - request.getTargetPackageName(), - request.getFunctionIdentifier()) + request.getTargetPackageName(), request.getFunctionIdentifier()) .setExtras(request.getExtras()) .setParameters(request.getParameters()) .build(); } /** - * Converts platform's {@link android.app.appfunctions.ExecuteAppFunctionResponse} - * into sidecar's {@link com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse} + * Converts platform's {@link android.app.appfunctions.ExecuteAppFunctionResponse} into + * sidecar's {@link ExecuteAppFunctionResponse} * * @hide */ @NonNull public static ExecuteAppFunctionResponse getSidecarExecuteAppFunctionResponse( @NonNull android.app.appfunctions.ExecuteAppFunctionResponse response) { - if (response.isSuccess()) { - return ExecuteAppFunctionResponse.newSuccess( - response.getResultDocument(), response.getExtras()); - } else { - return ExecuteAppFunctionResponse.newFailure( - response.getResultCode(), - response.getErrorMessage(), - response.getExtras()); - } + return new ExecuteAppFunctionResponse(response.getResultDocument(), response.getExtras()); + } + + /** + * Converts platform's {@link android.app.appfunctions.AppFunctionException} into + * sidecar's {@link AppFunctionException} + * + * @hide + */ + @NonNull + public static AppFunctionException getSidecarAppFunctionException( + @NonNull android.app.appfunctions.AppFunctionException exception) { + return new AppFunctionException( + exception.getErrorCode(), exception.getErrorMessage(), exception.getExtras()); } } diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java deleted file mode 100644 index 4e88fb025a9d..000000000000 --- a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java +++ /dev/null @@ -1,359 +0,0 @@ -/* - * Copyright (C) 2024 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.google.android.appfunctions.sidecar; - -import android.annotation.IntDef; -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.app.appsearch.GenericDocument; -import android.os.Bundle; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.util.Objects; - -/** - * The response to an app function execution. - * - * <p>This class copies {@link android.app.appfunctions.ExecuteAppFunctionResponse} without parcel - * functionality and exposes it here as a sidecar library (avoiding direct dependency on the - * platform API). - */ -public final class ExecuteAppFunctionResponse { - /** - * The name of the property that stores the function return value within the {@code - * resultDocument}. - * - * <p>See {@link GenericDocument#getProperty(String)} for more information. - * - * <p>If the function returns {@code void} or throws an error, the {@code resultDocument} will - * be empty {@link GenericDocument}. - * - * <p>If the {@code resultDocument} is empty, {@link GenericDocument#getProperty(String)} will - * return {@code null}. - * - * <p>See {@link #getResultDocument} for more information on extracting the return value. - */ - public static final String PROPERTY_RETURN_VALUE = "returnValue"; - - /** - * The call was successful. - * - * <p>This result code does not belong in an error category. - */ - public static final int RESULT_OK = 0; - - /** - * The caller does not have the permission to execute an app function. - * - * <p>This error is in the {@link #ERROR_CATEGORY_REQUEST_ERROR} category. - */ - public static final int RESULT_DENIED = 1000; - - /** - * The caller supplied invalid arguments to the execution request. - * - * <p>This error may be considered similar to {@link IllegalArgumentException}. - * - * <p>This error is in the {@link #ERROR_CATEGORY_REQUEST_ERROR} category. - */ - public static final int RESULT_INVALID_ARGUMENT = 1001; - - /** - * The caller tried to execute a disabled app function. - * - * <p>This error is in the {@link #ERROR_CATEGORY_REQUEST_ERROR} category. - */ - public static final int RESULT_DISABLED = 1002; - - /** - * The caller tried to execute a function that does not exist. - * - * <p>This error is in the {@link #ERROR_CATEGORY_REQUEST_ERROR} category. - */ - public static final int RESULT_FUNCTION_NOT_FOUND = 1003; - - /** - * An internal unexpected error coming from the system. - * - * <p>This error is in the {@link #ERROR_CATEGORY_SYSTEM} category. - */ - public static final int RESULT_SYSTEM_ERROR = 2000; - - /** - * The operation was cancelled. Use this error code to report that a cancellation is done after - * receiving a cancellation signal. - * - * <p>This error is in the {@link #ERROR_CATEGORY_SYSTEM} category. - */ - public static final int RESULT_CANCELLED = 2001; - - /** - * An unknown error occurred while processing the call in the AppFunctionService. - * - * <p>This error is thrown when the service is connected in the remote application but an - * unexpected error is thrown from the bound application. - * - * <p>This error is in the {@link #ERROR_CATEGORY_APP} category. - */ - public static final int RESULT_APP_UNKNOWN_ERROR = 3000; - - /** - * The error category is unknown. - * - * <p>This is the default value for {@link #getErrorCategory}. - */ - public static final int ERROR_CATEGORY_UNKNOWN = 0; - - /** - * The error is caused by the app requesting a function execution. - * - * <p>For example, the caller provided invalid parameters in the execution request e.g. an - * invalid function ID. - * - * <p>Errors in the category fall in the range 1000-1999 inclusive. - */ - public static final int ERROR_CATEGORY_REQUEST_ERROR = 1; - - /** - * The error is caused by an issue in the system. - * - * <p>For example, the AppFunctionService implementation is not found by the system. - * - * <p>Errors in the category fall in the range 2000-2999 inclusive. - */ - public static final int ERROR_CATEGORY_SYSTEM = 2; - - /** - * The error is caused by the app providing the function. - * - * <p>For example, the app crashed when the system is executing the request. - * - * <p>Errors in the category fall in the range 3000-3999 inclusive. - */ - public static final int ERROR_CATEGORY_APP = 3; - - /** The result code of the app function execution. */ - @ResultCode private final int mResultCode; - - /** - * The error message associated with the result, if any. This is {@code null} if the result code - * is {@link #RESULT_OK}. - */ - @Nullable private final String mErrorMessage; - - /** - * Returns the return value of the executed function. - * - * <p>The return value is stored in a {@link GenericDocument} with the key {@link - * #PROPERTY_RETURN_VALUE}. - * - * <p>See {@link #getResultDocument} for more information on extracting the return value. - */ - @NonNull private final GenericDocument mResultDocument; - - /** Returns the additional metadata data relevant to this function execution response. */ - @NonNull private final Bundle mExtras; - - private ExecuteAppFunctionResponse( - @NonNull GenericDocument resultDocument, - @NonNull Bundle extras, - @ResultCode int resultCode, - @Nullable String errorMessage) { - mResultDocument = Objects.requireNonNull(resultDocument); - mExtras = Objects.requireNonNull(extras); - mResultCode = resultCode; - mErrorMessage = errorMessage; - } - - /** - * Returns result codes from throwable. - * - * @hide - */ - static @ResultCode int getResultCode(@NonNull Throwable t) { - if (t instanceof IllegalArgumentException) { - return ExecuteAppFunctionResponse.RESULT_INVALID_ARGUMENT; - } - return ExecuteAppFunctionResponse.RESULT_APP_UNKNOWN_ERROR; - } - - /** - * Returns a successful response. - * - * @param resultDocument The return value of the executed function. - * @param extras The additional metadata data relevant to this function execution response. - */ - @NonNull - public static ExecuteAppFunctionResponse newSuccess( - @NonNull GenericDocument resultDocument, @Nullable Bundle extras) { - Objects.requireNonNull(resultDocument); - Bundle actualExtras = getActualExtras(extras); - - return new ExecuteAppFunctionResponse( - resultDocument, actualExtras, RESULT_OK, /* errorMessage= */ null); - } - - /** - * Returns a failure response. - * - * @param resultCode The result code of the app function execution. - * @param extras The additional metadata data relevant to this function execution response. - * @param errorMessage The error message associated with the result, if any. - */ - @NonNull - public static ExecuteAppFunctionResponse newFailure( - @ResultCode int resultCode, @Nullable String errorMessage, @Nullable Bundle extras) { - if (resultCode == RESULT_OK) { - throw new IllegalArgumentException("resultCode must not be RESULT_OK"); - } - Bundle actualExtras = getActualExtras(extras); - GenericDocument emptyDocument = new GenericDocument.Builder<>("", "", "").build(); - return new ExecuteAppFunctionResponse( - emptyDocument, actualExtras, resultCode, errorMessage); - } - - private static Bundle getActualExtras(@Nullable Bundle extras) { - if (extras == null) { - return Bundle.EMPTY; - } - return extras; - } - - /** - * Returns the error category of the {@link ExecuteAppFunctionResponse}. - * - * <p>This method categorizes errors based on their underlying cause, allowing developers to - * implement targeted error handling and provide more informative error messages to users. It - * maps ranges of result codes to specific error categories. - * - * <p>When constructing a {@link #newFailure} response, use the appropriate result code value to - * ensure correct categorization of the failed response. - * - * <p>This method returns {@code ERROR_CATEGORY_UNKNOWN} if the result code does not belong to - * any error category, for example, in the case of a successful result with {@link #RESULT_OK}. - * - * <p>See {@link ErrorCategory} for a complete list of error categories and their corresponding - * result code ranges. - */ - @ErrorCategory - public int getErrorCategory() { - if (mResultCode >= 1000 && mResultCode < 2000) { - return ERROR_CATEGORY_REQUEST_ERROR; - } - if (mResultCode >= 2000 && mResultCode < 3000) { - return ERROR_CATEGORY_SYSTEM; - } - if (mResultCode >= 3000 && mResultCode < 4000) { - return ERROR_CATEGORY_APP; - } - return ERROR_CATEGORY_UNKNOWN; - } - - /** - * Returns a generic document containing the return value of the executed function. - * - * <p>The {@link #PROPERTY_RETURN_VALUE} key can be used to obtain the return value. - * - * <p>An empty document is returned if {@link #isSuccess} is {@code false} or if the executed - * function does not produce a return value. - * - * <p>Sample code for extracting the return value: - * - * <pre> - * GenericDocument resultDocument = response.getResultDocument(); - * Object returnValue = resultDocument.getProperty(PROPERTY_RETURN_VALUE); - * if (returnValue != null) { - * // Cast returnValue to expected type, or use {@link GenericDocument#getPropertyString}, - * // {@link GenericDocument#getPropertyLong} etc. - * // Do something with the returnValue - * } - * </pre> - */ - @NonNull - public GenericDocument getResultDocument() { - return mResultDocument; - } - - /** Returns the extras of the app function execution. */ - @NonNull - public Bundle getExtras() { - return mExtras; - } - - /** - * Returns {@code true} if {@link #getResultCode} equals {@link - * ExecuteAppFunctionResponse#RESULT_OK}. - */ - public boolean isSuccess() { - return getResultCode() == RESULT_OK; - } - - /** - * Returns one of the {@code RESULT} constants defined in {@link ExecuteAppFunctionResponse}. - */ - @ResultCode - public int getResultCode() { - return mResultCode; - } - - /** - * Returns the error message associated with this result. - * - * <p>If {@link #isSuccess} is {@code true}, the error message is always {@code null}. - */ - @Nullable - public String getErrorMessage() { - return mErrorMessage; - } - - /** - * Result codes. - * - * @hide - */ - @IntDef( - prefix = {"RESULT_"}, - value = { - RESULT_OK, - RESULT_DENIED, - RESULT_APP_UNKNOWN_ERROR, - RESULT_SYSTEM_ERROR, - RESULT_FUNCTION_NOT_FOUND, - RESULT_INVALID_ARGUMENT, - RESULT_DISABLED, - RESULT_CANCELLED - }) - @Retention(RetentionPolicy.SOURCE) - public @interface ResultCode {} - - /** - * Error categories. - * - * @hide - */ - @IntDef( - prefix = {"ERROR_CATEGORY_"}, - value = { - ERROR_CATEGORY_UNKNOWN, - ERROR_CATEGORY_REQUEST_ERROR, - ERROR_CATEGORY_APP, - ERROR_CATEGORY_SYSTEM - }) - @Retention(RetentionPolicy.SOURCE) - public @interface ErrorCategory {} -} diff --git a/libs/appfunctions/tests/Android.bp b/libs/appfunctions/tests/Android.bp index 6f5eff305d8d..db79675ae9f7 100644 --- a/libs/appfunctions/tests/Android.bp +++ b/libs/appfunctions/tests/Android.bp @@ -25,7 +25,7 @@ android_test { "androidx.test.rules", "androidx.test.ext.junit", "androidx.core_core-ktx", - "com.google.android.appfunctions.sidecar.impl", + "com.android.extensions.appfunctions.impl", "junit", "kotlin-test", "mockito-target-extended-minus-junit4", diff --git a/libs/appfunctions/tests/src/com/google/android/appfunctions/sidecar/tests/SidecarConverterTest.kt b/libs/appfunctions/tests/src/com/android/extensions/appfunctions/tests/SidecarConverterTest.kt index 264f84209caf..11202d58e484 100644 --- a/libs/appfunctions/tests/src/com/google/android/appfunctions/sidecar/tests/SidecarConverterTest.kt +++ b/libs/appfunctions/tests/src/com/android/extensions/appfunctions/tests/SidecarConverterTest.kt @@ -14,14 +14,15 @@ * limitations under the License. */ -package com.google.android.appfunctions.sidecar.tests +package com.android.extensions.appfunctions.tests +import android.app.appfunctions.AppFunctionException import android.app.appfunctions.ExecuteAppFunctionRequest import android.app.appfunctions.ExecuteAppFunctionResponse import android.app.appsearch.GenericDocument import android.os.Bundle import androidx.test.ext.junit.runners.AndroidJUnit4 -import com.google.android.appfunctions.sidecar.SidecarConverter +import com.android.extensions.appfunctions.SidecarConverter import com.google.common.truth.Truth.assertThat import org.junit.Test import org.junit.runner.RunWith @@ -60,7 +61,7 @@ class SidecarConverterTest { .setPropertyLong("testLong", 23) .build() val sidecarRequest = - com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest.Builder( + com.android.extensions.appfunctions.ExecuteAppFunctionRequest.Builder( "targetPkg", "targetFunctionId" ) @@ -83,44 +84,38 @@ class SidecarConverterTest { GenericDocument.Builder<GenericDocument.Builder<*>>("", "", "") .setPropertyBoolean(ExecuteAppFunctionResponse.PROPERTY_RETURN_VALUE, true) .build() - val platformResponse = ExecuteAppFunctionResponse.newSuccess(resultGd, null) + val platformResponse = ExecuteAppFunctionResponse(resultGd) val sidecarResponse = SidecarConverter.getSidecarExecuteAppFunctionResponse( platformResponse ) - assertThat(sidecarResponse.isSuccess).isTrue() assertThat( sidecarResponse.resultDocument.getProperty( ExecuteAppFunctionResponse.PROPERTY_RETURN_VALUE ) ) .isEqualTo(booleanArrayOf(true)) - assertThat(sidecarResponse.resultCode).isEqualTo(ExecuteAppFunctionResponse.RESULT_OK) - assertThat(sidecarResponse.errorMessage).isNull() } @Test - fun getSidecarExecuteAppFunctionResponse_errorResponse_sameContents() { - val emptyGd = GenericDocument.Builder<GenericDocument.Builder<*>>("", "", "").build() - val platformResponse = - ExecuteAppFunctionResponse.newFailure( - ExecuteAppFunctionResponse.RESULT_SYSTEM_ERROR, - null, - null + fun getSidecarAppFunctionException_sameContents() { + val bundle = Bundle() + bundle.putString("key", "value") + val platformException = + AppFunctionException( + AppFunctionException.ERROR_SYSTEM_ERROR, + "error", + bundle ) - val sidecarResponse = SidecarConverter.getSidecarExecuteAppFunctionResponse( - platformResponse + val sidecarException = SidecarConverter.getSidecarAppFunctionException( + platformException ) - assertThat(sidecarResponse.isSuccess).isFalse() - assertThat(sidecarResponse.resultDocument.namespace).isEqualTo(emptyGd.namespace) - assertThat(sidecarResponse.resultDocument.id).isEqualTo(emptyGd.id) - assertThat(sidecarResponse.resultDocument.schemaType).isEqualTo(emptyGd.schemaType) - assertThat(sidecarResponse.resultCode) - .isEqualTo(ExecuteAppFunctionResponse.RESULT_SYSTEM_ERROR) - assertThat(sidecarResponse.errorMessage).isNull() + assertThat(sidecarException.errorCode).isEqualTo(AppFunctionException.ERROR_SYSTEM_ERROR) + assertThat(sidecarException.errorMessage).isEqualTo("error") + assertThat(sidecarException.extras.getString("key")).isEqualTo("value") } @Test @@ -129,44 +124,39 @@ class SidecarConverterTest { GenericDocument.Builder<GenericDocument.Builder<*>>("", "", "") .setPropertyBoolean(ExecuteAppFunctionResponse.PROPERTY_RETURN_VALUE, true) .build() - val sidecarResponse = com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse - .newSuccess(resultGd, null) + val sidecarResponse = + com.android.extensions.appfunctions.ExecuteAppFunctionResponse(resultGd) val platformResponse = SidecarConverter.getPlatformExecuteAppFunctionResponse( sidecarResponse ) - assertThat(platformResponse.isSuccess).isTrue() assertThat( platformResponse.resultDocument.getProperty( ExecuteAppFunctionResponse.PROPERTY_RETURN_VALUE ) ) .isEqualTo(booleanArrayOf(true)) - assertThat(platformResponse.resultCode).isEqualTo(ExecuteAppFunctionResponse.RESULT_OK) - assertThat(platformResponse.errorMessage).isNull() } @Test - fun getPlatformExecuteAppFunctionResponse_errorResponse_sameContents() { - val emptyGd = GenericDocument.Builder<GenericDocument.Builder<*>>("", "", "").build() - val sidecarResponse = - com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse.newFailure( - ExecuteAppFunctionResponse.RESULT_SYSTEM_ERROR, - null, - null + fun getPlatformAppFunctionException_sameContents() { + val bundle = Bundle() + bundle.putString("key", "value") + val sidecarException = + com.android.extensions.appfunctions.AppFunctionException( + AppFunctionException.ERROR_SYSTEM_ERROR, + "error", + bundle ) - val platformResponse = SidecarConverter.getPlatformExecuteAppFunctionResponse( - sidecarResponse + val platformException = SidecarConverter.getPlatformAppFunctionException( + sidecarException ) - assertThat(platformResponse.isSuccess).isFalse() - assertThat(platformResponse.resultDocument.namespace).isEqualTo(emptyGd.namespace) - assertThat(platformResponse.resultDocument.id).isEqualTo(emptyGd.id) - assertThat(platformResponse.resultDocument.schemaType).isEqualTo(emptyGd.schemaType) - assertThat(platformResponse.resultCode) - .isEqualTo(ExecuteAppFunctionResponse.RESULT_SYSTEM_ERROR) - assertThat(platformResponse.errorMessage).isNull() + assertThat(platformException.errorCode) + .isEqualTo(AppFunctionException.ERROR_SYSTEM_ERROR) + assertThat(platformException.errorMessage).isEqualTo("error") + assertThat(platformException.extras.getString("key")).isEqualTo("value") } } diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp index b71abdc011c1..fcb7efc35c94 100644 --- a/libs/hwui/Android.bp +++ b/libs/hwui/Android.bp @@ -580,6 +580,7 @@ cc_defaults { "utils/Color.cpp", "utils/LinearAllocator.cpp", "utils/StringUtils.cpp", + "utils/StatsUtils.cpp", "utils/TypefaceUtils.cpp", "utils/VectorDrawableUtils.cpp", "AnimationContext.cpp", diff --git a/libs/hwui/FeatureFlags.h b/libs/hwui/FeatureFlags.h index fddcf29b9197..5f84f47b725d 100644 --- a/libs/hwui/FeatureFlags.h +++ b/libs/hwui/FeatureFlags.h @@ -33,9 +33,9 @@ inline bool letter_spacing_justification() { #endif // __ANDROID__ } -inline bool typeface_redesign() { +inline bool typeface_redesign_readonly() { #ifdef __ANDROID__ - static bool flag = com_android_text_flags_typeface_redesign(); + static bool flag = com_android_text_flags_typeface_redesign_readonly(); return flag; #else return true; diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp index ae46a99f09c8..064cac2a6fc6 100644 --- a/libs/hwui/Properties.cpp +++ b/libs/hwui/Properties.cpp @@ -113,7 +113,6 @@ float Properties::maxHdrHeadroomOn8bit = 5.f; // TODO: Refine this number bool Properties::clipSurfaceViews = false; bool Properties::hdr10bitPlus = false; bool Properties::skipTelemetry = false; -bool Properties::resampleGainmapRegions = false; bool Properties::queryGlobalPriority = false; int Properties::timeoutMultiplier = 1; @@ -190,8 +189,6 @@ bool Properties::load() { clipSurfaceViews = base::GetBoolProperty("debug.hwui.clip_surfaceviews", hwui_flags::clip_surfaceviews()); hdr10bitPlus = hwui_flags::hdr_10bit_plus(); - resampleGainmapRegions = base::GetBoolProperty("debug.hwui.resample_gainmap_regions", - hwui_flags::resample_gainmap_regions()); queryGlobalPriority = hwui_flags::query_global_priority(); timeoutMultiplier = android::base::GetIntProperty("ro.hw_timeout_multiplier", 1); @@ -288,5 +285,11 @@ bool Properties::initializeGlAlways() { return base::GetBoolProperty(PROPERTY_INITIALIZE_GL_ALWAYS, hwui_flags::initialize_gl_always()); } +bool Properties::resampleGainmapRegions() { + static bool sResampleGainmapRegions = base::GetBoolProperty( + "debug.hwui.resample_gainmap_regions", hwui_flags::resample_gainmap_regions()); + return sResampleGainmapRegions; +} + } // namespace uirenderer } // namespace android diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h index 6f84796fb11e..db930f3904de 100644 --- a/libs/hwui/Properties.h +++ b/libs/hwui/Properties.h @@ -345,7 +345,6 @@ public: static bool clipSurfaceViews; static bool hdr10bitPlus; static bool skipTelemetry; - static bool resampleGainmapRegions; static bool queryGlobalPriority; static int timeoutMultiplier; @@ -381,6 +380,7 @@ public: static void setDrawingEnabled(bool enable); static bool initializeGlAlways(); + static bool resampleGainmapRegions(); private: static StretchEffectBehavior stretchEffectBehavior; diff --git a/libs/hwui/aconfig/hwui_flags.aconfig b/libs/hwui/aconfig/hwui_flags.aconfig index 5ad788c67816..fa27af671be6 100644 --- a/libs/hwui/aconfig/hwui_flags.aconfig +++ b/libs/hwui/aconfig/hwui_flags.aconfig @@ -154,3 +154,13 @@ flag { description: "API's that enable animated image drawables to use nearest sampling when scaling." bug: "370523334" } + +flag { + name: "remove_vri_sketchy_destroy" + namespace: "core_graphics" + description: "Remove the eager yet thread-violating destroyHardwareResources in VRI#die" + bug: "377057106" + metadata { + purpose: PURPOSE_BUGFIX + } +}
\ No newline at end of file diff --git a/libs/hwui/hwui/ImageDecoder.cpp b/libs/hwui/hwui/ImageDecoder.cpp index e074a27db38f..a9a5db8181ba 100644 --- a/libs/hwui/hwui/ImageDecoder.cpp +++ b/libs/hwui/hwui/ImageDecoder.cpp @@ -27,8 +27,8 @@ #include <SkColorSpace.h> #include <SkColorType.h> #include <SkEncodedOrigin.h> -#include <SkImageInfo.h> #include <SkGainmapInfo.h> +#include <SkImageInfo.h> #include <SkMatrix.h> #include <SkPaint.h> #include <SkPngChunkReader.h> @@ -43,6 +43,8 @@ #include <memory> +#include "modules/skcms/src/skcms_public.h" + using namespace android; sk_sp<SkColorSpace> ImageDecoder::getDefaultColorSpace() const { diff --git a/libs/hwui/hwui/MinikinUtils.cpp b/libs/hwui/hwui/MinikinUtils.cpp index 9cd6e253140e..e5fb75575ac3 100644 --- a/libs/hwui/hwui/MinikinUtils.cpp +++ b/libs/hwui/hwui/MinikinUtils.cpp @@ -49,6 +49,7 @@ minikin::MinikinPaint MinikinUtils::prepareMinikinPaint(const Paint* paint, minikinPaint.fontStyle = resolvedFace->fStyle; minikinPaint.fontFeatureSettings = paint->getFontFeatureSettings(); minikinPaint.fontVariationSettings = paint->getFontVariationOverride(); + minikinPaint.verticalText = paint->isVerticalText(); const std::optional<minikin::FamilyVariant>& familyVariant = paint->getFamilyVariant(); if (familyVariant.has_value()) { diff --git a/libs/hwui/hwui/MinikinUtils.h b/libs/hwui/hwui/MinikinUtils.h index 1510ce1378d8..20acf981d9b9 100644 --- a/libs/hwui/hwui/MinikinUtils.h +++ b/libs/hwui/hwui/MinikinUtils.h @@ -73,7 +73,7 @@ public: static void forFontRun(const minikin::Layout& layout, Paint* paint, F& f) { float saveSkewX = paint->getSkFont().getSkewX(); bool savefakeBold = paint->getSkFont().isEmbolden(); - if (text_feature::typeface_redesign()) { + if (text_feature::typeface_redesign_readonly()) { for (uint32_t runIdx = 0; runIdx < layout.getFontRunCount(); ++runIdx) { uint32_t start = layout.getFontRunStart(runIdx); uint32_t end = layout.getFontRunEnd(runIdx); diff --git a/libs/hwui/hwui/Paint.h b/libs/hwui/hwui/Paint.h index 7eb849fe6e3d..594ea31387ad 100644 --- a/libs/hwui/hwui/Paint.h +++ b/libs/hwui/hwui/Paint.h @@ -158,6 +158,7 @@ public: SkSamplingOptions sampling() const { return SkSamplingOptions(this->filterMode()); } + bool isVerticalText() const { return mVerticalText; } void setVariationOverride(minikin::VariationSettings&& varSettings) { mFontVariationOverride = std::move(varSettings); @@ -202,6 +203,7 @@ private: bool mUnderline = false; bool mDevKern = false; minikin::RunFlag mRunFlag = minikin::RunFlag::NONE; + bool mVerticalText = false; }; } // namespace android diff --git a/libs/hwui/hwui/PaintImpl.cpp b/libs/hwui/hwui/PaintImpl.cpp index 6dfcedc3d918..fa5325d90218 100644 --- a/libs/hwui/hwui/PaintImpl.cpp +++ b/libs/hwui/hwui/PaintImpl.cpp @@ -49,7 +49,8 @@ Paint::Paint(const Paint& paint) , mStrikeThru(paint.mStrikeThru) , mUnderline(paint.mUnderline) , mDevKern(paint.mDevKern) - , mRunFlag(paint.mRunFlag) {} + , mRunFlag(paint.mRunFlag) + , mVerticalText(paint.mVerticalText) {} Paint::~Paint() {} @@ -71,6 +72,7 @@ Paint& Paint::operator=(const Paint& other) { mUnderline = other.mUnderline; mDevKern = other.mDevKern; mRunFlag = other.mRunFlag; + mVerticalText = other.mVerticalText; return *this; } @@ -83,7 +85,8 @@ bool operator==(const Paint& a, const Paint& b) { a.mFamilyVariant == b.mFamilyVariant && a.mHyphenEdit == b.mHyphenEdit && a.mTypeface == b.mTypeface && a.mAlign == b.mAlign && a.mFilterBitmap == b.mFilterBitmap && a.mStrikeThru == b.mStrikeThru && - a.mUnderline == b.mUnderline && a.mDevKern == b.mDevKern && a.mRunFlag == b.mRunFlag; + a.mUnderline == b.mUnderline && a.mDevKern == b.mDevKern && a.mRunFlag == b.mRunFlag && + a.mVerticalText == b.mVerticalText; } void Paint::reset() { @@ -97,6 +100,7 @@ void Paint::reset() { mStrikeThru = false; mUnderline = false; mDevKern = false; + mVerticalText = false; mRunFlag = minikin::RunFlag::NONE; } @@ -135,6 +139,7 @@ static const uint32_t sForceAutoHinting = 0x800; // flags related to minikin::Paint static const uint32_t sUnderlineFlag = 0x08; static const uint32_t sStrikeThruFlag = 0x10; +static const uint32_t sVerticalTextFlag = 0x1000; static const uint32_t sTextRunLeftEdge = 0x2000; static const uint32_t sTextRunRightEdge = 0x4000; // flags no longer supported on native side (but mirrored for compatibility) @@ -190,6 +195,7 @@ uint32_t Paint::getJavaFlags() const { flags |= -(int)mUnderline & sUnderlineFlag; flags |= -(int)mDevKern & sDevKernFlag; flags |= -(int)mFilterBitmap & sFilterBitmapFlag; + flags |= -(int)mVerticalText & sVerticalTextFlag; if (mRunFlag & minikin::RunFlag::LEFT_EDGE) { flags |= sTextRunLeftEdge; } @@ -206,6 +212,7 @@ void Paint::setJavaFlags(uint32_t flags) { mUnderline = (flags & sUnderlineFlag) != 0; mDevKern = (flags & sDevKernFlag) != 0; mFilterBitmap = (flags & sFilterBitmapFlag) != 0; + mVerticalText = (flags & sVerticalTextFlag) != 0; std::underlying_type<minikin::RunFlag>::type rawFlag = minikin::RunFlag::NONE; if (flags & sTextRunLeftEdge) { diff --git a/libs/hwui/jni/BitmapFactory.cpp b/libs/hwui/jni/BitmapFactory.cpp index 49a7f73fb3a3..8b43f1db84af 100644 --- a/libs/hwui/jni/BitmapFactory.cpp +++ b/libs/hwui/jni/BitmapFactory.cpp @@ -10,6 +10,7 @@ #include <stdint.h> #include <stdio.h> #include <sys/stat.h> +#include <utils/StatsUtils.h> #include <memory> @@ -630,6 +631,7 @@ static jobject doDecode(JNIEnv* env, std::unique_ptr<SkStreamRewindable> stream, } bitmap::reinitBitmap(env, javaBitmap, outputBitmap.info(), isPremultiplied); outputBitmap.notifyPixelsChanged(); + uirenderer::logBitmapDecode(*reuseBitmap); // If a java bitmap was passed in for reuse, pass it back return javaBitmap; } @@ -650,6 +652,7 @@ static jobject doDecode(JNIEnv* env, std::unique_ptr<SkStreamRewindable> stream, } } + uirenderer::logBitmapDecode(*hardwareBitmap); return bitmap::createBitmap(env, hardwareBitmap.release(), bitmapCreateFlags, ninePatchChunk, ninePatchInsets, -1); } @@ -659,6 +662,7 @@ static jobject doDecode(JNIEnv* env, std::unique_ptr<SkStreamRewindable> stream, heapBitmap->setGainmap(std::move(gainmap)); } + uirenderer::logBitmapDecode(*heapBitmap); // now create the java bitmap return bitmap::createBitmap(env, heapBitmap, bitmapCreateFlags, ninePatchChunk, ninePatchInsets, -1); diff --git a/libs/hwui/jni/BitmapRegionDecoder.cpp b/libs/hwui/jni/BitmapRegionDecoder.cpp index f7e8e073a272..491066b05952 100644 --- a/libs/hwui/jni/BitmapRegionDecoder.cpp +++ b/libs/hwui/jni/BitmapRegionDecoder.cpp @@ -19,6 +19,7 @@ #include <HardwareBitmapUploader.h> #include <androidfw/Asset.h> #include <sys/stat.h> +#include <utils/StatsUtils.h> #include <memory> @@ -111,9 +112,7 @@ public: return false; } - // Round out the subset so that we decode a slightly larger region, in - // case the subset has fractional components. - SkIRect roundedSubset = desiredSubset.roundOut(); + sampleSize = std::max(sampleSize, 1); // Map the desired subset to the space of the decoded gainmap. The // subset is repositioned relative to the resulting bitmap, and then @@ -122,10 +121,51 @@ public: // for existing gainmap formats. SkRect logicalSubset = desiredSubset.makeOffset(-std::floorf(desiredSubset.left()), -std::floorf(desiredSubset.top())); - logicalSubset.fLeft /= sampleSize; - logicalSubset.fTop /= sampleSize; - logicalSubset.fRight /= sampleSize; - logicalSubset.fBottom /= sampleSize; + logicalSubset = scale(logicalSubset, 1.0f / sampleSize); + + // Round out the subset so that we decode a slightly larger region, in + // case the subset has fractional components. When we round, we need to + // round the downsampled subset to avoid possibly rounding down by accident. + // Consider this concrete example if we round the desired subset directly: + // + // * We are decoding a 18x18 corner of an image + // + // * Gainmap is 1/4 resolution, which is logically a 4.5x4.5 gainmap + // that we would want + // + // * The app wants to downsample by a factor of 2x + // + // * The desired gainmap dimensions are computed to be 3x3 to fit the + // downsampled gainmap, since we need to fill a 2.25x2.25 region that's + // later upscaled to 3x3 + // + // * But, if we round out the desired gainmap region _first_, then we + // request to decode a 5x5 region, downsampled by 2, which actually + // decodes a 2x2 region since skia rounds down internally. But then we transfer + // the result to a 3x3 bitmap using a clipping allocator, which leaves an inset. + // Not only did we get a smaller region than we expected (so, our desired subset is + // not valid), but because the API allows for decoding regions using a recycled + // bitmap, we can't really safely fill in the inset since then we might + // extend the gainmap beyond intended the image bounds. Oops. + // + // * If we instead round out as if we downsampled, then we downsample + // the desired region to 2.25x2.25, round out to 3x3, then upsample back + // into the source gainmap space to get 6x6. Then we decode a 6x6 region + // downsampled into a 3x3 region, and everything's now correct. + // + // Note that we don't always run into this problem, because + // decoders actually round *up* for subsampling when decoding a subset + // that matches the dimensions of the image. E.g., if the original image + // size in the above example was a 20x20 image, so that the gainmap was + // 5x5, then we still manage to downsample into a 3x3 bitmap even with + // the "wrong" math. but that's what we wanted! + // + // Note also that if we overshoot the gainmap bounds with the requested + // subset it isn't a problem either, since now the decoded bitmap is too + // large, rather than too small, so now we can use the desired subset to + // avoid sampling "invalid" colors. + SkRect scaledSubset = scale(desiredSubset, 1.0f / sampleSize); + SkIRect roundedSubset = scale(scaledSubset.roundOut(), static_cast<float>(sampleSize)); RecyclingClippingPixelAllocator allocator(nativeBitmap.get(), false, logicalSubset); if (!mGainmapBRD->decodeRegion(&bm, &allocator, roundedSubset, sampleSize, decodeColorType, @@ -153,7 +193,7 @@ public: const float scaleX = ((float)mGainmapBRD->width()) / mMainImageBRD->width(); const float scaleY = ((float)mGainmapBRD->height()) / mMainImageBRD->height(); - if (uirenderer::Properties::resampleGainmapRegions) { + if (uirenderer::Properties::resampleGainmapRegions()) { const auto srcRect = SkRect::MakeLTRB( mainImageRegion.left() * scaleX, mainImageRegion.top() * scaleY, mainImageRegion.right() * scaleX, mainImageRegion.bottom() * scaleY); @@ -185,6 +225,22 @@ private: , mGainmapBRD(std::move(gainmapBRD)) , mGainmapInfo(info) {} + SkRect scale(SkRect rect, float scale) const { + rect.fLeft *= scale; + rect.fTop *= scale; + rect.fRight *= scale; + rect.fBottom *= scale; + return rect; + } + + SkIRect scale(SkIRect rect, float scale) const { + rect.fLeft *= scale; + rect.fTop *= scale; + rect.fRight *= scale; + rect.fBottom *= scale; + return rect; + } + std::unique_ptr<skia::BitmapRegionDecoder> mMainImageBRD; std::unique_ptr<skia::BitmapRegionDecoder> mGainmapBRD; SkGainmapInfo mGainmapInfo; @@ -376,6 +432,7 @@ static jobject nativeDecodeRegion(JNIEnv* env, jobject, jlong brdHandle, jint in recycledBitmap->setGainmap(std::move(gainmap)); } bitmap::reinitBitmap(env, javaBitmap, recycledBitmap->info(), !requireUnpremul); + uirenderer::logBitmapDecode(*recycledBitmap); return javaBitmap; } @@ -392,12 +449,14 @@ static jobject nativeDecodeRegion(JNIEnv* env, jobject, jlong brdHandle, jint in hardwareBitmap->setGainmap(std::move(gm)); } } + uirenderer::logBitmapDecode(*hardwareBitmap); return bitmap::createBitmap(env, hardwareBitmap.release(), bitmapCreateFlags); } Bitmap* heapBitmap = heapAlloc.getStorageObjAndReset(); if (hasGainmap && heapBitmap != nullptr) { heapBitmap->setGainmap(std::move(gainmap)); } + uirenderer::logBitmapDecode(*heapBitmap); return android::bitmap::createBitmap(env, heapBitmap, bitmapCreateFlags); } diff --git a/libs/hwui/jni/Graphics.cpp b/libs/hwui/jni/Graphics.cpp index 258bf91f2124..a210ddf54b2e 100644 --- a/libs/hwui/jni/Graphics.cpp +++ b/libs/hwui/jni/Graphics.cpp @@ -750,7 +750,7 @@ void RecyclingClippingPixelAllocator::copyIfNecessary() { std::optional<SkRect> RecyclingClippingPixelAllocator::getSourceBoundsForUpsample( std::optional<SkRect> subset) { - if (!uirenderer::Properties::resampleGainmapRegions || !subset || subset->isEmpty()) { + if (!uirenderer::Properties::resampleGainmapRegions() || !subset || subset->isEmpty()) { return std::nullopt; } diff --git a/libs/hwui/jni/ImageDecoder.cpp b/libs/hwui/jni/ImageDecoder.cpp index aebc4db37898..90fd3d87cba7 100644 --- a/libs/hwui/jni/ImageDecoder.cpp +++ b/libs/hwui/jni/ImageDecoder.cpp @@ -37,6 +37,7 @@ #include <hwui/Bitmap.h> #include <hwui/ImageDecoder.h> #include <sys/stat.h> +#include <utils/StatsUtils.h> #include "Bitmap.h" #include "BitmapFactory.h" @@ -485,6 +486,7 @@ static jobject ImageDecoder_nDecodeBitmap(JNIEnv* env, jobject /*clazz*/, jlong hwBitmap->setGainmap(std::move(gm)); } } + uirenderer::logBitmapDecode(*hwBitmap); return bitmap::createBitmap(env, hwBitmap.release(), bitmapCreateFlags, ninePatchChunk, ninePatchInsets); } @@ -498,6 +500,8 @@ static jobject ImageDecoder_nDecodeBitmap(JNIEnv* env, jobject /*clazz*/, jlong nativeBitmap->setImmutable(); } + + uirenderer::logBitmapDecode(*nativeBitmap); return bitmap::createBitmap(env, nativeBitmap.release(), bitmapCreateFlags, ninePatchChunk, ninePatchInsets); } diff --git a/libs/hwui/jni/text/TextShaper.cpp b/libs/hwui/jni/text/TextShaper.cpp index 70e6beda6cb9..5f693462af91 100644 --- a/libs/hwui/jni/text/TextShaper.cpp +++ b/libs/hwui/jni/text/TextShaper.cpp @@ -86,7 +86,7 @@ static jlong shapeTextRun(const uint16_t* text, int textSize, int start, int cou overallDescent = std::max(overallDescent, extent.descent); } - if (text_feature::typeface_redesign()) { + if (text_feature::typeface_redesign_readonly()) { uint32_t runCount = layout.getFontRunCount(); std::unordered_map<minikin::FakedFont, uint32_t, FakedFontKey> fakedToFontIds; @@ -229,7 +229,7 @@ float findValueFromVariationSettings(const minikin::FontFakery& fakery, minikin: // CriticalNative static jfloat TextShaper_Result_getWeightOverride(CRITICAL_JNI_PARAMS_COMMA jlong ptr, jint i) { const LayoutWrapper* layout = reinterpret_cast<LayoutWrapper*>(ptr); - if (text_feature::typeface_redesign()) { + if (text_feature::typeface_redesign_readonly()) { float value = findValueFromVariationSettings(layout->layout.getFakery(i), minikin::TAG_wght); return std::isnan(value) ? NO_OVERRIDE : value; @@ -241,7 +241,7 @@ static jfloat TextShaper_Result_getWeightOverride(CRITICAL_JNI_PARAMS_COMMA jlon // CriticalNative static jfloat TextShaper_Result_getItalicOverride(CRITICAL_JNI_PARAMS_COMMA jlong ptr, jint i) { const LayoutWrapper* layout = reinterpret_cast<LayoutWrapper*>(ptr); - if (text_feature::typeface_redesign()) { + if (text_feature::typeface_redesign_readonly()) { float value = findValueFromVariationSettings(layout->layout.getFakery(i), minikin::TAG_ital); return std::isnan(value) ? NO_OVERRIDE : value; diff --git a/libs/hwui/libhwui.map.txt b/libs/hwui/libhwui.map.txt index 2414299321a9..b5591941453d 100644 --- a/libs/hwui/libhwui.map.txt +++ b/libs/hwui/libhwui.map.txt @@ -67,6 +67,7 @@ LIBHWUI_PLATFORM { SkFILEStream::SkFILEStream*; SkImageInfo::*; SkMemoryStream::SkMemoryStream*; + android::uirenderer::logBitmapDecode*; }; local: *; diff --git a/libs/hwui/utils/StatsUtils.cpp b/libs/hwui/utils/StatsUtils.cpp new file mode 100644 index 000000000000..5c4027e1a846 --- /dev/null +++ b/libs/hwui/utils/StatsUtils.cpp @@ -0,0 +1,102 @@ +/* + * Copyright 2024 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. + */ + +#ifdef __ANDROID__ +#include <dlfcn.h> +#include <log/log.h> +#include <statslog_hwui.h> +#include <statssocket_lazy.h> +#include <utils/Errors.h> + +#include <mutex> +#endif + +#include <unistd.h> + +#include "StatsUtils.h" + +namespace android { +namespace uirenderer { + +#ifdef __ANDROID__ + +namespace { + +int32_t toStatsColorSpaceTransfer(skcms_TFType transferType) { + switch (transferType) { + case skcms_TFType_sRGBish: + return stats::IMAGE_DECODED__COLOR_SPACE_TRANSFER__COLOR_SPACE_TRANSFER_SRGBISH; + case skcms_TFType_PQish: + return stats::IMAGE_DECODED__COLOR_SPACE_TRANSFER__COLOR_SPACE_TRANSFER_PQISH; + case skcms_TFType_HLGish: + return stats::IMAGE_DECODED__COLOR_SPACE_TRANSFER__COLOR_SPACE_TRANSFER_HLGISH; + default: + return stats::IMAGE_DECODED__COLOR_SPACE_TRANSFER__COLOR_SPACE_TRANSFER_UNKNOWN; + } +} + +int32_t toStatsBitmapFormat(SkColorType type) { + switch (type) { + case kAlpha_8_SkColorType: + return stats::IMAGE_DECODED__FORMAT__BITMAP_FORMAT_A_8; + case kRGB_565_SkColorType: + return stats::IMAGE_DECODED__FORMAT__BITMAP_FORMAT_RGB_565; + case kN32_SkColorType: + return stats::IMAGE_DECODED__FORMAT__BITMAP_FORMAT_ARGB_8888; + case kRGBA_F16_SkColorType: + return stats::IMAGE_DECODED__FORMAT__BITMAP_FORMAT_RGBA_F16; + case kRGBA_1010102_SkColorType: + return stats::IMAGE_DECODED__FORMAT__BITMAP_FORMAT_RGBA_1010102; + default: + return stats::IMAGE_DECODED__FORMAT__BITMAP_FORMAT_UNKNOWN; + } +} + +} // namespace + +#endif + +void logBitmapDecode(const SkImageInfo& info, bool hasGainmap) { +#ifdef __ANDROID__ + + if (!statssocket::lazy::IsAvailable()) { + std::once_flag once; + std::call_once(once, []() { ALOGD("libstatssocket not available, dropping stats"); }); + return; + } + + skcms_TFType tfnType = skcms_TFType_Invalid; + + if (info.colorSpace()) { + skcms_TransferFunction tfn; + info.colorSpace()->transferFn(&tfn); + tfnType = skcms_TransferFunction_getType(&tfn); + } + + auto status = + stats::stats_write(uirenderer::stats::IMAGE_DECODED, static_cast<int32_t>(getuid()), + uirenderer::toStatsColorSpaceTransfer(tfnType), hasGainmap, + uirenderer::toStatsBitmapFormat(info.colorType())); + ALOGW_IF(status != OK, "Image decoding logging dropped!"); +#endif +} + +void logBitmapDecode(const Bitmap& bitmap) { + logBitmapDecode(bitmap.info(), bitmap.hasGainmap()); +} + +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/utils/StatsUtils.h b/libs/hwui/utils/StatsUtils.h new file mode 100644 index 000000000000..0c247014a8eb --- /dev/null +++ b/libs/hwui/utils/StatsUtils.h @@ -0,0 +1,33 @@ +/* + * Copyright 2024 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. + */ + +#pragma once + +#include <SkBitmap.h> +#include <SkColorSpace.h> +#include <SkColorType.h> +#include <cutils/compiler.h> +#include <hwui/Bitmap.h> + +namespace android { +namespace uirenderer { + +ANDROID_API void logBitmapDecode(const SkImageInfo& info, bool hasGainmap); + +ANDROID_API void logBitmapDecode(const Bitmap& bitmap); + +} // namespace uirenderer +} // namespace android |